From c11b7617645266716025961ae144aac3a18d1274 Mon Sep 17 00:00:00 2001
From: mkirsz <s1351949@sms.ed.ac.uk>
Date: Mon, 21 Oct 2024 17:37:12 +0100
Subject: [PATCH] Major reorganisation

---
 CMakeLists.txt                                |   82 +-
 bin/tadah_cli.cpp                             |   43 +-
 bin/tadah_cli.h                               |    3 +-
 external/CLI11/.all-contributorsrc            |  483 +++
 external/CLI11/.appveyor.yml                  |   36 +
 external/CLI11/.ci/azure-build.yml            |   10 +
 external/CLI11/.ci/azure-cmake.yml            |   16 +
 external/CLI11/.ci/azure-test.yml             |   10 +
 external/CLI11/.ci/build_doxygen.sh           |   25 +
 external/CLI11/.ci/build_lcov.sh              |   17 +
 external/CLI11/.ci/make_and_test.sh           |   23 +
 external/CLI11/.ci/run_codecov.sh             |   27 +
 external/CLI11/.clang-format                  |   86 +
 external/CLI11/.clang-tidy                    |   24 +
 external/CLI11/.cmake-format.yaml             |    6 +
 external/CLI11/.codecov.yml                   |    4 +
 external/CLI11/.editorconfig                  |   11 +
 external/CLI11/.github/CONTRIBUTING.md        |   83 +
 .../.github/actions/quick_cmake/action.yml    |   25 +
 external/CLI11/.github/dependabot.yml         |   16 +
 external/CLI11/.github/labeler_merged.yml     |    4 +
 external/CLI11/.github/workflows/build.yml    |   58 +
 .../CLI11/.github/workflows/pr_merged.yml     |   15 +
 external/CLI11/.github/workflows/tests.yml    |  168 +
 external/CLI11/.gitignore                     |   15 +
 external/CLI11/.pre-commit-config.yaml        |   63 +
 external/CLI11/.remarkrc                      |    7 +
 external/CLI11/CHANGELOG.md                   |  701 ++++
 external/CLI11/CLI11.CPack.Description.txt    |    1 +
 external/CLI11/CLI11.hpp.in                   |   69 +
 external/CLI11/CMakeLists.txt                 |  331 ++
 external/CLI11/CPPLINT.cfg                    |   13 +
 external/CLI11/LICENSE                        |   25 +
 external/CLI11/README.md                      | 1085 ++++++
 external/CLI11/azure-pipelines.yml            |  131 +
 external/CLI11/book/.gitignore                |   20 +
 external/CLI11/book/CMakeLists.txt            |    7 +
 external/CLI11/book/README.md                 |   65 +
 external/CLI11/book/SUMMARY.md                |   15 +
 external/CLI11/book/book.json                 |   16 +
 .../CLI11/book/chapters/advanced-topics.md    |  137 +
 .../book/chapters/an-advanced-example.md      |   31 +
 external/CLI11/book/chapters/basics.md        |   26 +
 external/CLI11/book/chapters/config.md        |  235 ++
 external/CLI11/book/chapters/flags.md         |  123 +
 external/CLI11/book/chapters/formatting.md    |   73 +
 external/CLI11/book/chapters/installation.md  |   92 +
 external/CLI11/book/chapters/internals.md     |   43 +
 external/CLI11/book/chapters/options.md       |  291 ++
 external/CLI11/book/chapters/subcommands.md   |  128 +
 external/CLI11/book/chapters/toolkits.md      |   28 +
 external/CLI11/book/chapters/validators.md    |   61 +
 external/CLI11/book/code/CMakeLists.txt       |   32 +
 external/CLI11/book/code/flags.cpp            |   36 +
 external/CLI11/book/code/geet.cpp             |   50 +
 external/CLI11/book/code/intro.cpp            |   15 +
 external/CLI11/book/code/simplest.cpp         |   11 +
 external/CLI11/cmake/CLI11.pc.in              |    9 +
 .../CLI11/cmake/CLI11ConfigVersion.cmake.in   |   13 +
 .../CLI11/cmake/CLI11GeneratePkgConfig.cmake  |    3 +
 external/CLI11/cmake/CodeCoverage.cmake       |  243 ++
 external/CLI11/conanfile.py                   |   48 +
 external/CLI11/docs/.gitignore                |    2 +
 external/CLI11/docs/CLI11.svg                 |  114 +
 external/CLI11/docs/CLI11_100.png             |  Bin 0 -> 3355 bytes
 external/CLI11/docs/CLI11_300.png             |  Bin 0 -> 9957 bytes
 external/CLI11/docs/CMakeLists.txt            |   11 +
 external/CLI11/docs/Doxyfile                  | 2475 +++++++++++++
 external/CLI11/docs/mainpage.md               |   22 +
 external/CLI11/examples/CMakeLists.txt        |  249 ++
 .../CLI11/examples/callback_passthrough.cpp   |   28 +
 external/CLI11/examples/config_app.cpp        |   50 +
 external/CLI11/examples/custom_parse.cpp      |   39 +
 external/CLI11/examples/digit_args.cpp        |   21 +
 external/CLI11/examples/enum.cpp              |   33 +
 external/CLI11/examples/enum_ostream.cpp      |   50 +
 external/CLI11/examples/formatter.cpp         |   37 +
 external/CLI11/examples/groups.cpp            |   39 +
 .../CLI11/examples/inter_argument_order.cpp   |   51 +
 external/CLI11/examples/json.cpp              |  123 +
 external/CLI11/examples/modhelp.cpp           |   35 +
 external/CLI11/examples/nested.cpp            |   30 +
 external/CLI11/examples/option_groups.cpp     |   46 +
 external/CLI11/examples/positional_arity.cpp  |   46 +
 .../CLI11/examples/positional_validation.cpp  |   37 +
 external/CLI11/examples/prefix_command.cpp    |   35 +
 external/CLI11/examples/ranges.cpp            |   41 +
 external/CLI11/examples/retired.cpp           |   46 +
 external/CLI11/examples/shapes.cpp            |   56 +
 external/CLI11/examples/simple.cpp            |   38 +
 external/CLI11/examples/subcom_help.cpp       |   21 +
 .../examples/subcom_in_files/CMakeLists.txt   |    1 +
 .../examples/subcom_in_files/subcommand_a.cpp |   37 +
 .../examples/subcom_in_files/subcommand_a.hpp |   23 +
 .../subcom_in_files/subcommand_main.cpp       |   26 +
 .../CLI11/examples/subcom_partitioned.cpp     |   46 +
 external/CLI11/examples/subcommands.cpp       |   35 +
 external/CLI11/examples/validators.cpp        |   25 +
 external/CLI11/include/CLI/App.hpp            | 3201 +++++++++++++++++
 external/CLI11/include/CLI/CLI.hpp            |   36 +
 external/CLI11/include/CLI/Config.hpp         |  396 ++
 external/CLI11/include/CLI/ConfigFwd.hpp      |  182 +
 external/CLI11/include/CLI/Error.hpp          |  351 ++
 external/CLI11/include/CLI/Formatter.hpp      |  292 ++
 external/CLI11/include/CLI/FormatterFwd.hpp   |  184 +
 external/CLI11/include/CLI/Macros.hpp         |   44 +
 external/CLI11/include/CLI/Option.hpp         | 1348 +++++++
 external/CLI11/include/CLI/Split.hpp          |  143 +
 external/CLI11/include/CLI/StringTools.hpp    |  430 +++
 external/CLI11/include/CLI/Timer.hpp          |  134 +
 external/CLI11/include/CLI/TypeTools.hpp      | 1548 ++++++++
 external/CLI11/include/CLI/Validators.hpp     | 1142 ++++++
 external/CLI11/include/CLI/Version.hpp        |   16 +
 external/CLI11/meson.build                    |   11 +
 external/CLI11/scripts/ExtractVersion.py      |   17 +
 external/CLI11/scripts/MakeSingleHeader.py    |  154 +
 external/CLI11/scripts/check_style.sh         |   10 +
 external/CLI11/scripts/check_style_docker.sh  |   14 +
 .../CLI11/scripts/clang-format-pre-commit     |   36 +
 external/CLI11/scripts/mdlint_style.rb        |    8 +
 external/CLI11/test_package/CMakeLists.txt    |   16 +
 external/CLI11/test_package/conanfile.py      |   21 +
 external/CLI11/test_package/example.cpp       |   20 +
 external/CLI11/tests/.syntastic_cpp_config    |    1 +
 external/CLI11/tests/AppTest.cpp              | 2310 ++++++++++++
 external/CLI11/tests/BoostOptionTypeTest.cpp  |  126 +
 external/CLI11/tests/CMakeLists.txt           |  221 ++
 external/CLI11/tests/ComplexTypeTest.cpp      |  191 +
 external/CLI11/tests/ConfigFileTest.cpp       | 2758 ++++++++++++++
 external/CLI11/tests/CreationTest.cpp         |  758 ++++
 external/CLI11/tests/DeprecatedTest.cpp       |   14 +
 external/CLI11/tests/FormatterTest.cpp        |  211 ++
 external/CLI11/tests/HelpTest.cpp             | 1263 +++++++
 external/CLI11/tests/HelpersTest.cpp          | 1310 +++++++
 external/CLI11/tests/NewParseTest.cpp         |  492 +++
 external/CLI11/tests/OptionGroupTest.cpp      |  797 ++++
 external/CLI11/tests/OptionTypeTest.cpp       |  999 +++++
 external/CLI11/tests/OptionalTest.cpp         |  333 ++
 external/CLI11/tests/SetTest.cpp              |  711 ++++
 external/CLI11/tests/SimpleTest.cpp           |   34 +
 external/CLI11/tests/StringParseTest.cpp      |  108 +
 external/CLI11/tests/SubcommandTest.cpp       | 1957 ++++++++++
 external/CLI11/tests/TimerTest.cpp            |   70 +
 external/CLI11/tests/TransformTest.cpp        |  970 +++++
 external/CLI11/tests/TrueFalseTest.cpp        |   29 +
 external/CLI11/tests/WindowsTest.cpp          |   19 +
 external/CLI11/tests/app_helper.hpp           |   67 +
 external/CLI11/tests/catch.hpp                |    9 +
 external/CLI11/tests/informational.cpp        |   56 +
 external/CLI11/tests/link_test_1.cpp          |   10 +
 external/CLI11/tests/link_test_2.cpp          |   17 +
 external/CLI11/tests/main.cpp                 |    8 +
 external/CLI11/tests/mesonTest/README.md      |   10 +
 external/CLI11/tests/mesonTest/main.cpp       |   17 +
 external/CLI11/tests/mesonTest/meson.build    |    5 +
 .../CLI11/tests/mesonTest/subprojects/CLI11   |    1 +
 156 files changed, 35104 insertions(+), 67 deletions(-)
 create mode 100644 external/CLI11/.all-contributorsrc
 create mode 100644 external/CLI11/.appveyor.yml
 create mode 100644 external/CLI11/.ci/azure-build.yml
 create mode 100644 external/CLI11/.ci/azure-cmake.yml
 create mode 100644 external/CLI11/.ci/azure-test.yml
 create mode 100644 external/CLI11/.ci/build_doxygen.sh
 create mode 100644 external/CLI11/.ci/build_lcov.sh
 create mode 100755 external/CLI11/.ci/make_and_test.sh
 create mode 100755 external/CLI11/.ci/run_codecov.sh
 create mode 100644 external/CLI11/.clang-format
 create mode 100644 external/CLI11/.clang-tidy
 create mode 100644 external/CLI11/.cmake-format.yaml
 create mode 100644 external/CLI11/.codecov.yml
 create mode 100644 external/CLI11/.editorconfig
 create mode 100644 external/CLI11/.github/CONTRIBUTING.md
 create mode 100644 external/CLI11/.github/actions/quick_cmake/action.yml
 create mode 100644 external/CLI11/.github/dependabot.yml
 create mode 100644 external/CLI11/.github/labeler_merged.yml
 create mode 100644 external/CLI11/.github/workflows/build.yml
 create mode 100644 external/CLI11/.github/workflows/pr_merged.yml
 create mode 100644 external/CLI11/.github/workflows/tests.yml
 create mode 100644 external/CLI11/.gitignore
 create mode 100644 external/CLI11/.pre-commit-config.yaml
 create mode 100644 external/CLI11/.remarkrc
 create mode 100644 external/CLI11/CHANGELOG.md
 create mode 100644 external/CLI11/CLI11.CPack.Description.txt
 create mode 100644 external/CLI11/CLI11.hpp.in
 create mode 100644 external/CLI11/CMakeLists.txt
 create mode 100644 external/CLI11/CPPLINT.cfg
 create mode 100644 external/CLI11/LICENSE
 create mode 100644 external/CLI11/README.md
 create mode 100644 external/CLI11/azure-pipelines.yml
 create mode 100644 external/CLI11/book/.gitignore
 create mode 100644 external/CLI11/book/CMakeLists.txt
 create mode 100644 external/CLI11/book/README.md
 create mode 100644 external/CLI11/book/SUMMARY.md
 create mode 100644 external/CLI11/book/book.json
 create mode 100644 external/CLI11/book/chapters/advanced-topics.md
 create mode 100644 external/CLI11/book/chapters/an-advanced-example.md
 create mode 100644 external/CLI11/book/chapters/basics.md
 create mode 100644 external/CLI11/book/chapters/config.md
 create mode 100644 external/CLI11/book/chapters/flags.md
 create mode 100644 external/CLI11/book/chapters/formatting.md
 create mode 100644 external/CLI11/book/chapters/installation.md
 create mode 100644 external/CLI11/book/chapters/internals.md
 create mode 100644 external/CLI11/book/chapters/options.md
 create mode 100644 external/CLI11/book/chapters/subcommands.md
 create mode 100644 external/CLI11/book/chapters/toolkits.md
 create mode 100644 external/CLI11/book/chapters/validators.md
 create mode 100644 external/CLI11/book/code/CMakeLists.txt
 create mode 100644 external/CLI11/book/code/flags.cpp
 create mode 100644 external/CLI11/book/code/geet.cpp
 create mode 100644 external/CLI11/book/code/intro.cpp
 create mode 100644 external/CLI11/book/code/simplest.cpp
 create mode 100644 external/CLI11/cmake/CLI11.pc.in
 create mode 100644 external/CLI11/cmake/CLI11ConfigVersion.cmake.in
 create mode 100644 external/CLI11/cmake/CLI11GeneratePkgConfig.cmake
 create mode 100644 external/CLI11/cmake/CodeCoverage.cmake
 create mode 100644 external/CLI11/conanfile.py
 create mode 100644 external/CLI11/docs/.gitignore
 create mode 100644 external/CLI11/docs/CLI11.svg
 create mode 100644 external/CLI11/docs/CLI11_100.png
 create mode 100644 external/CLI11/docs/CLI11_300.png
 create mode 100644 external/CLI11/docs/CMakeLists.txt
 create mode 100644 external/CLI11/docs/Doxyfile
 create mode 100644 external/CLI11/docs/mainpage.md
 create mode 100644 external/CLI11/examples/CMakeLists.txt
 create mode 100644 external/CLI11/examples/callback_passthrough.cpp
 create mode 100644 external/CLI11/examples/config_app.cpp
 create mode 100644 external/CLI11/examples/custom_parse.cpp
 create mode 100644 external/CLI11/examples/digit_args.cpp
 create mode 100644 external/CLI11/examples/enum.cpp
 create mode 100644 external/CLI11/examples/enum_ostream.cpp
 create mode 100644 external/CLI11/examples/formatter.cpp
 create mode 100644 external/CLI11/examples/groups.cpp
 create mode 100644 external/CLI11/examples/inter_argument_order.cpp
 create mode 100644 external/CLI11/examples/json.cpp
 create mode 100644 external/CLI11/examples/modhelp.cpp
 create mode 100644 external/CLI11/examples/nested.cpp
 create mode 100644 external/CLI11/examples/option_groups.cpp
 create mode 100644 external/CLI11/examples/positional_arity.cpp
 create mode 100644 external/CLI11/examples/positional_validation.cpp
 create mode 100644 external/CLI11/examples/prefix_command.cpp
 create mode 100644 external/CLI11/examples/ranges.cpp
 create mode 100644 external/CLI11/examples/retired.cpp
 create mode 100644 external/CLI11/examples/shapes.cpp
 create mode 100644 external/CLI11/examples/simple.cpp
 create mode 100644 external/CLI11/examples/subcom_help.cpp
 create mode 100644 external/CLI11/examples/subcom_in_files/CMakeLists.txt
 create mode 100644 external/CLI11/examples/subcom_in_files/subcommand_a.cpp
 create mode 100644 external/CLI11/examples/subcom_in_files/subcommand_a.hpp
 create mode 100644 external/CLI11/examples/subcom_in_files/subcommand_main.cpp
 create mode 100644 external/CLI11/examples/subcom_partitioned.cpp
 create mode 100644 external/CLI11/examples/subcommands.cpp
 create mode 100644 external/CLI11/examples/validators.cpp
 create mode 100644 external/CLI11/include/CLI/App.hpp
 create mode 100644 external/CLI11/include/CLI/CLI.hpp
 create mode 100644 external/CLI11/include/CLI/Config.hpp
 create mode 100644 external/CLI11/include/CLI/ConfigFwd.hpp
 create mode 100644 external/CLI11/include/CLI/Error.hpp
 create mode 100644 external/CLI11/include/CLI/Formatter.hpp
 create mode 100644 external/CLI11/include/CLI/FormatterFwd.hpp
 create mode 100644 external/CLI11/include/CLI/Macros.hpp
 create mode 100644 external/CLI11/include/CLI/Option.hpp
 create mode 100644 external/CLI11/include/CLI/Split.hpp
 create mode 100644 external/CLI11/include/CLI/StringTools.hpp
 create mode 100644 external/CLI11/include/CLI/Timer.hpp
 create mode 100644 external/CLI11/include/CLI/TypeTools.hpp
 create mode 100644 external/CLI11/include/CLI/Validators.hpp
 create mode 100644 external/CLI11/include/CLI/Version.hpp
 create mode 100644 external/CLI11/meson.build
 create mode 100755 external/CLI11/scripts/ExtractVersion.py
 create mode 100755 external/CLI11/scripts/MakeSingleHeader.py
 create mode 100755 external/CLI11/scripts/check_style.sh
 create mode 100755 external/CLI11/scripts/check_style_docker.sh
 create mode 100755 external/CLI11/scripts/clang-format-pre-commit
 create mode 100644 external/CLI11/scripts/mdlint_style.rb
 create mode 100644 external/CLI11/test_package/CMakeLists.txt
 create mode 100644 external/CLI11/test_package/conanfile.py
 create mode 100644 external/CLI11/test_package/example.cpp
 create mode 100644 external/CLI11/tests/.syntastic_cpp_config
 create mode 100644 external/CLI11/tests/AppTest.cpp
 create mode 100644 external/CLI11/tests/BoostOptionTypeTest.cpp
 create mode 100644 external/CLI11/tests/CMakeLists.txt
 create mode 100644 external/CLI11/tests/ComplexTypeTest.cpp
 create mode 100644 external/CLI11/tests/ConfigFileTest.cpp
 create mode 100644 external/CLI11/tests/CreationTest.cpp
 create mode 100644 external/CLI11/tests/DeprecatedTest.cpp
 create mode 100644 external/CLI11/tests/FormatterTest.cpp
 create mode 100644 external/CLI11/tests/HelpTest.cpp
 create mode 100644 external/CLI11/tests/HelpersTest.cpp
 create mode 100644 external/CLI11/tests/NewParseTest.cpp
 create mode 100644 external/CLI11/tests/OptionGroupTest.cpp
 create mode 100644 external/CLI11/tests/OptionTypeTest.cpp
 create mode 100644 external/CLI11/tests/OptionalTest.cpp
 create mode 100644 external/CLI11/tests/SetTest.cpp
 create mode 100644 external/CLI11/tests/SimpleTest.cpp
 create mode 100644 external/CLI11/tests/StringParseTest.cpp
 create mode 100644 external/CLI11/tests/SubcommandTest.cpp
 create mode 100644 external/CLI11/tests/TimerTest.cpp
 create mode 100644 external/CLI11/tests/TransformTest.cpp
 create mode 100644 external/CLI11/tests/TrueFalseTest.cpp
 create mode 100644 external/CLI11/tests/WindowsTest.cpp
 create mode 100644 external/CLI11/tests/app_helper.hpp
 create mode 100644 external/CLI11/tests/catch.hpp
 create mode 100644 external/CLI11/tests/informational.cpp
 create mode 100644 external/CLI11/tests/link_test_1.cpp
 create mode 100644 external/CLI11/tests/link_test_2.cpp
 create mode 100644 external/CLI11/tests/main.cpp
 create mode 100644 external/CLI11/tests/mesonTest/README.md
 create mode 100644 external/CLI11/tests/mesonTest/main.cpp
 create mode 100644 external/CLI11/tests/mesonTest/meson.build
 create mode 120000 external/CLI11/tests/mesonTest/subprojects/CLI11

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9cbbcf1..4313383 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,15 +23,6 @@ if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
   message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt and CMakeFiles dir. ")
 endif()
 
-if(UNIX AND NOT APPLE)
-endif()
-
-if(APPLE)
-endif()
-
-if(WIN32)
-endif()
-
 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 set(CMAKE_CXX_EXTENSIONS OFF)
@@ -109,73 +100,70 @@ set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING
 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
 
 include(FetchContent)
-get_filename_component(BUILD_DIR_NAME ${CMAKE_BINARY_DIR} NAME)
+#get_filename_component(BUILD_DIR_NAME ${CMAKE_BINARY_DIR} NAME)
 
 FetchContent_Declare(
   Tadah.MLIP
   GIT_REPOSITORY https://git.ecdf.ed.ac.uk/tadah/mlip.git
-  GIT_TAG origin/main
-  SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/MLIP
-  PATCH_COMMAND  mkdir -p ${BUILD_DIR_NAME}
-  BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/MLIP/${BUILD_DIR_NAME}
+  GIT_TAG origin/develop
   )
 FetchContent_MakeAvailable(Tadah.MLIP)
 
-FetchContent_Declare(
-  Tadah.LIBS
-  GIT_REPOSITORY https://git.ecdf.ed.ac.uk/tadah/libs.git
-  GIT_TAG origin/main
-  SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/LIBS
-  PATCH_COMMAND  mkdir -p ${BUILD_DIR_NAME}
-  BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/LIBS/${BUILD_DIR_NAME}
-  )
-FetchContent_MakeAvailable(Tadah.LIBS)
-
 if(TADAH_ENABLE_HPO)
   add_compile_definitions(TADAH_ENABLE_HPO)
   FetchContent_Declare(
     Tadah.HPO
     GIT_REPOSITORY https://git.ecdf.ed.ac.uk/tadah/hpo.git
-    GIT_TAG origin/main
-    SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/HPO
-    PATCH_COMMAND  mkdir -p ${BUILD_DIR_NAME}
-    BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/HPO/${BUILD_DIR_NAME}
+    GIT_TAG origin/develop
     )
   FetchContent_MakeAvailable(Tadah.HPO)
   #add_subdirectory(HPO)
 endif()
 
-file(READ "MLIP/version.h" ver)
-string(REGEX MATCH "TADAH_WORLD_VERSION ([0-9])" _ "${ver}")
+file(READ "${tadah.mlip_SOURCE_DIR}/include/tadah/mlip/version.h" ver)
+string(REGEX MATCH "TADAH_WORLD_VERSION ([0-9]+)" _ "${ver}")
 set(ver_world ${CMAKE_MATCH_1})
-string(REGEX MATCH "TADAH_MAJOR_VERSION ([0-9])" _ "${ver}")
+string(REGEX MATCH "TADAH_MAJOR_VERSION ([0-9]+)" _ "${ver}")
 set(ver_major ${CMAKE_MATCH_1})
-string(REGEX MATCH "TADAH_MINOR_VERSION ([0-9])" _ "${ver}")
+string(REGEX MATCH "TADAH_MINOR_VERSION ([0-9]+)" _ "${ver}")
 set(ver_minor ${CMAKE_MATCH_1})
 
 project(Tadah VERSION ${ver_world}.${ver_major}.${ver_minor})
 message(STATUS "${TADAH} VERSION: ${ver_world}.${ver_major}.${ver_minor}")
 
-add_library(
-  libtadah 
-  $<TARGET_OBJECTS:tadah.core.obj>
-  $<TARGET_OBJECTS:tadah.models.obj>
-  $<TARGET_OBJECTS:tadah.mlip.obj>
-  $<TARGET_OBJECTS:config::rc>
-  )
+add_executable (tadah bin/main.cpp bin/tadah_cli.cpp)
+
+#add_library(
+#  libtadah 
+#"${Tadah.MODELS_SOURCE_DIR}/src"
+#"${Tadah.MODELS_SOURCE_DIR}/src"
+#"${Tadah.MLIP_SOURCE_DIR}/src"
+#)
+
+#add_subdirectory(external/CLI11)
+include_directories(external/CLI11/include)
+
 
-set_target_properties(libtadah PROPERTIES OUTPUT_NAME "tadah")
-target_link_libraries(libtadah PUBLIC config::rc)
+#set_target_properties(libtadah PROPERTIES OUTPUT_NAME "tadah")
+#target_link_libraries(libtadah PUBLIC config::rc)
 
 if(TADAH_ENABLE_HPO)
-  target_link_libraries(libtadah PUBLIC tadah.hpo)
+  #target_link_libraries(libtadah PUBLIC tadah.hpo)
 endif()
 
-add_executable (tadah bin/main.cpp bin/tadah_cli.cpp)
+target_include_directories(tadah PRIVATE ${Tadah.MLIP_SOURCE_DIR}/include)
+target_include_directories(tadah PRIVATE ${Tadah.MODELS_SOURCE_DIR}/include)
+target_include_directories(tadah PRIVATE ${Tadah.CORE_SOURCE_DIR}/include)
+target_include_directories(tadah PRIVATE ${Tadah.CORE_SOURCE_DIR}/external/toml11)
+
+#link_directories(<build_dir>/_deps/tadah-mlip-build/)
+target_link_libraries(tadah PRIVATE tadah.mlip tadah.models) # TODO
+
 if(TADAH_BUILD_MPI)
   set_target_properties(tadah PROPERTIES OUTPUT_NAME "tadah_mpi")
 endif()
-target_link_libraries(tadah PRIVATE libtadah)
+
+#target_link_libraries(tadah PRIVATE libtadah)
 
 #########################################################################
 if(TADAH_MARCH_NATIVE)
@@ -250,7 +238,7 @@ message(STATUS "${TADAH}: Build with Hyperparameter optimiser is ${TADAH_ENABLE_
 if(TADAH_ENABLE_OPENMP)
   find_package(OpenMP REQUIRED)
   if(OpenMP_CXX_FOUND)
-    target_link_libraries(libtadah PUBLIC OpenMP::OpenMP_CXX)
+    #target_link_libraries(libtadah PUBLIC OpenMP::OpenMP_CXX)
     target_link_libraries(tadah PUBLIC OpenMP::OpenMP_CXX)
     add_compile_definitions(TADAH_ENABLE_OPENMP)
   endif()
@@ -261,11 +249,11 @@ message(STATUS "${TADAH}: Build with OpenMP is ${TADAH_ENABLE_OPENMP}")
 #########################################################################
 
 find_package(LAPACK)
-target_link_libraries (libtadah PRIVATE ${LAPACK_LIBRARIES})
+#target_link_libraries (libtadah PRIVATE ${LAPACK_LIBRARIES})
 target_link_libraries (tadah PUBLIC ${LAPACK_LIBRARIES})
 
 install(TARGETS tadah DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
-install(TARGETS libtadah DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
+#install(TARGETS libtadah DESTINATION ${CMAKE_INSTALL_PREFIX}/lib)
 
 if(TADAH_BUILD_TESTS)
   include(CTest) 
diff --git a/bin/tadah_cli.cpp b/bin/tadah_cli.cpp
index 8a42413..407b6d5 100644
--- a/bin/tadah_cli.cpp
+++ b/bin/tadah_cli.cpp
@@ -1,20 +1,22 @@
 #include "tadah_cli.h"
-#include "../MLIP/output/output.h"
-#include "../CORE/core_types.h"
-#include "../LIBS/CLI11/Timer.hpp"
-#include "../CORE/config/config.h"
-#include "../MODELS/dc_selector.h"
-#include "../MLIP/descriptors_calc.h"
-#include "../MLIP/design_matrix/functions/dm_function_base.h"
-#include "../MLIP/models/m_tadah_base.h"
-#include "../MLIP/structure_db.h"
-#include "../MLIP/nn_finder.h"
-#include "../MLIP/analytics/analytics.h"
-#include "../MLIP/version.h"
-#include "../MLIP/design_matrix/design_matrix.h"
-#include "../MLIP/trainer.h"
+#include <tadah/core/core_types.h>
+#include <tadah/core/config.h>
+#include <tadah/models/dc_selector.h>
+#include <tadah/mlip/output/output.h>
+#include <tadah/mlip/descriptors_calc.h>
+#include <tadah/mlip/design_matrix/functions/dm_function_base.h>
+#include <tadah/mlip/models/m_tadah_base.h>
+#include <tadah/mlip/structure_db.h>
+#include <tadah/mlip/nn_finder.h>
+#include <tadah/mlip/analytics/analytics.h>
+#include <tadah/mlip/version.h>
+#include <tadah/mlip/design_matrix/design_matrix.h>
+#include <tadah/mlip/trainer.h>
+
+#include <CLI/Timer.hpp>
+
 #ifdef TADAH_ENABLE_HPO
-#include "../HPO/hpo.h"
+#include <tadah/hpo/hpo.h>
 #endif
 
 #include <iostream>
@@ -510,15 +512,20 @@ TadahCLI::TadahCLI():
   /*     Hyperparameter Optimizer                                              */
   /*---------------------------------------------------------------------------*/
   ss.str(std::string());
-#ifdef TADAH_ENABLE_HPO
+#ifndef TADAH_ENABLE_HPO
+  ss << "(UNAVAILABLE)" << std::endl;
+#endif
+
   ss << "Optimize the model's architecture and determine\n";
   ss << "the best hyperparameters within predefined constraints.\n";
   ss << "This option uses hyperparameter optimization\n";
   ss << "to minimize a custom loss function.\n";
   ss << "See documentation for more details.\n";
-
   hpo = app.add_subcommand("hpo", ss.str());
 
+#ifdef TADAH_ENABLE_HPO
+
+
   ss.str(std::string());
   ss << "A config file containing model inital parameters\n";
   ss << "and training dataset(s).\n";
@@ -552,8 +559,6 @@ TadahCLI::TadahCLI():
   //hpo->add_flag("-u,--uncertainty",
   //        "Dump uncertainty on weights."); // TODO check this
 
-#else
-  ss << "(UNAVAILABLE)" << std::endl;
 #endif
 }
 
diff --git a/bin/tadah_cli.h b/bin/tadah_cli.h
index c79103c..2ee63f2 100644
--- a/bin/tadah_cli.h
+++ b/bin/tadah_cli.h
@@ -5,7 +5,8 @@
 #ifndef TADAH_CLI_H
 #define TADAH_CLI_H
 
-#include "../LIBS/CLI11/CLI11.hpp"
+#include <CLI/CLI.hpp>
+
 #include <string>
 #include <vector>
 
diff --git a/external/CLI11/.all-contributorsrc b/external/CLI11/.all-contributorsrc
new file mode 100644
index 0000000..7de6785
--- /dev/null
+++ b/external/CLI11/.all-contributorsrc
@@ -0,0 +1,483 @@
+{
+  "projectName": "CLI11",
+  "projectOwner": "CLIUtils",
+  "repoType": "github",
+  "repoHost": "https://github.com",
+  "files": [
+    "README.md"
+  ],
+  "imageSize": 100,
+  "commit": true,
+  "commitConvention": "atom",
+  "contributors": [
+    {
+      "login": "henryiii",
+      "name": "Henry Schreiner",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/4616906?v=4",
+      "profile": "http://iscinumpy.gitlab.io",
+      "contributions": [
+        "bug",
+        "doc",
+        "code"
+      ]
+    },
+    {
+      "login": "phlptp",
+      "name": "Philip Top",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/20667153?v=4",
+      "profile": "https://github.com/phlptp",
+      "contributions": [
+        "bug",
+        "doc",
+        "code"
+      ]
+    },
+    {
+      "login": "cbachhuber",
+      "name": "Christoph Bachhuber",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/27212661?v=4",
+      "profile": "https://www.linkedin.com/in/cbachhuber/",
+      "contributions": [
+        "example",
+        "code"
+      ]
+    },
+    {
+      "login": "lambdafu",
+      "name": "Marcus Brinkmann",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/1138455?v=4",
+      "profile": "https://lambdafu.net/",
+      "contributions": [
+        "bug",
+        "code"
+      ]
+    },
+    {
+      "login": "SkyToGround",
+      "name": "Jonas Nilsson",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/58835?v=4",
+      "profile": "https://github.com/SkyToGround",
+      "contributions": [
+        "bug",
+        "code"
+      ]
+    },
+    {
+      "login": "dvj",
+      "name": "Doug Johnston",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/77217?v=4",
+      "profile": "https://github.com/dvj",
+      "contributions": [
+        "bug",
+        "code"
+      ]
+    },
+    {
+      "login": "lczech",
+      "name": "Lucas Czech",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/4741887?v=4",
+      "profile": "http://lucas-czech.de",
+      "contributions": [
+        "bug",
+        "code"
+      ]
+    },
+    {
+      "login": "rafiw",
+      "name": "Rafi Wiener",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/3034707?v=4",
+      "profile": "https://github.com/rafiw",
+      "contributions": [
+        "bug",
+        "code"
+      ]
+    },
+    {
+      "login": "mensinda",
+      "name": "Daniel Mensinger",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/3407462?v=4",
+      "profile": "https://github.com/mensinda",
+      "contributions": [
+        "platform"
+      ]
+    },
+    {
+      "login": "jbriales",
+      "name": "Jesus Briales",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/6850478?v=4",
+      "profile": "https://github.com/jbriales",
+      "contributions": [
+        "code",
+        "bug"
+      ]
+    },
+    {
+      "login": "seanfisk",
+      "name": "Sean Fisk",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/410322?v=4",
+      "profile": "https://seanfisk.com/",
+      "contributions": [
+        "bug",
+        "code"
+      ]
+    },
+    {
+      "login": "fpeng1985",
+      "name": "fpeng1985",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/87981?v=4",
+      "profile": "https://github.com/fpeng1985",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "almikhayl",
+      "name": "almikhayl",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/6747040?v=4",
+      "profile": "https://github.com/almikhayl",
+      "contributions": [
+        "code",
+        "platform"
+      ]
+    },
+    {
+      "login": "andrew-hardin",
+      "name": "Andrew Hardin",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/16496326?v=4",
+      "profile": "https://github.com/andrew-hardin",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "SX91",
+      "name": "Anton",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/754754?v=4",
+      "profile": "https://github.com/SX91",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "helmesjo",
+      "name": "Fred Helmesjö",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/2501070?v=4",
+      "profile": "https://github.com/helmesjo",
+      "contributions": [
+        "bug",
+        "code"
+      ]
+    },
+    {
+      "login": "skannan89",
+      "name": "Kannan",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/11918764?v=4",
+      "profile": "https://github.com/skannan89",
+      "contributions": [
+        "bug",
+        "code"
+      ]
+    },
+    {
+      "login": "kraj",
+      "name": "Khem Raj",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/465279?v=4",
+      "profile": "http://himvis.com",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "mogigoma",
+      "name": "Mak Kolybabi",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/130862?v=4",
+      "profile": "https://www.mogigoma.com/",
+      "contributions": [
+        "doc"
+      ]
+    },
+    {
+      "login": "msoeken",
+      "name": "Mathias Soeken",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/1998245?v=4",
+      "profile": "http://msoeken.github.io",
+      "contributions": [
+        "doc"
+      ]
+    },
+    {
+      "login": "nathanhourt",
+      "name": "Nathan Hourt",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/271977?v=4",
+      "profile": "https://github.com/nathanhourt",
+      "contributions": [
+        "bug",
+        "code"
+      ]
+    },
+    {
+      "login": "pleroux0",
+      "name": "Paul le Roux",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/39619854?v=4",
+      "profile": "https://github.com/pleroux0",
+      "contributions": [
+        "code",
+        "platform"
+      ]
+    },
+    {
+      "login": "chfast",
+      "name": "Paweł Bylica",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/573380?v=4",
+      "profile": "https://github.com/chfast",
+      "contributions": [
+        "platform"
+      ]
+    },
+    {
+      "login": "peterazmanov",
+      "name": "Peter Azmanov",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/15322318?v=4",
+      "profile": "https://github.com/peterazmanov",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "delpinux",
+      "name": "Stéphane Del Pino",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/35096584?v=4",
+      "profile": "https://github.com/delpinux",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "metopa",
+      "name": "Viacheslav Kroilov",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/3974178?v=4",
+      "profile": "https://github.com/metopa",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "ChristosT",
+      "name": "christos",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/6725596?v=4",
+      "profile": "http://cs.odu.edu/~ctsolakis",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "deining",
+      "name": "deining",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/18169566?v=4",
+      "profile": "https://github.com/deining",
+      "contributions": [
+        "doc"
+      ]
+    },
+    {
+      "login": "elszon",
+      "name": "elszon",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/2971495?v=4",
+      "profile": "https://github.com/elszon",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "ncihnegn",
+      "name": "ncihnegn",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/12021721?v=4",
+      "profile": "https://github.com/ncihnegn",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "nurelin",
+      "name": "nurelin",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/5276274?v=4",
+      "profile": "https://github.com/nurelin",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "ryan4729",
+      "name": "ryan4729",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/40183301?v=4",
+      "profile": "https://github.com/ryan4729",
+      "contributions": [
+        "test"
+      ]
+    },
+    {
+      "login": "slurps-mad-rips",
+      "name": "Isabella Muerte",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/63051?v=4",
+      "profile": "https://izzys.casa",
+      "contributions": [
+        "platform"
+      ]
+    },
+    {
+      "login": "KOLANICH",
+      "name": "KOLANICH",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/240344?v=4",
+      "profile": "https://github.com/KOLANICH",
+      "contributions": [
+        "platform"
+      ]
+    },
+    {
+      "login": "jgerityneurala",
+      "name": "James Gerity",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/57360646?v=4",
+      "profile": "https://github.com/jgerityneurala",
+      "contributions": [
+        "doc"
+      ]
+    },
+    {
+      "login": "jsoref",
+      "name": "Josh Soref",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/2119212?v=4",
+      "profile": "https://github.com/jsoref",
+      "contributions": [
+        "tool"
+      ]
+    },
+    {
+      "login": "geir-t",
+      "name": "geir-t",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/35292136?v=4",
+      "profile": "https://github.com/geir-t",
+      "contributions": [
+        "platform"
+      ]
+    },
+    {
+      "login": "certik",
+      "name": "Ondřej Čertík",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/20568?v=4",
+      "profile": "https://ondrejcertik.com/",
+      "contributions": [
+        "bug"
+      ]
+    },
+    {
+      "login": "samhocevar",
+      "name": "Sam Hocevar",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/245089?v=4",
+      "profile": "http://sam.hocevar.net/",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "rcurtin",
+      "name": "Ryan Curtin",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/1845039?v=4",
+      "profile": "http://www.ratml.org/",
+      "contributions": [
+        "doc"
+      ]
+    },
+    {
+      "login": "mbhall88",
+      "name": "Michael Hall",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/20403931?v=4",
+      "profile": "https://mbh.sh",
+      "contributions": [
+        "doc"
+      ]
+    },
+    {
+      "login": "ferdymercury",
+      "name": "ferdymercury",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/10653970?v=4",
+      "profile": "https://github.com/ferdymercury",
+      "contributions": [
+        "doc"
+      ]
+    },
+    {
+      "login": "jakoblover",
+      "name": "Jakob Lover",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/14160441?v=4",
+      "profile": "https://github.com/jakoblover",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "ZeeD26",
+      "name": "Dominik Steinberger",
+      "avatar_url": "https://avatars2.githubusercontent.com/u/2487468?v=4",
+      "profile": "https://github.com/ZeeD26",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "dfleury2",
+      "name": "D. Fleury",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/4805384?v=4",
+      "profile": "https://github.com/dfleury2",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "dbarowy",
+      "name": "Dan Barowy",
+      "avatar_url": "https://avatars3.githubusercontent.com/u/573142?v=4",
+      "profile": "https://github.com/dbarowy",
+      "contributions": [
+        "doc"
+      ]
+    },
+    {
+      "login": "paddy-hack",
+      "name": "Olaf Meeuwissen",
+      "avatar_url": "https://avatars.githubusercontent.com/u/6804372?v=4",
+      "profile": "https://github.com/paddy-hack",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "dryleev",
+      "name": "dryleev",
+      "avatar_url": "https://avatars.githubusercontent.com/u/83670813?v=4",
+      "profile": "https://github.com/dryleev",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "AnticliMaxtic",
+      "name": "Max",
+      "avatar_url": "https://avatars.githubusercontent.com/u/43995389?v=4",
+      "profile": "https://github.com/AnticliMaxtic",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "alexdewar",
+      "name": "Alex Dewar",
+      "avatar_url": "https://avatars.githubusercontent.com/u/23149834?v=4",
+      "profile": "https://profiles.sussex.ac.uk/p281168-alex-dewar/publications",
+      "contributions": [
+        "code"
+      ]
+    }
+  ],
+  "contributorsPerLine": 7,
+  "skipCi": true
+}
diff --git a/external/CLI11/.appveyor.yml b/external/CLI11/.appveyor.yml
new file mode 100644
index 0000000..59bb832
--- /dev/null
+++ b/external/CLI11/.appveyor.yml
@@ -0,0 +1,36 @@
+version: 2.1.2.{build}
+
+branches:
+  only:
+    - main
+    - v1
+
+install:
+  - git submodule update --init --recursive
+  - py -3 --version
+  - set PATH=C:\Python38-x64;C:\Python38-x64\Scripts;%PATH%
+  - cmake --version
+  - python --version
+  - python -m pip --version
+  - python -m pip install conan
+  - conan user
+  - conan --version
+
+build_script:
+  - mkdir build
+  - cd build
+  - ps: cmake .. -DCLI11_WARNINGS_AS_ERRORS=ON -DCLI11_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_GENERATOR="Visual Studio 14 2015"
+  - ps: cmake --build .
+  - cd ..
+  - conan create . CLIUtils/CLI11
+
+test_script:
+  - cd build
+  - ps: ctest --output-on-failure -C Debug
+
+notifications:
+  - provider: Webhook
+    url: https://webhooks.gitter.im/e/0185e91c5d989a476d7b
+    on_build_success: false
+    on_build_failure: true
+    on_build_status_changed: true
diff --git a/external/CLI11/.ci/azure-build.yml b/external/CLI11/.ci/azure-build.yml
new file mode 100644
index 0000000..f51620c
--- /dev/null
+++ b/external/CLI11/.ci/azure-build.yml
@@ -0,0 +1,10 @@
+steps:
+
+- task: CMake@1
+  inputs:
+    cmakeArgs: .. -DCLI11_WARNINGS_AS_ERRORS=ON -DCLI11_SINGLE_FILE=$(cli11.single) -DCMAKE_CXX_STANDARD=$(cli11.std) -DCLI11_SINGLE_FILE_TESTS=$(cli11.single) -DCMAKE_BUILD_TYPE=$(cli11.build_type) $(cli11.options)
+  displayName: 'Configure'
+
+- script: cmake --build .
+  displayName: 'Build'
+  workingDirectory: build
diff --git a/external/CLI11/.ci/azure-cmake.yml b/external/CLI11/.ci/azure-cmake.yml
new file mode 100644
index 0000000..59b6ceb
--- /dev/null
+++ b/external/CLI11/.ci/azure-cmake.yml
@@ -0,0 +1,16 @@
+steps:
+
+# Note that silkeh/clang does not include ca-certificates, so check the shasum for verification
+- bash: |
+    wget --no-check-certificate "https://cmake.org/files/v3.14/cmake-3.14.3-Linux-x86_64.tar.gz"
+    echo "29faa62fb3a0b6323caa3d9557e1a5f1205614c0d4c5c2a9917f16a74f7eff68  cmake-3.14.3-Linux-x86_64.tar.gz" | shasum -sca 256
+  displayName: Download CMake
+
+- task: ExtractFiles@1
+  inputs:
+    archiveFilePatterns: 'cmake*.tar.gz'
+    destinationFolder: 'cmake_program'
+    displayName: Extract CMake
+
+- bash: echo "##vso[task.prependpath]$(Build.SourcesDirectory)/cmake_program/cmake-3.14.3-Linux-x86_64/bin"
+  displayName: Add CMake to PATH
diff --git a/external/CLI11/.ci/azure-test.yml b/external/CLI11/.ci/azure-test.yml
new file mode 100644
index 0000000..cf89a99
--- /dev/null
+++ b/external/CLI11/.ci/azure-test.yml
@@ -0,0 +1,10 @@
+steps:
+
+- script: ctest --output-on-failure -C $(cli11.build_type) -T test
+  displayName: 'Test'
+  workingDirectory: build
+
+- task: PublishTestResults@2
+  inputs:
+    testResultsFormat: 'cTest'
+    testResultsFiles: '**/Test.xml'
diff --git a/external/CLI11/.ci/build_doxygen.sh b/external/CLI11/.ci/build_doxygen.sh
new file mode 100644
index 0000000..bd877d5
--- /dev/null
+++ b/external/CLI11/.ci/build_doxygen.sh
@@ -0,0 +1,25 @@
+#!/bin/env sh
+# (Source me)
+
+set -evx
+
+#DOXYGEN_URL="http://doxygen.nl/files/doxygen-1.8.17.src.tar.gz"
+DOXYGEN_URL="https://github.com/doxygen/doxygen/archive/Release_1_8_15.tar.gz"
+cd "${DEPS_DIR}"
+
+if [[ ! -f "${DEPS_DIR}/doxygen/build/bin/doxygen" ]] ; then
+  echo "Downloading Doxygen"
+  mkdir -p doxygen
+  travis_retry wget --no-check-certificate --quiet -O - "${DOXYGEN_URL}" | tar --strip-components=1 -xz -C doxygen
+  cd doxygen
+  mkdir -p build
+  cd build
+  cmake ..
+  make -j2
+fi
+
+export PATH="${DEPS_DIR}/doxygen/build/bin:${PATH}"
+
+cd "${TRAVIS_BUILD_DIR}"
+
+set +evx
diff --git a/external/CLI11/.ci/build_lcov.sh b/external/CLI11/.ci/build_lcov.sh
new file mode 100644
index 0000000..7232e99
--- /dev/null
+++ b/external/CLI11/.ci/build_lcov.sh
@@ -0,0 +1,17 @@
+#!/bin/env sh
+# (Source me)
+set -evx
+
+LCOV_URL="http://ftp.de.debian.org/debian/pool/main/l/lcov/lcov_1.13.orig.tar.gz"
+cd "${DEPS_DIR}"
+
+if [[ ! -f "${DEPS_DIR}/lcov/bin/lcov" ]] ; then
+  echo "Downloading lcov"
+  mkdir -p lcov
+  travis_retry wget --no-check-certificate --quiet -O - "${LCOV_URL}" | tar --strip-components=1 -xz -C lcov
+fi
+
+export PATH="${DEPS_DIR}/lcov/bin:${PATH}"
+cd "${TRAVIS_BUILD_DIR}"
+
+set +evx
diff --git a/external/CLI11/.ci/make_and_test.sh b/external/CLI11/.ci/make_and_test.sh
new file mode 100755
index 0000000..07df22e
--- /dev/null
+++ b/external/CLI11/.ci/make_and_test.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+echo -en "travis_fold:start:script.build\\r"
+echo "Building..."
+STD="$1"
+shift
+set -evx
+
+
+mkdir -p build
+cd build
+cmake .. -DCLI11_WARNINGS_AS_ERRORS=ON -DCLI11_SINGLE_FILE=ON -DCMAKE_CXX_STANDARD="$STD" -DCLI11_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER_LAUNCHER=ccache $@
+cmake --build . -- -j2
+
+set +evx
+echo -en "travis_fold:end:script.build\\r"
+echo -en "travis_fold:start:script.test\\r"
+echo "Testing..."
+set -evx
+
+ctest --output-on-failure
+
+set +evx
+echo -en "travis_fold:end:script.test\\r"
diff --git a/external/CLI11/.ci/run_codecov.sh b/external/CLI11/.ci/run_codecov.sh
new file mode 100755
index 0000000..fbc199c
--- /dev/null
+++ b/external/CLI11/.ci/run_codecov.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+
+echo -en "travis_fold:start:script.build\\r"
+echo "Building..."
+set -evx
+
+cd "${TRAVIS_BUILD_DIR}"
+mkdir -p build
+cd build
+cmake .. -DCLI11_SINGLE_FILE_TESTS=OFF -DCLI11_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Coverage
+cmake --build . -- -j2
+cmake --build . --target CLI11_coverage
+
+set +evx
+echo -en "travis_fold:end:script.build\\r"
+echo -en "travis_fold:start:script.lcov\\r"
+echo "Capturing and uploading LCov..."
+set -evx
+
+lcov --directory . --capture --output-file coverage.info # capture coverage info
+lcov --remove coverage.info '*/tests/*' '*/examples/*' '*gtest*' '*gmock*' '/usr/*' --output-file coverage.info # filter out system
+lcov --list coverage.info #debug info
+# Uploading report to CodeCov
+bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports"
+
+set +evx
+echo -en "travis_fold:end:script.lcov\\r"
diff --git a/external/CLI11/.clang-format b/external/CLI11/.clang-format
new file mode 100644
index 0000000..0879ffa
--- /dev/null
+++ b/external/CLI11/.clang-format
@@ -0,0 +1,86 @@
+Language:        Cpp
+BasedOnStyle:  LLVM
+# AccessModifierOffset: -2
+# AlignAfterOpenBracket: Align
+# AlignConsecutiveAssignments: false
+# AlignConsecutiveDeclarations: false
+# AlignEscapedNewlinesLeft: false
+# AlignOperands:   true
+# AlignTrailingComments: true
+# AllowAllParametersOfDeclarationOnNextLine: true
+# AllowShortBlocksOnASingleLine: false
+# AllowShortCaseLabelsOnASingleLine: false
+# AllowShortFunctionsOnASingleLine: All
+# AllowShortIfStatementsOnASingleLine: false
+# AllowShortLoopsOnASingleLine: false
+# AlwaysBreakAfterDefinitionReturnType: None
+# AlwaysBreakAfterReturnType: None
+# AlwaysBreakBeforeMultilineStrings: false
+# AlwaysBreakTemplateDeclarations: false
+BinPackArguments: false
+BinPackParameters: false
+# BraceWrapping:
+#   AfterClass:      false
+#   AfterControlStatement: false
+#   AfterEnum:       false
+#   AfterFunction:   false
+#   AfterNamespace:  false
+#   AfterObjCDeclaration: false
+#   AfterStruct:     false
+#   AfterUnion:      false
+#   BeforeCatch:     false
+#   BeforeElse:      false
+#   IndentBraces:    false
+# BreakBeforeBinaryOperators: None
+# BreakBeforeBraces: Attach
+# BreakBeforeTernaryOperators: true
+# BreakConstructorInitializersBeforeComma: false
+# BreakAfterJavaFieldAnnotations: false
+# BreakStringLiterals: true
+ColumnLimit:     120
+# CommentPragmas:  '^ IWYU pragma:'
+# ConstructorInitializerAllOnOneLineOrOnePerLine: false
+# ConstructorInitializerIndentWidth: 4
+# ContinuationIndentWidth: 4
+# Cpp11BracedListStyle: true
+# DerivePointerAlignment: false
+# DisableFormat:   false
+# ExperimentalAutoDetectBinPacking: false
+# ForEachMacros:   [ foreach, Q_FOREACH, BOOST_FOREACH ]
+# IncludeIsMainRegex: '$'
+# IndentCaseLabels: false
+IndentWidth:     4
+# IndentWrappedFunctionNames: false
+# JavaScriptQuotes: Leave
+# JavaScriptWrapImports: true
+# KeepEmptyLinesAtTheStartOfBlocks: true
+# MacroBlockBegin: ''
+# MacroBlockEnd:   ''
+# MaxEmptyLinesToKeep: 1
+# NamespaceIndentation: None
+# ObjCBlockIndentWidth: 2
+# ObjCSpaceAfterProperty: false
+# ObjCSpaceBeforeProtocolList: true
+# PenaltyBreakBeforeFirstCallParameter: 19
+# PenaltyBreakComment: 300
+# PenaltyBreakFirstLessLess: 120
+# PenaltyBreakString: 1000
+# PenaltyExcessCharacter: 1000000
+# PenaltyReturnTypeOnItsOwnLine: 60
+# PointerAlignment: Right
+# ReflowComments:  true
+SortIncludes:    true
+# SpaceAfterCStyleCast: false
+# SpaceAfterTemplateKeyword: true
+# SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: Never
+# SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+# SpacesInAngles:  false
+# SpacesInContainerLiterals: true
+# SpacesInCStyleCastParentheses: false
+# SpacesInParentheses: false
+# SpacesInSquareBrackets: false
+Standard:        Cpp11
+TabWidth:        4
+UseTab:          Never
diff --git a/external/CLI11/.clang-tidy b/external/CLI11/.clang-tidy
new file mode 100644
index 0000000..075f35d
--- /dev/null
+++ b/external/CLI11/.clang-tidy
@@ -0,0 +1,24 @@
+# Checks that will be implemented in future PRs:
+# performance-unnecessary-value-param, hints to ~110 issues. Be careful with implementing the suggested changes of this one, as auto-fixes may break the code
+
+FormatStyle: file
+
+Checks: '
+-*,
+google-*,
+-google-runtime-references,
+llvm-include-order,
+llvm-namespace-comment,
+misc-throw-by-value-catch-by-reference,
+modernize*,
+-modernize-use-trailing-return-type,
+readability-container-size-empty,
+'
+
+WarningsAsErrors: '*'
+
+HeaderFilterRegex: '.*hpp'
+
+CheckOptions:
+- key:             google-readability-braces-around-statements.ShortStatementLines
+  value:           '3'
diff --git a/external/CLI11/.cmake-format.yaml b/external/CLI11/.cmake-format.yaml
new file mode 100644
index 0000000..543ddf1
--- /dev/null
+++ b/external/CLI11/.cmake-format.yaml
@@ -0,0 +1,6 @@
+format:
+  line_width: 99
+
+# Causes a few issues - can be solved later, possibly.
+markup:
+  enable_markup: false
diff --git a/external/CLI11/.codecov.yml b/external/CLI11/.codecov.yml
new file mode 100644
index 0000000..4181c54
--- /dev/null
+++ b/external/CLI11/.codecov.yml
@@ -0,0 +1,4 @@
+
+ignore:
+  - "tests"
+  - "examples"
diff --git a/external/CLI11/.editorconfig b/external/CLI11/.editorconfig
new file mode 100644
index 0000000..979b049
--- /dev/null
+++ b/external/CLI11/.editorconfig
@@ -0,0 +1,11 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+end_of_line = lf
+trim_trailing_whitespace = true
+
+[*.yml]
+indent_size = 2
diff --git a/external/CLI11/.github/CONTRIBUTING.md b/external/CLI11/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..2f44c9d
--- /dev/null
+++ b/external/CLI11/.github/CONTRIBUTING.md
@@ -0,0 +1,83 @@
+# Contributing
+
+Thanks for considering to write a Pull Request (PR) for CLI11! Here are a few guidelines to get you started:
+
+Make sure you are comfortable with the license; all contributions are licensed under the original license.
+
+## Adding functionality
+
+Make sure any new functions you add are are:
+
+* Documented by `///` documentation for Doxygen
+* Mentioned in the instructions in the README, though brief mentions are okay
+* Explained in your PR (or previously explained in an Issue mentioned in the PR)
+* Completely covered by tests
+
+In general, make sure the addition is well thought out and does not increase the complexity of CLI11 needlessly.
+
+## Things you should know
+
+* Once you make the PR, tests will run to make sure your code works on all supported platforms
+* The test coverage is also measured, and that should remain 100%
+* Formatting should be done with pre-commit, otherwise the format check will not pass. However, it is trivial to apply this to your PR, so don't worry about this check. If you do want to run it, see below.
+* Everything must pass clang-tidy as well, run with `-DCLI11_CLANG_TIDY=ON` (if you set `-DCLI11_CLANG_TIDY_OPTIONS="-fix"`, make sure you use a single threaded build process, or just build one example target).
+* Your changes must also conform to most of the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) rules checked by [cpplint](https://github.com/cpplint/cpplint). For unused cpplint filters and justifications, see [CPPLINT.cfg](/CPPLINT.cfg).
+
+## Pre-commit
+
+Format is handled by pre-commit. You should install it (or use [pipx](https://pypa.github.io/pipx/)):
+
+```bash
+python3 -m pip install pre-commit
+```
+
+Then, you can run it on the items you've added to your staging area, or all files:
+
+```bash
+pre-commit run
+# OR
+pre-commit run --all-files
+```
+
+And, if you want to always use it, you can install it as a git hook (hence the name, pre-commit):
+
+```bash
+pre-commit install
+```
+
+## For developers releasing to Conan.io
+
+This is now done by the CI system on tagged releases. Previously, the steps to make a Conan.io release were:
+
+```bash
+conan remove '*' # optional, I like to be clean
+conan create . cliutils/stable
+conan upload "*" -r cli11 --all
+```
+
+Here I've assumed that the remote is `cli11`.
+
+## For maintainers: remember to add contributions
+
+In a commit to a PR, just add "`@all-contributors please add <username> for <contributions>`" or similar (see <https://allcontributors.org>). Use `code` for code, `bug` if an issue was submitted, `platform` for packaging stuff, and `doc` for documentation updates.
+
+To run locally, do:
+
+```bash
+yarn add --dev all-contributors-cli
+yarn all-contributors add username code,bug
+```
+
+## For maintainers: Making a release
+
+Remember to replace the emoji in the readme, being careful not to replace the ones in all-contributors if any overlap.
+
+Steps:
+
+* Update changelog if needed
+* Update the version in `.appveyor.yml` and `include/CLI/Version.hpp`.
+* Find and replace in README (new minor/major release only):
+  * Replace " 🆕" and "🆕 " with "" (ignores the description line)
+  * Check for `\/\/$` (vi syntax) to catch leftover `// 🆕`
+  * Replace "🚧" with "🆕" (manually ignore the description line)
+* Make a release in the GitHub UI, use a name such as "Version X.Y(.Z): Title"
diff --git a/external/CLI11/.github/actions/quick_cmake/action.yml b/external/CLI11/.github/actions/quick_cmake/action.yml
new file mode 100644
index 0000000..28a8278
--- /dev/null
+++ b/external/CLI11/.github/actions/quick_cmake/action.yml
@@ -0,0 +1,25 @@
+name: Quick CMake config
+description: 'Runs CMake 3.4+ (if already setup)'
+inputs:
+  args:
+    description: 'Other arguments'
+    required: false
+    default: ''
+  cmake-version:
+    description: 'The CMake version to run'
+    required: true
+
+runs:
+  using: composite
+  steps:
+    - name: CMake ${{ inputs.cmake-version }}
+      uses: jwlawson/actions-setup-cmake@v1.11
+      with:
+        cmake-version: "${{ inputs.cmake-version }}"
+    - run: |
+        mkdir -p build-tmp
+        touch build-tmp/tmp
+        rm -r build-tmp/*
+        (cd build-tmp && cmake .. ${{ inputs.args }})
+        rm -r build-tmp
+      shell: bash
diff --git a/external/CLI11/.github/dependabot.yml b/external/CLI11/.github/dependabot.yml
new file mode 100644
index 0000000..7327336
--- /dev/null
+++ b/external/CLI11/.github/dependabot.yml
@@ -0,0 +1,16 @@
+version: 2
+updates:
+  # Maintain dependencies for GitHub Actions
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "daily"
+    ignore:
+      # Official actions have moving tags like v1
+      # that are used, so they don't need updates here
+      - dependency-name: "actions/checkout"
+      - dependency-name: "actions/setup-python"
+      - dependency-name: "actions/cache"
+      - dependency-name: "actions/upload-artifact"
+      - dependency-name: "actions/download-artifact"
+      - dependency-name: "actions/labeler"
diff --git a/external/CLI11/.github/labeler_merged.yml b/external/CLI11/.github/labeler_merged.yml
new file mode 100644
index 0000000..434ab58
--- /dev/null
+++ b/external/CLI11/.github/labeler_merged.yml
@@ -0,0 +1,4 @@
+needs changelog:
+  - all: ['!CHANGELOG.md']
+needs README:
+  - all: ['!README.md']
diff --git a/external/CLI11/.github/workflows/build.yml b/external/CLI11/.github/workflows/build.yml
new file mode 100644
index 0000000..a6f2504
--- /dev/null
+++ b/external/CLI11/.github/workflows/build.yml
@@ -0,0 +1,58 @@
+name: Build
+on:
+  push:
+    branches:
+      - main
+      - v*
+    tags:
+      - "*"
+  pull_request:
+
+jobs:
+  single-header:
+    name: Single header
+    runs-on: ubuntu-latest
+    steps:
+
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+
+    - uses: actions/setup-python@v2
+
+    - name: Prepare CMake config
+      run: cmake -S . -B build -DCLI11_SINGLE_FILE=ON
+
+    - name: Make package
+      run: cmake --build build --target package_source
+
+    - name: Copy source packages
+      run: |
+        mkdir -p CLI11-Source
+        cp build/CLI11-*-Source.* CLI11-Source
+        cp build/CLI11-*-Source.* .
+
+    - name: Make header
+      run: cmake --build build --target CLI11-generate-single-file
+
+    - name: Copy file to main folder
+      run: cp build/include/CLI11.hpp CLI11.hpp
+
+    - uses: actions/upload-artifact@v2
+      with:
+        name: CLI11.hpp
+        path: CLI11.hpp
+
+    - uses: actions/upload-artifact@v2
+      with:
+        name: CLI11-Source
+        path: CLI11-Source
+
+    - name: Release
+      uses: softprops/action-gh-release@v1
+      if: startsWith(github.ref, 'refs/tags/')
+      with:
+        files: |
+          CLI11.hpp
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/external/CLI11/.github/workflows/pr_merged.yml b/external/CLI11/.github/workflows/pr_merged.yml
new file mode 100644
index 0000000..6fadc0f
--- /dev/null
+++ b/external/CLI11/.github/workflows/pr_merged.yml
@@ -0,0 +1,15 @@
+name: PR merged
+on:
+  pull_request_target:
+    types: [closed]
+
+jobs:
+  label-merged:
+    name: Changelog needed
+    runs-on: ubuntu-latest
+    if: github.event.pull_request.merged == true
+    steps:
+    - uses: actions/labeler@main
+      with:
+        repo-token: ${{ secrets.GITHUB_TOKEN }}
+        configuration-path: .github/labeler_merged.yml
diff --git a/external/CLI11/.github/workflows/tests.yml b/external/CLI11/.github/workflows/tests.yml
new file mode 100644
index 0000000..a48b4e3
--- /dev/null
+++ b/external/CLI11/.github/workflows/tests.yml
@@ -0,0 +1,168 @@
+name: Tests
+on:
+  push:
+    branches:
+      - main
+      - v*
+  pull_request:
+
+jobs:
+  cuda-build:
+    name: CUDA build only
+    runs-on: ubuntu-latest
+    container: nvidia/cuda:10.2-devel-ubuntu18.04
+    steps:
+    - uses: actions/checkout@v1
+      with:
+        submodules: true
+    - name: Add wget
+      run: apt-get update && apt-get install -y wget
+    - name: Get cmake
+      uses: jwlawson/actions-setup-cmake@v1.11
+    - name: Configure
+      run: cmake -S . -B build -DCLI11_CUDA_TESTS=ON
+    - name: Build
+      run: cmake --build build -j2
+
+
+  boost-build:
+    name: Boost build
+    runs-on: ubuntu-latest
+    container: zouzias/boost:1.76.0
+    steps:
+    - uses: actions/checkout@v1
+      with:
+        submodules: true
+    - name: Add deps
+      run: apt-get update && apt-get install make
+    - name: Get CMake
+      uses: jwlawson/actions-setup-cmake@v1.11
+    - name: Configure
+      run: cmake -S . -B build -DCLI11_BOOST=ON
+    - name: Build
+      run: cmake --build build -j2
+    - name: Run tests
+      run: ctest --output-on-failure
+      working-directory: build
+
+  cmake-config:
+    name: CMake config check
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+
+    - name: Check CMake 3.4
+      with:
+        cmake-version: "3.4"
+      uses: ./.github/actions/quick_cmake
+
+    - name: Check CMake 3.5
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.5"
+      if: success() || failure()
+
+    - name: Check CMake 3.6
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.6"
+      if: success() || failure()
+
+    - name: Check CMake 3.7
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.7"
+      if: success() || failure()
+
+    - name: Check CMake 3.8
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.8"
+      if: success() || failure()
+
+    - name: Check CMake 3.9
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.9"
+      if: success() || failure()
+
+    - name: Check CMake 3.10
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.10"
+      if: success() || failure()
+
+    - name: Check CMake 3.11 (full)
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.11"
+        args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON
+      if: success() || failure()
+
+    - name: Check CMake 3.12
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.12"
+      if: success() || failure()
+
+    - name: Check CMake 3.13
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.13"
+      if: success() || failure()
+
+    - name: Check CMake 3.14
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.14"
+      if: success() || failure()
+
+    - name: Check CMake 3.15
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.15"
+      if: success() || failure()
+
+    - name: Check CMake 3.16
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.16"
+      if: success() || failure()
+
+    - name: Check CMake 3.17
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.17"
+      if: success() || failure()
+
+    - name: Check CMake 3.18
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.18"
+      if: success() || failure()
+
+    - name: Check CMake 3.19
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.19"
+      if: success() || failure()
+
+    - name: Check CMake 3.20
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.20"
+      if: success() || failure()
+
+    - name: Check CMake 3.21 (full)
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.21"
+        args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON
+      if: success() || failure()
+
+    - name: Check CMake 3.22 (full)
+      uses: ./.github/actions/quick_cmake
+      with:
+        cmake-version: "3.22"
+        args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON
+      if: success() || failure()
diff --git a/external/CLI11/.gitignore b/external/CLI11/.gitignore
new file mode 100644
index 0000000..cc1b9d0
--- /dev/null
+++ b/external/CLI11/.gitignore
@@ -0,0 +1,15 @@
+a.out*
+*.swp
+/*build*
+/test_package/build
+/Makefile
+/CMakeFiles/*
+/cmake_install.cmake
+/*.kdev4
+/html/*
+!/meson.build
+
+/node_modules/*
+/package.json
+/yarn.lock
+/CLI11.hpp
diff --git a/external/CLI11/.pre-commit-config.yaml b/external/CLI11/.pre-commit-config.yaml
new file mode 100644
index 0000000..8413542
--- /dev/null
+++ b/external/CLI11/.pre-commit-config.yaml
@@ -0,0 +1,63 @@
+ci:
+  autoupdate_commit_msg: "chore(deps): pre-commit.ci autoupdate"
+  autofix_commit_msg: "style: pre-commit.ci fixes"
+
+
+repos:
+- repo: https://github.com/psf/black
+  rev: 21.9b0
+  hooks:
+  - id: black
+
+- repo: https://github.com/pre-commit/pre-commit-hooks
+  rev: v4.0.1
+  hooks:
+  - id: check-added-large-files
+  - id: check-case-conflict
+  - id: check-merge-conflict
+  - id: check-symlinks
+  - id: check-yaml
+  - id: end-of-file-fixer
+  - id: mixed-line-ending
+  - id: trailing-whitespace
+
+- repo: https://github.com/pre-commit/mirrors-clang-format
+  rev: v13.0.0
+  hooks:
+   - id: clang-format
+
+- repo: https://github.com/cheshirekow/cmake-format-precommit
+  rev: v0.6.13
+  hooks:
+  - id: cmake-format
+    additional_dependencies: [pyyaml]
+
+- repo: https://github.com/markdownlint/markdownlint
+  rev: v0.11.0
+  hooks:
+  - id: markdownlint
+    args: ["--style=scripts/mdlint_style.rb"]
+
+- repo: local
+  hooks:
+  - id: remarklint
+    name: remarklint
+    language: node
+    entry: remark
+    types: [markdown]
+    args: ["--frail", "--quiet"]
+    additional_dependencies: [remark, remark-lint, remark-cli, remark-preset-lint-recommended, remark-lint-list-item-indent, remark-lint-no-undefined-references]
+
+- repo: local
+  hooks:
+  - id: disallow-caps
+    name: Disallow improper capitalization
+    language: pygrep
+    entry: PyBind|Numpy|Cmake|CCache|PyTest|Github
+    exclude: .pre-commit-config.yaml
+
+- repo: https://github.com/codespell-project/codespell
+  rev: v2.1.0
+  hooks:
+  - id: codespell
+    args: ["-L", "atleast,ans,doub,inout"]
diff --git a/external/CLI11/.remarkrc b/external/CLI11/.remarkrc
new file mode 100644
index 0000000..73cad83
--- /dev/null
+++ b/external/CLI11/.remarkrc
@@ -0,0 +1,7 @@
+{
+  "plugins": [
+    "remark-preset-lint-recommended",
+    ["remark-lint-list-item-indent", "space"],
+    ["remark-lint-no-undefined-references", {"allow": ["^1"]}]
+  ]
+}
diff --git a/external/CLI11/CHANGELOG.md b/external/CLI11/CHANGELOG.md
new file mode 100644
index 0000000..e35ca38
--- /dev/null
+++ b/external/CLI11/CHANGELOG.md
@@ -0,0 +1,701 @@
+# Changelog
+
+## Version 2.1: Names and callbacks
+
+The name restrictions for options and subcommands are now much looser, allowing
+a wider variety of characters than before, even spaces can be used (use quotes
+to include a space in most shells). The default configuration parser was
+improved, allowing your configuration to sit in a larger file. And option
+callbacks have a few new settings, allowing them to be run even if the option
+is not passed, or every time the option is parsed.
+
+* Option/subcommand name restrictions have been relaxed. Most characters are now allowed. [#627][]
+* The config parser can accept streams, specify a specific section, and inline comment characters are supported [#630][]
+* `force_callback` & `trigger_on_parse` added, allowing a callback to always run on parse even if not present or every time the option is parsed [#631][]
+* Bugfix(cmake): Only add `CONFIGURE_DEPENDS` if CLI11 is the main project [#633][]
+* Bugfix(cmake): Ensure the cmake/pkg-config files install to a arch independent path [#635][]
+* Bugfix: The single header file generation was missing the include guard. [#620][]
+
+[#620]: https://github.com/CLIUtils/CLI11/pull/620
+[#627]: https://github.com/CLIUtils/CLI11/pull/627
+[#630]: https://github.com/CLIUtils/CLI11/pull/630
+[#631]: https://github.com/CLIUtils/CLI11/pull/631
+[#633]: https://github.com/CLIUtils/CLI11/pull/633
+[#635]: https://github.com/CLIUtils/CLI11/pull/635
+
+### Version 2.1.1: Quick Windows fix
+
+* A collision with `min`/`max` macros on Windows has been fixed. [#642][]
+* Tests pass with Boost again [#646][]
+* Running the pre-commit hooks in development no longer requires docker for clang-format [#647][]
+
+[#642]: https://github.com/CLIUtils/CLI11/pull/642
+[#646]: https://github.com/CLIUtils/CLI11/pull/646
+[#647]: https://github.com/CLIUtils/CLI11/pull/647
+
+## Version 2.1.2: Better subproject builds
+
+* Use `main` for the main branch of the repository [#657][]
+* Bugfix(cmake): Enforce at least C++11 when using CMake target [#656][]
+* Build: Don't run doxygen and CTest includes if a submodule [#656][]
+* Build: Avoid a warning on CMake 3.22 [#656][]
+* Build: Support compiling the tests with an external copy of Catch2 [#653][]
+
+[#653]: https://github.com/CLIUtils/CLI11/pull/653
+[#656]: https://github.com/CLIUtils/CLI11/pull/656
+[#657]: https://github.com/CLIUtils/CLI11/pull/657
+
+## Version 2.0: Simplification
+
+This version focuses on cleaning up deprecated functionality, and some minor
+default changes. The config processing is TOML compliant now. Atomics and
+complex numbers are directly supported, along with other container
+improvements. A new version flag option has finally been added. Subcommands are
+significantly improved with new features and bugfixes for corner cases.  This
+release contains a lot of backend cleanup, including a complete overhaul of the
+testing system and single file generation system.
+
+* Built-in config format is TOML compliant now [#435][]
+  * Support multiline TOML [#528][]
+  * Support for configurable quotes [#599][]
+  * Support short/positional options in config mode [#443][]
+* More powerful containers, support for `%%` separator [#423][]
+* Support atomic types [#520][] and complex types natively [#423][]
+* Add a type validator `CLI::TypeValidator<TYPE>` [#526][]
+* Add a version flag easily [#452][], with help message [#601][]
+* Support `->silent()` on subcommands. [#529][]
+* Add alias section to help for subcommands [#545][]
+* Allow quotes to specify a program name [#605][]
+* Backend: redesigned MakeSingleFiles to have a higher level of manual control, to support future features. [#546][]
+* Backend: moved testing from GTest to Catch2 [#574][]
+* Bugfix: avoid duplicated and missed calls to the final callback [#584][]
+* Bugfix: support embedded newlines in more places [#592][]
+* Bugfix: avoid listing helpall as a required flag [#530][]
+* Bugfix: avoid a clash with WINDOWS define [#563][]
+* Bugfix: the help flag didn't get processed when a config file was required [#606][]
+* Bugfix: fix description of non-configurable subcommands in config [#604][]
+* Build: support pkg-config [#523][]
+
+> ### Converting from CLI11 1.9
+>
+> * Removed deprecated set commands, use validators instead. [#565][]
+> * The final "defaulted" bool has been removed, use `->capture_default_str()`
+>   instead. Use `app.option_defaults()->always_capture_default()` to set this for
+>   all future options. [#597][]
+> * Use `add_option` on a complex number instead of `add_complex`, which has been removed.
+
+[#423]: https://github.com/CLIUtils/CLI11/pull/423
+[#435]: https://github.com/CLIUtils/CLI11/pull/435
+[#443]: https://github.com/CLIUtils/CLI11/pull/443
+[#452]: https://github.com/CLIUtils/CLI11/pull/452
+[#520]: https://github.com/CLIUtils/CLI11/pull/520
+[#523]: https://github.com/CLIUtils/CLI11/pull/523
+[#526]: https://github.com/CLIUtils/CLI11/pull/526
+[#528]: https://github.com/CLIUtils/CLI11/pull/528
+[#529]: https://github.com/CLIUtils/CLI11/pull/529
+[#530]: https://github.com/CLIUtils/CLI11/pull/530
+[#545]: https://github.com/CLIUtils/CLI11/pull/545
+[#546]: https://github.com/CLIUtils/CLI11/pull/546
+[#563]: https://github.com/CLIUtils/CLI11/pull/563
+[#565]: https://github.com/CLIUtils/CLI11/pull/565
+[#574]: https://github.com/CLIUtils/CLI11/pull/574
+[#584]: https://github.com/CLIUtils/CLI11/pull/584
+[#592]: https://github.com/CLIUtils/CLI11/pull/592
+[#597]: https://github.com/CLIUtils/CLI11/pull/597
+[#599]: https://github.com/CLIUtils/CLI11/pull/599
+[#601]: https://github.com/CLIUtils/CLI11/pull/601
+[#604]: https://github.com/CLIUtils/CLI11/pull/604
+[#605]: https://github.com/CLIUtils/CLI11/pull/605
+[#606]: https://github.com/CLIUtils/CLI11/pull/606
+
+## Version 1.9: Config files and cleanup
+
+Config file handling was revamped to fix common issues, and now supports reading [TOML](https://github.com/toml-lang/toml).
+
+Adding options is significantly more powerful with support for things like
+`std::tuple` and `std::array`, including with transforms. Several new
+configuration options were added to facilitate a wider variety of apps.  GCC
+4.7 is no longer supported.
+
+* Config files refactored, supports TOML (may become default output in 2.0) [#362][]
+* Added two template parameter form of `add_option`, allowing `std::optional` to be supported without a special import [#285][]
+* `string_view` now supported in reasonable places [#300][], [#285][]
+* `immediate_callback`, `final_callback`, and `parse_complete_callback` added to support controlling the App callback order [#292][], [#313][]
+* Multiple positional arguments maintain order if `positionals_at_end` is set. [#306][]
+* Pair/tuple/array now supported, and validators indexed to specific components in the objects [#307][], [#310][]
+* Footer callbacks supported [#309][]
+* Subcommands now support needs (including nameless subcommands) [#317][]
+* More flexible type size, more useful `add_complex` [#325][], [#370][]
+* Added new validators `CLI::NonNegativeNumber` and `CLI::PositiveNumber` [#342][]
+* Transform now supports arrays [#349][]
+* Option groups can be hidden [#356][]
+* Add `CLI::deprecate_option` and `CLI::retire_option` functions [#358][]
+* More flexible and safer Option `default_val` [#387][]
+* Backend: Cleaner type traits [#286][]
+* Backend: File checking updates [#341][]
+* Backend: Using pre-commit to format, checked in GitHub Actions [#336][]
+* Backend: Clang-tidy checked again, CMake option now `CL11_CLANG_TIDY` [#390][]
+* Backend: Warning cleanup, more checks from klocwork [#350][], Effective C++ [#354][], clang-tidy [#360][], CUDA NVCC [#365][], cross compile [#373][], sign conversion [#382][], and cpplint [#400][]
+* Docs: CLI11 Tutorial now hosted in the same repository [#304][], [#318][], [#374][]
+* Bugfix: Fixed undefined behavior in `checked_multiply` [#290][]
+* Bugfix: `->check()` was adding the name to the wrong validator [#320][]
+* Bugfix: Resetting config option works properly [#301][]
+* Bugfix: Hidden flags were showing up in error printout [#333][]
+* Bugfix: Enum conversion no longer broken if stream operator added [#348][]
+* Build: The meson build system supported [#299][]
+* Build: GCC 4.7 is no longer supported, due mostly to GoogleTest. GCC 4.8+ is now required. [#160][]
+* Build: Restructured significant portions of CMake build system [#394][]
+
+> ### Converting from CLI11 1.8
+>
+> * Some deprecated methods dropped
+>   * `add_set*` should be replaced with `->check`/`->transform` and `CLI::IsMember` since 1.8
+>   * `get_defaultval` was replaced by `get_default_str`  in 1.8
+> * The true/false 4th argument to `add_option` is expected to be removed in 2.0, use `->capture_default_str()` since 1.8
+
+[#160]: https://github.com/CLIUtils/CLI11/pull/160
+[#285]: https://github.com/CLIUtils/CLI11/pull/285
+[#286]: https://github.com/CLIUtils/CLI11/pull/286
+[#290]: https://github.com/CLIUtils/CLI11/pull/290
+[#292]: https://github.com/CLIUtils/CLI11/pull/292
+[#299]: https://github.com/CLIUtils/CLI11/pull/299
+[#300]: https://github.com/CLIUtils/CLI11/pull/300
+[#301]: https://github.com/CLIUtils/CLI11/pull/301
+[#304]: https://github.com/CLIUtils/CLI11/pull/304
+[#306]: https://github.com/CLIUtils/CLI11/pull/306
+[#307]: https://github.com/CLIUtils/CLI11/pull/307
+[#309]: https://github.com/CLIUtils/CLI11/pull/309
+[#310]: https://github.com/CLIUtils/CLI11/pull/310
+[#313]: https://github.com/CLIUtils/CLI11/pull/313
+[#317]: https://github.com/CLIUtils/CLI11/pull/317
+[#318]: https://github.com/CLIUtils/CLI11/pull/318
+[#320]: https://github.com/CLIUtils/CLI11/pull/320
+[#325]: https://github.com/CLIUtils/CLI11/pull/325
+[#333]: https://github.com/CLIUtils/CLI11/pull/333
+[#336]: https://github.com/CLIUtils/CLI11/pull/336
+[#341]: https://github.com/CLIUtils/CLI11/pull/341
+[#342]: https://github.com/CLIUtils/CLI11/pull/342
+[#348]: https://github.com/CLIUtils/CLI11/pull/348
+[#349]: https://github.com/CLIUtils/CLI11/pull/349
+[#350]: https://github.com/CLIUtils/CLI11/pull/350
+[#354]: https://github.com/CLIUtils/CLI11/pull/354
+[#356]: https://github.com/CLIUtils/CLI11/pull/356
+[#358]: https://github.com/CLIUtils/CLI11/pull/358
+[#360]: https://github.com/CLIUtils/CLI11/pull/360
+[#362]: https://github.com/CLIUtils/CLI11/pull/362
+[#365]: https://github.com/CLIUtils/CLI11/pull/365
+[#370]: https://github.com/CLIUtils/CLI11/pull/370
+[#373]: https://github.com/CLIUtils/CLI11/pull/373
+[#374]: https://github.com/CLIUtils/CLI11/pull/374
+[#382]: https://github.com/CLIUtils/CLI11/pull/382
+[#387]: https://github.com/CLIUtils/CLI11/pull/387
+[#390]: https://github.com/CLIUtils/CLI11/pull/390
+[#394]: https://github.com/CLIUtils/CLI11/pull/394
+[#400]: https://github.com/CLIUtils/CLI11/pull/400
+
+### Version 1.9.1: Backporting fixes
+
+This is a patch version that backports fixes from the development of 2.0.
+
+* Support relative inclusion [#475][]
+* Fix cases where spaces in paths could break CMake support [#471][]
+* Fix an issue with string conversion [#421][]
+* Cross-compiling improvement for Conan.io [#430][]
+* Fix option group default propagation [#450][]
+* Fix for C++20 [#459][]
+* Support compiling with RTTI off [#461][]
+
+[#421]: https://github.com/CLIUtils/CLI11/pull/421
+[#430]: https://github.com/CLIUtils/CLI11/pull/430
+[#450]: https://github.com/CLIUtils/CLI11/pull/450
+[#459]: https://github.com/CLIUtils/CLI11/pull/459
+[#461]: https://github.com/CLIUtils/CLI11/pull/461
+[#471]: https://github.com/CLIUtils/CLI11/pull/471
+[#475]: https://github.com/CLIUtils/CLI11/pull/475
+
+## Version 1.8: Transformers, default strings, and flags
+
+Set handling has been completely replaced by a new backend that works as a Validator or Transformer. This provides a single interface instead of the 16 different functions in App. It also allows ordered collections to be used, custom functions for filtering, and better help and error messages. You can also use a collection of pairs (like `std::map`) to transform the match into an output. Also new are inverted flags, which can cancel or reduce the count of flags, and can also support general flag types. A new `add_option_fn` lets you more easily program CLI11 options with the types you choose. Vector options now support a custom separator. Apps can now be composed with unnamed subcommand support. The final bool "defaults" flag when creating options has been replaced by `->capture_default_str()` (ending an old limitation in construction made this possible); the old method is still available but may be removed in future versions.
+
+* Replaced default help capture: `.add_option("name", value, "", True)` becomes `.add_option("name", value)->capture_default_str()` [#242][]
+* Added `.always_capture_default()` [#242][]
+* New `CLI::IsMember` validator replaces set validation [#222][]
+* `IsMember` also supports container of pairs, transform allows modification of result [#228][]
+* Added new Transformers, `CLI::AsNumberWithUnit` and `CLI::AsSizeValue` [#253][]
+* Much more powerful flags with different values [#211][], general types [#235][]
+* `add_option` now supports bool due to unified bool handling [#211][]
+* Support for composable unnamed subcommands [#216][]
+* Reparsing is better supported with `.remaining_for_passthrough()` [#265][]
+* Custom vector separator using `->delimiter(char)` [#209][], [#221][], [#240][]
+* Validators added for IP4 addresses and positive numbers [#210][] and numbers [#262][]
+* Minimum required Boost for optional Optionals has been corrected to 1.61 [#226][]
+* Positionals can stop options from being parsed with `app.positionals_at_end()` [#223][]
+* Added `validate_positionals` [#262][]
+* Positional parsing is much more powerful [#251][], duplicates supported [#247][]
+* Validators can be negated with `!` [#230][], and now handle tname functions [#228][]
+* Better enum support and streaming helper [#233][] and [#228][]
+* Cleanup for shadow warnings [#232][]
+* Better alignment on multiline descriptions [#269][]
+* Better support for aarch64 [#266][]
+* Respect `BUILD_TESTING` only if CLI11 is the main project; otherwise, `CLI11_TESTING` must be used [#277][]
+* Drop auto-detection of experimental optional and boost::optional; must be enabled explicitly (too fragile) [#277][] [#279][]
+
+> ### Converting from CLI11 1.7
+>
+> * `.add_option(..., true)` should be replaced by `.add_option(...)->capture_default_str()` or `app.option_defaults()->always_capture_default()` can be used
+> * `app.add_set("--name", value, {"choice1", "choice2"})` should become `app.add_option("--name", value)->check(CLI::IsMember({"choice1", "choice2"}))`
+> * The `_ignore_case` version of this can be replaced by adding `CLI::ignore_case` to the argument list in `IsMember`
+> * The `_ignore_underscore` version of this can be replaced by adding `CLI::ignore_underscore` to the argument list in `IsMember`
+> * The `_ignore_case_underscore` version of this can be replaced by adding both functions listed above to the argument list in `IsMember`
+> * If you want an exact match to the original choice after one of the modifier functions matches, use `->transform` instead of `->check`
+> * The `_mutable` versions of this can be replaced by passing a pointer or shared pointer into `IsMember`
+> * An error with sets now produces a `ValidationError` instead of a `ConversionError`
+
+[#209]: https://github.com/CLIUtils/CLI11/pull/209
+[#210]: https://github.com/CLIUtils/CLI11/pull/210
+[#211]: https://github.com/CLIUtils/CLI11/pull/211
+[#216]: https://github.com/CLIUtils/CLI11/pull/216
+[#221]: https://github.com/CLIUtils/CLI11/pull/221
+[#222]: https://github.com/CLIUtils/CLI11/pull/222
+[#223]: https://github.com/CLIUtils/CLI11/pull/223
+[#226]: https://github.com/CLIUtils/CLI11/pull/226
+[#228]: https://github.com/CLIUtils/CLI11/pull/228
+[#230]: https://github.com/CLIUtils/CLI11/pull/230
+[#232]: https://github.com/CLIUtils/CLI11/pull/232
+[#233]: https://github.com/CLIUtils/CLI11/pull/233
+[#235]: https://github.com/CLIUtils/CLI11/pull/235
+[#240]: https://github.com/CLIUtils/CLI11/pull/240
+[#242]: https://github.com/CLIUtils/CLI11/pull/242
+[#247]: https://github.com/CLIUtils/CLI11/pull/247
+[#251]: https://github.com/CLIUtils/CLI11/pull/251
+[#253]: https://github.com/CLIUtils/CLI11/pull/253
+[#262]: https://github.com/CLIUtils/CLI11/pull/262
+[#265]: https://github.com/CLIUtils/CLI11/pull/265
+[#266]: https://github.com/CLIUtils/CLI11/pull/266
+[#269]: https://github.com/CLIUtils/CLI11/pull/269
+[#277]: https://github.com/CLIUtils/CLI11/pull/277
+[#279]: https://github.com/CLIUtils/CLI11/pull/279
+
+## Version 1.7: Parse breakup
+
+The parsing procedure now maps much more sensibly to complex, nested subcommand structures. Each phase of the parsing happens on all subcommands before moving on with the next phase of the parse. This allows several features, like required environment variables, to work properly even through subcommand boundaries.
+Passing the same subcommand multiple times is better supported. Several new features were added as well, including Windows style option support, parsing strings directly, and ignoring underscores in names. Adding a set that you plan to change later must now be done with `add_mutable_set`.
+
+* Support Windows style options with `->allow_windows_style_options`. [#187][] On by default on Windows. [#190][]
+* Added `parse(string)` to split up and parse a command-line style string directly. [#186][]
+* Added `ignore_underscore` and related functions, to ignore underscores when matching names. [#185][]
+* The default INI Config will now add quotes to strings with spaces [#195][]
+* The default message now will mention the help-all flag also if present [#197][]
+* Added `->description` to set Option descriptions [#199][]
+* Mutating sets (introduced in Version 1.6) now have a clear add method, `add_mutable_set*`, since the set reference should not expire [#200][]
+* Subcommands now track how many times they were parsed in a parsing process. `count()` with no arguments will return the number of times a subcommand was encountered. [#178][]
+* Parsing is now done in phases: `shortcurcuits`, `ini`, `env`, `callbacks`, and `requirements`; all subcommands complete a phase before moving on. [#178][]
+* Calling parse multiple times is now officially supported without `clear` (automatic). [#178][]
+* Dropped the mostly undocumented `short_circuit` property, as help flag parsing is a bit more complex, and the default callback behavior of options now works properly. [#179][]
+* Use the standard `BUILD_TESTING` over `CLI11_TESTING` if defined [#183][]
+* Cleanup warnings [#191][]
+* Remove deprecated names: `set_footer`, `set_name`, `set_callback`, and `set_type_name`. Use without the `set_` instead. [#192][]
+
+> ### Converting from CLI11 1.6
+>
+> * `->short_circuit()` is no longer needed, just remove it if you were using it - raising an exception will happen in the proper place now without it.
+> * `->add_set*` becomes `->add_mutable_set*` if you were using the editable set feature
+> * `footer`, `name`, `callback`, and `type_name` must be used instead of the `set_*` versions (deprecated previously).
+
+[#178]: https://github.com/CLIUtils/CLI11/pull/178
+[#183]: https://github.com/CLIUtils/CLI11/pull/183
+[#185]: https://github.com/CLIUtils/CLI11/pull/185
+[#186]: https://github.com/CLIUtils/CLI11/pull/186
+[#187]: https://github.com/CLIUtils/CLI11/pull/187
+[#190]: https://github.com/CLIUtils/CLI11/pull/190
+[#191]: https://github.com/CLIUtils/CLI11/pull/191
+[#192]: https://github.com/CLIUtils/CLI11/pull/192
+[#197]: https://github.com/CLIUtils/CLI11/pull/197
+[#195]: https://github.com/CLIUtils/CLI11/issues/195
+[#199]: https://github.com/CLIUtils/CLI11/pull/199
+[#200]: https://github.com/CLIUtils/CLI11/pull/200
+
+### Version 1.7.1: Quick patch
+
+This version provides a quick patch for a (correct) warning from GCC 8 for the windows options code.
+
+* Fix for Windows style option parsing [#201][]
+* Improve `add_subcommand` when throwing an exception [#204][]
+* Better metadata for Conan package [#202][]
+
+[#201]: https://github.com/CLIUtils/CLI11/pull/201
+[#202]: https://github.com/CLIUtils/CLI11/pull/202
+[#204]: https://github.com/CLIUtils/CLI11/pull/204
+
+## Version 1.6: Formatting help
+
+Added a new formatting system [#109][]. You can now set the formatter on Apps. This has also simplified the internals of Apps and Options a bit by separating most formatting code.
+
+* Added `CLI::Formatter` and `formatter` slot for apps, inherited.
+* `FormatterBase` is the minimum required.
+* `FormatterLambda` provides for the easy addition of an arbitrary function.
+* Added `help_all` support (not added by default).
+
+Changes to the help system (most normal users will not notice this):
+
+* Renamed `single_name` to `get_name(false, false)` (the default).
+* The old `get_name()` is now `get_name(false, true)`.
+* The old `get_pname()` is now `get_name(true, false)`.
+* Removed `help_*` functions.
+* Protected function `_has_help_positional` removed.
+* `format_help` can now be chained.
+* Added getters for the missing parts of options (help no longer uses any private parts).
+* Help flags now use new `short_circuit` property to simplify parsing. [#121][]
+
+New for Config file reading and writing [#121][]:
+
+* Overridable, bidirectional Config.
+* ConfigINI provided and used by default.
+* Renamed ini to config in many places.
+* Has `config_formatter()` and `get_config_formatter()`.
+* Dropped prefix argument from `config_to_str`.
+* Added `ConfigItem`.
+* Added an example of a custom config format using [nlohmann/json][]. [#138][]
+
+Validators are now much more powerful [#118][], all built in validators upgraded to the new form:
+
+* A subclass of `CLI::Validator` is now also accepted.
+* They now can set the type name to things like `PATH` and `INT in [1-4]`.
+* Validators can be combined with `&` and `|`.
+* Old form simple validators are still accepted.
+
+Other changes:
+
+* Fixing `parse(args)`'s `args` setting and ordering after parse. [#141][]
+* Replaced `set_custom_option` with `type_name` and `type_size` instead of `set_custom_option`. Methods return `this`. [#136][]
+* Dropped `set_` on Option's `type_name`, `default_str`, and `default_val`. [#136][]
+* Removed `set_` from App's `failure_message`, `footer`, `callback`, and `name`. [#136][]
+* Fixed support `N<-1` for `type_size`. [#140][]
+* Added `->each()` to make adding custom callbacks easier. [#126][]
+* Allow empty options `add_option("-n",{})` to be edited later with `each` [#142][]
+* Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering.
+* Added `get_groups()` to get groups.
+* Better support for manual options with `get_option`, `set_results`, and `empty`. [#119][]
+* `lname` and `sname` have getters, added `const get_parent`. [#120][]
+* Using `add_set` will now capture L-values for sets, allowing further modification. [#113][]
+* Dropped duplicate way to run `get_type_name` (`get_typeval`).
+* Removed `requires` in favor of `needs` (deprecated in last version). [#112][]
+* Const added to argv. [#126][]
+
+Backend and testing changes:
+
+* Internally, `type_name` is now a lambda function; for sets, this reads the set live. [#116][]
+* Cleaner tests without `app.reset()` (and `reset` is now `clear`). [#141][]
+* Better CMake policy handling. [#110][]
+* Includes are properly sorted. [#120][]
+* Testing (only) now uses submodules. [#111][]
+
+[#109]: https://github.com/CLIUtils/CLI11/pull/109
+[#110]: https://github.com/CLIUtils/CLI11/pull/110
+[#111]: https://github.com/CLIUtils/CLI11/pull/111
+[#112]: https://github.com/CLIUtils/CLI11/pull/112
+[#113]: https://github.com/CLIUtils/CLI11/issues/113
+[#116]: https://github.com/CLIUtils/CLI11/pull/116
+[#118]: https://github.com/CLIUtils/CLI11/pull/118
+[#119]: https://github.com/CLIUtils/CLI11/pull/119
+[#120]: https://github.com/CLIUtils/CLI11/pull/120
+[#121]: https://github.com/CLIUtils/CLI11/pull/121
+[#126]: https://github.com/CLIUtils/CLI11/pull/126
+[#136]: https://github.com/CLIUtils/CLI11/pull/136
+[#138]: https://github.com/CLIUtils/CLI11/pull/138
+[#140]: https://github.com/CLIUtils/CLI11/pull/140
+[#141]: https://github.com/CLIUtils/CLI11/pull/141
+[#142]: https://github.com/CLIUtils/CLI11/pull/142
+
+[nlohmann/json]: https://github.com/nlohmann/json
+
+### Version 1.6.1: Platform fixes
+
+This version provides a few fixes for special cases, such as mixing with `Windows.h` and better defaults
+for systems like Hunter. The one new feature is the ability to produce "branded" single file output for
+providing custom namespaces or custom macro names.
+
+* Added fix and test for including Windows.h [#145][]
+* No longer build single file by default if main project, supports systems stuck on Python 2.6 [#149][], [#151][]
+* Branding support for single file output [#150][]
+
+[#145]: https://github.com/CLIUtils/CLI11/pull/145
+[#149]: https://github.com/CLIUtils/CLI11/pull/149
+[#150]: https://github.com/CLIUtils/CLI11/pull/150
+[#151]: https://github.com/CLIUtils/CLI11/pull/151
+
+### Version 1.6.2: Help-all
+
+This version fixes some formatting bugs with help-all. It also adds fixes for several warnings, including an experimental optional error on Clang 7. Several smaller fixes.
+
+* Fixed help-all formatting [#163][]
+  * Printing help-all on nested command now fixed (App)
+  * Missing space after help-all restored (Default formatter)
+  * More detail printed on help all (Default formatter)
+  * Help-all subcommands get indented with inner blank lines removed (Default formatter)
+  * `detail::find_and_replace` added to utilities
+* Fixed CMake install as subproject with `CLI11_INSTALL` flag. [#156][]
+* Fixed warning about local variable hiding class member with MSVC [#157][]
+* Fixed compile error with default settings on Clang 7 and libc++ [#158][]
+* Fixed special case of `--help` on subcommands (general fix planned for 1.7) [#168][]
+* Removing an option with links  [#179][]
+
+[#156]: https://github.com/CLIUtils/CLI11/issues/156
+[#157]: https://github.com/CLIUtils/CLI11/issues/157
+[#158]: https://github.com/CLIUtils/CLI11/issues/158
+[#163]: https://github.com/CLIUtils/CLI11/pull/163
+[#168]: https://github.com/CLIUtils/CLI11/issues/168
+[#179]: https://github.com/CLIUtils/CLI11/pull/179
+
+## Version 1.5: Optionals
+
+This version introduced support for optionals, along with clarification and examples of custom conversion overloads. Enums now have been dropped from the automatic conversion system, allowing explicit protection for out-of-range ints (or a completely custom conversion). This version has some internal cleanup and improved support for the newest compilers. Several bugs were fixed, as well.
+
+Note: This is the final release with `requires`, please switch to `needs`.
+
+* Fix unlimited short options eating two values before checking for positionals when no space present [#90][]
+* Symmetric exclude text when excluding options, exclude can be called multiple times [#64][]
+* Support for `std::optional`, `std::experimental::optional`, and `boost::optional` added if `__has_include` is supported [#95][]
+* All macros/CMake variables now start with `CLI11_` instead of just `CLI_` [#95][]
+* The internal stream was not being cleared before use in some cases. Fixed. [#95][]
+* Using an enum now requires explicit conversion overload [#97][]
+* The separator `--` now is removed when it ends unlimited arguments [#100][]
+
+Other, non-user facing changes:
+
+* Added `Macros.hpp` with better C++ mode discovery [#95][]
+* Deprecated macros added for all platforms
+* C++17 is now tested on supported platforms [#95][]
+* Informational printout now added to CTest [#95][]
+* Better single file generation [#95][]
+* Added support for GTest on MSVC 2017 (but not in C++17 mode, will need next version of GTest)
+* Types now have a specific size, separate from the expected number - cleaner and more powerful internally [#92][]
+* Examples now run as part of testing [#99][]
+
+[#64]: https://github.com/CLIUtils/CLI11/issues/64
+[#90]: https://github.com/CLIUtils/CLI11/issues/90
+[#92]: https://github.com/CLIUtils/CLI11/issues/92
+[#95]: https://github.com/CLIUtils/CLI11/pull/95
+[#97]: https://github.com/CLIUtils/CLI11/pull/97
+[#99]: https://github.com/CLIUtils/CLI11/pull/99
+[#100]: https://github.com/CLIUtils/CLI11/pull/100
+
+### Version 1.5.1: Access
+
+This patch release adds better access to the App programmatically, to assist with writing custom converters to other formats. It also improves the help output, and uses a new feature in CLI11 1.5 to fix an old "quirk" in the way unlimited options and positionals interact.
+
+* Make mixing unlimited positionals and options more intuitive [#102][]
+* Add missing getters `get_options` and `get_description` to App [#105][]
+* The app name now can be set, and will override the auto name if present [#105][]
+* Add `(REQUIRED)` for required options [#104][]
+* Print simple name for Needs/Excludes [#104][]
+* Use Needs instead of Requires in help print [#104][]
+* Groups now are listed in the original definition order [#106][]
+
+[#102]: https://github.com/CLIUtils/CLI11/issues/102
+[#104]: https://github.com/CLIUtils/CLI11/pull/104
+[#105]: https://github.com/CLIUtils/CLI11/pull/105
+[#106]: https://github.com/CLIUtils/CLI11/pull/106
+
+### Version 1.5.2: LICENSE in single header mode
+
+This is a quick patch release that makes LICENSE part of the single header file, making it easier to include. Minor cleanup from codacy. No significant code changes from 1.5.1.
+
+### Version 1.5.3: Compiler compatibility
+
+This version fixes older AppleClang compilers by removing the optimization for casting. The minimum version of Boost Optional supported has been clarified to be 1.58. CUDA 7.0 NVCC is now supported.
+
+### Version 1.5.4: Optionals
+
+This version fixes the optional search in the single file version; some macros were not yet defined when it did the search. You can define the `CLI11_*_OPTIONAL` macros to 0 if needed to eliminate the search.
+
+## Version 1.4: More feedback
+
+This version adds lots of smaller fixes and additions after the refactor in version 1.3. More ways to download and use CLI11 in CMake have been added. INI files have improved support.
+
+* Lexical cast is now more strict than before [#68][] and fails on overflow [#84][]
+* Added `get_parent()` to access the parent from a subcommand
+* Added `ExistingPath` validator  [#73][]
+* `app.allow_ini_extras()` added to allow extras in INI files [#70][]
+* Multiline INI comments now supported
+* Descriptions can now be written with `config_to_str` [#66][]
+* Double printing of error message fixed [#77][]
+* Renamed `requires` to `needs` to avoid C++20 keyword [#75][], [#82][]
+* MakeSingleHeader now works if outside of git [#78][]
+* Adding install support for CMake [#79][], improved support for `find_package` [#83][], [#84][]
+* Added support for Conan.io [#83][]
+
+[#70]: https://github.com/CLIUtils/CLI11/issues/70
+[#75]: https://github.com/CLIUtils/CLI11/issues/75
+
+[#84]: https://github.com/CLIUtils/CLI11/pull/84
+[#83]: https://github.com/CLIUtils/CLI11/pull/83
+[#82]: https://github.com/CLIUtils/CLI11/pull/82
+[#79]: https://github.com/CLIUtils/CLI11/pull/79
+[#78]: https://github.com/CLIUtils/CLI11/pull/78
+[#77]: https://github.com/CLIUtils/CLI11/pull/77
+[#73]: https://github.com/CLIUtils/CLI11/pull/73
+[#68]: https://github.com/CLIUtils/CLI11/pull/68
+[#66]: https://github.com/CLIUtils/CLI11/pull/66
+
+## Version 1.3: Refactor
+
+This version focused on refactoring several key systems to ensure correct behavior in the interaction of different settings. Most caveats about
+features only working on the main App have been addressed, and extra arguments have been reworked. Inheritance
+of defaults makes configuring CLI11 much easier without having to subclass. Policies add new ways to handle multiple arguments to match your
+favorite CLI programs. Error messages and help messages are better and more flexible. Several bugs and odd behaviors in the parser have been fixed.
+
+* Added a version macro, `CLI11_VERSION`, along with `*_MAJOR`, `*_MINOR`, and `*_PATCH`, for programmatic access to the version.
+* Reworked the way defaults are set and inherited; explicit control given to user with `->option_defaults()` [#48](https://github.com/CLIUtils/CLI11/pull/48)
+* Hidden options now are based on an empty group name, instead of special "hidden" keyword [#48](https://github.com/CLIUtils/CLI11/pull/48)
+* `parse` no longer returns (so `CLI11_PARSE` is always usable) [#37](https://github.com/CLIUtils/CLI11/pull/37)
+* Added `remaining()` and `remaining_size()` [#37](https://github.com/CLIUtils/CLI11/pull/37)
+* `allow_extras` and `prefix_command` are now valid on subcommands [#37](https://github.com/CLIUtils/CLI11/pull/37)
+* Added `take_last` to only take last value passed [#40](https://github.com/CLIUtils/CLI11/pull/40)
+* Added `multi_option_policy` and shortcuts to provide more control than just a take last policy [#59](https://github.com/CLIUtils/CLI11/pull/59)
+* More detailed error messages in a few cases [#41](https://github.com/CLIUtils/CLI11/pull/41)
+* Footers can be added to help [#42](https://github.com/CLIUtils/CLI11/pull/42)
+* Help flags are easier to customize [#43](https://github.com/CLIUtils/CLI11/pull/43)
+* Subcommand now support groups [#46](https://github.com/CLIUtils/CLI11/pull/46)
+* `CLI::RuntimeError` added, for easy exit with error codes [#45](https://github.com/CLIUtils/CLI11/pull/45)
+* The clang-format script is now no longer "hidden" [#48](https://github.com/CLIUtils/CLI11/pull/48)
+* The order is now preserved for subcommands (list and callbacks) [#49](https://github.com/CLIUtils/CLI11/pull/49)
+* Tests now run individually, utilizing CMake 3.10 additions if possible [#50](https://github.com/CLIUtils/CLI11/pull/50)
+* Failure messages are now customizable, with a shorter default [#52](https://github.com/CLIUtils/CLI11/pull/52)
+* Some improvements to error codes [#53](https://github.com/CLIUtils/CLI11/pull/53)
+* `require_subcommand` now offers a two-argument form and negative values on the one-argument form are more useful [#51](https://github.com/CLIUtils/CLI11/pull/51)
+* Subcommands no longer match after the max required number is obtained [#51](https://github.com/CLIUtils/CLI11/pull/51)
+* Unlimited options no longer prioritize over remaining/unlimited positionals [#51](https://github.com/CLIUtils/CLI11/pull/51)
+* Added `->transform` which modifies the string parsed [#54](https://github.com/CLIUtils/CLI11/pull/54)
+* Changed of API in validators to `void(std::string &)` (const for users), throwing providing nicer errors [#54](https://github.com/CLIUtils/CLI11/pull/54)
+* Added `CLI::ArgumentMismatch` [#56](https://github.com/CLIUtils/CLI11/pull/56) and fixed missing failure if one arg expected [#55](https://github.com/CLIUtils/CLI11/issues/55)
+* Support for minimum unlimited expected arguments [#56](https://github.com/CLIUtils/CLI11/pull/56)
+* Single internal arg parse function [#56](https://github.com/CLIUtils/CLI11/pull/56)
+* Allow options to be disabled from INI file, rename `add_config` to `set_config` [#60](https://github.com/CLIUtils/CLI11/pull/60)
+
+> ### Converting from CLI11 1.2
+>
+> * `app.parse` no longer returns a vector. Instead, use `app.remaining(true)`.
+> * `"hidden"` is no longer a special group name, instead use `""`
+> * Validators API has changed to return an error string; use `.empty()` to get the old bool back
+> * Use `.set_help_flag` instead of accessing the help pointer directly (discouraged, but not removed yet)
+> * `add_config` has been renamed to `set_config`
+> * Errors thrown in some cases are slightly more specific
+
+## Version 1.2: Stability
+
+This release focuses on making CLI11 behave properly in corner cases, and with config files on the command line. This includes fixes for a variety of reported issues. A few features were added to make life easier, as well; such as a new flag callback and a macro for the parse command.
+
+* Added functional form of flag [#33](https://github.com/CLIUtils/CLI11/pull/33), automatic on C++14
+* Fixed Config file search if passed on command line [#30](https://github.com/CLIUtils/CLI11/issues/30)
+* Added `CLI11_PARSE(app, argc, argv)` macro for simple parse commands (does not support returning arg)
+* The name string can now contain spaces around commas [#29](https://github.com/CLIUtils/CLI11/pull/29)
+* `set_default_str` now only sets string, and `set_default_val` will evaluate the default string given [#26](https://github.com/CLIUtils/CLI11/issues/26)
+* Required positionals now take priority over subcommands [#23](https://github.com/CLIUtils/CLI11/issues/23)
+* Extra requirements enforced by Travis
+
+## Version 1.1: Feedback
+
+This release incorporates feedback from the release announcement. The examples are slowly being expanded, some corner cases improved, and some new functionality for tricky parsing situations.
+
+* Added simple support for enumerations, allow non-printable objects [#12](https://github.com/CLIUtils/CLI11/issues/12)
+* Added `app.parse_order()` with original parse order ([#13](https://github.com/CLIUtils/CLI11/issues/13), [#16](https://github.com/CLIUtils/CLI11/pull/16))
+* Added `prefix_command()`, which is like `allow_extras` but instantly stops and returns. ([#8](https://github.com/CLIUtils/CLI11/issues/8), [#17](https://github.com/CLIUtils/CLI11/pull/17))
+* Removed Windows warning ([#10](https://github.com/CLIUtils/CLI11/issues/10), [#20](https://github.com/CLIUtils/CLI11/pull/20))
+* Some improvements to CMake, detect Python and no dependencies on Python 2 (like Python 3) ([#18](https://github.com/CLIUtils/CLI11/issues/18), [#21](https://github.com/CLIUtils/CLI11/pull/21))
+
+## Version 1.0: Official release
+
+This is the first stable release for CLI11. Future releases will try to remain backward compatible and will follow semantic versioning if possible. There were a few small changes since version 0.9:
+
+* Cleanup using `clang-tidy` and `clang-format`
+* Small improvements to Timers, easier to subclass Error
+* Move to 3-Clause BSD license
+
+## Version 0.9: Polish
+
+This release focused on cleaning up the most exotic compiler warnings, fixing a few oddities of the config parser, and added a more natural method to check subcommands.
+
+* Better CMake named target (CLI11)
+* More warnings added, fixed
+* Ini output now includes `=false` when `default_also` is true
+* Ini no longer lists the help pointer
+* Added test for inclusion in multiple files and linking, fixed issues (rarely needed for CLI, but nice for tools)
+* Support for complex numbers
+* Subcommands now test true/false directly or with `->parsed()`, cleaner parse
+
+## Version 0.8: CLIUtils
+
+This release moved the repository to the CLIUtils main organization.
+
+* Moved to CLIUtils on GitHub
+* Fixed docs build and a few links
+
+## Version 0.7: Code coverage 100%
+
+Lots of small bugs fixed when adding code coverage, better in edge cases. Much more powerful ini support.
+
+* Allow comments in ini files (lines starting with `;`)
+* Ini files support flags, vectors, subcommands
+* Added CodeCov code coverage reports
+* Lots of small bugfixes related to adding tests to increase coverage to 100%
+* Error handling now uses scoped enum in errors
+* Reparsing rules changed a little to accommodate Ini files. Callbacks are now called when parsing INI, and reset any time results are added.
+* Adding extra utilities in full version only, `Timer` (not needed for parsing, but useful for general CLI applications).
+* Better support for custom `add_options` like functions.
+
+## Version 0.6: Cleanup
+
+Lots of cleanup and docs additions made it into this release. Parsing is simpler and more robust; fall through option added and works as expected; much more consistent variable names internally.
+
+* Simplified parsing to use `vector<string>` only
+* Fixed fallthrough, made it optional as well (default: off): `.fallthrough()`.
+* Added string versions of `->requires()` and `->excludes()` for consistency.
+* Renamed protected members for internal consistency, grouped docs.
+* Added the ability to add a number to `.require_subcommand()`.
+
+## Version 0.5: Windows support
+
+* Allow `Hidden` options.
+* Throw `OptionAlreadyAdded` errors for matching subcommands or options, with ignore-case included, tests
+* `->ignore_case()` added to subcommands, options, and `add_set_ignore_case`. Subcommands inherit setting from parent App on creation.
+* Subcommands now can be "chained", that is, left over arguments can now include subcommands that then get parsed. Subcommands are now a list (`get_subcommands`). Added `got_subcommand(App_or_name)` to check for subcommands.
+* Added `.allow_extras()` to disable error on failure. Parse returns a vector of leftover options. Renamed error to `ExtrasError`, and now triggers on extra options too.
+* Added `require_subcommand` to `App`, to simplify forcing subcommands. Do **not** do `add_subcommand()->require_subcommand`, since that is the subcommand, not the main `App`.
+* Added printout of ini file text given parsed options, skips flags.
+* Support for quotes and spaces in ini files
+* Fixes to allow support for Windows (added Appveyor) (Uses `-`, not `/` syntax)
+
+## Version 0.4: Ini support
+
+* Updates to help print
+* Removed `run`, please use `parse` unless you subclass and add it
+* Supports ini files mixed with command line, tested
+* Added Range for further Plumbum compatibility
+* Added function to print out ini file
+
+## Version 0.3: Plumbum compatibility
+
+* Added `->requires`, `->excludes`, and `->envname` from [Plumbum](http://plumbum.readthedocs.io/en/latest/)
+* Supports `->mandatory` from Plumbum
+* More tests for help strings, improvements in formatting
+* Support type and set syntax in positionals help strings
+* Added help groups, with `->group("name")` syntax
+* Added initial support for ini file reading with `add_config` option.
+* Supports GCC 4.7 again
+* Clang 3.5 now required for tests due to googlemock usage, 3.4 should still work otherwise
+* Changes `setup` for an explicit help bool in constructor/`add_subcommand`
+
+## Version 0.2: Leaner and meaner
+
+* Moved to simpler syntax, where `Option` pointers are returned and operated on
+* Removed `make_` style options
+* Simplified Validators, now only requires `->check(function)`
+* Removed Combiners
+* Fixed pointers to Options, stored in `unique_ptr` now
+* Added `Option_p` and `App_p`, mostly for internal use
+* Startup sequence, including help flag, can be modified by subclasses
+
+## Version 0.1: First release
+
+First release before major cleanup. Still has make syntax and combiners; very clever syntax but not the best or most commonly expected way to work.
diff --git a/external/CLI11/CLI11.CPack.Description.txt b/external/CLI11/CLI11.CPack.Description.txt
new file mode 100644
index 0000000..9a01891
--- /dev/null
+++ b/external/CLI11/CLI11.CPack.Description.txt
@@ -0,0 +1 @@
+CLI11 provides all the features you expect in a powerful command line parser, with a beautiful, minimal syntax and no dependencies beyond C++11. It is header only, and comes in a single file form for easy inclusion in projects. It is easy to use for small projects, but powerful enough for complex command line projects, and can be customized for frameworks.
diff --git a/external/CLI11/CLI11.hpp.in b/external/CLI11/CLI11.hpp.in
new file mode 100644
index 0000000..9439a43
--- /dev/null
+++ b/external/CLI11/CLI11.hpp.in
@@ -0,0 +1,69 @@
+// CLI11: Version {version}
+// Originally designed by Henry Schreiner
+// https://github.com/CLIUtils/CLI11
+//
+// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts
+// from: {git}
+//
+// CLI11 {version} Copyright (c) 2017-2021 University of Cincinnati, developed by Henry
+// Schreiner under NSF AWARD 1414736. All rights reserved.
+//
+// Redistribution and use in source and binary forms of CLI11, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holder nor the names of its contributors
+//    may be used to endorse or promote products derived from this software without
+//    specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+// Standard combined includes:
+{public_includes}
+
+{version_hpp}
+
+{macros_hpp}
+
+{validators_hpp_filesystem}
+
+namespace {namespace} {{
+
+{string_tools_hpp}
+
+{error_hpp}
+
+{type_tools_hpp}
+
+{split_hpp}
+
+{config_fwd_hpp}
+
+{validators_hpp}
+
+{formatter_fwd_hpp}
+
+{option_hpp}
+
+{app_hpp}
+
+{config_hpp}
+
+{formatter_hpp}
+
+}} // namespace {namespace}
diff --git a/external/CLI11/CMakeLists.txt b/external/CLI11/CMakeLists.txt
new file mode 100644
index 0000000..1f4313f
--- /dev/null
+++ b/external/CLI11/CMakeLists.txt
@@ -0,0 +1,331 @@
+cmake_minimum_required(VERSION 3.4)
+# Note: this is a header only library. If you have an older CMake than 3.4,
+# just add the CLI11/include directory and that's all you need to do.
+
+# Make sure users don't get warnings on a tested (3.4 to 3.22) version
+# of CMake. For most of the policies, the new version is better (hence the change).
+# We don't use the 3.4...3.21 syntax because of a bug in an older MSVC's
+# built-in and modified CMake 3.11
+if(${CMAKE_VERSION} VERSION_LESS 3.22)
+  cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
+else()
+  cmake_policy(VERSION 3.22)
+endif()
+
+set(VERSION_REGEX "#define CLI11_VERSION[ \t]+\"(.+)\"")
+
+# Read in the line containing the version
+file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/Version.hpp" VERSION_STRING
+     REGEX ${VERSION_REGEX})
+
+# Pick out just the version
+string(REGEX REPLACE ${VERSION_REGEX} "\\1" VERSION_STRING "${VERSION_STRING}")
+
+# Add the project
+project(
+  CLI11
+  LANGUAGES CXX
+  VERSION ${VERSION_STRING})
+
+# Print the version number of CMake if this is the main project
+if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
+  message(STATUS "CMake ${CMAKE_VERSION}")
+
+  find_package(Doxygen)
+
+  if(CMAKE_VERSION VERSION_LESS 3.10)
+    message(STATUS "CMake 3.10+ adds Doxygen support. Update CMake to build documentation")
+  elseif(NOT Doxygen_FOUND)
+    message(STATUS "Doxygen not found, building docs has been disabled")
+  endif()
+
+  include(CTest)
+else()
+  if(NOT DEFINED BUILD_TESTING)
+    set(BUILD_TESTING OFF)
+  endif()
+endif()
+
+include(CMakeDependentOption)
+include(GNUInstallDirs)
+
+if(NOT CMAKE_VERSION VERSION_LESS 3.11)
+  include(FetchContent)
+endif()
+
+list(APPEND force-libcxx "CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\"")
+list(APPEND force-libcxx "CMAKE_SYSTEM_NAME STREQUAL \"Linux\"")
+list(APPEND force-libcxx "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME")
+
+list(APPEND build-docs "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME")
+list(APPEND build-docs "NOT CMAKE_VERSION VERSION_LESS 3.11")
+list(APPEND build-docs "Doxygen_FOUND")
+
+# Necessary to support paths with spaces, see #457
+if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/docs")
+  set(docs_EXIST TRUE)
+else()
+  set(docs_EXIST FALSE)
+endif()
+list(APPEND build-docs "docs_EXIST")
+
+if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/examples")
+  set(examples_EXIST TRUE)
+else()
+  set(examples_EXIST FALSE)
+endif()
+
+option(CLI11_WARNINGS_AS_ERRORS "Turn all warnings into errors (for CI)")
+option(CLI11_SINGLE_FILE "Generate a single header file")
+cmake_dependent_option(CLI11_SANITIZERS "Download the sanitizers CMake config" OFF
+                       "NOT CMAKE_VERSION VERSION_LESS 3.11" OFF)
+
+cmake_dependent_option(CLI11_BUILD_DOCS "Build CLI11 documentation" ON "${build-docs}" OFF)
+
+cmake_dependent_option(CLI11_BUILD_TESTS "Build CLI11 tests" ON
+                       "BUILD_TESTING;CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME" OFF)
+
+cmake_dependent_option(CLI11_BUILD_EXAMPLES "Build CLI11 examples" ON
+                       "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME;${examples_EXIST}" OFF)
+
+cmake_dependent_option(CLI11_BUILD_EXAMPLES_JSON "Build CLI11 json example" OFF
+                       "CLI11_BUILD_EXAMPLES;NOT CMAKE_VERSION VERSION_LESS 3.11" OFF)
+
+cmake_dependent_option(CLI11_SINGLE_FILE_TESTS "Duplicate all the tests for a single file build"
+                       OFF "BUILD_TESTING;CLI11_SINGLE_FILE" OFF)
+
+cmake_dependent_option(CLI11_INSTALL "Install the CLI11 folder to include during install process"
+                       ON "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME" OFF)
+
+cmake_dependent_option(
+  CLI11_FORCE_LIBCXX "Force clang to use libc++ instead of libstdc++ (Linux only)" OFF
+  "${force-libcxx}" OFF)
+
+cmake_dependent_option(
+  CLI11_CUDA_TESTS "Build the tests with NVCC to check for warnings there - requires CMake 3.9+"
+  OFF "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME" OFF)
+
+cmake_dependent_option(
+  CLI11_CLANG_TIDY "Look for and use Clang-Tidy" OFF
+  "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME;NOT CMAKE_VERSION VERSION_LESS 3.6" OFF)
+set(CLI11_CLANG_TIDY_OPTIONS
+    ""
+    CACHE STRING "Clang tidy options, such as -fix, semicolon separated")
+
+if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND NOT DEFINED CMAKE_CXX_STANDARD)
+  set(CMAKE_CXX_STANDARD 11)
+endif()
+
+if(NOT DEFINED CMAKE_CXX_EXTENSIONS)
+  set(CMAKE_CXX_EXTENSIONS OFF)
+endif()
+
+if(NOT DEFINED CMAKE_CXX_STANDARD_REQUIRED)
+  set(CMAKE_CXX_STANDARD_REQUIRED ON)
+endif()
+
+# Allow IDE's to group targets into folders
+if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
+  set_property(GLOBAL PROPERTY USE_FOLDERS ON)
+endif()
+
+# Special target that adds warnings. Is not exported.
+add_library(CLI11_warnings INTERFACE)
+
+set(unix-warnings -Wall -Wextra -pedantic -Wshadow -Wsign-conversion -Wswitch-enum)
+
+# Buggy in GCC 4.8
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9)
+  list(APPEND unix-warnings -Weffc++)
+endif()
+
+target_compile_options(
+  CLI11_warnings
+  INTERFACE $<$<BOOL:${CLI11_FORCE_LIBCXX}>:-stdlib=libc++>
+            $<$<CXX_COMPILER_ID:MSVC>:/W4
+            $<$<BOOL:${CLI11_WARNINGS_AS_ERRORS}>:/WX>>
+            $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:${unix-warnings}
+            $<$<BOOL:${CLI11_WARNINGS_AS_ERRORS}>:-Werror>>)
+
+if(NOT CMAKE_VERSION VERSION_LESS 3.13)
+  target_link_options(CLI11_warnings INTERFACE $<$<BOOL:${CLI11_FORCE_LIBCXX}>:-stdlib=libc++>)
+endif()
+
+# Allow IDE's to group targets into folders
+add_library(CLI11 INTERFACE)
+add_library(CLI11::CLI11 ALIAS CLI11) # for add_subdirectory calls
+
+# Duplicated because CMake adds the current source dir if you don't.
+target_include_directories(CLI11 INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+                                           $<INSTALL_INTERFACE:include>)
+
+if(CMAKE_VERSION VERSION_LESS 3.8)
+  # This might not be a complete list
+  target_compile_features(
+    CLI11
+    INTERFACE cxx_lambdas
+              cxx_nullptr
+              cxx_override
+              cxx_range_for
+              cxx_right_angle_brackets
+              cxx_strong_enums
+              cxx_constexpr
+              cxx_auto_type)
+else()
+  target_compile_features(CLI11 INTERFACE cxx_std_11)
+endif()
+
+# To see in IDE, headers must be listed for target
+set(header-patterns "${PROJECT_SOURCE_DIR}/include/CLI/*")
+if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND NOT CMAKE_VERSION VERSION_LESS 3.12)
+  list(INSERT header-patterns 0 CONFIGURE_DEPENDS)
+endif()
+
+file(GLOB CLI11_headers ${header-patterns})
+
+# Allow tests to be run on CUDA
+if(CLI11_CUDA_TESTS)
+  enable_language(CUDA)
+
+  # Print out warning and error numbers
+  set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcudafe --display_error_number")
+endif()
+
+# Prepare Clang-Tidy
+if(CLI11_CLANG_TIDY)
+  find_program(
+    CLANG_TIDY_EXE
+    NAMES "clang-tidy"
+    DOC "Path to clang-tidy executable" REQUIRED)
+
+  set(DO_CLANG_TIDY "${CLANG_TIDY_EXE}" ${CLI11_CLANG_TIDY_OPTIONS})
+endif()
+
+# This folder should be installed
+if(CLI11_INSTALL)
+  install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
+
+  # Make an export target
+  install(TARGETS CLI11 EXPORT CLI11Targets)
+
+  # Use find_package on the installed package
+  # Since we have no custom code, we can directly write this
+  # to Config.cmake (otherwise we'd have a custom config and would
+  # import Targets.cmake
+
+  # Add the version in a CMake readable way
+  configure_file("cmake/CLI11ConfigVersion.cmake.in" "CLI11ConfigVersion.cmake" @ONLY)
+
+  # Make version available in the install
+  install(FILES "${PROJECT_BINARY_DIR}/CLI11ConfigVersion.cmake"
+          DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/CLI11")
+
+  # Install the export target as a file
+  install(
+    EXPORT CLI11Targets
+    FILE CLI11Config.cmake
+    NAMESPACE CLI11::
+    DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/CLI11")
+
+  # Use find_package on the installed package
+  export(
+    TARGETS CLI11
+    NAMESPACE CLI11::
+    FILE CLI11Targets.cmake)
+
+  include(cmake/CLI11GeneratePkgConfig.cmake)
+
+  # Register in the user cmake package registry
+  export(PACKAGE CLI11)
+endif()
+
+if(CLI11_SINGLE_FILE)
+  # Single file test
+  if(CMAKE_VERSION VERSION_LESS 3.12)
+    find_package(PythonInterp REQUIRED)
+    add_executable(Python::Interpreter IMPORTED)
+    set_target_properties(Python::Interpreter PROPERTIES IMPORTED_LOCATION "${PYTHON_EXECUTABLE}"
+                                                         VERSION "${PYTHON_VERSION_STRING}")
+  else()
+    find_package(
+      Python
+      COMPONENTS Interpreter
+      REQUIRED)
+  endif()
+
+  file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include")
+  add_custom_command(
+    OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp"
+    COMMAND
+      Python::Interpreter "${CMAKE_CURRENT_SOURCE_DIR}/scripts/MakeSingleHeader.py"
+      ${CLI11_headers} --main "${CMAKE_CURRENT_SOURCE_DIR}/CLI11.hpp.in" --output
+      "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" --version "${CLI11_VERSION}"
+    DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI11_headers})
+  add_custom_target(CLI11-generate-single-file ALL
+                    DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp")
+  set_property(TARGET CLI11-generate-single-file PROPERTY FOLDER "Scripts")
+  install(FILES "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" DESTINATION include)
+  add_library(CLI11_SINGLE INTERFACE)
+  target_link_libraries(CLI11_SINGLE INTERFACE CLI11)
+  add_dependencies(CLI11_SINGLE CLI11-generate-single-file)
+  target_compile_definitions(CLI11_SINGLE INTERFACE -DCLI11_SINGLE_FILE)
+  target_include_directories(
+    CLI11_SINGLE INTERFACE $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include/>
+                           $<INSTALL_INTERFACE:include>)
+endif()
+
+if(CLI11_BUILD_TESTS)
+  include(CTest)
+  add_subdirectory(tests)
+endif()
+
+if(CLI11_BUILD_EXAMPLES)
+  add_subdirectory(examples)
+endif()
+
+if(CLI11_BUILD_DOCS)
+  add_subdirectory(docs)
+endif()
+
+# From a build system, this might not be included.
+if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/book")
+  add_subdirectory(book)
+endif()
+
+# Packaging support
+set(CPACK_PACKAGE_VENDOR "github.com/CLIUtils/CLI11")
+set(CPACK_PACKAGE_CONTACT "https://${CPACK_PACKAGE_VENDOR}")
+set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) # Automatic in CMake 3.12+
+set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) # Automatic in CMake 3.12+
+set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) # Automatic in CMake 3.12+
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Command line parser with simple and intuitive interface")
+set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
+set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
+set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CLI11.CPack.Description.txt")
+set(CPACK_SOURCE_GENERATOR "TGZ;ZIP")
+
+# CPack collects *everything* except what's listed here.
+set(CPACK_SOURCE_IGNORE_FILES
+    /.git
+    /dist
+    /.*build.*
+    /\\\\.DS_Store
+    /.*\\\\.egg-info
+    /var
+    /azure-pipelines.yml
+    /.ci
+    /docs
+    /examples
+    /test_package
+    /book
+    /.travis.yml
+    .swp
+    /.all-contributorsrc
+    /.appveyor.yml
+    /.pre-commit.*yaml)
+
+set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "all")
+set(CPACK_DEBIAN_COMPRESSION_TYPE "xz")
+set(CPACK_DEBIAN_PACKAGE_NAME "libcli11-dev")
+
+include(CPack)
diff --git a/external/CLI11/CPPLINT.cfg b/external/CLI11/CPPLINT.cfg
new file mode 100644
index 0000000..24dd865
--- /dev/null
+++ b/external/CLI11/CPPLINT.cfg
@@ -0,0 +1,13 @@
+set noparent
+linelength=120  # As in .clang-format
+
+# Unused filters
+filter=-build/c++11  # Reports e.g. chrono and thread, which overlap with Chromium's API. Not applicable to general C++ projects.
+filter=-build/include_order  # Requires unusual include order that encourages creating not self-contained headers
+filter=-readability/nolint  # Conflicts with clang-tidy
+filter=-readability/check  # Catch uses CHECK(a == b) (Tests only)
+filter=-build/namespaces  # Currently using it for one test (Tests only)
+filter=-runtime/references  # Requires fundamental change of API, don't see need for this
+filter=-whitespace/blank_line  # Unnecessarily strict with blank lines that otherwise help with readability
+filter=-whitespace/indent  # Requires strange 3-space indent of private/protected/public markers
+filter=-whitespace/parens,-whitespace/braces  # Conflict with clang-format
diff --git a/external/CLI11/LICENSE b/external/CLI11/LICENSE
new file mode 100644
index 0000000..17739d1
--- /dev/null
+++ b/external/CLI11/LICENSE
@@ -0,0 +1,25 @@
+CLI11 1.8 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry
+Schreiner under NSF AWARD 1414736. All rights reserved.
+
+Redistribution and use in source and binary forms of CLI11, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+3. Neither the name of the copyright holder nor the names of its contributors
+   may be used to endorse or promote products derived from this software without
+   specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/external/CLI11/README.md b/external/CLI11/README.md
new file mode 100644
index 0000000..ad1ac1e
--- /dev/null
+++ b/external/CLI11/README.md
@@ -0,0 +1,1085 @@
+# CLI11: Command line parser for C++11
+
+![CLI11 Logo](./docs/CLI11_300.png)
+
+[![Build Status Azure][azure-badge]][azure]
+[![Actions Status][actions-badge]][actions-link]
+[![Build Status AppVeyor][appveyor-badge]][appveyor]
+[![Code Coverage][codecov-badge]][codecov]
+[![Codacy Badge][codacy-badge]][codacy-link]
+[![License: BSD][license-badge]](./LICENSE)
+[![DOI][doi-badge]][doi-link]
+
+[![Gitter chat][gitter-badge]][gitter]
+[![Latest GHA release][releases-badge]][github releases]
+[![Latest release][repology-badge]][repology]
+[![Conan.io][conan-badge]][conan-link]
+[![Conda Version][conda-badge]][conda-link]
+[![Try CLI11 2.1 online][wandbox-badge]][wandbox-link]
+
+[What's new](./CHANGELOG.md) •
+[Documentation][gitbook] •
+[API Reference][api-docs]
+
+CLI11 is a command line parser for C++11 and beyond that provides a rich feature set with a simple and intuitive interface.
+
+## Table of Contents
+
+* [Background](#background)
+  * [Introduction](#introduction)
+  * [Why write another CLI parser?](#why-write-another-cli-parser)
+  * [Other parsers](#other-parsers)
+  * [Features not supported by this library](#features-not-supported-by-this-library)
+* [Install](#install)
+* [Usage](#usage)
+  * [Adding options](#adding-options)
+    * [Option types](#option-types)
+    * [Example](#example)
+    * [Option options](#option-options)
+    * [Validators](#validators)
+      * [Transforming Validators](#transforming-validators)
+      * [Validator operations](#validator-operations)
+      * [Custom Validators](#custom-validators)
+      * [Querying Validators](#querying-validators)
+      * [Getting Results](#getting-results)
+  * [Subcommands](#subcommands)
+    * [Subcommand options](#subcommand-options)
+    * [Option groups](#option-groups)
+    * [Callbacks](#callbacks)
+  * [Configuration file](#configuration-file)
+  * [Inheriting defaults](#inheriting-defaults)
+  * [Formatting](#formatting)
+  * [Subclassing](#subclassing)
+  * [How it works](#how-it-works)
+  * [Utilities](#utilities)
+  * [Other libraries](#other-libraries)
+* [API](#api)
+* [Examples](#Examples)
+* [Contribute](#contribute)
+* [License](#license)
+
+Features that were added in the last released major version are marked with "🆕". Features only available in main are marked with "🚧".
+
+## Background
+
+### Introduction
+
+CLI11 provides all the features you expect in a powerful command line parser, with a beautiful, minimal syntax and no dependencies beyond C++11. It is header only, and comes in a single file form for easy inclusion in projects. It is easy to use for small projects, but powerful enough for complex command line projects, and can be customized for frameworks.
+It is tested on [Azure][] and [GitHub Actions][actions-link], and was originally used by the [GooFit GPU fitting framework][goofit]. It was inspired by [`plumbum.cli`][plumbum] for Python. CLI11 has a user friendly introduction in this README, a more in-depth tutorial [GitBook][], as well as [API documentation][api-docs] generated by Travis.
+See the [changelog](./CHANGELOG.md) or [GitHub Releases][] for details for current and past releases. Also see the [Version 1.0 post][], [Version 1.3 post][], [Version 1.6 post][], or [Version 2.0 post][] for more information.
+
+You can be notified when new releases are made by subscribing to <https://github.com/CLIUtils/CLI11/releases.atom> on an RSS reader, like Feedly, or use the releases mode of the GitHub watching tool.
+
+### Why write another CLI parser?
+
+An acceptable CLI parser library should be all of the following:
+
+* Easy to include (i.e., header only, one file if possible, **no external requirements**).
+* Short, simple syntax: This is one of the main reasons to use a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability.
+* C++11 or better: Should work with GCC 4.8+ (default on CentOS/RHEL 7), Clang 3.4+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+.
+* Work on Linux, macOS, and Windows.
+* Well tested on all common platforms and compilers. "Well" is defined as having good coverage measured by [CodeCov][].
+* Clear help printing.
+* Nice error messages.
+* Standard shell idioms supported naturally, like grouping flags, a positional separator, etc.
+* Easy to execute, with help, parse errors, etc. providing correct exit and details.
+* Easy to extend as part of a framework that provides "applications" to users.
+* Usable subcommand syntax, with support for multiple subcommands, nested subcommands, option groups, and optional fallthrough (explained later).
+* Ability to add a configuration file (`TOML`, `INI`, or custom format), and produce it as well.
+* Produce real values that can be used directly in code, not something you have pay compute time to look up, for HPC applications.
+* Work with standard types, simple custom types, and extensible to exotic types.
+* Permissively licensed.
+
+### Other parsers
+
+<details><summary>The major CLI parsers for C++ include, with my biased opinions: (click to expand)</summary><p>
+
+| Library                             | My biased opinion                                                                                                                                                                                                                                                                                                                                                                                                                  |
+| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [Boost Program Options][]           | A great library if you already depend on Boost, but its pre-C++11 syntax is really odd and setting up the correct call in the main function is poorly documented (and is nearly a page of code). A simple wrapper for the Boost library was originally developed, but was discarded as CLI11 became more powerful. The idea of capturing a value and setting it originated with Boost PO. [See this comparison.][cli11-po-compare] |
+| [The Lean Mean C++ Option Parser][] | One header file is great, but the syntax is atrocious, in my opinion. It was quite impractical to wrap the syntax or to use in a complex project. It seems to handle standard parsing quite well.                                                                                                                                                                                                                                  |
+| [TCLAP][]                           | The not-quite-standard command line parsing causes common shortcuts to fail. It also seems to be poorly supported, with only minimal bugfixes accepted. Header only, but in quite a few files. Has not managed to get enough support to move to GitHub yet. No subcommands. Produces wrapped values.                                                                                                                               |
+| [Cxxopts][]                         | C++11, single file, and nice CMake support, but requires regex, therefore GCC 4.8 (CentOS 7 default) does not work. Syntax closely based on Boost PO, so not ideal but familiar.                                                                                                                                                                                                                                                   |
+| [DocOpt][]                          | Completely different approach to program options in C++11, you write the docs and the interface is generated. Too fragile and specialized.                                                                                                                                                                                                                                                                                         |
+
+After I wrote this, I also found the following libraries:
+
+| Library                 | My biased opinion                                                                                                                                                                    |
+| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| [GFlags][]              | The Google Commandline Flags library. Uses macros heavily, and is limited in scope, missing things like subcommands. It provides a simple syntax and supports config files/env vars. |
+| [GetOpt][]              | Very limited C solution with long, convoluted syntax. Does not support much of anything, like help generation. Always available on UNIX, though (but in different flavors).          |
+| [ProgramOptions.hxx][]  | Interesting library, less powerful and no subcommands. Nice callback system.                                                                                                         |
+| [Args][]                | Also interesting, and supports subcommands. I like the optional-like design, but CLI11 is cleaner and provides direct value access, and is less verbose.                             |
+| [Argument Aggregator][] | I'm a big fan of the [fmt][] library, and the try-catch statement looks familiar.  :thumbsup: Doesn't seem to support subcommands.                                                   |
+| [Clara][]               | Simple library built for the excellent [Catch][] testing framework. Unique syntax, limited scope.                                                                                    |
+| [Argh!][]               | Very minimalistic C++11 parser, single header. Don't have many features. No help generation?!?! At least it's exception-free.                                                        |
+| [CLI][]                 | Custom language and parser. Huge build-system overkill for very little benefit. Last release in 2009, but still occasionally active.                                                 |
+| [argparse][]            | C++17 single file argument parser. Design seems similar to CLI11 in some ways. The author has several other interesting projects.                                                    |
+
+See [Awesome C++][] for a less-biased list of parsers. You can also find other single file libraries at [Single file libs][].
+
+</p></details>
+<br/>
+
+None of these libraries fulfill all the above requirements, or really even come close. As you probably have already guessed, CLI11 does.
+So, this library was designed to provide a great syntax, good compiler compatibility, and minimal installation fuss.
+
+### Features not supported by this library
+
+There are some other possible "features" that are intentionally not supported by this library:
+
+* Non-standard variations on syntax, like `-long` options. This is non-standard and should be avoided, so that is enforced by this library.
+* Completion of partial options, such as Python's `argparse` supplies for incomplete arguments. It's better not to guess. Most third party command line parsers for python actually reimplement command line parsing rather than using argparse because of this perceived design flaw (recent versions do have an option to disable it).
+* Autocomplete: This might eventually be added to both Plumbum and CLI11, but it is not supported yet.
+* Wide strings / unicode: Since this uses the standard library only, it might be hard to properly implement, but I would be open to suggestions in how to do this.
+
+## Install
+
+To use, there are several methods:
+
+* All-in-one local header: Copy `CLI11.hpp` from the [most recent release][github releases] into your include directory, and you are set. This is combined from the source files  for every release. This includes the entire command parser library, but does not include separate utilities (like `Timer`, `AutoTimer`). The utilities are completely self contained and can be copied separately.
+* All-in-one global header: Like above, but copying the file to a shared folder location like `/opt/CLI11`. Then, the C++ include path has to be extended to point at this folder. With CMake, use `include_directories(/opt/CLI11)`
+* Local headers and target: Use `CLI/*.hpp` files. You could check out the repository as a git submodule, for example. With CMake, you can use `add_subdirectory` and the `CLI11::CLI11` interface target when linking. If not using a submodule, you must ensure that the copied files are located inside the same tree directory than your current project, to prevent an error with CMake and `add_subdirectory`.
+* Global headers: Use `CLI/*.hpp` files stored in a shared folder. You could check out the git repository to a system-wide folder, for example `/opt/`. With CMake, you could add to the include path via:
+
+```bash
+if(NOT DEFINED CLI11_DIR)
+set (CLI11_DIR "/opt/CLI11" CACHE STRING "CLI11 git repository")
+endif()
+include_directories(${CLI11_DIR}/include)
+```
+
+And then in the source code (adding several headers might be needed to prevent linker errors):
+
+```cpp
+#include "CLI/App.hpp"
+#include "CLI/Formatter.hpp"
+#include "CLI/Config.hpp"
+```
+
+* Global headers and target: configuring and installing the project is required for linking CLI11 to your project in the same way as you would do with any other external library. With CMake, this step allows using `find_package(CLI11 CONFIG REQUIRED)` and then using the `CLI11::CLI11` target when linking. If `CMAKE_INSTALL_PREFIX` was changed during install to a specific folder like `/opt/CLI11`, then you have to pass `-DCLI11_DIR=/opt/CLI11` when building your current project. You can also use [Conan.io][conan-link] or [Hunter][].
+    (These are just conveniences to allow you to use your favorite method of managing packages; it's just header only so including the correct path and
+    using C++11 is all you really need.)
+* Via FetchContent in CMake 3.14+ (or 3.11+ with more work): you can add this with fetch-content, then use the `CLI11::CLI11` target as above, and CMake will download the project in the configure stage:
+
+```cmake
+include(FetchContent)
+FetchContent_Declare(
+  cli11
+  GIT_REPOSITORY https://github.com/CLIUtils/CLI11
+  GIT_TAG        v2.1.2
+)
+
+FetchContent_MakeAvailable(cli11)
+```
+
+It is highly recommended that you use the git hash for `GIT_TAG` instead of a tag or branch, as that will both be more secure, as well as faster to reconfigure - CMake will not have to reach out to the internet to see if the tag moved. You can also download just the single header file from the releases using `file(DOWNLOAD`.
+
+To build the tests, checkout the repository and use CMake:
+
+```bash
+cmake -S . -B build
+cmake --build build
+CTEST_OUTPUT_ON_FAILURE=1 cmake --build build -t test
+```
+
+<details><summary>Note: Special instructions for GCC 8</summary><p>
+
+If you are using GCC 8 and using it in C++17 mode with CLI11.  CLI11 makes use of the `<filesystem>` header if available, but specifically for this compiler, the `filesystem` library is separate from the standard library and needs to be linked separately. So it is available but CLI11 doesn't use it by default.
+
+Specifically `libstdc++fs` needs to be added to the linking list and `CLI11_HAS_FILESYSTEM=1` has to be defined.  Then the filesystem variant of the Validators could be used on GCC 8.  GCC 9+ does not have this issue so the `<filesystem>` is used by default.
+
+There may also be other cases where a specific library needs to be linked.
+
+Defining `CLI11_HAS_FILESYSTEM=0`  which will remove the usage and hence any linking issue.
+
+In some cases certain clang compilations may require linking against `libc++fs`.  These situations have not been encountered so the specific situations requiring them are unknown yet.
+
+</p></details>
+</br>
+
+## Usage
+
+### Adding options
+
+To set up, add options, and run, your main function will look something like this:
+
+```cpp
+int main(int argc, char** argv) {
+    CLI::App app{"App description"};
+
+    std::string filename = "default";
+    app.add_option("-f,--file", filename, "A help string");
+
+    CLI11_PARSE(app, argc, argv);
+    return 0;
+}
+```
+
+<details><summary>Note: If you don't like macros, this is what that macro expands to: (click to expand)</summary><p>
+
+```cpp
+try {
+    app.parse(argc, argv);
+} catch (const CLI::ParseError &e) {
+    return app.exit(e);
+}
+```
+
+The try/catch block ensures that `-h,--help` or a parse error will exit with the correct return code (selected from `CLI::ExitCodes`). (The return here should be inside `main`). You should not assume that the option values have been set inside the catch block; for example, help flags intentionally short-circuit all other processing for speed and to ensure required options and the like do not interfere.
+
+</p></details>
+</br>
+
+The initialization is just one line, adding options is just two each. The parse macro is just one line (or 5 for the contents of the macro).  After the app runs, the filename will be set to the correct value if it was passed, otherwise it will be set to the default. You can check to see if this was passed on the command line with `app.count("--file")`.
+
+#### Option types
+
+While all options internally are the same type, there are several ways to add an option depending on what you need. The supported values are:
+
+```cpp
+// Add options
+app.add_option(option_name, help_str="")
+
+app.add_option(option_name,
+               variable_to_bind_to, // bool, char(see note), int, float, vector, enum, std::atomic, or string-like, or anything with a defined conversion from a string or that takes an int, double, or string in a constructor. Also allowed are tuples, std::array or std::pair. Also supported are complex numbers, wrapper types, and containers besides vectorof any other supported type.
+               help_string="")
+
+app.add_option_function<type>(option_name,
+               function <void(const type &value)>, // type can be any type supported by add_option
+               help_string="")
+
+// char as an option type is supported before 2.0 but in 2.0 it defaulted to allowing single non numerical characters in addition to the numeric values.
+
+// There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type.  The conversion type should have a known way to convert from a string, such as any of the types that work in the non-template version.  If XC is a std::pair and T is some non pair type.  Then a two argument constructor for T is called to assign the value.  For tuples or other multi element types, XC must be a single type or a tuple like object of the same size as the assignment type
+app.add_option<typename T, typename XC>(option_name,
+               T &output, // output must be assignable or constructible from a value of type XC
+               help_string="")
+
+// Add flags
+app.add_flag(option_name,
+             help_string="")
+
+app.add_flag(option_name,
+             variable_to_bind_to, // bool, int, float, complex, containers, enum, std::atomic, or string-like, or any singular object with a defined conversion from a string like add_option
+             help_string="")
+
+app.add_flag_function(option_name,
+             function <void(std::int64_t count)>,
+             help_string="")
+
+app.add_flag_callback(option_name,function<void(void)>,help_string="")
+
+// Add subcommands
+App* subcom = app.add_subcommand(name, description);
+
+Option_group *app.add_option_group(name,description);
+```
+
+An option name may start with any character except ('-', ' ', '\n', and '!') 🆕. For long options, after the first character all characters are allowed except ('=',':','{',' ', '\n')🆕. For the `add_flag*` functions '{' and '!' have special meaning which is why they are not allowed. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on the help line for its positional form.
+
+The `add_option_function<type>(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed.  The type can be any type supported by the `add_option` function. The function should throw an error (`CLI::ConversionError` or `CLI::ValidationError` possibly) if the value is not valid.
+
+The two parameter template overload can be used in cases where you want to restrict the input such as
+
+```cpp
+double val
+app.add_option<double,unsigned int>("-v",val);
+```
+
+which would first verify the input is convertible to an `unsigned int` before assigning it.  Or using some variant type
+
+```cpp
+using vtype=std::variant<int, double, std::string>;
+ vtype v1;
+app.add_option<vtype,std:string>("--vs",v1);
+app.add_option<vtype,int>("--vi",v1);
+app.add_option<vtype,double>("--vf",v1);
+```
+
+otherwise the output would default to a string.  The `add_option` can be used with any integral or floating point types, enumerations, or strings.  Or any type that takes an int, double, or std\::string in an assignment operator or constructor.  If an object can take multiple varieties of those, std::string takes precedence, then double then int.    To better control which one is used or to use another type for the underlying conversions use the two parameter template to directly specify the conversion type.
+
+Types such as (std or boost) `optional<int>`, `optional<double>`, and `optional<string>` and any other wrapper types are supported directly. For purposes of CLI11 wrapper types are those which `value_type` definition.  See [CLI11 Advanced Topics/Custom Converters][] for information on how you can add your own converters for additional types.
+
+Vector types can also be used in the two parameter template overload
+
+```cpp
+std::vector<double> v1;
+app.add_option<std::vector<double>,int>("--vs",v1);
+```
+
+would load a vector of doubles but ensure all values can be represented as integers.
+
+Automatic direct capture of the default string is disabled when using the two parameter template.  Use `set_default_str(...)` or `->default_function(std::string())` to set the default string or capture function directly for these cases.
+
+Flag options specified through the `add_flag*` functions allow a syntax for the option names to default particular options to a false value or any other value if some flags are passed.  For example:
+
+```cpp
+app.add_flag("--flag,!--no-flag",result,"help for flag");
+```
+
+specifies that if `--flag` is passed on the command line result will be true or contain a value of 1. If `--no-flag` is
+passed `result` will contain false or -1 if `result` is a signed integer type, or 0 if it is an unsigned type.  An
+alternative form of the syntax is more explicit: `"--flag,--no-flag{false}"`; this is equivalent to the previous
+example.  This also works for short form options `"-f,!-n"` or `"-f,-n{false}"`. If `variable_to_bind_to` is anything but an integer value the
+default behavior is to take the last value given, while if `variable_to_bind_to` is an integer type the behavior will be to sum
+all the given arguments and return the result.  This can be modified if needed by changing the `multi_option_policy` on each flag (this is not inherited).
+The default value can be any value. For example if you wished to define a numerical flag:
+
+```cpp
+app.add_flag("-1{1},-2{2},-3{3}",result,"numerical flag")
+```
+
+Using any of those flags on the command line will result in the specified number in the output.  Similar things can be done for string values, and enumerations, as long as the default value can be converted to the given type.
+
+On a `C++14` compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure.
+
+#### Example
+
+* `"one,-o,--one"`: Valid as long as not a flag, would create an option that can be specified positionally, or with `-o` or `--one`
+* `"this"` Can only be passed positionally
+* `"-a,-b,-c"` No limit to the number of non-positional option names
+
+The add commands return a pointer to an internally stored `Option`.
+This option can be used directly to check for the count (`->count()`) after parsing to avoid a string based lookup.
+
+#### Option options
+
+Before parsing, you can set the following options:
+
+* `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works.
+* `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option or subcommand.
+* `->expected(MIN,MAX)`: Set a range of expected values to accompany an option.  `expected(0,1)` is the equivalent of making a flag.
+* `->type_name(typename)`: Set the name of an Option's type (`type_name_fn` allows a function instead)
+* `->type_size(N)`: Set the intrinsic size of an option value. The parser will require multiples of this number if negative. Most of the time this is detected automatically though can be modified for specific use cases.
+* `->type_size(MIN,MAX)`: Set the intrinsic size of an option to a range.
+* `->needs(opt)`: This option requires another option to also be present, opt is an `Option` pointer. Options can be removed from the `needs` with `remove_needs(opt)`. The option can also be specified with a string containing the name of the option
+* `->excludes(opt)`: This option cannot be given with `opt` present, opt is an `Option` pointer.  Can also be given as a string containing the name of the option.  Options can be removed from the excludes list with `->remove_excludes(opt)`
+* `->envname(name)`: Gets the value from the environment if present and not passed on the command line.
+* `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden).
+* `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments).
+* `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone".  This does not apply to short form options since they only have one character
+* `->disable_flag_override()`: From the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line.  The `=` does not work with short form flag options.
+* `->allow_extra_args(true/false)`: If set to true the option will take an unlimited number of arguments like a vector, if false it will limit the number of arguments to the size of the type used in the option.  Default value depends on the nature of the type use, containers default to true, others default to false.
+* `->delimiter(char)`: Allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value.
+* `->description(str)`: Set/change the description.
+* `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`,`->take_all()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy). `->join(delim)` can also be used to join with a specific delimiter. This equivalent to calling `->delimiter(delim)` and `->join()`
+* `->check(std::string(const std::string &), validator_name="",validator_description="")`: Define a check function.  The function should return a non empty string with the error message if the check fails
+* `->check(Validator)`: Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones.
+* `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options.
+* `->transform(Validator)`: Uses a Validator object to do the transformation see [Validators](#validators) for a description of available Validators and how to create new ones.
+* `->each(void(const std::string &)>`: Run this function on each value received, as it is received. It should throw a `ValidationError` if an error is encountered.
+* `->configurable(false)`: Disable this option from being in a configuration file.
+* `->capture_default_str()`: Store the current value attached and display it in the help string.
+* `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses.
+* `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`.
+* `->default_str(string)`:  Set the default string directly (NO VALIDATION OR CALLBACKS).  This string will also be used as a default value if no arguments are passed and the value is requested.
+* `->default_val(value)`: Generate the default string from a value and validate that the value is also valid.  For options that assign directly to a value type the value in that type is also updated.  Value must be convertible to a string(one of known types or have a stream operator). The callback may be triggered if the `run_callback_for_default` is set.
+* `->run_callback_for_default()`: This will force the option callback to be executed or the variable set when the `default_val` is set.
+* `->option_text(string)`: Sets the text between the option name and description.
+* `->force_callback()`: 🆕 Causes the option callback or value set to be triggered even if the option was not present in parsing.
+* `->trigger_on_parse()`: 🆕 If set, causes the callback and all associated validation checks for the option to be executed when the option value is parsed vs. at the end of all parsing. This could cause the callback to be executed multiple times.
+
+These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results.
+
+On the command line, options can be given as:
+
+* `-a` (flag)
+* `-abc` (flags can be combined)
+* `-f filename` (option)
+* `-ffilename` (no space required)
+* `-abcf filename` (flags and option can be combined)
+* `--long` (long flag)
+* `--long_flag=true` (long flag with equals to override default value)
+* `--file filename` (space)
+* `--file=filename` (equals)
+
+If `allow_windows_style_options()` is specified in the application or subcommand options can also be given as:
+
+* `/a` (flag)
+* `/f filename` (option)
+* `/long` (long flag)
+* `/file filename` (space)
+* `/file:filename` (colon)
+* `/long_flag:false` (long flag with : to override the default value)
+  * Windows style options do not allow combining short options or values not separated from the short option like with `-` options
+
+Long flag options may be given with an `=<value>` to allow specifying a false value, or some other value to the flag. See [config files](#configuration-file) for details on the values supported.  NOTE: only the `=` or `:` for windows-style options may be used for this, using a space will result in the argument being interpreted as a positional argument.  This syntax can override the default values, and can be disabled by using `disable_flag_override()`.
+
+Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments.
+If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included).
+If the remaining arguments are to processed by another `App` then the function `remaining_for_passthrough()` can be used to get the remaining arguments in reverse order such that `app.parse(vector)` works directly and could even be used inside a subcommand callback.
+
+You can access a vector of pointers to the parsed options in the original order using `parse_order()`.
+If `--` is present in the command line that does not end an unlimited option, then
+everything after that is positional only.
+
+#### Validators
+Validators are structures to check or modify inputs, they can be used to verify that an input meets certain criteria or transform it into another value.  They are added through the `check` or `transform` functions.  The differences between the two function are that checks do not modify the input whereas transforms can and are executed before any Validators added through `check`.
+
+CLI11 has several Validators built-in that perform some common checks
+
+* `CLI::IsMember(...)`: Require an option be a member of a given set. See [Transforming Validators](#transforming-validators) for more details.
+* `CLI::Transformer(...)`: Modify the input using a map. See [Transforming Validators](#transforming-validators) for more details.
+* `CLI::CheckedTransformer(...)`: Modify the input using a map, and require that the input is either in the set or already one of the outputs of the set. See [Transforming Validators](#transforming-validators) for more details.
+* `CLI::AsNumberWithUnit(...)`: Modify the `<NUMBER> <UNIT>` pair by matching the unit and multiplying the number by the corresponding factor. It can be used as a base for transformers, that accept things like size values (`1 KB`) or durations (`0.33 ms`).
+* `CLI::AsSizeValue(...)`: Convert inputs like `100b`, `42 KB`, `101 Mb`, `11 Mib` to absolute values. `KB` can be configured to be interpreted as 10^3 or 2^10.
+* `CLI::ExistingFile`: Requires that the file exists if given.
+* `CLI::ExistingDirectory`: Requires that the directory exists.
+* `CLI::ExistingPath`: Requires that the path (file or directory) exists.
+* `CLI::NonexistentPath`: Requires that the path does not exist.
+* `CLI::Range(min,max)`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0.
+* `CLI::Bounded(min,max)`: Modify the input such that it is always between min and max (make sure to use floating point if needed). Min defaults to 0.  Will produce an error if conversion is not possible.
+* `CLI::PositiveNumber`: Requires the number be greater than 0
+* `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0
+* `CLI::Number`: Requires the input be a number.
+* `CLI::ValidIPV4`: Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`.
+* `CLI::TypeValidator<TYPE>`:Requires that the option be convertible to the specified type e.g.  `CLI::TypeValidator<unsigned int>()` would require that the input be convertible to an `unsigned int` regardless of the end conversion.
+
+These Validators can be used by simply passing the name into the `check` or `transform` methods on an option
+
+```cpp
+->check(CLI::ExistingFile);
+->check(CLI::Range(0,10));
+```
+
+Validators can be merged using `&` and `|` and inverted using `!`. For example:
+
+```cpp
+->check(CLI::Range(0,10)|CLI::Range(20,30));
+```
+
+will produce a check to ensure a value is between 0 and 10 or 20 and 30.
+
+```cpp
+->check(!CLI::PositiveNumber);
+```
+
+will produce a check for a number less than or equal to 0.
+
+##### Transforming Validators
+
+There are a few built in Validators that let you transform values if used with the `transform` function.  If they also do some checks then they can be used `check` but some may do nothing in that case.
+
+* `CLI::Bounded(min,max)` will bound values between min and max and values outside of that range are limited to min or max,  it will fail if the value cannot be converted and produce a `ValidationError`
+* The `IsMember` Validator lets you specify a set of predefined options. You can pass any container or copyable pointer (including `std::shared_ptr`) to a container to this Validator; the container just needs to be iterable and have a `::value_type`. The key type should be convertible from a string,  You can use an initializer list directly if you like. If you need to modify the set later, the pointer form lets you do that; the type message and check will correctly refer to the current version of the set.  The container passed in can be a set, vector, or a map like structure. If used in the `transform` method the output value will be the matching key as it could be modified by filters.
+
+After specifying a set of options, you can also specify "filter" functions of the form `T(T)`, where `T` is the type of the values. The most common choices probably will be `CLI::ignore_case` an `CLI::ignore_underscore`, and `CLI::ignore_space`.  These all work on strings but it is possible to define functions that work on other types. Here are some examples of `IsMember`:
+
+* `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices.
+* `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too.
+* `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`.
+* `CLI::IsMember(std::map<std::string, TYPE>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the matched key.  The value member of the map is not used in `IsMember`, so it can be any type.
+* `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later.
+* The `Transformer` and `CheckedTransformer` Validators transform one value into another. Any container or copyable pointer (including `std::shared_ptr`) to a container that generates pairs of values can be passed to these `Validator's`; the container just needs to be iterable and have a `::value_type` that consists of pairs. The key type should be convertible from a string, and the value type should be convertible to a string  You can use an initializer list directly if you like. If you need to modify the map later, the pointer form lets you do that; the description message will correctly refer to the current version of the map.  `Transformer` does not do any checking so values not in the map are ignored.  `CheckedTransformer` takes an extra step of verifying that the value is either one of the map key values, in which case it is transformed, or one of the expected output values, and if not will generate a `ValidationError`.  A Transformer placed using `check` will not do anything.
+
+After specifying a map of options, you can also specify "filter" just like in `CLI::IsMember`.
+Here are some examples (`Transformer` and `CheckedTransformer` are interchangeable in the examples)
+of `Transformer`:
+
+* `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`: Select from key values and produce map values.
+* `CLI::Transformer(std::map<std::string,int>({"two",2},{"three",3},{"four",4}}))`: most maplike containers work,  the `::value_type` needs to produce a pair of some kind.
+* `CLI::CheckedTransformer(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched key with the value.  `CheckedTransformer` also requires that the value either match one of the keys or match one of known outputs.
+* `auto p = std::make_shared<CLI::TransformPairs<std::string>>(std::initializer_list<std::pair<std::string,std::string>>({"key1", "map1"},{"key2","map2"})); CLI::Transformer(p)`: You can modify `p` later. `TransformPairs<T>` is an alias for `std::vector<std::pair<<std::string,T>>`
+
+NOTES:  If the container used in `IsMember`, `Transformer`, or `CheckedTransformer` has a `find` function like `std::unordered_map`  or `std::map` then that function is used to do the searching. If it does not have a `find` function a linear search is performed.  If there are filters present, the fast search is performed first, and if that fails a linear search with the filters on the key values is performed.
+
+##### Validator operations
+
+Validators are copyable and have a few operations that can be performed on them to alter settings.  Most of the built in Validators have a default description that is displayed in the help.  This can be altered via `.description(validator_description)`.
+The name of a Validator, which is useful for later reference from the `get_validator(name)` method of an `Option` can be set via `.name(validator_name)`
+The operation function of a Validator can be set via
+`.operation(std::function<std::string(std::string &>)`.  The `.active()` function can activate or deactivate a Validator from the operation.  A validator can be set to apply only to a specific element of the output.  For example in a pair option `std::pair<int, std::string>` the first element may need to be a positive integer while the second may need to be a valid file.  The `.application_index(int)` function can specify this.  It is zero based and negative indices apply to all values.
+
+```cpp
+opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
+opt->check(CLI::Validator(CLI::ExistingFile).application_index(1));
+```
+
+All the validator operation functions return a Validator reference allowing them to be chained.  For example
+
+```cpp
+opt->check(CLI::Range(10,20).description("range is limited to sensible values").active(false).name("range"));
+```
+
+will specify a check on an option with a name "range", but deactivate it for the time being.
+The check can later be activated through
+
+```cpp
+opt->get_validator("range")->active();
+```
+
+##### Custom Validators
+
+A validator object with a custom function can be created via
+
+```cpp
+CLI::Validator(std::function<std::string(std::string &)>,validator_description,validator_name="");
+```
+
+or if the operation function is set later they can be created with
+
+```cpp
+CLI::Validator(validator_description);
+```
+
+ It is also possible to create a subclass of `CLI::Validator`, in which case it can also set a custom description function, and operation function.
+
+##### Querying Validators
+
+Once loaded into an Option, a pointer to a named Validator can be retrieved via
+
+```cpp
+opt->get_validator(name);
+```
+
+This will retrieve a Validator with the given name or throw a `CLI::OptionNotFound` error.  If no name is given or name is empty the first unnamed Validator will be returned or the first Validator if there is only one.
+
+or
+
+```cpp
+opt->get_validator(index);
+```
+
+Which will return a validator in the index it is applied which isn't necessarily the order in which was defined.  The pointer can be `nullptr` if an invalid index is given.
+Validators have a few functions to query the current values:
+
+* `get_description()`: Will return a description string
+* `get_name()`: Will return the Validator name
+* `get_active()`: Will return the current active state, true if the Validator is active.
+* `get_application_index()`: Will return the current application index.
+* `get_modifying()`: Will return true if the Validator is allowed to modify the input, this can be controlled via the `non_modifying()` method, though it is recommended to let `check` and `transform` option methods manipulate it if needed.
+
+#### Getting results
+
+In most cases, the fastest and easiest way is to return the results through a callback or variable specified in one of the `add_*` functions.  But there are situations where this is not possible or desired.  For these cases the results may be obtained through one of the following functions. Please note that these functions will do any type conversions and processing during the call so should not used in performance critical code:
+
+* `->results()`: Retrieves a vector of strings with all the results in the order they were given.
+* `->results(variable_to_bind_to)`: Gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable.
+* `Value=opt->as<type>()`: Returns the result or default value directly as the specified type if possible, can be vector to return all results, and a non-vector to get the result according to the MultiOptionPolicy in place.
+
+### Subcommands
+
+Subcommands are supported, and can be nested infinitely. To add a subcommand, call the `add_subcommand` method with a name and an optional description. This gives a pointer to an `App` that behaves just like the main app, and can take options or further subcommands. Add `->ignore_case()` to a subcommand to allow any variation of caps to also be accepted. `->ignore_underscore()` is similar, but for underscores. Children inherit the current setting from the parent. You cannot add multiple matching subcommand names at the same level (including `ignore_case` and `ignore_underscore`).
+
+If you want to require that at least one subcommand is given, use `.require_subcommand()` on the parent app. You can optionally give an exact number of subcommands to require, as well. If you give two arguments, that sets the min and max number allowed.
+0 for the max number allowed will allow an unlimited number of subcommands. As a handy shortcut, a single negative value N will set "up to N" values. Limiting the maximum number allows you to keep arguments that match a previous
+subcommand name from matching.
+
+If an `App` (main or subcommand) has been parsed on the command line, `->parsed` will be true (or convert directly to bool).
+All `App`s have a `get_subcommands()` method, which returns a list of pointers to the subcommands passed on the command line. A `got_subcommand(App_or_name)` method is also provided that will check to see if an `App` pointer or a string name was collected on the command line.
+
+For many cases, however, using an app's callback capabilities may be easier. Every app has a set of callbacks that can be executed at various stages of parsing; a `C++` lambda function (with capture to get parsed values) can be used as input to the callback definition function. If you throw `CLI::Success` or `CLI::RuntimeError(return_value)`, you can
+even exit the program through the callback.
+
+Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved).  The same subcommand can be triggered multiple times but all positional arguments will take precedence over the second and future calls of the subcommand.  `->count()` on the subcommand will return the number of times the subcommand was called.  The subcommand callback will only be triggered once unless the `.immediate_callback()`  flag is set or the callback is specified through the `parse_complete_callback()` function. The `final_callback()` is triggered only once.  In which case the callback executes on completion of the subcommand arguments but after the arguments for that subcommand have been parsed, and can be triggered multiple times.
+
+Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments.
+Nameless subcommands function a similarly to groups in the main `App`. See [Option groups](#option-groups) to see how this might work.  If an option is not defined in the main App, all nameless subcommands are checked as well.  This allows for the options to be defined in a composable group.  The `add_subcommand` function has an overload for adding a `shared_ptr<App>` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`.  Multiple nameless subcommands are allowed.  Callbacks for nameless subcommands are only triggered if any options from the subcommand were parsed. Subcommand names given through the `add_subcommand` method have the same restrictions as option names.
+
+#### Subcommand options
+
+There are several options that are supported on the main app and subcommands and option_groups. These are:
+
+* `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`.
+* `.ignore_underscore()`: Ignore any underscores in the subcommand name. Inherited by added subcommands, so is usually used on the main `App`.
+* `.allow_windows_style_options()`: Allow command line options to be parsed in the form of `/s /long /file:file_name.ext`  This option does not change how options are specified in the `add_option` calls or the ability to process options in the form of `-s --long --file=file_name.ext`.
+* `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent option. Subcommands always are allowed to "fall through" as in they will first attempt to match on the current subcommand and if they fail will progressively check parents for matching subcommands.
+* `.configurable()`: Allow the subcommand to be triggered from a configuration file. By default subcommand options in a configuration file do not trigger a subcommand but will just update default values.
+* `.disable()`: Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group.
+* `.disabled_by_default()`: Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others.
+* `.enabled_by_default()`: Specify that at the start of each parse the subcommand/option_group should be enabled.  This is useful for allowing some Subcommands to disable others.
+* `.silent()`: Specify that the subcommand is silent meaning that if used it won't show up in the subcommand list.  This allows the use of subcommands as modifiers
+* `.validate_positionals()`: Specify that positionals should pass validation before matching.  Validation is specified through `transform`, `check`, and `each` for an option.  If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments.
+* `.excludes(option_or_subcommand)`: If given an option pointer or pointer to another subcommand, these subcommands cannot be given together.  In the case of options, if the option is passed the subcommand cannot be used and will generate an error.
+* `.needs(option_or_subcommand)`: If given an option pointer or pointer to another subcommand, the subcommands will require the given option to have been given before this subcommand is validated which occurs prior to execution of any callback or after parsing is completed.
+* `.require_option()`: Require 1 or more options or option groups be used.
+* `.require_option(N)`: Require `N` options or option groups, if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more.
+* `.require_option(min, max)`: Explicitly set min and max allowed options or option groups. Setting `max` to 0 implies unlimited options.
+* `.require_subcommand()`: Require 1 or more subcommands.
+* `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more.
+* `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited.
+* `.add_subcommand(name="", description="")`: Add a subcommand, returns a pointer to the internally stored subcommand.
+* `.add_subcommand(shared_ptr<App>)`: Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand.
+* `.remove_subcommand(App)`: Remove a subcommand from the app or subcommand.
+* `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line.
+* `.get_subcommands(filter)`: The list of subcommands that match a particular filter function.
+* `.add_option_group(name="", description="")`: Add an [option group](#option-groups) to an App,  an option group is specialized subcommand intended for containing groups of options or other groups for controlling how options interact.
+* `.get_parent()`: Get the parent App or `nullptr` if called on main App.
+* `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available,  nameless subcommands are also searched
+* `.get_option_no_throw(name)`: Get an option pointer by option name. This function will return a `nullptr` instead of throwing if the option is not available.
+* `.get_options(filter)`: Get the list of all defined option pointers (useful for processing the app for custom output formats).
+* `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates).
+* `.formatter(fmt)`: Set a formatter, with signature `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more details.
+* `.description(str)`: Set/change the description.
+* `.get_description()`: Access the description.
+* `.alias(str)`: set an alias for the subcommand, this allows subcommands to be called by more than one name.
+* `.parsed()`: True if this subcommand was given on the command line.
+* `.count()`: Returns the number of times the subcommand was called.
+* `.count(option_name)`: Returns the number of times a particular option was called.
+* `.count_all()`: Returns the total number of arguments a particular subcommand processed, on the main App it returns the total number of processed commands.
+* `.name(name)`: Add or change the name.
+* `.callback(void() function)`: Set the callback for an app. Either sets the `pre_parse_callback` or the `final_callback` depending on the value of `immediate_callback`. See [Subcommand callbacks](#callbacks) for some additional details.
+* `.parse_complete_callback(void() function)`: Set the callback that runs at the completion of parsing. For subcommands this is executed at the completion of the single subcommand and can be executed multiple times. See [Subcommand callbacks](#callbacks) for some additional details.
+* `.final_callback(void() function)`: Set the callback that runs at the end of all processing. This is the last thing that is executed before returning. See [Subcommand callbacks](#callbacks) for some additional details.
+* `.immediate_callback()`: Specifies whether the callback for a subcommand should be run as a `parse_complete_callback`(true) or `final_callback`(false). When used on the main app it will execute the main app callback prior to the callbacks for a subcommand if they do not also have the `immediate_callback` flag set. It is preferable to use the `parse_complete_callback` or `final_callback` directly instead of the `callback` and `immediate_callback` if one wishes to control the ordering and timing of callback.  Though `immediate_callback` can be used to swap them if that is needed.
+* `.pre_parse_callback(void(std::size_t) function)`: Set a callback that executes after the first argument of an application is processed.  See [Subcommand callbacks](#callbacks) for some additional details.
+* `.allow_extras()`: Do not throw an error if extra arguments are left over.
+* `.positionals_at_end()`: Specify that positional arguments occur as the last arguments and throw an error if an unexpected positional is encountered.
+* `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognized item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app.
+* `.footer(message)`: Set text to appear at the bottom of the help string.
+* `.footer(std::string())`: Set a callback to generate a string that will appear at the end of the help string.
+* `.set_help_flag(name, message)`: Set the help flag name and message, returns a pointer to the created option.
+* `.set_version_flag(name, versionString or callback, help_message)`: Set the version flag name and version string or callback and optional help message, returns a pointer to the created option.
+* `.set_help_all_flag(name, message)`: Set the help all flag name and message, returns a pointer to the created option. Expands subcommands.
+* `.failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default).
+* `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand.
+* `[option_name]`: retrieve a const pointer to an option given by `option_name` for Example `app["--flag1"]` will get a pointer to the option for the "--flag1" value,  `app["--flag1"]->as<bool>()` will get the results of the command line for a flag. The operation will throw an exception if the option name is not valid.
+
+> Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function, and any positional argument will match before repeated subcommand names.
+
+#### Callbacks
+
+A subcommand has three optional callbacks that are executed at different stages of processing.  The `preparse_callback` is executed once after the first argument of a subcommand or application is processed and gives an argument for the number of remaining arguments to process.  For the main app the first argument is considered the program name,  for subcommands the first argument is the subcommand name.  For Option groups and nameless subcommands the first argument is after the first argument or subcommand is processed from that group.
+The second callback is executed after parsing.  This is known as the `parse_complete_callback`. For subcommands this is executed immediately after parsing and can be executed multiple times if a subcommand is called multiple times.    On the main app this callback is executed after all the `parse_complete_callback`s for the subcommands are executed but prior to any `final_callback` calls in the subcommand or option groups. If the main app or subcommand has a config file, no data from the config file will be reflected in `parse_complete_callback` on named subcommands.  For `option_group`s the `parse_complete_callback` is executed prior to the `parse_complete_callback` on the main app but after the `config_file` is loaded (if specified).  The `final_callback` is executed after all processing is complete.  After the `parse_complete_callback` is executed on the main app, the used subcommand `final_callback` are executed followed by the "final callback" for option groups.  The last thing to execute is the `final_callback` for the `main_app`.
+For example say an application was set up like
+
+```cpp
+app.parse_complete_callback(ac1);
+app.final_callback(ac2);
+auto sub1=app.add_subcommand("sub1")->parse_complete_callback(c1)->preparse_callback(pc1);
+auto sub2=app.add_subcommand("sub2")->final_callback(c2)->preparse_callback(pc2);
+app.preparse_callback( pa);
+
+... A bunch of other options
+```
+
+Then the command line is given as
+
+```bash
+program --opt1 opt1_val  sub1 --sub1opt --sub1optb val sub2 --sub2opt sub1 --sub1opt2 sub2 --sub2opt2 val
+```
+
+* `pa` will be called prior to parsing any values with an argument of 13.
+* `pc1` will be called immediately after processing the `sub1` command with a value of 10.
+* `c1` will be called when the `sub2` command is encountered.
+* `pc2` will be called with value of 6 after the `sub2` command is encountered.
+* `c1` will be called again after the second `sub2` command is encountered.
+* `ac1` will be called after processing of all arguments
+* `c2` will be called once after processing all arguments.
+* `ac2` will be called last after completing  all lower level callbacks have been executed.
+
+A subcommand is considered terminated when one of the following conditions are met.
+
+1. There are no more arguments to process
+2. Another subcommand is encountered that would not fit in an optional slot of the subcommand
+3. The `positional_mark` (`--`) is encountered and there are no available positional slots in the subcommand.
+4. The `subcommand_terminator` mark (`++`) is encountered
+
+Prior to executed a `parse_complete_callback` all contained options are processed before the callback is triggered.  If a subcommand with a `parse_complete_callback` is called again, then the contained options are reset, and can be triggered again.
+
+#### Option groups
+
+The subcommand method
+
+```cpp
+.add_option_group(name,description)
+```
+
+Will create an option group, and return a pointer to it. The argument for `description` is optional and can be omitted.  An option group allows creation of a collection of options, similar to the groups function on options, but with additional controls and requirements.  They allow specific sets of options to be composed and controlled as a collective.  For an example see [range example](https://github.com/CLIUtils/CLI11/blob/main/examples/ranges.cpp).  Option groups are a specialization of an App so all [functions](#subcommand-options) that work with an App or subcommand also work on option groups.  Options can be created as part of an option group using the add functions just like a subcommand, or previously created options can be added through.  The name given in an option group must not contain newlines or null characters.🆕
+
+```cpp
+ogroup->add_option(option_pointer);
+ogroup->add_options(option_pointer);
+ogroup->add_options(option1,option2,option3,...);
+```
+
+The option pointers used in this function must be options defined in the parent application of the option group otherwise an error will be generated.  Subcommands can also be added via
+
+```cpp
+ogroup->add_subcommand(subcom_pointer);
+```
+
+This results in the subcommand being moved from its parent into the option group.
+
+Options in an option group are searched for a command line match after any options in the main app, so any positionals in the main app would be matched first.  So care must be taken to make sure of the order when using positional arguments and option groups.
+Option groups work well with `excludes` and `require_options` methods, as an application will treat an option group as a single option for the purpose of counting and requirements, and an option group will be considered used if any of the options or subcommands contained in it are used.  Option groups allow specifying requirements such as requiring 1 of 3 options in one group and 1 of 3 options in a different group. Option groups can contain other groups as well.   Disabling an option group will turn off all options within the group.
+
+The `CLI::TriggerOn` and `CLI::TriggerOff` methods are helper functions to allow the use of options/subcommands from one group to trigger another group on or off.
+
+```cpp
+CLI::TriggerOn(group1_pointer, triggered_group);
+CLI::TriggerOff(group2_pointer, disabled_group);
+```
+
+These functions make use of `preparse_callback`, `enabled_by_default()` and `disabled_by_default`.  The triggered group may be a vector of group pointers.  These methods should only be used once per group and will override any previous use of the underlying functions.  More complex arrangements can be accomplished using similar methodology with a custom `preparse_callback` function that does more.
+
+Additional helper functions `deprecate_option` and `retire_option` are available to deprecate or retire options
+
+```cpp
+CLI::deprecate_option(option *, replacement_name="");
+CLI::deprecate_option(App,option_name,replacement_name="");
+```
+
+will specify that the option is deprecated which will display a message in the help and a warning on first usage.  Deprecated options function normally but will add a message in the help and display a warning on first use.
+
+```cpp
+CLI::retire_option(App,option *);
+CLI::retire_option(App,option_name);
+```
+
+will create an option that does nothing by default and will display a warning on first usage that the option is retired and has no effect.  If the option exists it is replaces with a dummy option that takes the same arguments.
+
+If an empty string is passed the option group name the entire group will be hidden in the help results.  For example.
+
+```cpp
+auto hidden_group=app.add_option_group("");
+```
+
+will create a group such that no options in that group are displayed in the help string.
+
+### Configuration file
+
+```cpp
+app.set_config(option_name="",
+               default_file_name="",
+               help_string="Read an ini file",
+               required=false)
+```
+
+If this is called with no arguments, it will remove the configuration file option (like `set_help_flag`). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in [TOML][] format by default, though the default reader can also accept files in INI format as well. It should be noted that CLI11 does not contain a full TOML parser but can read strings from most TOML file and run them through the CLI11 parser. Other formats can be added by an adept user, some variations are available through customization points in the default formatter. An example of a TOML file:
+
+```toml
+# Comments are supported, using a #
+# The default section is [default], case insensitive
+
+value = 1
+str = "A string"
+vector = [1,2,3]
+str_vector = ["one","two","and three"]
+
+# Sections map to subcommands
+[subcommand]
+in_subcommand = Wow
+sub.subcommand = true
+```
+
+or equivalently in INI format
+
+```ini
+; Comments are supported, using a ;
+; The default section is [default], case insensitive
+
+value = 1
+str = "A string"
+vector = 1 2 3
+str_vector = "one" "two" "and three"
+
+; Sections map to subcommands
+[subcommand]
+in_subcommand = Wow
+sub.subcommand = true
+```
+
+Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`, `enable`; or `false`, `off`, `0`, `no`, `disable` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not necessarily mean that subcommand was passed, it just sets the "defaults"). You cannot set positional-only arguments.  Subcommands can be triggered from configuration files if the `configurable` flag was set on the subcommand.  Then the use of `[subcommand]` notation will trigger a subcommand and cause it to act as if it were on the command line.
+
+To print a configuration file from the passed
+arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include the app and option descriptions.  See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details and customization points.
+
+If it is desired that multiple configuration be allowed.  Use
+
+```cpp
+app.set_config("--config")->expected(1, X);
+```
+
+Where X is some positive number and will allow up to `X` configuration files to be specified by separate `--config` arguments.  Value strings with quote characters in it will be printed with a single quote. All other arguments will use double quote.  Empty strings will use a double quoted argument. Numerical or boolean values are not quoted.
+
+### Inheriting defaults
+
+Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `ignore_underscore`, `fallthrough`, `group`, `footer`,`immediate_callback` and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well.
+
+Options have defaults for `group`, `required`, `multi_option_policy`, `ignore_case`, `ignore_underscore`, `delimiter`, and `disable_flag_override`. To set these defaults, you should set the `option_defaults()` object, for example:
+
+```cpp
+app.option_defaults()->required();
+// All future options will be required
+```
+
+The default settings for options are inherited to subcommands, as well.
+
+### Formatting
+
+The job of formatting help printouts is delegated to a formatter callable object on Apps and Options. You are free to replace either formatter by calling `formatter(fmt)` on an `App`, where fmt is any copyable callable with the correct signature.
+CLI11 comes with a default App formatter functional, `Formatter`. It is customizable; you can set `label(key, value)` to replace the default labels like `REQUIRED`, and `column_width(n)` to set the width of the columns before you add the functional to the app or option. You can also override almost any stage of the formatting process in a subclass of either formatter. If you want to make a new formatter from scratch, you can do
+that too; you just need to implement the correct signature. The first argument is a const pointer to the in question. The formatter will get a `std::string` usage name as the second option, and a `AppFormatMode` mode for the final option. It should return a `std::string`.
+
+The `AppFormatMode` can be `Normal`, `All`, or `Sub`, and it indicates the situation the help was called in. `Sub` is optional, but the default formatter uses it to make sure expanded subcommands are called with
+their own formatter since you can't access anything but the call operator once a formatter has been set.
+
+### Subclassing
+
+The App class was designed allow toolkits to subclass it, to provide preset default options (see above) and setup/teardown code. Subcommands remain an unsubclassed `App`, since those are not expected to need setup and teardown. The default `App` only adds a help flag, `-h,--help`, than can removed/replaced using `.set_help_flag(name, help_string)`. You can also set a help-all flag with `.set_help_all_flag(name, help_string)`; this will expand the subcommands (one level only). You can remove options if you have pointers to them using `.remove_option(opt)`. You can add a `pre_callback` override to customize the after parse
+but before run behavior, while
+still giving the user freedom to `callback` on the main app.
+
+The most important parse function is `parse(std::vector<std::string>)`, which takes a reversed list of arguments (so that `pop_back` processes the args in the correct order). `get_help_ptr` and `get_config_ptr` give you access to the help/config option pointers. The standard `parse` manually sets the name from the first argument, so it should not be in this vector. You can also use `parse(string, bool)` to split up and parse a single string; the optional boolean should be set to true if you are
+including the program name in the string, and false otherwise.  The program name can contain spaces if it is an existing file,  otherwise can be enclosed in quotes(single quote, double quote or backtick).  Embedded quote characters can be escaped with `\`.
+
+Also, in a related note, the `App` you get a pointer to is stored in the parent `App` in a `shared_ptr`s (similar to `Option`s) and are deleted when the main `App` goes out of scope unless the object has another owner.
+
+### How it works
+
+Every `add_` option you have seen so far depends on one method that takes a lambda function. Each of these methods is just making a different lambda function with capture to populate the option. The function has full access to the vector of strings, so it knows how many times an option was passed or how many arguments it received. The lambda returns `true` if it could validate the option strings, and
+`false` if it failed.
+
+Other values can be added as long as they support `operator>>` (and defaults can be printed if they support `operator<<`). To add a new type, for example, provide a custom `operator>>` with an `istream` (inside the CLI namespace is fine if you don't want to interfere with an existing `operator>>`).
+
+If you wanted to extend this to support a completely new type, use a lambda or add a specialization of the `lexical_cast` function template in the namespace of the type you need to convert to. Some examples of some new parsers for `complex<double>` that support all of the features of a standard `add_options` call are in [one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below:
+
+#### Example
+
+```cpp
+app.add_option("--fancy-count", [](std::vector<std::string> val){
+    std::cout << "This option was given " << val.size() << " times." << std::endl;
+    return true;
+    });
+```
+
+### Utilities
+
+There are a few other utilities that are often useful in CLI programming. These are in separate headers, and do not appear in `CLI11.hpp`, but are completely independent and can be used as needed. The `Timer`/`AutoTimer` class allows you to easily time a block of code, with custom print output.
+
+```cpp
+{
+CLI::AutoTimer timer {"My Long Process", CLI::Timer::Big};
+some_long_running_process();
+}
+```
+
+This will create a timer with a title (default: `Timer`), and will customize the output using the predefined `Big` output (default: `Simple`). Because it is an `AutoTimer`, it will print out the time elapsed when the timer is destroyed at the end of the block. If you use `Timer` instead, you can use `to_string` or `std::cout << timer << std::endl;` to print the time. The print function can be any function that takes two strings, the title and the time, and returns a formatted
+string for printing.
+
+### Other libraries
+
+If you use the excellent [Rang][] library to add color to your terminal in a safe, multi-platform way, you can combine it with CLI11 nicely:
+
+```cpp
+std::atexit([](){std::cout << rang::style::reset;});
+try {
+    app.parse(argc, argv);
+} catch (const CLI::ParseError &e) {
+    std::cout << (e.get_exit_code()==0 ? rang::fg::blue : rang::fg::red);
+    return app.exit(e);
+}
+```
+
+This will print help in blue, errors in red, and will reset before returning the terminal to the user.
+
+If you are on a Unix-like system, and you'd like to handle control-c and color, you can add:
+
+```cpp
+ #include <csignal>
+ void signal_handler(int s) {
+     std::cout << std::endl << rang::style::reset << rang::fg::red << rang::fg::bold;
+     std::cout << "Control-C detected, exiting..." << rang::style::reset << std::endl;
+     std::exit(1); // will call the correct exit func, no unwinding of the stack though
+ }
+```
+
+And, in your main function:
+
+```cpp
+     // Nice Control-C
+     struct sigaction sigIntHandler;
+     sigIntHandler.sa_handler = signal_handler;
+     sigemptyset(&sigIntHandler.sa_mask);
+     sigIntHandler.sa_flags = 0;
+     sigaction(SIGINT, &sigIntHandler, nullptr);
+```
+
+## API
+
+The API is [documented here][api-docs]. Also see the [CLI11 tutorial GitBook][gitbook].
+
+## Examples
+
+Several short examples of different features are included in the repository. A brief description of each is included here
+
+* [callback_passthrough](https://github.com/CLIUtils/CLI11/blob/main/examples/callback_passthrough.cpp): Example of directly passing remaining arguments through to a callback function which generates a CLI11 application based on existing arguments.
+* [custom_parse](https://github.com/CLIUtils/CLI11/blob/main/examples/custom_parse.cpp): Based on [Issue #566](https://github.com/CLIUtils/CLI11/issues/566), example of custom parser
+* [digit_args](https://github.com/CLIUtils/CLI11/blob/main/examples/digit_args.cpp): Based on [Issue #123](https://github.com/CLIUtils/CLI11/issues/123), uses digit flags to pass a value
+* [enum](https://github.com/CLIUtils/CLI11/blob/main/examples/enum.cpp): Using enumerations in an option, and the use of [CheckedTransformer](#transforming-validators)
+* [enum_ostream](https://github.com/CLIUtils/CLI11/blob/main/examples/enum_ostream.cpp): In addition to the contents of example enum.cpp, this example shows how a custom ostream operator overrides CLI11's enum streaming.
+* [formatter](https://github.com/CLIUtils/CLI11/blob/main/examples/formatter.cpp): Illustrating usage of a custom formatter
+* [groups](https://github.com/CLIUtils/CLI11/blob/main/examples/groups.cpp): Example using groups of options for help grouping and a the timer helper class
+* [inter_argument_order](https://github.com/CLIUtils/CLI11/blob/main/examples/inter_argument_order.cpp): An app to practice mixing unlimited arguments, but still recover the original order.
+* [json](https://github.com/CLIUtils/CLI11/blob/main/examples/json.cpp): Using JSON as a config file parser
+* [modhelp](https://github.com/CLIUtils/CLI11/blob/main/examples/modhelp.cpp): How to modify the help flag to do something other than default
+* [nested](https://github.com/CLIUtils/CLI11/blob/main/examples/nested.cpp): Nested subcommands
+* [option_groups](https://github.com/CLIUtils/CLI11/blob/main/examples/option_groups.cpp): Illustrating the use of option groups and a required number of options. Based on [Issue #88](https://github.com/CLIUtils/CLI11/issues/88) to set interacting groups of options
+* [positional_arity](https://github.com/CLIUtils/CLI11/blob/main/examples/positional_arity.cpp): Illustrating use of `preparse_callback` to handle situations where the number of arguments can determine which should get parsed,  Based on [Issue #166](https://github.com/CLIUtils/CLI11/issues/166)
+* [positional_validation](https://github.com/CLIUtils/CLI11/blob/main/examples/positional_validation.cpp): Example of how positional arguments are validated using the `validate_positional` flag, also based on [Issue #166](https://github.com/CLIUtils/CLI11/issues/166)
+* [prefix_command](https://github.com/CLIUtils/CLI11/blob/main/examples/prefix_command.cpp): Illustrating use of the `prefix_command` flag.
+* [ranges](https://github.com/CLIUtils/CLI11/blob/main/examples/ranges.cpp): App to demonstrate exclusionary option groups based on [Issue #88](https://github.com/CLIUtils/CLI11/issues/88)
+* [shapes](https://github.com/CLIUtils/CLI11/blob/main/examples/shapes.cpp): Illustrating how to set up repeated subcommands Based on [gitter discussion](https://gitter.im/CLI11gitter/Lobby?at=5c7af6b965ffa019ea788cd5)
+* [simple](https://github.com/CLIUtils/CLI11/blob/main/examples/simple.cpp): A simple example of how to set up a CLI11 Application with different flags and options
+* [subcom_help](https://github.com/CLIUtils/CLI11/blob/main/examples/subcom_help.cpp): Configuring help for subcommands
+* [subcom_partitioned](https://github.com/CLIUtils/CLI11/blob/main/examples/subcom_partitioned.cpp): Example with a timer and subcommands generated separately and added to the main app later.
+* [subcommands](https://github.com/CLIUtils/CLI11/blob/main/examples/subcommands.cpp): Short example of subcommands
+* [validators](https://github.com/CLIUtils/CLI11/blob/main/examples/validators.cpp): Example illustrating use of validators
+
+## Contribute
+
+To contribute, open an [issue][github issues] or [pull request][github pull requests] on GitHub, or ask a question on [gitter][]. There is also a short note to contributors [here](./.github/CONTRIBUTING.md).
+This readme roughly follows the [Standard Readme Style][] and includes a mention of almost every feature of the library. More complex features are documented in more detail in the [CLI11 tutorial GitBook][gitbook].
+
+This project was created by [Henry Schreiner](https://github.com/henryiii) and major features were added by  [Philip Top](https://github.com/phlptp). Special thanks to all the contributors ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+
+<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
+<!-- prettier-ignore-start -->
+<!-- markdownlint-disable -->
+<table>
+  <tr>
+    <td align="center"><a href="http://iscinumpy.gitlab.io"><img src="https://avatars1.githubusercontent.com/u/4616906?v=4" width="100px;" alt=""/><br /><sub><b>Henry Schreiner</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Ahenryiii" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=henryiii" title="Documentation">📖</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=henryiii" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/phlptp"><img src="https://avatars0.githubusercontent.com/u/20667153?v=4" width="100px;" alt=""/><br /><sub><b>Philip Top</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Aphlptp" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=phlptp" title="Documentation">📖</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=phlptp" title="Code">💻</a></td>
+    <td align="center"><a href="https://www.linkedin.com/in/cbachhuber/"><img src="https://avatars0.githubusercontent.com/u/27212661?v=4" width="100px;" alt=""/><br /><sub><b>Christoph Bachhuber</b></sub></a><br /><a href="#example-cbachhuber" title="Examples">💡</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=cbachhuber" title="Code">💻</a></td>
+    <td align="center"><a href="https://lambdafu.net/"><img src="https://avatars1.githubusercontent.com/u/1138455?v=4" width="100px;" alt=""/><br /><sub><b>Marcus Brinkmann</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Alambdafu" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=lambdafu" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/SkyToGround"><img src="https://avatars1.githubusercontent.com/u/58835?v=4" width="100px;" alt=""/><br /><sub><b>Jonas Nilsson</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3ASkyToGround" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=SkyToGround" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/dvj"><img src="https://avatars2.githubusercontent.com/u/77217?v=4" width="100px;" alt=""/><br /><sub><b>Doug Johnston</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Advj" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=dvj" title="Code">💻</a></td>
+    <td align="center"><a href="http://lucas-czech.de"><img src="https://avatars0.githubusercontent.com/u/4741887?v=4" width="100px;" alt=""/><br /><sub><b>Lucas Czech</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Alczech" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=lczech" title="Code">💻</a></td>
+  </tr>
+  <tr>
+    <td align="center"><a href="https://github.com/rafiw"><img src="https://avatars3.githubusercontent.com/u/3034707?v=4" width="100px;" alt=""/><br /><sub><b>Rafi Wiener</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Arafiw" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=rafiw" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/mensinda"><img src="https://avatars3.githubusercontent.com/u/3407462?v=4" width="100px;" alt=""/><br /><sub><b>Daniel Mensinger</b></sub></a><br /><a href="#platform-mensinda" title="Packaging/porting to new platform">📦</a></td>
+    <td align="center"><a href="https://github.com/jbriales"><img src="https://avatars1.githubusercontent.com/u/6850478?v=4" width="100px;" alt=""/><br /><sub><b>Jesus Briales</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=jbriales" title="Code">💻</a> <a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Ajbriales" title="Bug reports">🐛</a></td>
+    <td align="center"><a href="https://seanfisk.com/"><img src="https://avatars0.githubusercontent.com/u/410322?v=4" width="100px;" alt=""/><br /><sub><b>Sean Fisk</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Aseanfisk" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=seanfisk" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/fpeng1985"><img src="https://avatars1.githubusercontent.com/u/87981?v=4" width="100px;" alt=""/><br /><sub><b>fpeng1985</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=fpeng1985" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/almikhayl"><img src="https://avatars2.githubusercontent.com/u/6747040?v=4" width="100px;" alt=""/><br /><sub><b>almikhayl</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=almikhayl" title="Code">💻</a> <a href="#platform-almikhayl" title="Packaging/porting to new platform">📦</a></td>
+    <td align="center"><a href="https://github.com/andrew-hardin"><img src="https://avatars0.githubusercontent.com/u/16496326?v=4" width="100px;" alt=""/><br /><sub><b>Andrew Hardin</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=andrew-hardin" title="Code">💻</a></td>
+  </tr>
+  <tr>
+    <td align="center"><a href="https://github.com/SX91"><img src="https://avatars2.githubusercontent.com/u/754754?v=4" width="100px;" alt=""/><br /><sub><b>Anton</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=SX91" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/helmesjo"><img src="https://avatars0.githubusercontent.com/u/2501070?v=4" width="100px;" alt=""/><br /><sub><b>Fred Helmesjö</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Ahelmesjo" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=helmesjo" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/skannan89"><img src="https://avatars0.githubusercontent.com/u/11918764?v=4" width="100px;" alt=""/><br /><sub><b>Kannan</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Askannan89" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=skannan89" title="Code">💻</a></td>
+    <td align="center"><a href="http://himvis.com"><img src="https://avatars3.githubusercontent.com/u/465279?v=4" width="100px;" alt=""/><br /><sub><b>Khem Raj</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=kraj" title="Code">💻</a></td>
+    <td align="center"><a href="https://www.mogigoma.com/"><img src="https://avatars2.githubusercontent.com/u/130862?v=4" width="100px;" alt=""/><br /><sub><b>Mak Kolybabi</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=mogigoma" title="Documentation">📖</a></td>
+    <td align="center"><a href="http://msoeken.github.io"><img src="https://avatars0.githubusercontent.com/u/1998245?v=4" width="100px;" alt=""/><br /><sub><b>Mathias Soeken</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=msoeken" title="Documentation">📖</a></td>
+    <td align="center"><a href="https://github.com/nathanhourt"><img src="https://avatars2.githubusercontent.com/u/271977?v=4" width="100px;" alt=""/><br /><sub><b>Nathan Hourt</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Anathanhourt" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=nathanhourt" title="Code">💻</a></td>
+  </tr>
+  <tr>
+    <td align="center"><a href="https://github.com/pleroux0"><img src="https://avatars2.githubusercontent.com/u/39619854?v=4" width="100px;" alt=""/><br /><sub><b>Paul le Roux</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=pleroux0" title="Code">💻</a> <a href="#platform-pleroux0" title="Packaging/porting to new platform">📦</a></td>
+    <td align="center"><a href="https://github.com/chfast"><img src="https://avatars1.githubusercontent.com/u/573380?v=4" width="100px;" alt=""/><br /><sub><b>Paweł Bylica</b></sub></a><br /><a href="#platform-chfast" title="Packaging/porting to new platform">📦</a></td>
+    <td align="center"><a href="https://github.com/peterazmanov"><img src="https://avatars0.githubusercontent.com/u/15322318?v=4" width="100px;" alt=""/><br /><sub><b>Peter Azmanov</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=peterazmanov" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/delpinux"><img src="https://avatars0.githubusercontent.com/u/35096584?v=4" width="100px;" alt=""/><br /><sub><b>Stéphane Del Pino</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=delpinux" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/metopa"><img src="https://avatars2.githubusercontent.com/u/3974178?v=4" width="100px;" alt=""/><br /><sub><b>Viacheslav Kroilov</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=metopa" title="Code">💻</a></td>
+    <td align="center"><a href="http://cs.odu.edu/~ctsolakis"><img src="https://avatars0.githubusercontent.com/u/6725596?v=4" width="100px;" alt=""/><br /><sub><b>christos</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ChristosT" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/deining"><img src="https://avatars3.githubusercontent.com/u/18169566?v=4" width="100px;" alt=""/><br /><sub><b>deining</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=deining" title="Documentation">📖</a></td>
+  </tr>
+  <tr>
+    <td align="center"><a href="https://github.com/elszon"><img src="https://avatars0.githubusercontent.com/u/2971495?v=4" width="100px;" alt=""/><br /><sub><b>elszon</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=elszon" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/ncihnegn"><img src="https://avatars3.githubusercontent.com/u/12021721?v=4" width="100px;" alt=""/><br /><sub><b>ncihnegn</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ncihnegn" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/nurelin"><img src="https://avatars3.githubusercontent.com/u/5276274?v=4" width="100px;" alt=""/><br /><sub><b>nurelin</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=nurelin" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/ryan4729"><img src="https://avatars3.githubusercontent.com/u/40183301?v=4" width="100px;" alt=""/><br /><sub><b>ryan4729</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ryan4729" title="Tests">⚠️</a></td>
+    <td align="center"><a href="https://izzys.casa"><img src="https://avatars0.githubusercontent.com/u/63051?v=4" width="100px;" alt=""/><br /><sub><b>Isabella Muerte</b></sub></a><br /><a href="#platform-slurps-mad-rips" title="Packaging/porting to new platform">📦</a></td>
+    <td align="center"><a href="https://github.com/KOLANICH"><img src="https://avatars1.githubusercontent.com/u/240344?v=4" width="100px;" alt=""/><br /><sub><b>KOLANICH</b></sub></a><br /><a href="#platform-KOLANICH" title="Packaging/porting to new platform">📦</a></td>
+    <td align="center"><a href="https://github.com/jgerityneurala"><img src="https://avatars2.githubusercontent.com/u/57360646?v=4" width="100px;" alt=""/><br /><sub><b>James Gerity</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=jgerityneurala" title="Documentation">📖</a></td>
+  </tr>
+  <tr>
+    <td align="center"><a href="https://github.com/jsoref"><img src="https://avatars0.githubusercontent.com/u/2119212?v=4" width="100px;" alt=""/><br /><sub><b>Josh Soref</b></sub></a><br /><a href="#tool-jsoref" title="Tools">🔧</a></td>
+    <td align="center"><a href="https://github.com/geir-t"><img src="https://avatars3.githubusercontent.com/u/35292136?v=4" width="100px;" alt=""/><br /><sub><b>geir-t</b></sub></a><br /><a href="#platform-geir-t" title="Packaging/porting to new platform">📦</a></td>
+    <td align="center"><a href="https://ondrejcertik.com/"><img src="https://avatars3.githubusercontent.com/u/20568?v=4" width="100px;" alt=""/><br /><sub><b>Ondřej Čertík</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Acertik" title="Bug reports">🐛</a></td>
+    <td align="center"><a href="http://sam.hocevar.net/"><img src="https://avatars2.githubusercontent.com/u/245089?v=4" width="100px;" alt=""/><br /><sub><b>Sam Hocevar</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=samhocevar" title="Code">💻</a></td>
+    <td align="center"><a href="http://www.ratml.org/"><img src="https://avatars0.githubusercontent.com/u/1845039?v=4" width="100px;" alt=""/><br /><sub><b>Ryan Curtin</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=rcurtin" title="Documentation">📖</a></td>
+    <td align="center"><a href="https://mbh.sh"><img src="https://avatars3.githubusercontent.com/u/20403931?v=4" width="100px;" alt=""/><br /><sub><b>Michael Hall</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=mbhall88" title="Documentation">📖</a></td>
+    <td align="center"><a href="https://github.com/ferdymercury"><img src="https://avatars3.githubusercontent.com/u/10653970?v=4" width="100px;" alt=""/><br /><sub><b>ferdymercury</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ferdymercury" title="Documentation">📖</a></td>
+  </tr>
+  <tr>
+    <td align="center"><a href="https://github.com/jakoblover"><img src="https://avatars0.githubusercontent.com/u/14160441?v=4" width="100px;" alt=""/><br /><sub><b>Jakob Lover</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=jakoblover" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/ZeeD26"><img src="https://avatars2.githubusercontent.com/u/2487468?v=4" width="100px;" alt=""/><br /><sub><b>Dominik Steinberger</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ZeeD26" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/dfleury2"><img src="https://avatars1.githubusercontent.com/u/4805384?v=4" width="100px;" alt=""/><br /><sub><b>D. Fleury</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=dfleury2" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/dbarowy"><img src="https://avatars3.githubusercontent.com/u/573142?v=4" width="100px;" alt=""/><br /><sub><b>Dan Barowy</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=dbarowy" title="Documentation">📖</a></td>
+    <td align="center"><a href="https://github.com/paddy-hack"><img src="https://avatars.githubusercontent.com/u/6804372?v=4" width="100px;" alt=""/><br /><sub><b>Olaf Meeuwissen</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=paddy-hack" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/dryleev"><img src="https://avatars.githubusercontent.com/u/83670813?v=4" width="100px;" alt=""/><br /><sub><b>dryleev</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=dryleev" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/AnticliMaxtic"><img src="https://avatars.githubusercontent.com/u/43995389?v=4" width="100px;" alt=""/><br /><sub><b>Max</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=AnticliMaxtic" title="Code">💻</a></td>
+  </tr>
+  <tr>
+    <td align="center"><a href="https://profiles.sussex.ac.uk/p281168-alex-dewar/publications"><img src="https://avatars.githubusercontent.com/u/23149834?v=4" width="100px;" alt=""/><br /><sub><b>Alex Dewar</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=alexdewar" title="Code">💻</a></td>
+  </tr>
+</table>
+
+<!-- markdownlint-enable -->
+<!-- prettier-ignore-end -->
+<!-- ALL-CONTRIBUTORS-LIST:END -->
+
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
+
+## License
+
+As of version 1.0, this library is available under a 3-Clause BSD license. See the [LICENSE](./LICENSE) file for details.
+
+CLI11 was developed at the [University of Cincinnati][] to support of the [GooFit][] library under [NSF Award 1414736][]. Version 0.9 was featured in a [DIANA/HEP][] meeting at CERN ([see the slides][diana slides]). Please give it a try! Feedback is always welcome.
+
+[doi-badge]: https://zenodo.org/badge/80064252.svg
+[doi-link]: https://zenodo.org/badge/latestdoi/80064252
+[azure-badge]: https://dev.azure.com/CLIUtils/CLI11/_apis/build/status/CLIUtils.CLI11?branchName=main
+[azure]: https://dev.azure.com/CLIUtils/CLI11
+[actions-link]: https://github.com/CLIUtils/CLI11/actions
+[actions-badge]: https://github.com/CLIUtils/CLI11/actions/workflows/tests.yml/badge.svg
+[appveyor-badge]: https://ci.appveyor.com/api/projects/status/82niaxpaa28dwbms/branch/main?svg=true
+[appveyor]: https://ci.appveyor.com/project/HenrySchreiner/cli11
+[repology-badge]: https://repology.org/badge/latest-versions/cli11.svg
+[repology]: https://repology.org/project/cli11/versions
+[codecov-badge]: https://codecov.io/gh/CLIUtils/CLI11/branch/main/graph/badge.svg?token=2O4wfs8NJO
+[codecov]: https://codecov.io/gh/CLIUtils/CLI11
+[gitter-badge]: https://badges.gitter.im/CLI11gitter/Lobby.svg
+[gitter]: https://gitter.im/CLI11gitter/Lobby
+[license-badge]: https://img.shields.io/badge/License-BSD-blue.svg
+[conan-badge]: https://img.shields.io/badge/conan-io-blue
+[conan-link]: https://conan.io/center/cli11
+[conda-badge]: https://img.shields.io/conda/vn/conda-forge/cli11.svg
+[conda-link]: https://github.com/conda-forge/cli11-feedstock
+[github releases]: https://github.com/CLIUtils/CLI11/releases
+[github issues]: https://github.com/CLIUtils/CLI11/issues
+[github pull requests]: https://github.com/CLIUtils/CLI11/pulls
+[goofit]: https://GooFit.github.io
+[plumbum]: https://plumbum.readthedocs.io/en/latest/
+[click]: http://click.pocoo.org
+[api-docs]: https://CLIUtils.github.io/CLI11/index.html
+[rang]: https://github.com/agauniyal/rang
+[boost program options]: http://www.boost.org/doc/libs/1_63_0/doc/html/program_options.html
+[the lean mean c++ option parser]: http://optionparser.sourceforge.net
+[tclap]: http://tclap.sourceforge.net
+[cxxopts]: https://github.com/jarro2783/cxxopts
+[docopt]: https://github.com/docopt/docopt.cpp
+[gflags]: https://gflags.github.io/gflags
+[getopt]: https://www.gnu.org/software/libc/manual/html_node/Getopt.html
+[diana/hep]: http://diana-hep.org
+[nsf award 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736
+[university of cincinnati]: http://www.uc.edu
+[gitbook]: https://cliutils.github.io/CLI11/book/
+[cli11 advanced topics/custom converters]: https://cliutils.gitlab.io/CLI11Tutorial/chapters/advanced-topics.html#custom-converters
+[programoptions.hxx]: https://github.com/Fytch/ProgramOptions.hxx
+[argument aggregator]: https://github.com/vietjtnguyen/argagg
+[args]: https://github.com/Taywee/args
+[argh!]: https://github.com/adishavit/argh
+[fmt]: https://github.com/fmtlib/fmt
+[catch]: https://github.com/philsquared/Catch
+[clara]: https://github.com/philsquared/Clara
+[version 1.0 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-10/
+[version 1.3 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-13/
+[version 1.6 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-16/
+[version 2.0 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-20/
+[wandbox-badge]: https://img.shields.io/badge/try_2.1-online-blue.svg
+[wandbox-link]: https://wandbox.org/permlink/CA5bymNHh0AczdeN
+[releases-badge]: https://img.shields.io/github/release/CLIUtils/CLI11.svg
+[cli11-po-compare]: https://iscinumpy.gitlab.io/post/comparing-cli11-and-boostpo/
+[diana slides]: https://indico.cern.ch/event/619465/contributions/2507949/attachments/1448567/2232649/20170424-diana-2.pdf
+[awesome c++]: https://github.com/fffaraz/awesome-cpp/blob/master/README.md#cli
+[cli]: https://codesynthesis.com/projects/cli/
+[single file libs]: https://github.com/nothings/single_file_libs/blob/master/README.md
+[codacy-badge]: https://app.codacy.com/project/badge/Grade/2796b969c1b54321a02ad08affec0800
+[codacy-link]: https://www.codacy.com/gh/CLIUtils/CLI11/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=CLIUtils/CLI11&amp;utm_campaign=Badge_Grade
+[hunter]: https://docs.hunter.sh/en/latest/packages/pkg/CLI11.html
+[standard readme style]: https://github.com/RichardLitt/standard-readme
+[argparse]: https://github.com/p-ranav/argparse
+[toml]: https://toml.io
diff --git a/external/CLI11/azure-pipelines.yml b/external/CLI11/azure-pipelines.yml
new file mode 100644
index 0000000..750ac31
--- /dev/null
+++ b/external/CLI11/azure-pipelines.yml
@@ -0,0 +1,131 @@
+# C/C++ with GCC
+# Build your C/C++ project with GCC using make.
+# Add steps that publish test results, save build artifacts, deploy, and more:
+# https://docs.microsoft.com/azure/devops/pipelines/apps/c-cpp/gcc
+
+trigger:
+- main
+- 'v*'
+
+pr:
+- main
+- 'v*'
+
+variables:
+  cli11.single: ON
+  cli11.std: 14
+  cli11.build_type: Debug
+  cli11.options: -DCLI11_EXAMPLES_JSON=ON
+  CMAKE_BUILD_PARALLEL_LEVEL: 4
+
+jobs:
+
+- job: ClangTidy
+  variables:
+    CXX_FLAGS: "-Werror -Wcast-align -Wfloat-equal -Wimplicit-atomic-properties -Wmissing-declarations -Woverlength-strings -Wshadow -Wstrict-selector-match -Wundeclared-selector -Wunreachable-code -std=c++11"
+    cli11.options: -DCLI11_CLANG_TIDY=ON -DCLI11_CLANG_TIDY_OPTIONS="-fix"
+    cli11.std: 11
+    cli11.single: OFF
+    CMAKE_BUILD_PARALLEL_LEVEL: 1
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: silkeh/clang:8
+  steps:
+    - template: .ci/azure-cmake.yml
+    - template: .ci/azure-build.yml
+    - script: git diff --exit-code --color
+      displayName: Check tidy
+
+- job: CppLint
+  pool:
+    vmImage: 'ubuntu-latest'
+  container: sharaku/cpplint:latest
+  steps:
+    - bash: cpplint --counting=detailed --recursive examples include/CLI tests
+      displayName: Checking against google style guide
+
+# TODO: Fix macOS error and windows warning in c++17 mode
+- job: Native
+  strategy:
+    matrix:
+      Linux14:
+        vmImage: 'ubuntu-latest'
+      macOS17:
+        vmImage: 'macOS-latest'
+        cli11.std: 17
+      macOS11:
+        vmImage: 'macOS-latest'
+        cli11.std: 11
+      Windows17:
+        vmImage: 'vs2017-win2016'
+        cli11.std: 17
+      Windows11:
+        vmImage: 'vs2017-win2016'
+        cli11.std: 11
+      Windowslatest:
+        vmImage: 'windows-2019'
+        cli11.std: 20
+        cli11.options: -DCMAKE_CXX_FLAGS="/std:c++latest /EHsc"
+      Linux17nortti:
+        vmImage: 'ubuntu-latest'
+        cli11.std: 17
+        cli11.options: -DCMAKE_CXX_FLAGS="-fno-rtti"
+  pool:
+    vmImage: $(vmImage)
+  steps:
+    - template: .ci/azure-build.yml
+    - template: .ci/azure-test.yml
+
+- job: Meson
+  pool:
+    vmImage: 'ubuntu-latest'
+  steps:
+    - task: UsePythonVersion@0
+      inputs:
+        versionSpec: '3.6'
+    - script: python3 -m pip install meson ninja
+    - script: meson build
+      displayName: Run meson to generate build
+      workingDirectory: tests/mesonTest
+    - script: ninja -C tests/mesonTest/build
+      displayName: Build with Ninja
+    - script: ./tests/mesonTest/build/main --help
+      displayName: Run help
+
+- job: Docker
+  variables:
+    cli11.single: OFF
+  pool:
+    vmImage: 'ubuntu-latest'
+  strategy:
+    matrix:
+      gcc9:
+        containerImage: gcc:9
+        cli11.std: 17
+      gcc8:
+        containerImage: gcc:8
+        cli11.std: 17
+      gcc4.8:
+        containerImage: gcc:4.8
+        cli11.std: 11
+        cli11.options:
+      clang3.4:
+        containerImage: silkeh/clang:3.4
+        cli11.std: 11
+      clang8:
+        containerImage: silkeh/clang:8
+        cli11.std: 14
+        cli11.options: -DCLI11_FORCE_LIBCXX=ON
+      clang8_17:
+        containerImage: silkeh/clang:8
+        cli11.std: 17
+        cli11.options: -DCLI11_FORCE_LIBCXX=ON
+      clang10_20:
+        containerImage: silkeh/clang:10
+        cli11.std: 20
+        cli11.options: -DCLI11_FORCE_LIBCXX=ON -DCMAKE_CXX_FLAGS=-std=c++20
+  container: $[ variables['containerImage'] ]
+  steps:
+    - template: .ci/azure-cmake.yml
+    - template: .ci/azure-build.yml
+    - template: .ci/azure-test.yml
diff --git a/external/CLI11/book/.gitignore b/external/CLI11/book/.gitignore
new file mode 100644
index 0000000..bb77454
--- /dev/null
+++ b/external/CLI11/book/.gitignore
@@ -0,0 +1,20 @@
+# Node rules:
+## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+## Dependency directory
+## Commenting this out is preferred by some people, see
+## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git
+node_modules
+
+# Book build output
+_book
+
+# eBook build output
+*.epub
+*.mobi
+*.pdf
+
+a.out
+
+*build*
diff --git a/external/CLI11/book/CMakeLists.txt b/external/CLI11/book/CMakeLists.txt
new file mode 100644
index 0000000..8934873
--- /dev/null
+++ b/external/CLI11/book/CMakeLists.txt
@@ -0,0 +1,7 @@
+set(book_sources README.md SUMMARY.md)
+
+file(
+  GLOB book_chapters
+  RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+  chapters/*.md)
+add_custom_target(cli_book SOURCES ${book_sources} ${book_chapters})
diff --git a/external/CLI11/book/README.md b/external/CLI11/book/README.md
new file mode 100644
index 0000000..917e753
--- /dev/null
+++ b/external/CLI11/book/README.md
@@ -0,0 +1,65 @@
+# CLI11: An introduction
+
+This gitbook is designed to provide an introduction to using the CLI11 library to write your own command line programs. The library is designed to be clean, intuitive, but powerful. There are no requirements beyond C++11 support (and even `<regex>` support not required). It works on Mac, Linux, and Windows, and has 100% test coverage on all three systems. You can simply drop in a single header file (`CLI11.hpp` available in [releases][]) to use CLI11 in your own application. Other ways to integrate it into a build system are listed in the [README][].
+
+The library was inspired the Python libraries [Plumbum][] and [Click][], and incorporates many of their user friendly features. The library is extensively documented, with a [friendly introduction][README], this tutorial book, and more technical [API docs][].
+
+> Feel free to contribute to [this documentation here][CLI11Tutorial] if something can be improved!
+
+The syntax is simple and scales from a basic application to a massive physics analysis with multiple models and many parameters and switches. For example, this is a simple program that has an optional parameter that defaults to 0:
+
+```term
+gitbook $ ./a.out
+Parameter value: 0
+
+gitbook $ ./a.out -p 4
+Parameter value: 4
+
+gitbook $ ./a.out --help
+App description
+Usage: ./a.out [OPTIONS]
+
+Options:
+  -h,--help                   Print this help message and exit
+  -p INT                      Parameter
+```
+
+Like any good command line application, help is provided. This program can be implemented in 10 lines:
+
+[include](code/intro.cpp)
+
+[Source code](https://github.com/CLIUtils/CLI11/blob/main/book/code/intro.cpp)
+
+Unlike some other libraries, this is enough to exit correctly and cleanly if help is requested or if incorrect arguments are passed. You can try this example out for yourself. To compile with GCC:
+
+```term
+gitbook:examples $ c++ -std=c++11 intro.cpp
+```
+
+Much more complicated options are handled elegantly:
+
+```cpp
+std::string file;
+app.add_option("-f,--file", file, "Require an existing file")
+  ->required()
+  ->check(CLI::ExistingFile);
+```
+
+You can use any valid type; the above example could have used a `boost::file_system` file instead of a `std::string`. The value is a real value and does not require any special lookups to access. You do not have to risk typos by repeating the values after parsing like some libraries require. The library also handles positional arguments, flags, fixed or unlimited repeating options, interdependent options, flags, custom validators, help groups, and more.
+
+You can use subcommands, as well. Subcommands support callback lambda functions when parsed, or they can be checked later. You can infinitely nest subcommands, and each is a full `App` instance, supporting everything listed above.
+
+Reading/producing `.ini` files for configuration is also supported, as is using environment variables as input. The base `App` can be subclassed and customized for use in a toolkit (like [GooFit][]). All the standard shell idioms, like `--`, work as well.
+
+CLI11 was developed at the [University of Cincinnati][] in support of the [GooFit][] library under [NSF Award 1414736][NSF 1414736]. It was featured in a [DIANA/HEP][] meeting at CERN. Please give it a try! Feedback is always welcome.
+
+[goofit]: https://github.com/GooFit/GooFit
+[diana/hep]: https://diana-hep.org
+[cli11tutorial]: https://cliutils.github.io/CLI11/book
+[releases]: https://github.com/CLIUtils/CLI11/releases
+[api docs]: https://cliutils.github.io/CLI11
+[readme]: https://github.com/CLIUtils/CLI11/blob/main/README.md
+[nsf 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736
+[university of cincinnati]: https://www.uc.edu
+[plumbum]: https://plumbum.readthedocs.io/en/latest/
+[click]: https://click.palletsprojects.com
diff --git a/external/CLI11/book/SUMMARY.md b/external/CLI11/book/SUMMARY.md
new file mode 100644
index 0000000..38261d5
--- /dev/null
+++ b/external/CLI11/book/SUMMARY.md
@@ -0,0 +1,15 @@
+# Summary
+
+* [Introduction](/README.md)
+* [Installation](/chapters/installation.md)
+* [Basics](/chapters/basics.md)
+* [Flags](/chapters/flags.md)
+* [Options](/chapters/options.md)
+* [Validators](/chapters/validators.md)
+* [Subcommands and the App](/chapters/subcommands.md)
+* [An advanced example](/chapters/an-advanced-example.md)
+* [Configuration files](/chapters/config.md)
+* [Formatting help output](/chapters/formatting.md)
+* [Toolkits](/chapters/toolkits.md)
+* [Advanced topics](/chapters/advanced-topics.md)
+* [Internals](/chapters/internals.md)
diff --git a/external/CLI11/book/book.json b/external/CLI11/book/book.json
new file mode 100644
index 0000000..f8745d6
--- /dev/null
+++ b/external/CLI11/book/book.json
@@ -0,0 +1,16 @@
+{
+"title": "CLI11 Tutorial",
+"description": "A set of examples and detailed information about CLI11",
+"author": "Henry Schreiner",
+"plugins": [
+        "include-codeblock",
+        "term",
+        "hints"
+    ],
+"pluginsConfig": {
+        "include-codeblock": {
+            "unindent": true,
+            "fixlang": true
+        }
+    }
+}
diff --git a/external/CLI11/book/chapters/advanced-topics.md b/external/CLI11/book/chapters/advanced-topics.md
new file mode 100644
index 0000000..fee2821
--- /dev/null
+++ b/external/CLI11/book/chapters/advanced-topics.md
@@ -0,0 +1,137 @@
+# Advanced topics
+
+## Environment variables
+
+Environment variables can be used to fill in the value of an option:
+
+```cpp
+std::string opt;
+app.add_option("--my_option", opt)->envname("MY_OPTION");
+```
+
+If not given on the command line, the environment variable will be checked and read from if it exists. All the standard tools, like default and required, work as expected.
+If passed on the command line, this will ignore the environment variable.
+
+## Needs/excludes
+
+You can set a network of requirements. For example, if flag a needs flag b but cannot be given with flag c, that would be:
+
+```cpp
+auto a = app.add_flag("-a");
+auto b = app.add_flag("-b");
+auto c = app.add_flag("-c");
+
+a->needs(b);
+a->excludes(c);
+```
+
+CLI11 will make sure your network of requirements makes sense, and will throw an error immediately if it does not.
+
+## Custom option callbacks
+
+You can make a completely generic option with a custom callback. For example, if you wanted to add a complex number (already exists, so please don't actually do this):
+
+```cpp
+CLI::Option *
+add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) {
+    CLI::callback_t fun = [&variable](CLI::results_t res) {
+        double x, y;
+        bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y);
+        if(worked)
+            variable = cx(x, y);
+        return worked;
+    };
+
+    CLI::Option *opt = app.add_option(name, fun, description, defaulted);
+    opt->set_custom_option("COMPLEX", 2);
+    if(defaulted) {
+        std::stringstream out;
+        out << variable;
+        opt->set_default_str(out.str());
+    }
+    return opt;
+}
+```
+
+Then you could use it like this:
+
+```cpp
+std::complex<double> comp{0, 0};
+add_option(app, "-c,--complex", comp);
+```
+
+## Custom converters
+
+You can add your own converters to allow CLI11 to accept more option types in the standard calls. These can only be used for "single" size options (so complex, vector, etc. are a separate topic). If you set up a custom `istringstream& operator <<` overload before include CLI11, you can support different conversions. If you place this in the CLI namespace, you can even keep this from affecting the rest of your code. Here's how you could add `boost::optional` for a compiler that does not have `__has_include`:
+
+```cpp
+// CLI11 already does this if __has_include is defined
+#ifndef __has_include
+
+#include <boost/optional.hpp>
+
+// Use CLI namespace to avoid the conversion leaking into your other code
+namespace CLI {
+
+template <typename T> std::istringstream &operator>>(std::istringstream &in, boost::optional<T> &val) {
+    T v;
+    in >> v;
+    val = v;
+    return in;
+}
+
+}
+
+#endif
+
+#include <CLI11.hpp>
+```
+
+This is an example of how to use the system only; if you are just looking for a way to activate `boost::optional` support on older compilers, you should define `CLI11_BOOST_OPTIONAL` before including a CLI11 file, you'll get the `boost::optional` support.
+
+## Custom converters and type names: std::chrono example
+
+An example of adding a custom converter and typename for `std::chrono` follows:
+
+```cpp
+namespace CLI
+{
+    template <typename T, typename R>
+    std::istringstream &operator>>(std::istringstream &in, std::chrono::duration<T,R> &val)
+    {
+        T v;
+        in >> v;
+        val = std::chrono::duration<T,R>(v);
+        return in;
+    }
+
+    template <typename T, typename R>
+    std::stringstream &operator<<(std::stringstream &in, std::chrono::duration<T,R> &val)
+    {
+        in << val.count();
+        return in;
+    }
+ }
+
+#include <CLI/CLI.hpp>
+
+namespace CLI
+{
+    namespace detail
+    {
+        template <>
+        constexpr const char *type_name<std::chrono::hours>()
+        {
+            return "TIME [H]";
+        }
+
+        template <>
+        constexpr const char *type_name<std::chrono::minutes>()
+        {
+            return "TIME [MIN]";
+        }
+        }
+}
+```
+
+Thanks to Olivier Hartmann for the example.
diff --git a/external/CLI11/book/chapters/an-advanced-example.md b/external/CLI11/book/chapters/an-advanced-example.md
new file mode 100644
index 0000000..84e838e
--- /dev/null
+++ b/external/CLI11/book/chapters/an-advanced-example.md
@@ -0,0 +1,31 @@
+# Making a git clone
+
+Let's try our hand at a little `git` clone, called `geet`. It will just print it's intent, rather than running actual code, since it's just a demonstration. Let's start by adding an app and requiring 1 subcommand to run:
+
+[include:"Intro"](../code/geet.cpp)
+
+Now, let's define the first subcommand, `add`, along with a few options:
+
+[include:"Add"](../code/geet.cpp)
+
+Now, let's add `commit`:
+
+[include:"Commit"](../code/geet.cpp)
+
+All that's need now is the parse call. We'll print a little message after the code runs, and then return:
+
+[include:"Parse"](../code/geet.cpp)
+
+[Source code](https://github.com/CLIUtils/CLI11/tree/main/book/code/geet.cpp)
+
+If you compile and run:
+
+```term
+gitbook:examples $ c++ -std=c++11 geet.cpp -o geet
+```
+
+You'll see it behaves pretty much like `git`.
+
+## Multi-file App parse code
+
+This example could be made much nicer if it was split into files, one per subcommand. If you simply use shared pointers instead of raw values in the lambda capture, you can tie the lifetime to the lambda function lifetime.  CLI11 has a multifile example in its example folder.
diff --git a/external/CLI11/book/chapters/basics.md b/external/CLI11/book/chapters/basics.md
new file mode 100644
index 0000000..0c55ec4
--- /dev/null
+++ b/external/CLI11/book/chapters/basics.md
@@ -0,0 +1,26 @@
+# The Basics
+
+The simplest CLI11 program looks like this:
+
+[include](../code/simplest.cpp)
+
+The first line includes the library; this explicitly uses the single file edition (see [Selecting an edition](/chapters/installation)).
+
+After entering the main function, you'll see that a `CLI::App` object is created. This is the basis for all interactions with the library. You could optionally provide a description for your app here.
+
+A normal CLI11 application would define some flags and options next. This is a simplest possible example, so we'll go on.
+
+The macro `CLI11_PARSE` just runs five simple lines. This internally runs `app.parse(argc, argv)`, which takes the command line info from C++ and parses it. If there is an error, it throws a `ParseError`; if you catch it, you can use `app.exit` with  the error as an argument to print a nice message and produce the correct return code for your application.
+
+If you just use `app.parse` directly, your application will still work, but the stack will not be correctly unwound since you have an uncaught exception, and the command line output will be cluttered, especially for help.
+
+For this (and most of the examples in this book) we will assume that we have the `CLI11.hpp` file in the current directory and that we are creating an output executable `a.out` on a macOS or Linux system. The commands to compile and test this example would be:
+
+```term
+gitbook:examples $ g++ -std=c++11 simplest.cpp
+gitbook:examples $ ./a.out -h
+Usage: ./a.out [OPTIONS]
+
+Options:
+  -h,--help                   Print this help message and exit
+```
diff --git a/external/CLI11/book/chapters/config.md b/external/CLI11/book/chapters/config.md
new file mode 100644
index 0000000..79295bd
--- /dev/null
+++ b/external/CLI11/book/chapters/config.md
@@ -0,0 +1,235 @@
+# Accepting configure files
+
+## Reading a configure file
+
+You can tell your app to allow configure files with `set_config("--config")`. There are arguments: the first is the option name. If empty, it will clear the config flag. The second item is the default file name. If that is specified, the config will try to read that file. The third item is the help string, with a reasonable default, and the final argument is a boolean (default: false) that indicates that the configuration file is required and an error will be thrown if the file is not found and this is set to true.
+
+### Extra fields
+
+Sometimes configuration files are used for multiple purposes so CLI11 allows options on how to deal with extra fields
+
+```cpp
+app.allow_config_extras(true);
+```
+
+will allow capture the extras in the extras field of the app. (NOTE:  This also sets the `allow_extras` in the app to true)
+
+```cpp
+app.allow_config_extras(false);
+```
+
+will generate an error if there are any extra fields
+for slightly finer control there is a scoped enumeration of the modes or
+
+```cpp
+app.allow_config_extras(CLI::config_extras_mode::ignore);
+```
+
+will completely ignore extra parameters in the config file.   This mode is the default.
+
+```cpp
+app.allow_config_extras(CLI::config_extras_mode::capture);
+```
+
+will store the unrecognized options in the app extras fields. This option is the closest equivalent to `app.allow_config_extras(true);` with the exception that it does not also set the `allow_extras` flag so using this option without also setting `allow_extras(true)` will generate an error which may or may not be the desired behavior.
+
+```cpp
+app.allow_config_extras(CLI::config_extras_mode::error);
+```
+
+is equivalent to `app.allow_config_extras(false);`
+
+```cpp
+app.allow_config_extras(CLI::config_extras_mode::ignore_all);
+```
+
+will completely ignore any mismatches, extras, or other issues with the config file
+
+### Getting the used configuration file name
+
+If it is needed to get the configuration file name used this can be obtained via
+`app.get_config_ptr()->as<std::string>()`  or
+`app["--config"]->as<std::string>()` assuming `--config` was the configuration option name.
+
+## Configure file format
+
+Here is an example configuration file, in [TOML](https://github.com/toml-lang/toml) format:
+
+```ini
+# Comments are supported, using a #
+# The default section is [default], case insensitive
+
+value = 1
+str = "A string"
+vector = [1,2,3]
+
+# Section map to subcommands
+[subcommand]
+in_subcommand = Wow
+[subcommand.sub]
+subcommand = true # could also be give as sub.subcommand=true
+```
+
+Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `y`, `t`, `+`, `yes`, `enable`; or `false`, `off`, `0`, `no`, `n`, `f`, `-`, `disable`, (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not necessarily mean that subcommand was passed, it just sets the "defaults". If a subcommand is set to `configurable` then passing the subcommand using `[sub]` in a configuration file will trigger the subcommand.)
+
+CLI11 also supports configuration file in INI format.
+
+```ini
+; Comments are supported, using a ;
+; The default section is [default], case insensitive
+
+value = 1
+str = "A string"
+vector = 1 2 3
+
+; Section map to subcommands
+[subcommand]
+in_subcommand = Wow
+sub.subcommand = true
+```
+
+The main differences are in vector notation and comment character.  Note: CLI11 is not a full TOML parser as it just reads values as strings.  It is possible (but not recommended) to mix notation.
+
+## Multiple configuration files
+
+If it is desired that multiple configuration be allowed.  Use
+
+```cpp
+app.set_config("--config")->expected(1, X);
+```
+
+Where X is some positive integer and will allow up to `X` configuration files to be specified by separate `--config` arguments.
+
+## Writing out a configure file
+
+To print a configuration file from the passed arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include option descriptions and the App description
+
+```cpp
+ CLI::App app;
+ app.add_option(...);
+    // several other options
+ CLI11_PARSE(app, argc, argv);
+ //the config printout should be after the parse to capture the given arguments
+ std::cout<<app.config_to_str(true,true);
+```
+
+if a prefix is needed to print before the options, for example to print a config for just a subcommand, the config formatter can be obtained directly.
+
+```cpp
+  auto fmtr=app.get_config_formatter();
+  //std::string to_config(const App *app, bool default_also, bool write_description, std::string prefix)
+  fmtr->to_config(&app,true,true,"sub.");
+  //prefix can be used to set a prefix before each argument,  like "sub."
+```
+
+### Customization of configure file output
+
+The default config parser/generator has some customization points that allow variations on the TOML format.  The default formatter has a base configuration that matches the TOML format.  It defines 5 characters that define how different aspects of the configuration are handled.  You must use `get_config_formatter_base()` to have access to these fields
+
+```cpp
+/// the character used for comments
+char commentChar = '#';
+/// the character used to start an array '\0' is a default to not use
+char arrayStart = '[';
+/// the character used to end an array '\0' is a default to not use
+char arrayEnd = ']';
+/// the character used to separate elements in an array
+char arraySeparator = ',';
+/// the character used separate the name from the value
+char valueDelimiter = '=';
+/// the character to use around strings
+char stringQuote = '"';
+/// the character to use around single characters
+char characterQuote = '\'';
+/// the maximum number of layers to allow
+uint8_t maximumLayers{255};
+/// the separator used to separator parent layers
+char parentSeparatorChar{'.'};
+/// Specify the configuration index to use for arrayed sections
+uint16_t configIndex{0};
+/// Specify the configuration section that should be used
+std::string configSection;
+```
+
+These can be modified via setter functions
+
+* `ConfigBase *comment(char cchar)`: Specify the character to start a comment block
+* `ConfigBase *arrayBounds(char aStart, char aEnd)`: Specify the start and end characters for an array
+* `ConfigBase *arrayDelimiter(char aSep)`: Specify the delimiter character for an array
+* `ConfigBase *valueSeparator(char vSep)`: Specify the delimiter between a name and value
+* `ConfigBase *quoteCharacter(char qString, char qChar)` :specify the characters to use around strings and single characters
+* `ConfigBase *maxLayers(uint8_t layers)` : specify the maximum number of parent layers to process. This is useful to limit processing for larger config files
+* `ConfigBase *parentSeparator(char sep)` : specify the character to separate parent layers from options
+* `ConfigBase *section(const std::string &sectionName)` : specify the section name to use to get the option values, only this section will be processed
+* `ConfigBase *index(uint16_t sectionIndex)` : specify an index section to use for processing if multiple TOML sections of the same name are present `[[section]]`
+
+For example, to specify reading a configure file that used `:` to separate name and values:
+
+```cpp
+auto config_base=app.get_config_formatter_base();
+config_base->valueSeparator(':');
+```
+
+The default configuration file will read INI files, but will write out files in the TOML format.  To specify outputting INI formatted files use
+
+```cpp
+app.config_formatter(std::make_shared<CLI::ConfigINI>());
+```
+
+which makes use of a predefined modification of the ConfigBase class which TOML also uses. If a custom formatter is used that is not inheriting from the from ConfigBase class `get_config_formatter_base() will return a nullptr if RTTI is on (usually the default), or garbage if RTTI is off, so some care must be exercised in its use with custom configurations.
+
+## Custom formats
+
+You can invent a custom format and set that instead of the default INI formatter. You need to inherit from `CLI::Config` and implement the following two functions:
+
+```cpp
+std::string to_config(const CLI::App *app, bool default_also, bool, std::string) const;
+std::vector<CLI::ConfigItem> from_config(std::istream &input) const;
+```
+
+The `CLI::ConfigItem`s that you return are simple structures with a name, a vector of parents, and a vector of results. A optionally customizable `to_flag` method on the formatter lets you change what happens when a ConfigItem turns into a flag.
+
+Finally, set your new class as new config formatter:
+
+```cpp
+app.config_formatter(std::make_shared<NewConfig>());
+```
+
+See [`examples/json.cpp`](https://github.com/CLIUtils/CLI11/blob/main/examples/json.cpp) for a complete JSON config example.
+
+### Trivial JSON configuration example
+
+```JSON
+{
+   "test": 56,
+   "testb": "test",
+   "flag": true
+}
+```
+
+The parser can handle these structures with only a minor tweak
+
+```cpp
+app.get_config_formatter_base()->valueSeparator(':');
+```
+
+The open and close brackets must be on a separate line and the comma gets interpreted as an array separator but since no values are after the comma they get ignored as well.  This will not support multiple layers or sections or any other moderately complex JSON, but can work if the input file is simple.
+
+## Triggering Subcommands
+
+Configuration files can be used to trigger subcommands if a subcommand is set to configure.  By default configuration file just set the default values of a subcommand.  But if the `configure()` option is set on a subcommand then the if the subcommand is utilized via a `[subname]` block in the configuration file it will act as if it were called from the command line.  Subsubcommands can be triggered via `[subname.subsubname]`.  Using the `[[subname]]` will be as if the subcommand were triggered multiple times from the command line.  This functionality can allow the configuration file to act as a scripting file.
+
+For custom configuration files this behavior can be triggered by specifying the parent subcommands in the structure and `++` as the name to open a new subcommand scope and `--` to close it.  These names trigger the different callbacks of configurable subcommands.
+
+## Stream parsing
+
+In addition to the regular parse functions a `parse_from_stream(std::istream &input)` is available to directly parse a stream operator.  For example to process some arguments in an already open file stream.  The stream is fed directly in the config parser so bypasses the normal command line parsing.
+
+## Implementation Notes
+
+The config file input works with any form of the option given:  Long, short, positional, or the environment variable name.  When generating a config file it will create an option name in following priority.
+
+1. First long name
+2. Positional name
+3. First short name
+4. Environment name
diff --git a/external/CLI11/book/chapters/flags.md b/external/CLI11/book/chapters/flags.md
new file mode 100644
index 0000000..4e269ac
--- /dev/null
+++ b/external/CLI11/book/chapters/flags.md
@@ -0,0 +1,123 @@
+# Adding Flags
+
+The most basic addition to a command line program is a flag. This is simply something that does not take any arguments. Adding a flag in CLI11 is done in one of three ways.
+
+## Boolean flags
+
+The simplest way to add a flag is probably a boolean flag:
+
+```cpp
+bool my_flag{false};
+app.add_flag("-f", my_flag, "Optional description");
+```
+
+This will bind the flag `-f` to the boolean `my_flag`. After the parsing step, `my_flag` will be `false` if the flag was not found on the command line, or `true` if it was. By default, it will be allowed any number of times, but if you explicitly\[^1\] request `->take_last(false)`, it will only be allowed once; passing something like `./my_app -f -f` or `./my_app -ff` will throw a `ParseError` with a nice help description. A flag name may start with any character except ('-', ' ', '\n', and '!'). For long flags, after the first character all characters are allowed except ('=',':','{',' ', '\n').  Names are given as a comma separated string, with the dash or dashes. An flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed.
+
+## Integer flags
+
+If you want to allow multiple flags, simply use any integer-like instead of a bool:
+
+```cpp
+int my_flag{0};
+app.add_flag("-f", my_flag, "Optional description");
+```
+
+After the parsing step, `my_flag` will contain the number of times this flag was found on the command line, including 0 if not found.
+
+## Arbitrary type flags
+
+CLI11 allows the type of the variable to assign to in the `add_flag` function to be any supported type.   This is particularly useful in combination with specifying default values for flags.  The allowed types include  bool, int, float, vector, enum, or string-like.
+
+### Default Flag Values
+
+Flag options specified through the `add_flag*` functions allow a syntax for the option names to default particular options to a false value or any other value if some flags are passed.  For example:
+
+```cpp
+app.add_flag("--flag,!--no-flag",result,"help for flag");
+```
+
+specifies that if `--flag` is passed on the command line result will be true or contain a value of 1. If `--no-flag` is
+passed `result` will contain false or -1 if `result` is a signed integer type, or 0 if it is an unsigned type.  An
+alternative form of the syntax is more explicit: `"--flag,--no-flag{false}"`; this is equivalent to the previous
+example.  This also works for short form options `"-f,!-n"` or `"-f,-n{false}"`. If `variable_to_bind_to` is anything but an integer value the
+default behavior is to take the last value given, while if `variable_to_bind_to` is an integer type the behavior will be to sum
+all the given arguments and return the result.  This can be modified if needed by changing the `multi_option_policy` on each flag (this is not inherited).
+The default value can be any value. For example if you wished to define a numerical flag:
+
+```cpp
+app.add_flag("-1{1},-2{2},-3{3}",result,"numerical flag")
+```
+
+using any of those flags on the command line will result in the specified number in the output.  Similar things can be done for string values, and enumerations, as long as the default value can be converted to the given type.
+
+## Pure flags
+
+Every command that starts with `add_`, such as the flag commands, return a pointer to the internally stored `CLI::Option` that describes your addition. If you prefer, you can capture this pointer and use it, and that allows you to skip adding a variable to bind to entirely:
+
+```cpp
+CLI::Option* my_flag = app.add_flag("-f", "Optional description");
+```
+
+After parsing, you can use `my_flag->count()` to count the number of times this was found. You can also directly use the value (`*my_flag`) as a bool. `CLI::Option` will be discussed in more detail later.
+
+## Callback flags
+
+If you want to define a callback that runs when you make a flag, you can use `add_flag_function` (C++11 or newer) or `add_flag` (C++14 or newer only) to add a callback function. The function should have the signature `void(std::size_t)`. This could be useful for a version printout, etc.
+
+```cpp
+auto callback = [](int count){std::cout << "This was called " << count << " times";};
+app.add_flag_function("-c", callback, "Optional description");
+```
+
+## Aliases
+
+The name string, the first item of every `add_` method, can contain as many short and long names as you want, separated by commas. For example, `"-a,--alpha,-b,--beta"` would allow any of those to be recognized on the command line. If you use the same name twice, or if you use the same name in multiple flags, CLI11 will immediately throw a `CLI::ConstructionError` describing your problem (it will not wait until the parsing step).
+
+If you want to make an option case insensitive, you can use the `->ignore_case()` method on the `CLI::Option` to do that. For example,
+
+```cpp
+bool flag{false};
+app.add_flag("--flag", flag)
+    ->ignore_case();
+```
+
+would allow the following to count as passing the flag:
+
+```term
+gitbook $ ./my_app --fLaG
+```
+
+## Example
+
+The following program will take several flags:
+
+[include:"define"](../code/flags.cpp)
+
+The values would be used like this:
+
+[include:"usage"](../code/flags.cpp)
+
+[Source code](https://github.com/CLIUtils/CLI11/tree/main/book/code/flags.cpp)
+
+If you compile and run:
+
+```term
+gitbook:examples $ g++ -std=c++11 flags.cpp
+gitbook:examples $ ./a.out -h
+Flag example program
+Usage: ./a.out [OPTIONS]
+
+Options:
+  -h,--help                   Print this help message and exit
+  -b,--bool                   This is a bool flag
+  -i,--int                    This is an int flag
+  -p,--plain                  This is a plain flag
+
+gitbook:examples $ ./a.out -bii --plain -i
+The flags program
+Bool flag passed
+Flag int: 3
+Flag plain: 1
+```
+
+\[^1\]: It will not inherit this from the parent defaults, since this is often useful even if you don't want all options to allow multiple passed options.
diff --git a/external/CLI11/book/chapters/formatting.md b/external/CLI11/book/chapters/formatting.md
new file mode 100644
index 0000000..f9cd36f
--- /dev/null
+++ b/external/CLI11/book/chapters/formatting.md
@@ -0,0 +1,73 @@
+# Formatting help output
+
+{% hint style='info' %}
+New in CLI11 1.6
+{% endhint %}
+
+## Customizing an existing formatter
+
+In CLI11, you can control the output of the help printout in full or in part. The default formatter was written in such a way as to be customizable. You can use `app.get_formatter()` to get the current formatter. The formatter you set will be inherited by subcommands that are created after you set the formatter.
+
+There are several configuration options that you can set:
+
+| Set method | Description | Availability |
+|------------|-------------|--------------|
+| `column_width(width)` | The width of the columns | Both |
+| `label(key, value)` | Set a label to a different value | Both |
+
+Labels will map the built in names and type names from key to value if present. For example, if you wanted to change the width of the columns to 40 and the `REQUIRED` label from `(REQUIRED)` to `(MUST HAVE)`:
+
+```cpp
+app.get_formatter()->column_width(40);
+app.get_formatter()->label("REQUIRED", "(MUST HAVE)");
+```
+
+## Subclassing
+
+You can further configure pieces of the code while still keeping most of the formatting intact by subclassing either formatter and replacing any of the methods with your own. The formatters use virtual functions most places, so you are free to add or change anything about them. For example, if you wanted to remove the info that shows up between the option name and the description:
+
+```cpp
+class MyFormatter : public CLI::Formatter {
+  public:
+    std::string make_option_opts(const CLI::Option *) const override {return "";}
+};
+app.formatter(std::make_shared<MyFormatter>());
+```
+
+Look at the class definitions in `FormatterFwd.hpp` or the method definitions in `Formatter.hpp` to see what methods you have access to and how they are put together.
+
+## Anatomy of a help message
+
+This is a normal printout, with `<>` indicating the methods used to produce each line.
+
+```text
+<make_description(app)>
+<make_usage(app)>
+<make_positionals(app)>
+  <make_group(app, "Positionals", true, filter>
+<make_groups(app, mode)>
+  <make_group(app, "Option Group 1"), false, filter>
+  <make_group(app, "Option Group 2"), false, filter>
+  ...
+<make_subcommands(app)>
+  <make_subcommand(sub1, Mode::Normal)>
+  <make_subcommand(sub2, Mode::Normal)>
+<make_footer(app)>
+```
+
+`make_usage` calls `make_option_usage(opt)` on all the positionals to build that part of the line. `make_subcommand` passes the subcommand as the app pointer.
+
+The `make_groups` print the group name then call `make_option(o)` on the options listed in that group. The normal printout for an option looks like this:
+
+```text
+        make_option_opts(o)
+            ┌───┴────┐
+ -n,--name  (REQUIRED)      This is a description
+└────┬────┘                └──────────┬──────────┘
+make_option_name(o,p)        make_option_desc(o)
+```
+
+Notes:
+
+* `*1`: This signature depends on whether the call is from a positional or optional.
+* `o` is opt pointer, `p` is true if positional.
diff --git a/external/CLI11/book/chapters/installation.md b/external/CLI11/book/chapters/installation.md
new file mode 100644
index 0000000..c7bcc18
--- /dev/null
+++ b/external/CLI11/book/chapters/installation.md
@@ -0,0 +1,92 @@
+# Installation
+
+## Single file edition
+
+```cpp
+#include <CLI11.hpp>
+```
+
+This example uses the single file edition of CLI11. You can download `CLI11.hpp` from the latest release and put it into the same folder as your source code, then compile this with C++ enabled. For a larger project, you can just put this in an include folder and you are set.
+
+## Full edition
+
+```cpp
+#include <CLI/CLI.hpp>
+```
+
+If you want to use CLI11 in its full form, you can also use the original multiple file edition. This has an extra utility (`Timer`), and is does not require that you use a release. The only change to your code would be the include shown above.
+
+### CMake support for the full edition
+
+If you use CMake 3.4+ for your project (highly recommended), CLI11 comes with a powerful CMakeLists.txt file that was designed to also be used with `add_subproject`. You can add the repository to your code (preferably as a git submodule), then add the following line to your project (assuming your folder is called CLI11):
+
+```cmake
+add_subdirectory(CLI11)
+```
+
+Then, you will have a target `CLI11::CLI11` that you can link to with `target_link_libraries`. It will provide the include paths you need for the library. This is the way [GooFit](https://github.com/GooFit/GooFit) uses CLI11, for example.
+
+You can also configure and optionally install CLI11, and CMake will create the necessary `lib/cmake/CLI11/CLI11Config.cmake` files, so `find_package(CLI11 CONFIG REQUIRED)` also works.
+
+If you use conan.io, CLI11 supports that too.
+
+### Running tests on the full edition
+
+CLI11 has examples and tests that can be accessed using a CMake build on any platform. Simply build and run ctest to run the 200+ tests to ensure CLI11 works on your system.
+
+As an example of the build system, the following code will download and test CLI11 in a simple Alpine Linux docker container [^1]:
+
+```term
+gitbook:~ $ docker run -it alpine
+root:/ # apk add --no-cache g++ cmake make git
+fetch ...
+root:/ # git clone https://github.com/CLIUtils/CLI11.git
+Cloning into 'CLI11' ...
+root:/ # cd CLI11
+root:CLI11 # mkdir build
+root:CLI11 # cd build
+root:build # cmake ..
+-- The CXX compiler identification is GNU 6.3.0 ...
+root:build # make
+Scanning dependencies ...
+root:build # make test
+[warning]Running tests...
+Test project /CLI11/build
+      Start  1: HelpersTest
+ 1/10 Test  #1: HelpersTest ......................   Passed    0.01 sec
+      Start  2: IniTest
+ 2/10 Test  #2: IniTest ..........................   Passed    0.01 sec
+      Start  3: SimpleTest
+ 3/10 Test  #3: SimpleTest .......................   Passed    0.01 sec
+      Start  4: AppTest
+ 4/10 Test  #4: AppTest ..........................   Passed    0.02 sec
+      Start  5: CreationTest
+ 5/10 Test  #5: CreationTest .....................   Passed    0.01 sec
+      Start  6: SubcommandTest
+ 6/10 Test  #6: SubcommandTest ...................   Passed    0.01 sec
+      Start  7: HelpTest
+ 7/10 Test  #7: HelpTest .........................   Passed    0.01 sec
+      Start  8: NewParseTest
+ 8/10 Test  #8: NewParseTest .....................   Passed    0.01 sec
+      Start  9: TimerTest
+ 9/10 Test  #9: TimerTest ........................   Passed    0.24 sec
+      Start 10: link_test_2
+10/10 Test #10: link_test_2 ......................   Passed    0.00 sec
+
+100% tests passed, 0 tests failed out of 10
+
+Total Test time (real) =   0.34 sec
+```
+
+For the curious, the CMake options and defaults are listed below. Most options default to off if CLI11 is used as a subdirectory in another project.
+
+| Option | Description |
+|--------|-------------|
+| `CLI11_SINGLE_FILE=ON` | Build the `CLI11.hpp` file from the sources. Requires Python (version 3 or 2.7). |
+| `CLI11_SINGLE_FILE_TESTS=OFF` | Run the tests on the generated single file version as well |
+| `CLI11_EXAMPLES=ON` | Build the example programs. |
+| `CLI11_TESTING=ON` | Build the tests. |
+| `CLI11_CLANG_TIDY=OFF` | Run `clang-tidy` on the examples and headers. Requires CMake 3.6+. |
+| `CLI11_CLANG_TIDY_OPTIONS=""` | Options to pass to `clang-tidy`, such as `-fix` (single threaded build only if applying fixes!) |
+
+[^1]: Docker is being used to create a pristine disposable environment; there is nothing special about this container. Alpine is being used because it is small, modern, and fast. Commands are similar on any other platform.
diff --git a/external/CLI11/book/chapters/internals.md b/external/CLI11/book/chapters/internals.md
new file mode 100644
index 0000000..1551c79
--- /dev/null
+++ b/external/CLI11/book/chapters/internals.md
@@ -0,0 +1,43 @@
+# CLI11 Internals
+
+## Callbacks
+
+The library was designed to bind to existing variables without requiring typed classes or inheritance. This is accomplished through lambda functions.
+
+This looks like:
+
+```cpp
+Option* add_option(string name, T item) {
+    this->function = [&item](string value){
+        item = detail::lexical_cast<T>(value);
+    }
+}
+```
+
+Obviously, you can't access `T` after the `add_` method is over, so it stores the string representation of the default value if it receives the special `true` value as the final argument (not shown above).
+
+## Parsing
+
+Parsing follows the following procedure:
+
+1. `_validate`: Make sure the defined options are self consistent.
+2. `_parse`: Main parsing routine. See below.
+3. `_run_callback`: Run an App callback if present.
+
+The parsing phase is the most interesting:
+
+1. `_parse_single`: Run on each entry on the command line and fill the options/subcommands.
+2. `_process`: Run the procedure listed below.
+3. `_process_extra`: This throws an error if needed on extra arguments that didn't fit in the parse.
+
+The `_process` procedure runs the following steps; each step is recursive and completes all subcommands before moving to the next step (new in 1.7). This ensures that interactions between options and subcommand options is consistent.
+
+1. `_process_ini`: This reads an INI file and fills/changes options as needed.
+2. `_process_env`: Look for environment variables.
+3. `_process_callbacks`: Run the callback functions - this fills out the variables.
+4. `_process_help_flags`: Run help flags if present (and quit).
+5. `_process_requirements`: Make sure needs/excludes, required number of options present.
+
+## Exceptions
+
+The library immediately returns a C++ exception when it detects a problem, such as an incorrect construction or a malformed command line.
diff --git a/external/CLI11/book/chapters/options.md b/external/CLI11/book/chapters/options.md
new file mode 100644
index 0000000..8b1daa0
--- /dev/null
+++ b/external/CLI11/book/chapters/options.md
@@ -0,0 +1,291 @@
+# Options
+
+## Simple options
+
+The most versatile addition to a command line program is a option. This is like a flag, but it takes an argument. CLI11 handles all the details for many types of options for you, based on their type. To add an option:
+
+```cpp
+int int_option{0};
+app.add_option("-i", int_option, "Optional description");
+```
+
+This will bind the option `-i` to the integer `int_option`. On the command line, a single value that can be converted to an integer will be expected. Non-integer results will fail. If that option is not given, CLI11 will not touch the initial value. This allows you to set up defaults by simply setting your value beforehand. If you want CLI11 to display your default value, you can add `->capture_default_str()` after the option.
+
+```cpp
+int int_option{0};
+app.add_option("-i", int_option, "Optional description")->capture_default_str();
+```
+
+You can use any C++ int-like type, not just `int`. CLI11 understands the following categories of types:
+
+| Type        | CLI11 |
+|-------------|-------|
+| number like    | Integers, floats, bools, or any type that can be constructed from an integer or floating point number.  Accepts common numerical strings like `0xFF` as well as octal, and decimal |
+| string-like | std\::string, or anything that can be constructed from or assigned a std\::string |
+| char | For a single char, single string values are accepted, otherwise longer strings are treated as integral values and a conversion is attempted |
+| complex-number | std::complex or any type which has a real(), and imag() operations available, will allow 1 or 2 string definitions like "1+2j" or two arguments "1","2" |
+| enumeration | any enum or enum class type is supported through conversion from the underlying type(typically int, though it can be specified otherwise) |
+| container-like | a container(like vector) of any available types including other containers |
+| wrapper | any other object with a `value_type` static definition where the type specified by `value_type` is one of the type in this list, including `std::atomic<>` |
+| tuple | a tuple, pair, or array, or other type with a tuple size and tuple_type operations defined and the members being a type contained in this list |
+| function | A function that takes an array of strings and returns a string that describes the conversion failure or empty for success. May be the empty function. (`{}`) |
+| streamable | any other type with a `<<` operator will also work |
+
+By default, CLI11 will assume that an option is optional, and one value is expected if you do not use a vector. You can change this on a specific option using option modifiers.  An option name may start with any character except ('-', ' ', '\n', and '!'). For long options, after the first character all characters are allowed except ('=',':','{',' ', '\n'). Names are given as a comma separated string, with the dash or dashes. An option can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on the help line for its positional form.
+
+## Positional options and aliases
+
+When you give an option on the command line without a name, that is a positional option. Positional options are accepted in the same order they are defined. So, for example:
+
+```term
+gitbook:examples $ ./a.out one --two three four
+```
+
+The string `one` would have to be the first positional option. If `--two` is a flag, then the remaining two strings are positional. If `--two` is a one-argument option, then `four` is the second positional. If `--two` accepts two or more arguments, then there are no more positionals.
+
+To make a positional option, you simply give CLI11 one name that does not start with a dash. You can have as many (non-overlapping) names as you want for an option, but only one positional name. So the following name string is valid:
+
+```cpp
+"-a,-b,--alpha,--beta,mypos"
+```
+
+This would make two short option aliases, two long option alias, and the option would be also be accepted as a positional.
+
+## Containers of options
+
+If you use a vector or other container instead of a plain option, you can accept more than one value on the command line. By default, a container accepts as many options as possible, until the next value that could be a valid option name. You can specify a set number using an option modifier `->expected(N)`. (The default unlimited behavior on vectors is restored with `N=-1`) CLI11 does not differentiate between these two methods for unlimited acceptance options.
+
+| Separate names    | Combined names  |
+|-------------------|-----------------|
+| `--vec 1 --vec 2` | `--vec 1 2`     |
+
+It is also possible to specify a minimum and maximum number through `->expected(Min,Max)`.  It is also possible to specify a min and max type size for the elements of the container.  It most cases these values will be automatically determined but a user can manually restrict them.
+
+An example of setting up a vector option:
+
+```cpp
+std::vector<int> int_vec;
+app.add_option("--vec", int_vec, "My vector option");
+```
+
+Vectors will be replaced by the parsed content if the option is given on the command line.
+
+A definition of a container for purposes of CLI11 is a type with a `end()`, `insert(...)`, `clear()` and `value_type` definitions.  This includes `vector`, `set`, `deque`, `list`, `forward_iist`, `map`, `unordered_map` and a few others from the standard library, and many other containers from the boost library.
+
+### Containers of containers
+
+Containers of containers are also supported.
+
+```cpp
+std::vector<std::vector<int>> int_vec;
+app.add_option("--vec", int_vec, "My vector of vectors option");
+```
+
+CLI11 inserts a separator sequence at the start of each argument call to separate the vectors.  So unless the separators are injected as part of the command line each call of the option on the command line will result in a separate element of the outer vector.  This can be manually controlled via `inject_separator(true|false)` but in nearly all cases this should be left to the defaults.  To insert of a separator from the command line add a `%%` where the separation should occur.
+
+```bash
+cmd --vec_of_vec 1 2 3 4 %% 1 2
+```
+
+would then result in a container of size 2 with the first element containing 4 values and the second 2.
+
+This separator is also the only way to get values into something like
+
+```cpp
+std::pair<std::vector<int>,std::vector<int>> two_vecs;
+app.add_option("--vec", two_vecs, "pair of vectors");
+```
+
+without calling the argument twice.
+
+Further levels of nesting containers should compile but intermediate layers will only have a single element in the container, so is probably not that useful.
+
+### Nested types
+
+Types can be nested. For example:
+
+```cpp
+std::map<int, std::pair<int,std::string>> map;
+app.add_option("--dict", map, "map of pairs");
+```
+
+will require 3 arguments for each invocation, and multiple sets of 3 arguments can be entered for a single invocation on the command line.
+
+```cpp
+std::map<int, std::pair<int,std::vector<std::string>>> map;
+app.add_option("--dict", map, "map of pairs");
+```
+
+will result in a requirement for 2 integers on each invocation and absorb an unlimited number of strings including 0.
+
+## Option modifiers
+
+When you call `add_option`, you get a pointer to the added option. You can use that to add option modifiers. A full listing of the option modifiers:
+
+| Modifier | Description |
+|----------|-------------|
+| `->required()` | The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. |
+| `->expected(N)` | Take `N` values instead of as many as possible, mainly for vector args. |
+| `->expected(Nmin,Nmax)` | Take between `Nmin` and `Nmax` values. |
+| `->type_size(N)` | specify that each block of values would consist of N elements |
+| `->type_size(Nmin,Nmax)` | specify that each block of values would consist of between Nmin and Nmax elements |
+| `->needs(opt)` | This option requires another option to also be present, opt is an `Option` pointer or a string with the name of the option.  Can be removed with `->remove_needs(opt)` |
+| `->excludes(opt)` | This option cannot be given with `opt` present, opt is an `Option` pointer or a string with the name of the option.  Can be removed with `->remove_excludes(opt)` |
+| `->envname(name)` | Gets the value from the environment if present and not passed on the command line. |
+| `->group(name)` | The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print. |
+| `->description(string)` | Set/change the description |
+| `->ignore_case()` | Ignore the case on the command line (also works on subcommands, does not affect arguments). |
+| `->ignore_underscore()` | Ignore any underscores on the command line (also works on subcommands, does not affect arguments). |
+| `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. |
+| `->disable_flag_override()` | specify that flag options cannot be overridden on the command line use `=<newval>` |
+| `->delimiter('<CH>')` | specify a character that can be used to separate elements in a command line argument, default is <none>, common values are ',', and ';' |
+| `->multi_option_policy( CLI::MultiOptionPolicy::Throw)` | Sets the policy for handling multiple arguments if the option was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, and `Join` are also available. See the next four lines for shortcuts to set this more easily. |
+| `->take_last()` | Only use the last option if passed several times. This is always true by default for bool options, regardless of the app default, but can be set to false explicitly with `->multi_option_policy()`. |
+| `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` |
+| `->take_all()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeAll)` |
+| `->join()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses newlines or the specified delimiter to join all arguments into a single string output. |
+| `->join(delim)` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses `delim` to join all arguments into a single string output. this also sets the delimiter |
+| `->check(Validator)` | perform a check on the returned results to verify they meet some criteria. See [Validators](./validators.md) for more info |
+| `->transform(Validator)` | Run a transforming validator on each value passed. See [Validators](./validators.md) for more info |
+| `->each(void(std::string))` | Run a function on each parsed value, *in order*. |
+| `->default_str(string)` | set a default string for use in the help and as a default value if no arguments are passed and a value is requested |
+| `->default_function(std::string())` | Advanced: Change the function that `capture_default_str()` uses. |
+| `->default_val(value)` | Generate the default string from a value and validate that the value is also valid.  For options that assign directly to a value type the value in that type is also updated.  Value must be convertible to a string(one of known types or have a stream operator). |
+| `->capture_default_str()` | Store the current value attached and display it in the help string. |
+| `->always_capture_default()` | Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. |
+| `->run_callback_for_default()` | Force the option callback to be executed or the variable set when the `default_val` is used.  |
+| `->force_callback()` | Force the option callback to be executed regardless of whether the option was used or not.  Will use the default_str if available, if no default is given the callback will be executed with an empty string as an argument, which will translate to a default initialized value, which can be compiler dependent |
+|`->trigger_on_parse()` | Have the option callback be triggered when the value is parsed vs. at the end of all parsing, the option callback can potentially be executed multiple times.  Generally only useful if you have a user defined callback or validation check. Or potentially if a vector input is given multiple times as it will clear the results when a repeat option is given via command line.  It will trigger the callbacks once per option call on the command line|
+| `->option_text(string)` | Sets the text between the option name and description. |
+
+The `->check(...)` and `->transform(...)` modifiers can also take a callback function of the form `bool function(std::string)` that runs on every value that the option receives, and returns a value that tells CLI11 whether the check passed or failed.
+
+## Using the `CLI::Option` pointer
+
+Each of the option creation mechanisms returns a pointer to the internally stored option. If you save that pointer, you can continue to access the option, and change setting on it later. The Option object can also be converted to a bool to see if it was passed, or `->count()` can be used to see how many times the option was passed. Since flags are also options, the same methods work on them.
+
+```cpp
+CLI::Option* opt = app.add_flag("--opt");
+
+CLI11_PARSE(app, argv, argc);
+
+if(* opt)
+    std::cout << "Flag received " << opt->count() << " times." << std::endl;
+```
+
+## Inheritance of defaults
+
+One of CLI11's systems to allow customizability without high levels of verbosity is the inheritance system. You can set default values on the parent `App`, and all options and subcommands created from it remember the default values at the point of creation. The default value for Options, specifically, are accessible through the `option_defaults()` method. There are a number of settings that can be set and inherited:
+
+* `group`: The group name starts as "Options"
+* `required`: If the option must be given. Defaults to `false`. Is ignored for flags.
+* `multi_option_policy`: What to do if several copies of an option are passed and one value is expected. Defaults to `CLI::MultiOptionPolicy::Throw`. This is also used for bool flags, but they always are created with the value `CLI::MultiOptionPolicy::TakeLast` regardless of the default, so that multiple bool flags does not cause an error. But you can override that flag by flag.
+* `ignore_case`: Allow any mixture of cases for the option or flag name
+* `ignore_underscore`: Allow any number of underscores in the option or flag name
+* `configurable`:  Specify whether an option can be configured through a config file
+* `disable_flag_override`:  do not allow flag values to be overridden on the command line
+* `always_capture_default`:  specify that the default values should be automatically captured.
+* `delimiter`:  A delimiter to use for capturing multiple values in a single command line string (e.g. --flag="flag,-flag2,flag3")
+
+An example of usage:
+
+```cpp
+app.option_defaults()->ignore_case()->group("Required");
+
+app.add_flag("--CaSeLeSs");
+app.get_group() // is "Required"
+```
+
+Groups are mostly for visual organization, but an empty string for a group name will hide the option.
+
+### Windows style options
+
+You can also set the app setting `app->allow_windows_style_options()` to allow windows style options to also be recognized on the command line:
+
+* `/a` (flag)
+* `/f filename` (option)
+* `/long` (long flag)
+* `/file filename` (space)
+* `/file:filename` (colon)
+* `/long_flag:false` (long flag with : to override the default value)
+
+Windows style options do not allow combining short options or values not separated from the short option like with `-` options. You still specify option names in the same manner as on Linux with single and double dashes when you use the `add_*` functions, and the Linux style on the command line will still work. If a long and a short option share the same name, the option will match on the first one defined.
+
+## Parse configuration
+
+How an option and its arguments are parsed depends on a set of controls that are part of the option structure.  In most circumstances these controls are set automatically based on the type or function used to create the option and the type the arguments are parsed into.  The variables define the size of the underlying type (essentially how many strings make up the type), the expected size (how many groups are expected) and a flag indicating if multiple groups are allowed with a single option.  And these interact with the `multi_option_policy` when it comes time to parse.
+
+### Examples
+
+How options manage this is best illustrated through some examples.
+
+```cpp
+std::string val;
+app.add_option("--opt",val,"description");
+```
+
+creates an option that assigns a value to a `std::string`  When this option is constructed it sets a type_size min and max of 1.  Meaning that the assignment uses a single string.  The Expected size is also set to 1 by default, and `allow_extra_args` is set to false. meaning that each time this option is called 1 argument is expected.  This would also be the case if val were a `double`, `int` or any other single argument types.
+
+now for example
+
+```cpp
+std::pair<int, std::string> val;
+app.add_option("--opt",val,"description");
+```
+
+In this case the typesize is automatically detected to be 2 instead of 1, so the parsing would expect 2 arguments associated with the option.
+
+```cpp
+std::vector<int> val;
+app.add_option("--opt",val,"description");
+```
+
+detects a type size of 1, since the underlying element type is a single string, so the minimum number of strings is 1.  But since it is a vector the expected number can be very big.  The default for a vector is (1<<30), and the allow_extra_args is set to true.  This means that at least 1 argument is expected to follow the option, but arbitrary numbers of arguments may follow.  These are checked if they have the form of an option but if not they are added to the argument.
+
+```cpp
+std::vector<std::tuple<int, double, std::string>> val;
+app.add_option("--opt",val,"description");
+```
+
+gets into the complicated cases where the type size is now 3.  and the expected max is set to a large number and `allow_extra_args` is set to true.  In this case at least 3 arguments are required to follow the option,  and subsequent groups must come in groups of three, otherwise an error will result.
+
+```cpp
+bool val{false};
+app.add_flag("--opt",val,"description");
+```
+
+Using the add_flag methods for creating options creates an option with an expected size of 0, implying no arguments can be passed.
+
+```cpp
+std::complex<double> val;
+app.add_option("--opt",val,"description");
+```
+
+triggers the complex number type which has a min of 1 and max of 2,  so 1 or 2 strings can be passed.  Complex number conversion supports arguments of the form "1+2j" or "1","2", or "1" "2i".  The imaginary number symbols `i` and `j` are interchangeable in this context.
+
+```cpp
+std::vector<std::vector<int>> val;
+app.add_option("--opt",val,"description");
+```
+
+has a type size of 1 to (1<<30).
+
+### Customization
+
+The `type_size(N)`, `type_size(Nmin, Nmax)`, `expected(N)`, `expected(Nmin,Nmax)`, and `allow_extra_args()` can be used to customize an option.  For example
+
+```cpp
+std::string val;
+auto opt=app.add_flag("--opt{vvv}",val,"description");
+opt->expected(0,1);
+```
+
+will create a hybrid option, that can exist on its own in which case the value "vvv" is used or if a value is given that value will be used.
+
+There are some additional options that can be specified to modify an option for specific cases:
+
+* `->run_callback_for_default()` will specify that the callback should be executed when a default_val is set. This is set automatically when appropriate though it can be turned on or off and any user specified callback for an option will be executed when the default value for an option is set.
+
+## Unusual circumstances
+
+There are a few cases where some things break down in the type system managing options and definitions.  Using the `add_option` method defines a lambda function to extract a default value if required.  In most cases this is either straightforward or a failure is detected automatically and handled.  But in a few cases a streaming template is available that several layers down may not actually be defined.  This results in CLI11 not being able to detect this circumstance automatically and will result in compile error.  One specific known case is `boost::optional` if the boost optional_io header is included.  This header defines a template for all boost optional values even if they do not actually have a streaming operator.  For example `boost::optional<std::vector>` does not have a streaming operator but one is detected since it is part of a template.  For these cases a secondary method `app->add_option_no_stream(...)` is provided that bypasses this operation completely and should compile in these cases.
diff --git a/external/CLI11/book/chapters/subcommands.md b/external/CLI11/book/chapters/subcommands.md
new file mode 100644
index 0000000..585f91c
--- /dev/null
+++ b/external/CLI11/book/chapters/subcommands.md
@@ -0,0 +1,128 @@
+# Subcommands and the App
+
+Subcommands are keyword that invoke a new set of options and features. For example, the `git`
+command has a long series of subcommands, like `add` and `commit`. Each can have its own options
+and implementations. This chapter will focus on implementations that are contained in the same
+C++ application, though the system git uses to extend the main command by calling other commands
+in separate executables is supported too; that's called "Prefix commands" and is included at the
+end of this chapter.
+
+## The parent App
+
+We'll start by discussing the parent `App`. You've already used it quite a bit, to create
+options and set option defaults. There are several other things you can do with an `App`, however.
+
+You are given a lot of control the help output. You can set a footer with `app.footer("My Footer")`.
+You can replace the default help print when a `ParseError` is thrown with `app.set_failure_message(CLI::FailureMessage::help)`.
+The default is `CLI:::FailureMessage::simple`, and you can easily define a new one. Just make a (lambda) function that takes an App pointer
+and a reference to an error code (even if you don't use them), and returns a string.
+
+## Adding a subcommand
+
+Subcommands can be added just like an option:
+
+```cpp
+CLI::App* sub = app.add_subcommand("sub", "This is a subcommand");
+```
+
+The subcommand should have a name as the first argument, and a little description for the
+second argument. A pointer to the internally stored subcommand is provided; you usually will
+be capturing that pointer and using it later (though you can use callbacks if you prefer). As
+always, feel free to use `auto sub = ...` instead of naming the type.
+
+You can check to see if the subcommand was received on the command line several ways:
+
+```cpp
+if(*sub) ...
+if(sub->parsed()) ...
+if(app.got_subcommand(sub)) ...
+if(app.got_subcommand("sub")) ...
+```
+
+You can also get a list of subcommands with `get_subcommands()`, and they will be in parsing order.
+
+There are a lot of options that you can set on a subcommand; in fact,
+subcommands have exactly the same options as your main app, since they are actually
+the same class of object (as you may have guessed from the type above). This has the
+pleasant side affect of making subcommands infinitely nestable.
+
+## Required subcommands
+
+Each App has controls to set the number of subcommands you expect. This is controlled by:
+
+```cpp
+app.require_subcommand(/* min */ 0, /* max */ 1);
+```
+
+If you set the max to 0, CLI11 will allow an unlimited number of subcommands. After the (non-unlimited) maximum
+is reached, CLI11 will stop trying to match subcommands. So the if you pass "`one two`" to a command, and both `one`
+and `two` are subcommands, it will depend on the maximum number as to whether the "`two`" is a subcommand or an argument to the
+"`one`" subcommand.
+
+As a shortcut, you can also call the `require_subcommand` method with one argument; that will be the fixed number of subcommands if positive, it
+will be the maximum number if negative. Calling it without an argument will set the required subcommands to 1 or more.
+
+The maximum number of subcommands is inherited by subcommands. This allows you to set the maximum to 1 once at the beginning on the parent app if you only want single subcommands throughout your app. You should keep this in mind, if you are dealing with lots of nested subcommands.
+
+## Using callbacks
+
+You've already seen how to check to see what subcommands were given. It's often much easier, however, to just define the code you want to run when you are making your parser, and not run a bunch of code after `CLI11_PARSE` to analyse the state (Procedural! Yuck!). You can do that with lambda functions. A `std::function<void()>` callback `.callback()` is provided, and CLI11 ensures that all options are prepared and usable by reference capture before entering the callback. An
+example is shown below in the `geet` program.
+
+## Inheritance of defaults
+
+The following values are inherited when you add a new subcommand. This happens at the point the subcommand is created:
+
+* The name and description for the help flag
+* The footer
+* The failure message printer function
+* Option defaults
+* Allow extras
+* Prefix command
+* Ignore case
+* Ignore underscore
+* Allow Windows style options
+* Fallthrough
+* Group name
+* Max required subcommands
+
+## Special modes
+
+There are several special modes for Apps and Subcommands.
+
+### Allow extras
+
+Normally CLI11 throws an error if you don't match all items given on the command line. However, you can enable `allow_extras()`
+to instead store the extra values in `.remaining()`. You can get all remaining options including those in contained subcommands recursively in the original order with `.remaining(true)`.
+`.remaining_size()` is also provided; this counts the size but ignores the `--` special separator if present.
+
+### Fallthrough
+
+Fallthrough allows an option that does not match in a subcommand to "fall through" to the parent command; if that parent
+allows that option, it matches there instead. This was added to allow CLI11 to represent models:
+
+```term
+gitbook:code $ ./my_program my_model_1 --model_flag --shared_flag
+```
+
+Here, `--shared_flag` was set on the main app, and on the command line it "falls through" `my_model_1` to match on the main app.
+
+### Prefix command
+
+This is a special mode that allows "prefix" commands, where the parsing completely stops when it gets to an unknown option. Further unknown options are ignored, even if they could match. Git is the traditional example for prefix commands; if you run git with an unknown subcommand, like "`git thing`", it then calls another command called "`git-thing`" with the remaining options intact.
+
+### Silent subcommands
+
+Subcommands can be modified by using the `silent` option.  This will prevent the subcommand from showing up in the get_subcommands list.  This can be used to make subcommands into modifiers. For example, a help subcommand might look like
+
+```c++
+    auto sub1 = app.add_subcommand("help")->silent();
+    sub1->parse_complete_callback([]() { throw CLI::CallForHelp(); });
+```
+
+This would allow calling help such as:
+
+```bash
+./app help
+./app help sub1
+```
diff --git a/external/CLI11/book/chapters/toolkits.md b/external/CLI11/book/chapters/toolkits.md
new file mode 100644
index 0000000..4c27638
--- /dev/null
+++ b/external/CLI11/book/chapters/toolkits.md
@@ -0,0 +1,28 @@
+# Using CLI11 in a Toolkit
+
+CLI11 was designed to be integrate into a toolkit, providing a native experience for users. This was used in GooFit to provide `GooFit::Application`, an class designed to make ROOT users feel at home.
+
+## Custom namespace
+
+If you want to provide CLI11 in a custom namespace, you'll want to at least put `using CLI::App` in your namespace. You can also include Option, some errors, and validators. You can also put `using namespace CLI` inside your namespace to import everything.
+
+You may also want to make your own copy of the `CLI11_PARSE` macro. Something like:
+
+```cpp
+ #define MYPACKAGE_PARSE(app, argv, argc)    \
+     try {                                   \
+         app.parse(argv, argc);              \
+     } catch(const CLI::ParseError &e) {     \
+         return app.exit(e);                 \
+     }
+```
+
+## Subclassing App
+
+If you subclass `App`, you'll just need to do a few things. You'll need a constructor; calling the base `App` constructor is a good idea, but not necessary (it just sets a description and adds a help flag.
+
+You can call anything you would like to configure in the constructor, like `option_defaults()->take_last()` or `fallthrough()`, and it will be set on all user instances. You can add flags and options, as well.
+
+## Virtual functions provided
+
+You are given a few virtual functions that you can change (only on the main App). `pre_callback` runs right before the callbacks run, letting you print out custom messages at the top of your app.
diff --git a/external/CLI11/book/chapters/validators.md b/external/CLI11/book/chapters/validators.md
new file mode 100644
index 0000000..06e42c8
--- /dev/null
+++ b/external/CLI11/book/chapters/validators.md
@@ -0,0 +1,61 @@
+# Validators
+
+There are two forms of validators:
+
+* `transform` validators: mutating
+* `check` validators: non-mutating (recommended unless the parsed string must be mutated)
+
+A transform validator comes in one form, a function with the signature `std::string(std::string)`.
+The function will take a string and return the modified version of the string. If there is an error,
+the function should throw a `CLI::ValidationError` with the appropriate reason as a message.
+
+However, `check` validators come in two forms; either a simple function with the const version of the
+above signature, `std::string(const std::string &)`, or a subclass of `struct CLI::Validator`. This
+structure has two members that a user should set; one (`func_`) is the function to add to the Option
+(exactly matching the above function signature, since it will become that function), and the other is
+`name_`, and is the type name to set on the Option (unless empty, in which case the typename will be
+left unchanged).
+
+Validators can be combined with `&` and `|`, and they have an `operator()` so that you can call them
+as if they were a function. In CLI11, const static versions of the validators are provided so that
+the user does not have to call a constructor also.
+
+An example of a custom validator:
+
+```cpp
+struct LowerCaseValidator : public Validator {
+    LowerCaseValidator() {
+        name_ = "LOWER";
+        func_ = [](const std::string &str) {
+            if(CLI::detail::to_lower(str) != str)
+                return std::string("String is not lower case");
+            else
+                return std::string();
+        };
+    }
+};
+const static LowerCaseValidator Lowercase;
+```
+
+If you were not interested in the extra features of Validator, you could simply pass the lambda function above to the `->check()` method of `Option`.
+
+The built-in validators for CLI11 are:
+
+| Validator           | Description |
+|---------------------|-------------|
+| `ExistingFile`      | Check for existing file (returns error message if check fails) |
+| `ExistingDirectory` | Check for an existing directory (returns error message if check fails) |
+| `ExistingPath`      | Check for an existing path |
+| `NonexistentPath`   | Check for an non-existing path |
+| `Range(min=0, max)` |  Produce a range (factory). Min and max are inclusive. |
+
+And, the protected members that you can set when you make your own are:
+
+| Type | Member | Description |
+|------|--------|-------------|
+| `std::function<std::string(std::string &)>` | `func_` | Core validation function - modifies input and returns "" if successful |
+| `std::function<std::string()>` | `desc_function` | Optional description function (uses `description_` instead if not set) |
+| `std::string` | `name_` | The name for search purposes |
+| `int` (`-1`) | `application_index_` | The element this validator applies to (-1 for all) |
+| `bool` (`true`) | `active_` | This can be disabled |
+| `bool` (`false`) | `non_modifying_` | Specify that this is a Validator instead of a Transformer |
diff --git a/external/CLI11/book/code/CMakeLists.txt b/external/CLI11/book/code/CMakeLists.txt
new file mode 100644
index 0000000..987d53c
--- /dev/null
+++ b/external/CLI11/book/code/CMakeLists.txt
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 3.11)
+
+project(CLI11_Examples LANGUAGES CXX)
+
+# Using CMake 3.11's ability to set imported interface targets
+add_library(CLI11::CLI11 IMPORTED INTERFACE)
+target_include_directories(CLI11::CLI11 INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/../../include")
+target_compile_features(CLI11::CLI11 INTERFACE cxx_std_11)
+
+# Add CTest
+enable_testing()
+
+# Quick function to add the base executable
+function(add_cli_exe NAME)
+  add_executable(${NAME} ${NAME}.cpp)
+  target_link_libraries(${NAME} CLI11::CLI11)
+endfunction()
+
+add_cli_exe(simplest)
+add_test(NAME simplest COMMAND simplest)
+
+add_cli_exe(intro)
+add_test(NAME intro COMMAND intro)
+add_test(NAME intro_p COMMAND intro -p 5)
+
+add_cli_exe(flags)
+add_test(NAME flags COMMAND flags)
+add_test(NAME flags_bip COMMAND flags -b -i -p)
+
+add_cli_exe(geet)
+add_test(NAME geet_add COMMAND geet add)
+add_test(NAME geet_commit COMMAND geet commit -m "Test")
diff --git a/external/CLI11/book/code/flags.cpp b/external/CLI11/book/code/flags.cpp
new file mode 100644
index 0000000..54ac71a
--- /dev/null
+++ b/external/CLI11/book/code/flags.cpp
@@ -0,0 +1,36 @@
+#include "CLI/CLI.hpp"
+#include <iostream>
+
+int main(int argc, char **argv) {
+    using std::cout;
+    using std::endl;
+    CLI::App app{"Flag example program"};
+
+    /// [define]
+    bool flag_bool;
+    app.add_flag("--bool,-b", flag_bool, "This is a bool flag");
+
+    int flag_int;
+    app.add_flag("-i,--int", flag_int, "This is an int flag");
+
+    CLI::Option *flag_plain = app.add_flag("--plain,-p", "This is a plain flag");
+    /// [define]
+
+    /// [parser]
+    try {
+        app.parse(argc, argv);
+    } catch(const CLI::ParseError &e) {
+        return app.exit(e);
+    }
+    /// [parser]
+
+    /// [usage]
+    cout << "The flags program" << endl;
+    if(flag_bool)
+        cout << "Bool flag passed" << endl;
+    if(flag_int > 0)
+        cout << "Flag int: " << flag_int << endl;
+    if(*flag_plain)
+        cout << "Flag plain: " << flag_plain->count() << endl;
+    /// [usage]
+}
diff --git a/external/CLI11/book/code/geet.cpp b/external/CLI11/book/code/geet.cpp
new file mode 100644
index 0000000..a4caf26
--- /dev/null
+++ b/external/CLI11/book/code/geet.cpp
@@ -0,0 +1,50 @@
+#include "CLI/CLI.hpp"
+
+#include <iostream>
+
+int main(int argc, char **argv) {
+
+    /// [Intro]
+    CLI::App app{"Geet, a command line git lookalike that does nothing"};
+    app.require_subcommand(1);
+    /// [Intro]
+
+    /// [Add]
+    auto add = app.add_subcommand("add", "Add file(s)");
+
+    bool add_update;
+    add->add_flag("-u,--update", add_update, "Add updated files only");
+
+    std::vector<std::string> add_files;
+    add->add_option("files", add_files, "Files to add");
+
+    add->callback([&]() {
+        std::cout << "Adding:";
+        if(add_files.empty()) {
+            if(add_update)
+                std::cout << " all updated files";
+            else
+                std::cout << " all files";
+        } else {
+            for(auto file : add_files)
+                std::cout << " " << file;
+        }
+    });
+    /// [Add]
+
+    /// [Commit]
+    auto commit = app.add_subcommand("commit", "Commit files");
+
+    std::string commit_message;
+    commit->add_option("-m,--message", commit_message, "A message")->required();
+
+    commit->callback([&]() { std::cout << "Commit message: " << commit_message; });
+    /// [Commit]
+
+    /// [Parse]
+    CLI11_PARSE(app, argc, argv);
+
+    std::cout << "\nThanks for using geet!\n" << std::endl;
+    return 0;
+    /// [Parse]
+}
diff --git a/external/CLI11/book/code/intro.cpp b/external/CLI11/book/code/intro.cpp
new file mode 100644
index 0000000..2db50f1
--- /dev/null
+++ b/external/CLI11/book/code/intro.cpp
@@ -0,0 +1,15 @@
+#include "CLI/CLI.hpp"
+#include <iostream>
+
+int main(int argc, char **argv) {
+    CLI::App app{"App description"};
+
+    // Define options
+    int p = 0;
+    app.add_option("-p", p, "Parameter");
+
+    CLI11_PARSE(app, argc, argv);
+
+    std::cout << "Parameter value: " << p << std::endl;
+    return 0;
+}
diff --git a/external/CLI11/book/code/simplest.cpp b/external/CLI11/book/code/simplest.cpp
new file mode 100644
index 0000000..a848ff3
--- /dev/null
+++ b/external/CLI11/book/code/simplest.cpp
@@ -0,0 +1,11 @@
+#include "CLI/CLI.hpp"
+
+int main(int argc, char **argv) {
+    CLI::App app;
+
+    // Add new options/flags here
+
+    CLI11_PARSE(app, argc, argv);
+
+    return 0;
+}
diff --git a/external/CLI11/cmake/CLI11.pc.in b/external/CLI11/cmake/CLI11.pc.in
new file mode 100644
index 0000000..8d41873
--- /dev/null
+++ b/external/CLI11/cmake/CLI11.pc.in
@@ -0,0 +1,9 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+includedir=${prefix}/include
+
+Name: CLI11
+Description: C++ command line parser
+Version: @PROJECT_VERSION@
+
+Cflags: -I${includedir}
diff --git a/external/CLI11/cmake/CLI11ConfigVersion.cmake.in b/external/CLI11/cmake/CLI11ConfigVersion.cmake.in
new file mode 100644
index 0000000..49faee5
--- /dev/null
+++ b/external/CLI11/cmake/CLI11ConfigVersion.cmake.in
@@ -0,0 +1,13 @@
+# Adapted from write_basic_package_version_file(... COMPATIBILITY AnyNewerVersion) output
+# ARCH_INDEPENDENT is only present in cmake 3.14 and onwards
+
+set(PACKAGE_VERSION "@VERSION_STRING@")
+
+if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
+  set(PACKAGE_VERSION_COMPATIBLE FALSE)
+else()
+  set(PACKAGE_VERSION_COMPATIBLE TRUE)
+  if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
+    set(PACKAGE_VERSION_EXACT TRUE)
+  endif()
+endif()
diff --git a/external/CLI11/cmake/CLI11GeneratePkgConfig.cmake b/external/CLI11/cmake/CLI11GeneratePkgConfig.cmake
new file mode 100644
index 0000000..5abb03d
--- /dev/null
+++ b/external/CLI11/cmake/CLI11GeneratePkgConfig.cmake
@@ -0,0 +1,3 @@
+configure_file("cmake/CLI11.pc.in" "CLI11.pc" @ONLY)
+
+install(FILES "${PROJECT_BINARY_DIR}/CLI11.pc" DESTINATION "${CMAKE_INSTALL_DATADIR}/pkgconfig")
diff --git a/external/CLI11/cmake/CodeCoverage.cmake b/external/CLI11/cmake/CodeCoverage.cmake
new file mode 100644
index 0000000..e011ef1
--- /dev/null
+++ b/external/CLI11/cmake/CodeCoverage.cmake
@@ -0,0 +1,243 @@
+# Copyright (c) 2012 - 2017, Lars Bilke
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors
+#    may be used to endorse or promote products derived from this software without
+#    specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# CHANGES:
+#
+# 2012-01-31, Lars Bilke
+# - Enable Code Coverage
+#
+# 2013-09-17, Joakim Söderberg
+# - Added support for Clang.
+# - Some additional usage instructions.
+#
+# 2016-02-03, Lars Bilke
+# - Refactored functions to use named parameters
+#
+# 2017-06-02, Lars Bilke
+# - Merged with modified version from github.com/ufz/ogs
+#
+#
+# USAGE:
+#
+# 1. Copy this file into your cmake modules path.
+#
+# 2. Add the following line to your CMakeLists.txt:
+#      include(CodeCoverage)
+#
+# 3. Append necessary compiler flags:
+#      APPEND_COVERAGE_COMPILER_FLAGS()
+#
+# 4. If you need to exclude additional directories from the report, specify them
+#    using the COVERAGE_EXCLUDES variable before calling SETUP_TARGET_FOR_COVERAGE.
+#    Example:
+#      set(COVERAGE_EXCLUDES 'dir1/*' 'dir2/*')
+#
+# 5. Use the functions described below to create a custom make target which
+#    runs your test executable and produces a code coverage report.
+#
+# 6. Build a Debug build:
+#      cmake -DCMAKE_BUILD_TYPE=Debug ..
+#      make
+#      make my_coverage_target
+#
+
+include(CMakeParseArguments)
+
+# Check prereqs
+find_program(GCOV_PATH gcov)
+find_program(LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl)
+find_program(GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat)
+find_program(GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test)
+find_program(SIMPLE_PYTHON_EXECUTABLE python)
+
+if(NOT GCOV_PATH)
+  message(FATAL_ERROR "gcov not found! Aborting...")
+endif() # NOT GCOV_PATH
+
+if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
+  if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3)
+    message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
+  endif()
+elseif(NOT CMAKE_COMPILER_IS_GNUCXX)
+  message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...")
+endif()
+
+set(COVERAGE_COMPILER_FLAGS
+    "-g -O0 --coverage -fprofile-arcs -ftest-coverage -fno-inline -fno-inline-small-functions -fno-default-inline"
+    CACHE INTERNAL "")
+
+set(CMAKE_CXX_FLAGS_COVERAGE
+    ${COVERAGE_COMPILER_FLAGS}
+    CACHE STRING "Flags used by the C++ compiler during coverage builds." FORCE)
+set(CMAKE_C_FLAGS_COVERAGE
+    ${COVERAGE_COMPILER_FLAGS}
+    CACHE STRING "Flags used by the C compiler during coverage builds." FORCE)
+set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
+    ""
+    CACHE STRING "Flags used for linking binaries during coverage builds." FORCE)
+set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
+    ""
+    CACHE STRING "Flags used by the shared libraries linker during coverage builds." FORCE)
+mark_as_advanced(CMAKE_CXX_FLAGS_COVERAGE CMAKE_C_FLAGS_COVERAGE CMAKE_EXE_LINKER_FLAGS_COVERAGE
+                 CMAKE_SHARED_LINKER_FLAGS_COVERAGE)
+
+if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
+  message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading")
+endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
+
+if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
+  link_libraries(gcov)
+else()
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
+endif()
+
+# Defines a target for running and collection code coverage information
+# Builds dependencies, runs the given executable and outputs reports.
+# NOTE! The executable should always have a ZERO as exit code otherwise
+# the coverage generation will not complete.
+#
+# SETUP_TARGET_FOR_COVERAGE(
+#     NAME testrunner_coverage                    # New target name
+#     EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
+#     DEPENDENCIES testrunner                     # Dependencies to build first
+# )
+function(SETUP_TARGET_FOR_COVERAGE)
+
+  set(options NONE)
+  set(oneValueArgs NAME)
+  set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
+  cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+  if(NOT LCOV_PATH)
+    message(FATAL_ERROR "lcov not found! Aborting...")
+  endif() # NOT LCOV_PATH
+
+  if(NOT GENHTML_PATH)
+    message(FATAL_ERROR "genhtml not found! Aborting...")
+  endif() # NOT GENHTML_PATH
+
+  # Setup target
+  add_custom_target(
+    ${Coverage_NAME}
+    # Cleanup lcov
+    COMMAND ${LCOV_PATH} --directory . --zerocounters
+    # Create baseline to make sure untouched files show up in the report
+    COMMAND ${LCOV_PATH} -c -i -d . -o ${Coverage_NAME}.base
+    # Run tests
+    COMMAND ${Coverage_EXECUTABLE}
+    # Capturing lcov counters and generating report
+    COMMAND ${LCOV_PATH} --directory . --capture --output-file ${Coverage_NAME}.info
+    # add baseline counters
+    COMMAND ${LCOV_PATH} -a ${Coverage_NAME}.base -a ${Coverage_NAME}.info --output-file
+            ${Coverage_NAME}.total
+    COMMAND ${LCOV_PATH} --remove ${Coverage_NAME}.total ${COVERAGE_EXCLUDES} --output-file
+            ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned
+    COMMAND ${GENHTML_PATH} -o ${Coverage_NAME} ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned
+    COMMAND ${CMAKE_COMMAND} -E remove ${Coverage_NAME}.base ${Coverage_NAME}.total
+            ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned
+    WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+    DEPENDS ${Coverage_DEPENDENCIES}
+    COMMENT
+      "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
+  )
+
+  # Show where to find the lcov info report
+  add_custom_command(
+    TARGET ${Coverage_NAME}
+    POST_BUILD
+    COMMAND ;
+    COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info.")
+
+  # Show info where to find the report
+  add_custom_command(
+    TARGET ${Coverage_NAME}
+    POST_BUILD
+    COMMAND ;
+    COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report.")
+
+endfunction() # SETUP_TARGET_FOR_COVERAGE
+
+# Defines a target for running and collection code coverage information
+# Builds dependencies, runs the given executable and outputs reports.
+# NOTE! The executable should always have a ZERO as exit code otherwise
+# the coverage generation will not complete.
+#
+# SETUP_TARGET_FOR_COVERAGE_COBERTURA(
+#     NAME ctest_coverage                    # New target name
+#     EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR
+#     DEPENDENCIES executable_target         # Dependencies to build first
+# )
+function(SETUP_TARGET_FOR_COVERAGE_COBERTURA)
+
+  set(options NONE)
+  set(oneValueArgs NAME)
+  set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
+  cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+  if(NOT SIMPLE_PYTHON_EXECUTABLE)
+    message(FATAL_ERROR "python not found! Aborting...")
+  endif() # NOT SIMPLE_PYTHON_EXECUTABLE
+
+  if(NOT GCOVR_PATH)
+    message(FATAL_ERROR "gcovr not found! Aborting...")
+  endif() # NOT GCOVR_PATH
+
+  # Combine excludes to several -e arguments
+  set(COBERTURA_EXCLUDES "")
+  foreach(EXCLUDE ${COVERAGE_EXCLUDES})
+    set(COBERTURA_EXCLUDES "-e ${EXCLUDE} ${COBERTURA_EXCLUDES}")
+  endforeach()
+
+  add_custom_target(
+    ${Coverage_NAME}
+    # Run tests
+    ${Coverage_EXECUTABLE}
+    # Running gcovr
+    COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} ${COBERTURA_EXCLUDES} -o ${Coverage_NAME}.xml
+    WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+    DEPENDS ${Coverage_DEPENDENCIES}
+    COMMENT "Running gcovr to produce Cobertura code coverage report.")
+
+  # Show info where to find the report
+  add_custom_command(
+    TARGET ${Coverage_NAME}
+    POST_BUILD
+    COMMAND ;
+    COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml.")
+
+endfunction() # SETUP_TARGET_FOR_COVERAGE_COBERTURA
+
+function(APPEND_COVERAGE_COMPILER_FLAGS)
+  set(CMAKE_C_FLAGS
+      "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}"
+      PARENT_SCOPE)
+  set(CMAKE_CXX_FLAGS
+      "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}"
+      PARENT_SCOPE)
+  message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
+endfunction() # APPEND_COVERAGE_COMPILER_FLAGS
diff --git a/external/CLI11/conanfile.py b/external/CLI11/conanfile.py
new file mode 100644
index 0000000..377cd01
--- /dev/null
+++ b/external/CLI11/conanfile.py
@@ -0,0 +1,48 @@
+from conans import ConanFile, CMake
+from conans.tools import load, cross_building
+import re
+
+
+def get_version():
+    try:
+        content = load("include/CLI/Version.hpp")
+        version = re.search(r'#define CLI11_VERSION "(.*)"', content).group(1)
+        return version
+    except Exception:
+        return None
+
+
+class CLI11Conan(ConanFile):
+    name = "CLI11"
+    version = get_version()
+    description = "Command Line Interface toolkit for C++11"
+    topics = ("cli", "c++11", "parser", "cli11")
+    url = "https://github.com/CLIUtils/CLI11"
+    homepage = "https://github.com/CLIUtils/CLI11"
+    author = "Henry Schreiner <hschrein@cern.ch>"
+    license = "BSD-3-Clause"
+
+    settings = "os", "compiler", "arch", "build_type"
+    exports_sources = (
+        "LICENSE",
+        "README.md",
+        "include/*",
+        "extern/*",
+        "cmake/*",
+        "CMakeLists.txt",
+        "CLI11.CPack.Description.txt",
+        "tests/*",
+    )
+
+    def build(self):  # this is not building a library, just tests
+        cmake = CMake(self)
+        cmake.definitions["CLI11_EXAMPLES"] = "OFF"
+        cmake.definitions["CLI11_SINGLE_FILE"] = "OFF"
+        cmake.configure()
+        cmake.build()
+        if not cross_building(self.settings):
+            cmake.test()
+        cmake.install()
+
+    def package_id(self):
+        self.info.header_only()
diff --git a/external/CLI11/docs/.gitignore b/external/CLI11/docs/.gitignore
new file mode 100644
index 0000000..a243610
--- /dev/null
+++ b/external/CLI11/docs/.gitignore
@@ -0,0 +1,2 @@
+/html/*
+/latex/*
diff --git a/external/CLI11/docs/CLI11.svg b/external/CLI11/docs/CLI11.svg
new file mode 100644
index 0000000..e12b99b
--- /dev/null
+++ b/external/CLI11/docs/CLI11.svg
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="34.342606mm"
+   height="15.300875mm"
+   viewBox="0 0 34.342606 15.300875"
+   version="1.1"
+   id="svg8"
+   inkscape:version="0.92.2 (unknown)"
+   sodipodi:docname="CLI11.svg"
+   inkscape:export-filename="/data/CLI11_300.png"
+   inkscape:export-xdpi="222.62143"
+   inkscape:export-ydpi="222.62143">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.206433"
+     inkscape:cx="93.996945"
+     inkscape:cy="15.843961"
+     inkscape:document-units="mm"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="2560"
+     inkscape:window-height="1347"
+     inkscape:window-x="0"
+     inkscape:window-y="1"
+     inkscape:window-maximized="1"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-53.018986,-23.9019)">
+    <g
+       id="g4602"
+       transform="rotate(-0.28559572,70.190289,31.552338)">
+      <path
+         inkscape:connector-curvature="0"
+         id="path3732"
+         transform="scale(0.26458333)"
+         d="m 233.33789,90.337891 -32.95117,28.619139 31.94726,28.91406 64.9004,0.29688 32.95117,-28.61914 -31.94727,-28.914064 z"
+         style="fill:#008080;stroke-width:0.54128456" />
+      <path
+         inkscape:connector-curvature="0"
+         style="fill:none;stroke:#ffffff;stroke-width:0.3148967;stroke-miterlimit:4;stroke-dasharray:none"
+         d="m 62.120274,24.413292 -8.32335,7.065988 8.069765,7.138804 16.393615,0.0733 8.32335,-7.065988 -8.069768,-7.138806 z"
+         id="path3774" />
+    </g>
+    <g
+       id="g4609"
+       transform="translate(-0.43472687)">
+      <path
+         inkscape:transform-center-y="0.00020894337"
+         inkscape:transform-center-x="0.0229185"
+         inkscape:connector-curvature="0"
+         id="path3730"
+         d="m 60.964519,27.804182 c 0.886395,-0.348655 1.859691,-0.390207 2.74248,-0.111952 0.651274,0.210103 1.042699,0.454066 1.576252,0.972044 l -1.506657,0.592635 c -0.744252,-0.446473 -1.423964,-0.497745 -2.270962,-0.164583 -0.738662,0.290549 -1.26082,0.814436 -1.498695,1.510755 -0.203801,0.580557 -0.182185,1.300104 0.05025,1.891033 0.534609,1.359137 2.079298,2.048044 3.418738,1.521183 0.699266,-0.275052 1.11846,-0.713017 1.465328,-1.565931 l 1.585527,-0.623658 c -0.04824,1.554281 -1.023053,2.892949 -2.510224,3.47792 -2.127345,0.836779 -4.497206,-0.187252 -5.322363,-2.28505 -0.809661,-2.058401 0.211919,-4.404734 2.270322,-5.214396 z"
+         style="fill:#ffffff;stroke-width:0.14321487" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3728"
+         transform="scale(0.26458333)"
+         d="m 253.49609,104.47266 h 5.48047 v 24.32031 h 9.03906 v 5.24023 h -14.51953 z"
+         style="fill:#ffffff;stroke-width:0.54128456" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3726"
+         transform="scale(0.26458333)"
+         d="m 271.07422,104.47266 h 5.48047 v 29.56054 h -5.48047 z"
+         style="fill:#ffffff;stroke-width:0.54128456" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3724"
+         transform="scale(0.26458333)"
+         d="m 294.68555,104.47266 v 29.56054 h -5.32032 v -24.56054 h -3.71875 z"
+         style="fill:#ffffff;stroke-width:0.54128456" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3705"
+         transform="scale(0.26458333)"
+         d="m 305.76758,104.47266 v 29.56054 h -5.32031 v -24.56054 h -3.71875 z"
+         style="fill:#ffffff;stroke-width:0.54128456" />
+    </g>
+  </g>
+</svg>
diff --git a/external/CLI11/docs/CLI11_100.png b/external/CLI11/docs/CLI11_100.png
new file mode 100644
index 0000000000000000000000000000000000000000..d318c48a0df1a7e23025a38cf592d4ce97dbdc97
GIT binary patch
literal 3355
zcmV+$4dn8PP)<h;3K|Lk000e1NJLTq003nG001or1^@s6w|Aw^00004b3#c}2nYxW
zd<bNS00009a7bBm000Yp000Yp0m&M)8vp<R8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H1446qoK~#90?VNdBlt-G!zuiqYw4fj;M1vuUGRBb!Vbw5RbWkxO
z%8er;9=SFXugT0uCuWo)W{kU;$*#w`NsJIdIoco^5isb`9*K%kV-h5Sq9}+GK%^D9
zn%+MQ#)jqwgKoZ`4}ZLG*ZVxxJa1L~>Zz)h(B1s~?*pi<jRl_Sc3%&Ntq8HUI~_>6
z(>Owm08$3_?(Gm85Wt{5eY(|MceCa6Y1SP*3J?jrh7fh#;ivn8BE(o=&$D)R{dUco
z#hSy1+4I8>-R7>lQOe~+`}uL9tc*n|Db&^1@1{?m5Z-+AZdd#2o}dU32*h|gI+}gt
z?ahKcd(d3I+%2viCMHrTo2O65!P1hToja+hu0D=J;myjGrCsT#>w^*zu@t@jgI8Q#
zrGFVa7N7WdekdvFayP#e5+Gcq!gu&^rtjE6p;mhin1&Eny2^*Fs|=x5%lPeYH_9ZE
z)$dK7Eb$#aoT=NllYi@0m$~REXf9u-s;-W$K0X}C%j;iKSvd<2kAq}n+~^AXT@#e|
z-#6#lwVmd2d1yjV5SFH<_{GO_tGc>N%=bjTxqhAOk`mNGL7XWn>YZO+{s&a5V`wz_
zoweQhpsZbMLseDE01J!B`{&N(`{H6=-n*CD`ua|r?&;*+yvf15JYoX_DXFY9`>wb+
z1eGcWjV7xzb~_i8HESHHtIKe<wRQPo?p!uzX7cWlBj`J2^wFaD&K*8GcaBY7UJU5n
z+eCBuvOg+S2^!7Gj@s<_Kre(F@VTd>WB-r5y;-<t4;hbv!s;l!6$;crK@@7W{CjdT
zb@lZd5Mmj${~hgEP$DAy(Cc@+;_9mW%h<8_#>bOwL}8gor99KGA5U9Z(O0SDZfz}B
zD=IisR7AB-*CE$E8HG&7Co^YaXKqfQT8*}*CYk{QUgoveYTIL{{XvO{SSr!$KL}T;
zq*EOo@rjS8_|6@JBEPq{XVI83OdB?gzGh}kOjPT1Xf9volhdc!m7Pt!zWJBw_R5u*
z%jNg4{Sp$`-O41?)6tRrp`rJ$>-BnM@4VC2ZwSCsmX^Hh;lc9sbgtjNZM4h`Cnr1|
z9a)^3djFmg$()W-8NiAa+P3;OX-7RJ2(ixG#ANx7fB<TBIy|;*<8Iy0nT8>K`?7J`
zG`xllYtfLYOvY44N2WSD^7XZAge4^zZE3#*)=DMI$Bko|n;WJw87tD$o0J(na3KHi
z^kkxg1N$#tH26#p2#tpP@^Uif&&MY&Zc27Z$w7qhMu_6J)S+!b*|5Qsva;v_78b#u
z&z{X!SFiG?{re5xaCZj>b_NI2zgMp|nQUOxz=526^;Lq@YEmy;XxFn{i9#mh^--e`
zDiu~nS~vXMdP1F@kw{wZWTL+LhJuO;QbR+Tmy|U6P=5X~)~tDvRjYDatBbVN+6rM!
zS=oV+wzk1X7A|D#nKLX+OEU<_<RL>y4+}FIkZPTdFRxxDF*}<tE?%VUu3@UEP^;y?
zjvf0&XZ>#W_N304!%{alqXCgir7RjVhW(+TOdC41)qpgRo|ngr*jTpu_z>dkJOrKY
zFhaPt_FT&?MTj9l%9J5Pe)I8+8AcV>Fl%enK|#o+hVeaLTFTlZN7$8}&Fz|+`}buM
z34tR<;O*puIxCBm++1`mj42O;;{^pwijKxyF6Z{jl}$dct*>WOW+t05GkIh5Xf}Cy
zwb}M}#l^TsMUfm5f|HHS6GEdogb+apk=EQZ&9@XG#sJ4&c6I&DCND3%cI+@Jc{h4_
zH5tr5K6MJ`&6|lnd)6Qz06M*%gsd#)CMB^qC+8Or$itvet7T$T6mItRY?(2`!bB?F
zgAgIjl{X#~A$%ngNrq6VY+rirIVMC!86Aix4Ia!?M@NHu9~?WzYsty~FJ|=ZL0eP9
z%(ytrOiV})2~qSmHQkO7!dTg+K@nn!xkB;TuHfKaBW-Q**tV^S<Za+(SHtcx>&G8i
zao|AbzrkZljZQ~cQWD1t3OF!#uEfsVJRBj`BZNU59=wnsgcx99u|8woJlU07w+P<3
z(?Ie*h|<IaZzm^%d;k3EE9!o2AuZA%nLj#pio**QFuY&CC4d?s?vDZYg0f+QDMG}K
zw6zUCvTz}hMif?KM)dEGg+lSOGrgXDxvi?bU-^m1GiO+ol0sT&C=(qV0)Q0Ot+ND>
z^4@!WDJ%P8%8(&{NS!l>#VIL#`1RMV8g;O;GPo|ed$-wc?pKVHo|nhWxHuwxe3(6Q
z<O@_)9YqLxDb>}nPBu0ZlS4vyb?@FbPv9&~O%1NAn(5|#rDeXoaf8JvDSYbdi<6Db
zNMMT;Sddd%dU|$ZBL6deI{!9cK%0y;o&?&On<H)Gq^c$4QYrq=JcGM~L#J&$3QQV2
zn2%=6z&{~@oYK<cC=_8*gt!j4@5;_j3rS4mv*6$+1Gq67C+SROGM;<li4IrJ$;O8N
zm^_)QZ@x)la4^<NWruA&DlBku;ggv&@rjFLUvBO$`t<Q&<;oxL4fI0X27Hbe6vR%B
zj^;n7OyQ5CN4IG3bWu^0<=)xO&K<7Y4{yA|@^Ry^GdBnBEw?_dNC2TyvE=#ZnGhAl
znd{d#06`7gm424QLewBc=*62iBPK*f65{O4`WIhpwo9t5ujfEsp25A>M~%Y1og3~H
zG8wDf-3{_dIi;msDlc!Boks;znT+lJ{yb}E$GFHyU4d4+1R<6p#QnKM<H@5QAw+3a
z)iTeRnEI!!tVjq9#LUEaitN9SA2+z}C6}`^C<s$o+gF*_O`go)MoRAEQ>WVe{G)`8
zQb~GP7$udJ1gO<jm9@1YgokfzT+Vo!B|Ln6WnEpczgk^&{q}7RhK143NCx=ml`Bow
z6Wr|WIS>|x?N8lMb4Y;I?(Qsm{&|D@g<9=n8I+;c)*M;55Or1-OViTI>h=052%$Ds
zw)uo)czB{tulHJ<ntDr}l|{z9c{o^FHYxUcax$fjl-T&Ep5pwXMZ7hBe3S0X?59k?
zS1-Lpgi2-5ZRz!T{<v=++D1vt2kEU)(63i7qkoEaw9tpixIu$RpF5Z3>FI3B%*;b3
z8;cM}n=9XP>OMR?10lw*KXD?hprYc*jQR5kQmZ*tRCK?{rSfuS?c7PqoH+*n6e}|`
z)=rwlyB;1~xpj*|triP~f??Ly^m<@<_d(wN*T4Au{P`BE6z%8N>}RtR6N&%+`xc*n
z*vxlvA>7>^|AYk2T)%z-n8~VDEv`VeWs&Q9V0>bB_W9-{^TA7(@QjI}q_VQ91BryE
zt*jU~Xb{iZ*%=LpPOoQKS{m;iZT97P<Y-V>%iP==6xMFQqb*Cqtp^1_h|9p(;{^p@
zPKk-({V7v;ebgv}B8T$x8M9?etD71c^IdT<<F;;P{fQIpdafs9Dw7f8@6VVgpJYO0
zBn1@}8-M_WX!_O~p>06{5TXq5yiit_JU%j#(BJ*8iDbT1UXEWv0u!R5*mm}8lUaWQ
zw`*$HbN)O5YBeq&e#nW!!Zuf<r_-RYN-Hbzj*Zo8Yic4IOWxLKe_lkbmT~UfW^=jx
z)wsYws_N<pOG+}D`!bPAak8<&!O{|IrIPZhDsJ4l!?!nXQ1ck>`x<I(&F8acv-#vn
zHf3g31M?9gzP)xjo~(+9SRsRT>!(d)MF0NyCnQi_Rn=j0k2>Q94Pv{$KZ{dSNzTbB
z1N;!;a7S!)TtjL!U+Pt=t0}p;)2+<Rq?^3FNXgB8tasWs$9xwTHcXpFfLhI={QOJ6
zB!uYjWUF(+UZXj~3om?oqM*R1_|6^qj(`A;T)9F~Gdl_&HyRYyd>0oyV`4a8T6z+>
zd=jfycj`Dq=Vx0Q&3RO+gV`k|zL|xEz0^TLTqr9ex2&wwrh77`G8v!x`7zAe8m}EY
z__4BbKT4%HZ@ty&FWxm%V2!2#m1<u>Ma7JLxw)2~`ud{N>-qZHwJtH=)38x0Ne&6&
z(#@O9+r67Aoo*`-!r%VZC4*|$o_V}?W<KQ!3k#RMKW%SamX`KdomXj4SpR$SBpWj`
z^}svLZ58S$U4IZ&2rFQZxm-RmE-;XrcklAb-o1~71a44ROHxzWo0C%mEI^3ZuJ+SC
zN4D>62;XKRmCoAa<;75IYr>L}xLez<V;VgQpW(w<HE|;T2??CNapNXZsUNFXAL<TY
z-T8|X5`=gk`14=KkLMNFR`)gcXs%XN5R#b4HLbP~@Ir`hyUSPi{tO6!5G#R~y4}yi
l;R<qj2yee#)Oyj5^M8x7?O#AE#i9TJ002ovPDHLkV1ja!ifjM?

literal 0
HcmV?d00001

diff --git a/external/CLI11/docs/CLI11_300.png b/external/CLI11/docs/CLI11_300.png
new file mode 100644
index 0000000000000000000000000000000000000000..3dcb4250604cfe8ffb1e3959eef7780ccd6afad7
GIT binary patch
literal 9957
zcmYLv1yq#X_w~@-A(AqLDBT?nLkuAyIW$Oz#Lz9>FCZb(Dbgh^-3Zd%ozflu$MyTx
z`dEuvYwq*hd+xb=@3YU1P<=0lhfRqM0)g;g@=$dU2#Fo|I}{Tg_!+LU=nnisb%DS%
zFo7>0%#Y!~|5%Rlx-K9PSp45NlHTVNU=-*jLPiJi;gbcz-Ne}(<nHdyVeMe+VrJrK
z&hg3F^4qZ(B?$Bi1cScQ@JK((@YJ8&ntxv5ZN~(MQ++q)C`NMl%A8i17muC$CK7o&
z&DvE#<q<yOqR?11Qk*%mC+sECH_Z2&eT;8%F=b4@ns2e*QALfL)I8DN&mWf;Z-Zr6
zV)gOqJngUZ+EC;6eXC^FeG{qt+c?6bKuOIrE0y-qz&ZIJF9ZYY3D<v?kT|)MmcP#)
z{4f0h7k*SPWv^WXF8)c4mP@!UsQr3<gvB63DoHAQ%&P7n9!+swXCAqk5Ii{*(ZG8K
zD*-1Y7G+O8(J?=3Wl#xP=y)_lC3tZyXJ+1bcr}UO!O%9z$+&&{@EyoVQ~eMm6v2ee
z`u`;_Fhos15_Xc@;HG}O8~g8%DtS+@`oz%CttadUSsxE?gkei+)ZAPl9T_N(&@=>R
z+}T!4KO;a73}s){d)0A~_dHWZ<${g}*`j~mWBf_7doUC(U_a}3Os(zW94&c<8k(zR
zB$qe$H}_(Z_%`2saOG(_JU6$lout=9r66H6H}C6YaU#8dAbX6i<n!SnZi#sP-{k^m
ziK{CmK2!U`H<_xc;He6N_KZ$QD5ud#=mo+h%s-mY-^u2)Lo^$_S1D^bbbQ>kouPLP
z{+4V7m#Q~Fs|@tVrcP<4ktAoV;S~(#niE?u$SM~twN&~N!~|!rDAVgJeYBt}-A3UA
zL#xht5GtFZv6-8j%T{$%fv+|kE3@eIWaA1gCkjD}>~BmierWn!O{kRjtE<Pbuy9!H
zjpfyT{}6Wd?i~vmHhQgtvH=~PK!$M8K+X1sl-%UL245ODh4GNNKNs!4bm}mzX5{bY
z)P5}{(m!KfN*{P#f^Nhmvvzx;bgvI8{yh}$&6tg@>pCh6Y3S?n@c#%qsrZb4?@f@V
zBvaBoQJk>(-WOK8+GgU6yW)Ht8`q`W25sfH)92*)WTnctrtu>QWqytDdh)?HYTk}y
zW16D=eS5Co<oj(`tL#LT#cCoOX^GA=c=AILF!yU&3MCkf&E`^sFO5xm>p+2anZN7{
zDM2jVv*1bfp4<m8_@>rK@(cEo<mj}^4n=Z;>4*wL{A|*%IElft2B8PrNf1IZc`WLM
z%bKBLx8|2Yt3qW(7q0}4?KL)dme0DP!?B=Lw9~9~j<Ku;slV45Oxn&6PRUtjps04+
zhluDlsU^T(zwd@Z9v+eU`v*)`6PT#``?<vVFhWczU@)qT%zPZr8$*H+D|xjs3n_c*
z@&fhKqi~{VyBYi&X?h>C4OxYf#s>~3&hoA81m{DF(ORo_UGTb-55@9!GqGJW<)^k<
zje-y|uJq;B(d;p5SGA`1J8N&@kYJRBK(38S4NRuvULc#2X~Y$v@|eGokySlCxN`0F
z%6GP_p9je{q{RbtCii{#(v~<BW{SncWE8`T0%D}hHY$rD9t@muNQ%kwA*$=n!@6M8
zVLdQud(HPLSG5wsoH309?VzwcD=co@8~hVN{BLiAffKaXY8(i>(;+A%IUF;a-R9DU
zFU`glQuZXnD(s1U!o-{$`i6*T*!Nhti?*|DXJpKO3_S5@L>!@K9LNNw_Q0;Y6Xi>5
z2Ii8ZVRWf#X`LDfD^e>aRQSO|t^<x;-Fw;N{nx<FD*?aIi<~{9z~l|rAECn{^(u{H
z`Y#$zY?aXTzOFpU4@H^Iz-RLB$jaKDTWLc=N&<_byE^>*N|lv1PebvLmcTnVlN!mi
zzE)pMVf?weQlJCbSUyBV{~I<&zaH#gZQ4?UkYeiYTI(rU-BLqaIHWc||D$Nr?*jBD
zRE!$mVG8(k84eH`^~*+0)%FAixwmE!C+|vP<jN$XWnJbqZ|4*DKhy^AV%s65QG|sB
zhgOTw2Z~5Y1OU;Q!oXo8GHl13+%K`>!_ZHz%=k_V#9ikVSz-rCA@*8wjb#M9MdxjB
zf=v)wGA0ujVck4Nj73z`cpZ*ro7Em0p39iMH3(rK`%FXDGilhW`o-QWGbLHJvwkH(
z`$DxIoS^aHA~_tX44&gt$&`y>bID+C0REA$Z|M4B-8DJXAE|;e>~XLuT2?Vc^^DLI
z2tAqqGwQMj!^$3PUVe%V4)H+230Uhjalu||nAG?ckDaqg--)c)E%6GS-a$xIPVc*y
zjH0ErJX#Y~STia{A|;(l7rbdZPw?!FqT23dGlwBUCYd-wv8tWe33)e;P;)y<y>7a8
z+5cHBGO8YlCZ?;(#3aAB4|qTLO&78v6y+6dofj+RZ_O74wnUmmR#;<XWp1p@iOz*k
zDA$Y?ehv^fVj6;Ks2!W+k6{9e2)HjOW;;4+yo^sX&x+|a0g{&4N9+HvbD-+n*L6@-
z^fWlwCi~pEpxImc-L=ugI9}!rX~rn3V+=%Rv@guq-yhl6*IKoP+0T3*SIWYH<Ey9V
z$L($Br)l=%<S#;O1QFfvUiZWP!&C~fq^64TtMi=*gP+5Qt3YxOD*C6jNNUy~(HHD!
z5FgYQdurU%YfELwXK6BsWKv|N^0)C4UEWNq$?%_nfvC54TKkLZ<LZ9D2iJEm4-S8M
z#IswDI<xUkI5g@)bRO?ewRk>>|BKi@VkdmnxJ6u?)z{~}Nscht@X<+yTF`<!&C1K(
z@%vObIM&>Pr@e*Al=(yi(vXodBpx+A{FT3d5Dtpw!gt5Qw{V4g&IwrTA2K@;T&cVt
zXZrR@x6ML1G$f2<<tO{nH$LtM%DJHEbn0X}(cXt33|RAiOWjC;bQtl<&oY@ERG$l?
zx%W5KdyN-&2k6$nc&HowGOlVYgt-_NzVB`I*cvnu0?(nb9Ce%ZO}n{2Hyq6bi1xHs
zc4!+8K%MDQe?02_EmG5S7Pn&Z$xy2#dX&|ql~^Kk<CK@;mj8Ns2%+o1paPl~MVMJv
zr;gA^O#W6x>jSVX;+l_GJD--S_uN$Ytvjifnz(nX`cA6Na(D{W%HT?|;l|ET)UXPJ
zjglaY+p`oS;GiP<9~npFidDJz*rHYLt_rM==F?j}C+M$>H;#UbihFS}fssF{s8}2>
zSf26&d?6d>d5)VLE?Z|)Wp=3Jk{rG>&~Ft?ScU1d;NTw|Bebn&!w#thT^Xxa0!ddX
z#xbH=`7>Qy5g2CoRwszoJ6fExrTzGH<0U2ecI9Vth5PJRPZdUzBTAXWU&1kMJT&mw
zq0atbz1r3G?uh*Ep0QfDqezuXWX^(whzt>@{kj>CB5(I7!{XmgK$<m;W(>vRyZ|ln
z{g@0!HQZmEyZB=nKu4HcwTDsO=^L3$Xb{l4E9;LU5^^FrHug~B@87jL*=TJI<utUr
zT%81R%kHIk+T@K#Q%LYEgG&kK#pRaK<GssW-3$da<$kpo<21=~Hj*td@vNB57n7JN
zB3!$rG?I#6gFsSKe)-K?i6MxCz1!?a9~DQ9gfQ^6#G`1BetJ}%iJ3=fCd{o{a&SO7
zR;vxBh<2#e&X(9LQje#k8)Y@OkFSHX4~V*PnOxeG%!9qXz0R!L{PLC4x_N(pTVQqh
z=9%wiuN6bl0lcRxF~7SN>+0OhO$j&5$nR_FbnHE?94f|nSA$dOd->Hd5m##GxXuJ(
z*)o0DSu9QH>}EJ-a!=1!iQoJfS6`ItoZpliO%R*%oV=0E<o0F_Uc;lTMC_{0W1!73
zn1yjweR%C9V6X5HNk*OU-mkuUW++O#8L_sMDSrB)_^m{T0(_>V55xD4i;Zn~^&Nr{
zE@j21KRB{|&Z7v`_Yk`D@u5gdFn!SaKrjn+KEq5%_~q_ldfkzv0>=&}Y{!kp)=F75
zzEVlIV$lIK-tO&`w$_VMwWEde^C-?H*N$-#c$<V|G@Z)FDDv|6GN!?nb6)Cqn*%<i
zFZU`xYXV3U2WAj{I2-B?2@@?ae#r|+KY&sli<DmNPqYX(_r~qSpB*56S=ALYzHZx{
z+>CZq9!nFhL5Hv*B}D%NNjo~;USn&a<gc*$Mwi>h2g70oUMBEov>*)a;u#JAZvK*x
zyqqh~DESbdt@rWSe4NpuubV<(V}U9u3<FR&LcHeKe-VF=)txLRzVcqA3dRk&AA|lB
zzC-a#=L&pH$<HG(j8f+2HYXi6;pF#EaS0%u{sVrK>qeI{b<8B}5EG=8xi+mUsjan+
zPI_tTCPT0%*FV*LpUQ1V%OB|$7)X|7jE4P+Lo7WJ$gZ}#>!P1y95~v!_0aT&oCBS_
zdgyMEPErmifpA?h*m0jH(Ism7{;wd08;bZPJs^yq@hOb8=xe7pZ{mLW;^EwA#^|A;
z`8chD0Hm=v+Oy#f%T^fcUdfu{=05z!)lsQEN+bqE?|#UR_%{kNwHuu+z%!@FmFX&y
zmrhy^BPIV$6iLEh1C;HR4M5gbv2nzHPi9BDKcpwM;jeH%va$VXF~G^Ot)xW`U=En!
zHHQ71TS~8=pBTKJBW}9bY*)Yp3tmS4I|OG~*fBu!wMyF0z9b=}4-<uXm}N#;d#&Ci
zFKCY|y=QS+nM{{mM!BmEM1z>XrGCCoEha&ytI-{czgqF=(M9rdvRxvzoSh{V6jSnS
z1CgZ813=Tv%*4RCG|mza-Vk+TEY{}yHo?DLG?b$2dd4Wqsc-z8#7Zh*%!^Rw<n-dV
zS3s0#G1l73iDzLv-{}7yFnQ_-8dg?h5N91K_f%xdGs}~uZg376E7AJ;=;LU`5+P8|
zd*|gz1iWG>j>hb^%FH6pvrUDLwnTfh@P3x0l?e<CJy}YlCVCf8FUVQ0Yxdx~i+v~+
z&LSsBitrgDHbsUXv2A$E0K9Hf8+Kyz9zQ6Vqt2^?CR<vSFiS>mA-!B8YxMfB<=9$R
zdX=%Ltf3mw!Ni1aR_2J_H$h%DvGn?8cg}id_1vjyZt)CG|J#tdA{D(Y;l$Z2`<)T-
z6PL$fCB1(3x29VWBm`pHqXx~NsmR#5K1EP0<)F|%JryR0I~sFN6v^@0&(q6oZS?0B
zNQE;hk#^>onE2QOQv!Ijn@!i$A;q)c6ky0N4MIM4KBaA`88LsXbeuQCe;=7+M@H!)
zZnzX<sZBKQuUaM>d8x)HzNRGi7A!c_#3+J``CNvc{+3fnP)jBa8b^fxEB*X^{HZ>`
zf4mFdAv7&wICE4Q8+Wx%#bBfiD}q8#0E`Poy9T(MhmEOcyBwKLPIi1g)Trq5y6MwX
z7r}$sDDBx%OQyz;sp{1~^yJjevjlO_b-s&aOy3mDR;JE;MJT8ei(Zxi8rkYn+2(b#
z7UtSNI3KRo;T#P4zBAAp^L4g3acU>4!70eWW#{Z^B*TqUhZyFXpO73**-o60`26wj
z{X#vUjiQ^~-BCiP9D(CMg@x6k&zv;h6!wP#I7-4n=r6Hz3Vhtp7&9zJx3fvS^<j)t
zfeLW2*dH57>F}4cRqk1j0J$_}tP&Oy@DpIJ_F<9WCWiv6Al5Fg)zjPTc01ZN-nvTv
zw{IqI-kj|zfUAgsJcsodfSDP(eJn<Q4o#EZld{v_AxnZf?o^?UT;8vhxO6D(mZYsB
z!yp|-S$^|BTt39on2pcZy|-s&i`S6DR#fm<R!RxK4Z`^5MJ|^ZUlkiCdDXlFCO7p4
zQckRroa<tkhLXHjAWuA4uEMD3AjN3@qcxYg$2S43TivUtE2=#0vRn;vwuo+yU3MZ8
ztqK99>z6>n9LU8owl6H){E<I9ucr@IzR$IL1Ha?Ib3y7dTLFk!J^G5!f#ZP3CaR!=
zx<Ur3<MZA@jmS)^EMdZ#kr+dDfP*L_u(jpckJV-7JMJ_YJG}<xDZeZ&8z6pD+8#PV
zqAbCMKw17tb7rhwz<ynOPb@{AT9&C9TAQ7}iBYIhuW(i0VueKnvO~?B2|F$#{MBF)
zBTc=sA>vkhlm4OYc_iJ)ugaT)I3iyxsTPW=25$=5++<O#_@p~_w;E(Dh4TH99spcG
zzKb9nfAD`(E^=6~5L4C$#|!-j?3~Y-jW$C!s$#jpZ7#pBmuO^^KdQ*C^z{eKlqE~v
z@8TY%hT>_uvx5u_ufzVn&qS!o5=V4X|KoP;Dki|~=u8OxoWu%;K-J+*I9zOu!qW77
zeKm0sHmAbp<KvsXA0xWa7R%C^AS<dTE0}5~%0C(aMIDDSZSv9DF&SbNyR5}$aesB=
zbpIM0FaI_KEje5}kc^C<)JOFnU)0Wfe$k~AOH!~9hS9gs9kC(WjT&9#946xB7XyIl
zhdSP0HSu(evTa8t6J}y^Q!yw88PfX<z_4?KZM-~n#h&d~wua=XilOVY8so${=x(PS
z7|O#28_T%`HFFh6xD225a|qKI0CbHKl%q@LMP(wcU-wH6-#tqX7a)D{tn;&|k$MYw
zc+9})3f9F;m=akh?rWCdYlhjCw)G&TN{h5ODA<Y3f}QB}6u_3=fY7~(A$y)Dpn4r6
zzajF_r=tWC@2bjd=uPladN$#5Toc)azM!1!oY{9^Ag2;L(H*}Fi6WFnK~XR<ow|r%
z7XbA$-re~@ie>ueA~}L&syeRa|1#gZzkU;Zo#*#EjXWz+Ph&GciIF%${Er^^$^F_v
zdN`UX7ae}2o{(sSegX}zvZ^yuEhO}MljK87(&dp?#*_7TWz_ayrg>4#;kQ*0@!n{H
z{30d$$w)H0e*egJ&36`3Y_vR4#H-nrtXcD+hB6ms55NR_K~MQMaGS!_`sDO1w>W-7
zzhugJ^UgKp1zP}^wTbt}W3J$DDzJ=KnF>rt$S6a~!EKHQ<hNTZUm^os6oTa;ITQq>
zD2u<mQvjRhw#kk#DA%t0p)WrE>LA62X<CPM|BGM+jFDKdt4$VfI(4@AsmYa<^UJ4!
z%pk}qHDq|E%$kYG7Y!HfOll^^@z8xGzQrueB=T%wskz4I#_?MpVh-|)?BK&RORQ!L
z;3HksnR+Ke*KIyOKKzNMQGDhRomxsD&NQlvByd0Ckzhk9sD2HD9ptolHBc5*6F8zg
zRW8Q~{&17->YNp^h}Ohezgn#(Q^*!oKB#*wk(mids~XQq7W~!=VikVwgRVsR#bo?<
z?;eCqkZGs{P*<-Xd@>*JY=hEtdbzV%CkhMpP_}&G6NN`zU3sq^-_fP{pS)6wB7gaF
z4}pVa@)w8m&mcaDJ9`i1=+uV!^Q);4peWnMrS8A1Uj{RA#P*Z3gZc+Y-JL9mVOuYW
zDF-uO7{1CsRNkE_Woz=iWs=lyvGSP}XlZR`fHtnS#@-ZvP+8rY!V~&VU@z_-OMAZk
zl9{2tR4=8KgjwiuW+^Rqs?o`;tM~Ya!Vx7u5MC1NMCQ|tU@Rq%bu61=jjh<mT?lI=
z)D#O)b2{8U4Qt+wmy5zp!(YwjHE(QRMYcU`Wj@btU(^1&cR)YktAx>w*JqA>k|^KV
z=qgmpqnK@~Ykf+_ZuGq_?~&jJRH67c0asPoSZSp1-}uec?AWwdd=dd^Mc`wjlwzOH
z<PZx$xaHW`7A1#|<`uJhNp}0qpRbtQ<n-9T4R)RkXoLHa(2-YmrL~V2*Y+xYnfiK(
zfPNVomPW?ysHjL-pVMjRUKuuhJQZe`uXU1Qj?F2LBLF1mNb!uJ8K4;fbE_QU4_C7;
zW`h&g%dRwP7_na9z343}q+|_dY9GSnMmt{FS?zeN4Q7FcLsv<BS&AzEd#9K}c~^E&
zvV)tqzbAjCqgHF$)d`fxT}X4YS%ApCW#Jjm<?oW#u26p!Hlncj#aL3(Etbl1m#9J;
zQ<)N=47@CDMC%}eV+!28z(VSeu7DJ&sQ9(kLRna`d$YPXAoeHpu*ES40E~ZC_lr`B
z&^0^2BzQUXl&O3~W~xk|BA?!|fk(BRM<}~oLTCYMl~=g?#WPdt6L*uJ|FWF|QLd4q
z3&uBSwwR)*oN0D-Def!fX_+pF($q@=EYpnuJ4WIK3Ls;E8uG6irhAe$X57Z!93K5!
z@wY?O)?yGgNKQDi!-4cU0WIVFcb8&bHlm?2tEdS;`U+-w4FW3-lQJ?YL>$?idV0#p
z*h%^?KViWb1yo@nj-r1uu9s_eF9*&V!(cEywYT1&l*=4;bMf^I^9cE>>XaFvB>vOc
zIZ`wN1|;W$k{xc6$<f}#=D>S$V!@7dh!7|H?{r85b+{~Ml&{jXa%!f)5hg9I3QExh
zy(RpA%MyvLNO=L+&Y>yeO_B?02s|tk_1kUBBnt;Pyaxlu_<q$7J7<6v67W?@3__aP
z{9Z=q`6xr=lKO<SQjTt14=f<mL3|n~?)_GxA6G@dVfeN0{k$;MK`~%1Jahz|Rzf_*
zz0aBTMlxNcw1^|j0W;w=C;}qnyru9Vq!Kk`oh&%@Goe;}Ox01mbU2%j?>%y;=5u-{
z1YM+qINodjizd!lw)cwPQsgCrcOD2hwN{r;{VG?7IU1~eZu0t30Pie9>Gi!XzMq;j
zwi_oTSj>4zF03Rjd$FdRoa_V$-~9&!y#UTmcerSMdHcM(kTTPRs8ZyZ=4o~RM7&T}
zv%d(~C);#lI;Vle5h&c6m~oaC<F`?q0OypjB()NnmWdx*XZ2*Pbtw{>68~39AlyY<
zb@GS)%HWs&X`qGbThig^$o0qN)(q1~epw_R>X!rF;@)uI825UIq)_z;MeZU(w=BU?
zM4Y3@3ExB^P34U5Q6soa1{0}8N>}=wV<{5^N6D~lxllX*+Ux40y(##zlp@Up?}^{0
z5;4oQe7wB0Zwt80#_x0!Wh1)hbg~JL(VFO?P2zt)`M*>S_hcdM0!dep0tQINM(-Ga
zJ>NcEPizhO{PJ&8o(Z>^OEB}lOPs2(Nh(k|-=<C#8Y^u5Po|)trNOICe<+~=6^FF*
z)9JN8U<A&JV)R)(!(hb$IXSNuZ6mC7O32fy%#KVWJX|K20as@p5%bE(h`g`&3@G<6
z9Hxi2)1$3lQ1lNHG0cj1@v+<cbUvJf(aE#k3x1JLhcS^s{^(MMp7&DWTc2$UbtMo=
zBDaFNBGdEOh;Dm&x+`?v{i2!n@De-2T<DE9SphKea57?E%c1t_^x~tu6$@3Cf2K5N
zYd6r^`R20V_aydr;gy6u<McR_Q0gPeYf92iDo@XC(oSO1NNxK%K8;{A!3`{@^tQ(`
zRvDRtK>U+1ru8-y^pkZG6^i9JJ-|9Rpb~^X&bwUPxf<%XriDm{e)+a*!fx<6XpfkB
z7y!w`MnnWq5+o$Ycf=pe3^ceDE#_4sFD05qiMWPiV&!-zGGF*!jCoUD&UN-2+O2Ds
zI^JmH115}(s?U+kg#Y6Zd!aC_)C`z>l&KjW&hfuClx}r5qC@;e2ski5pFeh_?^HB=
z#a9r8ep2e~ACjj^sD7hMSiWH^D1!+MNC7ogZTMq%?M*+?=f}reA)fK~0|(nuS2pus
z44Gq4YtGK`dd6mkhCC|smj(t$U)!9T0_MNSA*~nX)DQD(+a|Bb#BMLZ@%n!S4&cgj
z0|gGE<I&xi5C)qT{>9!ce6JhaAhXou8)$y_-!d`x$akz%aW<moW^X~>tD_qJ4ea8B
z4QZNBpY}bxMQe?h-gxtfJ8Yz70Hj6E5^Q;LvKvbH{*BIRX)YR<=b1sUJm|@q+AWxA
z{SuE#ez~fv5U~^pl>8TZ4XRf|2|{FTgRH@lx$3TF`;$f)U$bi5@okCLe^-Zo4h=~J
zqJ36N11Peh20=ml>ifIsnugR2<%-+$x%-7rfC+O(XfkhJo855ZhrOoo@OXbR(<bsr
z*3N%~4;=SwRSHUMWNN1Xru8iEYvOvJ#<e%)KPq>|nE*oVTC<(e&xi!9?sUk_VP{Ao
zHvKLeo>Oc!kSPaVwTw*csJYxWEFN1UG+y&_#HU9QB#2c0q5UI?k$jC3^X*^07lxGq
zw(oTiDe@P%Sa{|D+wrlHm;xu^Y%^WB&>K@xm4#V!eq5EP+@6i8n|$@<YLf(_56J+`
z@zF9bQ!<Od?Re7x79q}4FWSe;yT|V@@j<{Y`;W5A7Y!AqYr=i^%2XXn>;Py&!qZVC
zb&Qj*pcwtt#@ZN}P#M8X_lQ4cwkhF2C0FRM@;*$rX4dOC{kQc()M2yx?NBni*wgJ!
zVVezcg!HM#+uOL8AzWUkrY^~$mPFtlGS9U2yIi!U(9dtDKI{tUAjj4~)9)VL!EH}z
zeo^FKV^`hY3WtRF)Y`saw-{dCSZY?*d>PT5b5pDV*!brPJ=klyt8ap5w<5)UuhGij
ztXB9x(ip8+2D<i|$hk($BDQhG+wLNlamXvry0MB{6Ea#o6M_8$Z%(M(W|ip;47v}%
z3peRyD+pz&-ULT%lXd#;Hu0qkJhmq_%qW%BFAvIu?*!<!qx$N6in#IHln4B0DmEgN
z6b^I1FA-Q$cX<*^JKrhZ*^oAEcI&RQmahMgQ0!<{?g`>1z#2>UdTW;3@0ZW*Q=SVG
zF~$S{2!M0i_4z)gMJ$v}&%(iaUDTKnvMDUu7wI@dLyjFRkDL%46!Z9SBf&lPYG9k3
zEWXkd&G!Amby&Mf0R-r-`lbmM`Kl7yBcouQU6xHYqV@EoH+m*!aTr<5^h6!DA|et6
z9UAggL#8TJjG4~e{&_nMe^=;;sO`o|`vB6y|M+qCH8+kGIiayySnNYC*iFv_+ppTm
z%*NYW`|d_@ZVuRiYXurAD)L^yczZi9jVYqobx^K>Nf+Eq&Q1eq!5(hk<)2n5_@_&s
z*!7TAbEkOBAHAN=&qwi<3o_#flx=1T0rdsgoZmM#r2W*0fJu@jB|AGcp_zd|=vx2o
z0*DKyxCnkqh)>-3BgRN<%e5rkuyv@rp3Es|;5dUkF_h-n#C~j03ra`~%bwyu$1Rf)
zPg0Oh!4&7cKB_5J42G)H0QvNFtgX$Yg9d@Urf55p#?i2X)(h?y92pr?r>>uvF|^`)
zy*FV=1wS&*X5LjwwN=SN4?%X-?fM0@falTCtG6fL%%RJG-(72+9i6O&#<sxAlJ`X2
z&w5)Wk9Ap+P~;WhmRAsm(0fViy{{6jcm)LqfzD<neG*SXMgYrGv^QPXuTaz?-Uyy8
zIy|a^ohShxsT3y9Rr*X$`hu5(Kk3u)Qi2OUK^#lrdvVuMnZ++ZKGcGOt#C^*=BdIM
z0MmY9t?QKYfF~_8NoI1rCtpwag&Mec5b?jR$~&>9_>{iYPrtZd<36C`Lc<RV=^A)7
zORSK5ZcJ#7mzB{n1@P-}aQSq=pz`g_*&bs0cnu~tRm=~8Q1@Cs4d@gr9K>rm{fhO#
z*0{lGRyP?lqMHWbdVEC~_srHu{1a@5#z4nc%^EAD{Cp3`ha06;vW?0THX`DP60Rb`
z5oN`VEpM{Uw<gx#F|V}={`Q@1!THi$FOGe2cI9-DIN08p+@5am;b8tV=@ddf0}b)-
z2S5S9Yne}Fpk7ufMc)zy;ijaQaDnBz<?oZTi&OrVnA~@f!Zr6-iO?Eyf32dR7@Q9$
zS^qn_t(ND}WVfsE&WSs(?LKzY?{47Y&$o6^@Xhj^u*l~hMztWmH0g`uM+PnmF5I`1
z>5`)%hjzf_fS54IJC7jr3+u7ZKnuQ~^=;GhB~UmPDLQg3^u_Yqu{Wh9O0R_A%pta;
z_i}auBru)YfhR`(Xf8C5-J8hDuSKvET2Ut_W5QfJkwZ=_TUyZ1j+>``d~&~h3kLz+
z839mPbk{gBeK9_uYaF`!UdOsGH*Q=o(kNX50rtPwBb1R@LCN^vXMT9RP5P&{wnj4W
zs_t2WK`wzQKYkSCfPeI*CBIqtxs|KMiIfR~?{~Rl<LEkXV$x9Y=bA4yeHRm>SpLc0
zwzG9w<FlU7@W$^g8E~cIRgS&hY9Myb^7silrE;C-w;z^c$bYP`)k;#3=%wU*b>Lt|
z&4_IzBs(hJ(4w^6pNkSw^lYfTuY!j#RD{y@_5OuToiC05`xMB=_Xcn<1b}A7)rGZn
z#D5KFL>(P=FL^_O;q547o!?lJxEYzsT!G65*<LSz&5A*<yJMDR*7y%doO>Ri<@I-c
zqxi#g&jsLxUg%Z^YzR7}(t6Pn#95{a*tbn2B8Uj&#*kaFE?G{fWm``P3|j}6SNzNX
zgP31T+<N`5ik%JxVA^i|!HQ5!B_`@f2|~{~`{OGvm?f?_nJcr6enX26hAD^~98w#=
zE;R_~f(dO9%J;TZVFgT(bsIJfOhJ?`;2zH&##BaFg%uL!7`cH_IpIcSEmU4VBuk;E
zx<M#+P~h%>48m*H3fdm~KBlOYcA;8sB9;!&Ll8llx_%21CO`hS17dDZ0JB(|q4Lzw
z|L>2Vv~%Q5<thf1iHX_53+)z?mD~IjS>Ej7i{%o0tAXPF3q%l*)<A&iyOk_=b{)i8
zWN?fRrjbp}QvS=L!wx?9mncr?fC6z>Wicf(O#-4KO?#Y+R_W)9RnbzNFgz@kU2`f2
z&_*MmAZme`A4a2LuaT|l2G1THQZFTixhoG*4-EXxp7IMHXj6PnW`{*Yb*U5}uXe{!
ziM2Tw>}3zxgoQy&cBAR5=r|>B895PrcH_oZfha9a4jGNFZx}qh@K;-}+2}M4V&Y|n
zNa=f#jX;LIYq5A#)dJk{K+k~#rl|hNGG*iGeQxp8=w8tnR}Ye=lU0<K>CZKf-P{bc
zs#{SNpw=2?sI#qc*p#3DQY=OsQMXA(cKR@wR4U>*zSwteJ!!SxWdlHiZtZax&^WA<
zYQ!d6J^Ed1*m`n##^ayaZQswyQJ6g|-ds~E<hPjaqzeaJBQ`j}`r|cSLH<V+*&}<v
z3{{JUoMOXZF8Uea%?wdmGMHn8I$>(wR1w`Ho7t+kuisd=mXH)<_vdQ93_ih-K-zB*
sA%i8Y;j7NMPdvZsHj!igka6?;7cuvWys+mta3u`{lX(v<hZukUKVaq(r2qf`

literal 0
HcmV?d00001

diff --git a/external/CLI11/docs/CMakeLists.txt b/external/CLI11/docs/CMakeLists.txt
new file mode 100644
index 0000000..e7ceb1d
--- /dev/null
+++ b/external/CLI11/docs/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(DOXYGEN_EXTRACT_ALL YES)
+set(DOXYGEN_BUILTIN_STL_SUPPORT YES)
+set(PROJECT_BRIEF "C++11 Command Line Interface Parser")
+
+file(
+  GLOB DOC_LIST
+  RELATIVE "${PROJECT_SOURCE_DIR}/include"
+  "${PROJECT_SOURCE_DIR}/include/CLI/*.hpp")
+
+doxygen_add_docs(docs ${DOC_LIST} "${CMAKE_CURRENT_SOURCE_DIR}/mainpage.md"
+                 WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/include")
diff --git a/external/CLI11/docs/Doxyfile b/external/CLI11/docs/Doxyfile
new file mode 100644
index 0000000..c86b0ba
--- /dev/null
+++ b/external/CLI11/docs/Doxyfile
@@ -0,0 +1,2475 @@
+# Doxyfile 1.8.13
+
+# Designed to be run from the main directory with `doxygen docs/Doxygen`
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "CLI11"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         = $(TRAVIS_TAG)
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "C++11 Command Line Interface Parser"
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       =
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 0.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS   = 0
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR          = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = include docs/mainpage.md
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
+# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
+
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.idl \
+                         *.ddl \
+                         *.odl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.cs \
+                         *.d \
+                         *.php \
+                         *.php4 \
+                         *.php5 \
+                         *.phtml \
+                         *.inc \
+                         *.m \
+                         *.markdown \
+                         *.md \
+                         *.mm \
+                         *.dox \
+                         *.py \
+                         *.pyw \
+                         *.f90 \
+                         *.f95 \
+                         *.f03 \
+                         *.f08 \
+                         *.f \
+                         *.for \
+                         *.tcl \
+                         *.vhd \
+                         *.vhdl \
+                         *.ucf \
+                         *.qsf
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE = docs/mainpage.md
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the main .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side JavaScript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using JavaScript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE        = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sf.net) file that captures the
+# structure of the code including all documentation. Note that this feature is
+# still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP            = YES
diff --git a/external/CLI11/docs/mainpage.md b/external/CLI11/docs/mainpage.md
new file mode 100644
index 0000000..b1f2b39
--- /dev/null
+++ b/external/CLI11/docs/mainpage.md
@@ -0,0 +1,22 @@
+# Introduction {#mainpage}
+
+This is the Doxygen API documentation for CLI11 parser. There is a friendly introduction to CLI11 on the [GitHub page](https://github.com/CLIUtils/CLI11), and [a tutorial series](https://cliutils.github.io/CLI11/book/).
+
+The main classes are:
+
+| Name          | Where used                          |
+|---------------|-------------------------------------|
+|CLI::Option    | Options, stored in the app          |
+|CLI::App       | The main application or subcommands |
+|CLI::Validator | A check that can affect the type name |
+|CLI::Formatter | A subclassable formatter for help printing |
+|CLI::ExitCode  | A scoped enum with exit codes       |
+|CLI::Timer     | A timer class, only in CLI/Timer.hpp (not in `CLI11.hpp`) |
+|CLI::AutoTimer | A timer that prints on deletion     |
+
+Groups of related topics:
+
+| Name                 | Description                                    |
+|----------------------|------------------------------------------------|
+| @ref error_group     | Errors that can be thrown                      |
+| @ref validator_group | Common validators used in CLI::Option::check() |
diff --git a/external/CLI11/examples/CMakeLists.txt b/external/CLI11/examples/CMakeLists.txt
new file mode 100644
index 0000000..d0d45c6
--- /dev/null
+++ b/external/CLI11/examples/CMakeLists.txt
@@ -0,0 +1,249 @@
+function(add_cli_exe T)
+  add_executable(${T} ${ARGN} ${CLI11_headers})
+  target_link_libraries(${T} PUBLIC CLI11)
+  set_property(TARGET ${T} PROPERTY FOLDER "Examples")
+  if(CLI11_FORCE_LIBCXX)
+    set_property(
+      TARGET ${T}
+      APPEND_STRING
+      PROPERTY LINK_FLAGS -stdlib=libc++)
+  endif()
+  if(CLI11_CLANG_TIDY)
+    set_property(TARGET ${T} PROPERTY CXX_CLANG_TIDY "${DO_CLANG_TIDY}")
+  endif()
+endfunction()
+
+if(CLI11_BUILD_EXAMPLES_JSON)
+  message(STATUS "Using nlohmann/json")
+  FetchContent_Declare(
+    json
+    URL https://github.com/nlohmann/json/releases/download/v3.7.3/include.zip
+    URL_HASH "SHA256=87b5884741427220d3a33df1363ae0e8b898099fbc59f1c451113f6732891014")
+
+  FetchContent_GetProperties(json)
+  if(NOT json_POPULATED)
+    FetchContent_Populate(json)
+  endif()
+
+  add_cli_exe(json json.cpp)
+  target_include_directories(json PUBLIC SYSTEM "${json_SOURCE_DIR}/single_include")
+
+  add_test(NAME json_config_out COMMAND json --item 2)
+  set_property(TEST json_config_out PROPERTY PASS_REGULAR_EXPRESSION [[{]] [["item": "2"]]
+                                             [["simple": false]] [[}]])
+
+  file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/input.json" [=[{"item":3,"simple":false}]=])
+  add_test(NAME json_config_in COMMAND json --config "${CMAKE_CURRENT_BINARY_DIR}/input.json")
+  set_property(TEST json_config_in PROPERTY PASS_REGULAR_EXPRESSION [[{]] [["item": "3"]]
+                                            [["simple": false]] [[}]])
+endif()
+
+add_cli_exe(simple simple.cpp)
+add_test(NAME simple_basic COMMAND simple)
+add_test(NAME simple_all COMMAND simple -f filename.txt -c 12 --flag --flag -d 1.2)
+set_property(
+  TEST simple_all
+  PROPERTY PASS_REGULAR_EXPRESSION "Working on file: filename.txt, direct count: 1, opt count: 1"
+           "Working on count: 12, direct count: 1, opt count: 1" "Received flag: 2 (2) times"
+           "Some value: 1.2")
+
+add_test(NAME simple_version COMMAND simple --version)
+set_property(TEST simple_version PROPERTY PASS_REGULAR_EXPRESSION "${CLI11_VERSION}")
+
+add_cli_exe(subcommands subcommands.cpp)
+add_test(NAME subcommands_none COMMAND subcommands)
+set_property(TEST subcommands_none PROPERTY PASS_REGULAR_EXPRESSION "A subcommand is required")
+add_test(
+  NAME subcommands_all
+  COMMAND subcommands --random start --file
+  name stop --count)
+set_property(
+  TEST subcommands_all
+  PROPERTY PASS_REGULAR_EXPRESSION "Working on --file from start: name"
+           "Working on --count from stop: 1, direct count: 1" "Count of --random flag: 1"
+           "Subcommand: start" "Subcommand: stop")
+
+add_cli_exe(subcom_partitioned subcom_partitioned.cpp)
+add_test(NAME subcom_partitioned_none COMMAND subcom_partitioned)
+set_property(
+  TEST subcom_partitioned_none
+  PROPERTY PASS_REGULAR_EXPRESSION "This is a timer:" "--file is required"
+           "Run with --help for more information.")
+
+add_test(NAME subcom_partitioned_all COMMAND subcom_partitioned --file this --count --count -d 1.2)
+set_property(
+  TEST subcom_partitioned_all
+  PROPERTY PASS_REGULAR_EXPRESSION "This is a timer:"
+           "Working on file: this, direct count: 1, opt count: 1"
+           "Working on count: 2, direct count: 2, opt count: 2" "Some value: 1.2")
+# test shows that the help prints out for unnamed subcommands
+add_test(NAME subcom_partitioned_help COMMAND subcom_partitioned --help)
+set_property(TEST subcom_partitioned_help PROPERTY PASS_REGULAR_EXPRESSION
+                                                   "-f,--file TEXT REQUIRED" "-d,--double FLOAT")
+
+####################################################
+add_cli_exe(config_app config_app.cpp)
+add_test(NAME config_app1 COMMAND config_app -p)
+set_property(TEST config_app1 PROPERTY PASS_REGULAR_EXPRESSION "file=")
+
+add_test(NAME config_app2 COMMAND config_app -p -f /)
+set_property(TEST config_app2 PROPERTY PASS_REGULAR_EXPRESSION "file=\"/\"")
+
+add_test(NAME config_app3 COMMAND config_app -f "" -p)
+set_property(TEST config_app3 PROPERTY PASS_REGULAR_EXPRESSION "file=\"\"")
+
+add_test(NAME config_app4 COMMAND config_app -f "/" -p)
+set_property(TEST config_app4 PROPERTY PASS_REGULAR_EXPRESSION "file=\"/\"")
+
+####################################################
+
+add_cli_exe(option_groups option_groups.cpp)
+add_test(NAME option_groups_missing COMMAND option_groups)
+set_property(TEST option_groups_missing PROPERTY PASS_REGULAR_EXPRESSION "Exactly 1 option from"
+                                                 "is required")
+add_test(NAME option_groups_extra COMMAND option_groups --csv --binary)
+set_property(TEST option_groups_extra PROPERTY PASS_REGULAR_EXPRESSION "and 2 were given")
+add_test(NAME option_groups_extra2 COMMAND option_groups --csv --address "192.168.1.1" -o
+                                           "test.out")
+set_property(TEST option_groups_extra2 PROPERTY PASS_REGULAR_EXPRESSION "at most 1")
+
+add_cli_exe(positional_arity positional_arity.cpp)
+add_test(NAME positional_arity1 COMMAND positional_arity one)
+set_property(TEST positional_arity1 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one")
+add_test(NAME positional_arity2 COMMAND positional_arity one two)
+set_property(TEST positional_arity2 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one" "File 2 = two")
+add_test(NAME positional_arity3 COMMAND positional_arity 1 2 one)
+set_property(TEST positional_arity3 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one")
+add_test(NAME positional_arity_fail COMMAND positional_arity 1 one two)
+set_property(TEST positional_arity_fail PROPERTY PASS_REGULAR_EXPRESSION "Could not convert")
+
+add_cli_exe(positional_validation positional_validation.cpp)
+add_test(NAME positional_validation1 COMMAND positional_validation one)
+set_property(TEST positional_validation1 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one")
+add_test(NAME positional_validation2 COMMAND positional_validation one 1 2 two)
+set_property(TEST positional_validation2 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one"
+                                                  "File 2 = two")
+add_test(NAME positional_validation3 COMMAND positional_validation 1 2 one)
+set_property(TEST positional_validation3 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one")
+add_test(NAME positional_validation4 COMMAND positional_validation 1 one two 2)
+set_property(TEST positional_validation4 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one"
+                                                  "File 2 = two")
+
+add_cli_exe(shapes shapes.cpp)
+add_test(NAME shapes_all COMMAND shapes circle 4.4 circle 10.7 rectangle 4 4 circle 2.3 triangle
+                                 4.5 ++ rectangle 2.1 ++ circle 234.675)
+set_property(
+  TEST shapes_all PROPERTY PASS_REGULAR_EXPRESSION "circle2" "circle4"
+                           "rectangle2 with edges [2.1,2.1]" "triangel1 with sides [4.5]")
+
+add_cli_exe(ranges ranges.cpp)
+add_test(NAME ranges_range COMMAND ranges --range 1 2 3)
+set_property(TEST ranges_range PROPERTY PASS_REGULAR_EXPRESSION "[2:1:3]")
+add_test(NAME ranges_minmax COMMAND ranges --min 2 --max 3)
+set_property(TEST ranges_minmax PROPERTY PASS_REGULAR_EXPRESSION "[2:1:3]")
+add_test(NAME ranges_error COMMAND ranges --min 2 --max 3 --step 1 --range 1 2 3)
+set_property(TEST ranges_error PROPERTY PASS_REGULAR_EXPRESSION "Exactly 1 option from")
+
+add_cli_exe(validators validators.cpp)
+add_test(NAME validators_help COMMAND validators --help)
+set_property(
+  TEST validators_help
+  PROPERTY PASS_REGULAR_EXPRESSION "  -f,--file TEXT:FILE[\\r\\n\\t ]+File name"
+           "  -v,--value INT:INT in [3 - 6][\\r\\n\\t ]+Value in range")
+add_test(NAME validators_file COMMAND validators --file nonex.xxx)
+set_property(
+  TEST validators_file PROPERTY PASS_REGULAR_EXPRESSION "--file: File does not exist: nonex.xxx"
+                                "Run with --help for more information.")
+add_test(NAME validators_plain COMMAND validators --value 9)
+set_property(
+  TEST validators_plain PROPERTY PASS_REGULAR_EXPRESSION "--value: Value 9 not in range 3 to 6"
+                                 "Run with --help for more information.")
+
+add_cli_exe(groups groups.cpp)
+add_test(NAME groups_none COMMAND groups)
+set_property(
+  TEST groups_none PROPERTY PASS_REGULAR_EXPRESSION "This is a timer:" "--file is required"
+                            "Run with --help for more information.")
+add_test(NAME groups_all COMMAND groups --file this --count --count -d 1.2)
+set_property(
+  TEST groups_all
+  PROPERTY PASS_REGULAR_EXPRESSION "This is a timer:"
+           "Working on file: this, direct count: 1, opt count: 1"
+           "Working on count: 2, direct count: 2, opt count: 2" "Some value: 1.2")
+
+add_cli_exe(inter_argument_order inter_argument_order.cpp)
+add_test(NAME inter_argument_order COMMAND inter_argument_order --foo 1 2 3 --x --bar 4 5 6 --z
+                                           --foo 7 8)
+set_property(
+  TEST inter_argument_order
+  PROPERTY
+    PASS_REGULAR_EXPRESSION
+    [=[foo : 1
+foo : 2
+foo : 3
+bar : 4
+bar : 5
+bar : 6
+foo : 7
+foo : 8]=])
+
+add_cli_exe(prefix_command prefix_command.cpp)
+add_test(NAME prefix_command COMMAND prefix_command -v 3 2 1 -- other one two 3)
+set_property(TEST prefix_command PROPERTY PASS_REGULAR_EXPRESSION "Prefix: 3 : 2 : 1"
+                                          "Remaining commands: other one two 3")
+
+add_cli_exe(callback_passthrough callback_passthrough.cpp)
+add_test(NAME callback_passthrough1 COMMAND callback_passthrough --argname t2 --t2 test)
+set_property(TEST callback_passthrough1 PROPERTY PASS_REGULAR_EXPRESSION "the value is now test")
+add_test(NAME callback_passthrough2 COMMAND callback_passthrough --arg EEEK --argname arg)
+set_property(TEST callback_passthrough2 PROPERTY PASS_REGULAR_EXPRESSION "the value is now EEEK")
+
+add_cli_exe(enum enum.cpp)
+add_test(NAME enum_pass COMMAND enum -l 1)
+add_test(NAME enum_fail COMMAND enum -l 4)
+set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION "--level: Check 4 value in {"
+                                     "FAILED")
+
+add_cli_exe(enum_ostream enum_ostream.cpp)
+add_test(NAME enum_ostream_pass COMMAND enum_ostream --level medium)
+set_property(TEST enum_ostream_pass PROPERTY PASS_REGULAR_EXPRESSION "Enum received: Medium")
+
+add_cli_exe(digit_args digit_args.cpp)
+add_test(NAME digit_args COMMAND digit_args -h)
+set_property(TEST digit_args PROPERTY PASS_REGULAR_EXPRESSION "-3{3}")
+
+add_cli_exe(modhelp modhelp.cpp)
+add_test(NAME modhelp COMMAND modhelp -a test -h)
+set_property(TEST modhelp PROPERTY PASS_REGULAR_EXPRESSION "Option -a string in help: test")
+
+add_subdirectory(subcom_in_files)
+add_test(NAME subcom_in_files COMMAND subcommand_main subcommand_a -f this.txt --with-foo)
+set_property(TEST subcom_in_files PROPERTY PASS_REGULAR_EXPRESSION "Working on file: this\.txt"
+                                           "Using foo!")
+
+add_cli_exe(formatter formatter.cpp)
+
+add_cli_exe(nested nested.cpp)
+
+add_cli_exe(subcom_help subcom_help.cpp)
+add_test(NAME subcom_help_normal COMMAND subcom_help sub --help)
+add_test(NAME subcom_help_reversed COMMAND subcom_help --help sub)
+
+add_cli_exe(retired retired.cpp)
+add_test(NAME retired_retired_test COMMAND retired --retired_option)
+add_test(NAME retired_retired_test2 COMMAND retired --retired_option 567)
+add_test(NAME retired_retired_test3 COMMAND retired --retired_option2 567 689 789)
+add_test(NAME retired_deprecated COMMAND retired --deprecate 19 20)
+
+set_property(TEST retired_retired_test PROPERTY PASS_REGULAR_EXPRESSION "WARNING.*retired")
+
+set_property(TEST retired_retired_test2 PROPERTY PASS_REGULAR_EXPRESSION "WARNING.*retired")
+
+set_property(TEST retired_retired_test3 PROPERTY PASS_REGULAR_EXPRESSION "WARNING.*retired")
+
+set_property(TEST retired_deprecated PROPERTY PASS_REGULAR_EXPRESSION "deprecated.*not_deprecated")
+
+#--------------------------------------------
+add_cli_exe(custom_parse custom_parse.cpp)
+add_test(NAME cp_test COMMAND custom_parse --dv 1.7)
+set_property(TEST cp_test PROPERTY PASS_REGULAR_EXPRESSION "called correct")
diff --git a/external/CLI11/examples/callback_passthrough.cpp b/external/CLI11/examples/callback_passthrough.cpp
new file mode 100644
index 0000000..48a2487
--- /dev/null
+++ b/external/CLI11/examples/callback_passthrough.cpp
@@ -0,0 +1,28 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+
+int main(int argc, char **argv) {
+
+    CLI::App app("callback_passthrough");
+    app.allow_extras();
+    std::string argName;
+    std::string val;
+    app.add_option("--argname", argName, "the name of the custom command line argument");
+    app.callback([&app, &val, &argName]() {
+        if(!argName.empty()) {
+            CLI::App subApp;
+            subApp.add_option("--" + argName, val, "custom argument option");
+            subApp.parse(app.remaining_for_passthrough());
+        }
+    });
+
+    CLI11_PARSE(app, argc, argv);
+    std::cout << "the value is now " << val << '\n';
+}
diff --git a/external/CLI11/examples/config_app.cpp b/external/CLI11/examples/config_app.cpp
new file mode 100644
index 0000000..aec9fef
--- /dev/null
+++ b/external/CLI11/examples/config_app.cpp
@@ -0,0 +1,50 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+
+int main(int argc, char **argv) {
+
+    CLI::App app("configuration print example");
+
+    app.add_flag("-p,--print", "Print configuration and exit")->configurable(false);  // NEW: print flag
+
+    std::string file;
+    CLI::Option *opt = app.add_option("-f,--file,file", file, "File name")
+                           ->capture_default_str()
+                           ->run_callback_for_default();  // NEW: capture_default_str()
+
+    int count{0};
+    CLI::Option *copt =
+        app.add_option("-c,--count", count, "Counter")->capture_default_str();  // NEW: capture_default_str()
+
+    int v{0};
+    CLI::Option *flag = app.add_flag("--flag", v, "Some flag that can be passed multiple times")
+                            ->capture_default_str();  // NEW: capture_default_str()
+
+    double value{0.0};                                                          // = 3.14;
+    app.add_option("-d,--double", value, "Some Value")->capture_default_str();  // NEW: capture_default_str()
+
+    app.get_config_formatter_base()->quoteCharacter('"', '"');
+
+    CLI11_PARSE(app, argc, argv);
+
+    if(app.get_option("--print")->as<bool>()) {  // NEW: print configuration and exit
+        std::cout << app.config_to_str(true, false);
+        return 0;
+    }
+
+    std::cout << "Working on file: " << file << ", direct count: " << app.count("--file")
+              << ", opt count: " << opt->count() << std::endl;
+    std::cout << "Working on count: " << count << ", direct count: " << app.count("--count")
+              << ", opt count: " << copt->count() << std::endl;
+    std::cout << "Received flag: " << v << " (" << flag->count() << ") times\n";
+    std::cout << "Some value: " << value << std::endl;
+
+    return 0;
+}
diff --git a/external/CLI11/examples/custom_parse.cpp b/external/CLI11/examples/custom_parse.cpp
new file mode 100644
index 0000000..44fc77a
--- /dev/null
+++ b/external/CLI11/examples/custom_parse.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+// from Issue #566 on github https://github.com/CLIUtils/CLI11/issues/566
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <sstream>
+
+// example file to demonstrate a custom lexical cast function
+
+template <class T = int> struct Values {
+    T a;
+    T b;
+    T c;
+};
+
+// in C++20 this is constructible from a double due to the new aggregate initialization in C++20.
+using DoubleValues = Values<double>;
+
+// the lexical cast operator should be in the same namespace as the type for ADL to work properly
+bool lexical_cast(const std::string &input, Values<double> &v) {
+    std::cout << "called correct lexical_cast function ! val: " << input << std::endl;
+    return true;
+}
+
+DoubleValues doubles;
+void argparse(CLI::Option_group *group) { group->add_option("--dv", doubles)->default_str("0"); }
+
+int main(int argc, char **argv) {
+    CLI::App app;
+
+    argparse(app.add_option_group("param"));
+    CLI11_PARSE(app, argc, argv);
+    return 0;
+}
diff --git a/external/CLI11/examples/digit_args.cpp b/external/CLI11/examples/digit_args.cpp
new file mode 100644
index 0000000..af0891a
--- /dev/null
+++ b/external/CLI11/examples/digit_args.cpp
@@ -0,0 +1,21 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+
+int main(int argc, char **argv) {
+    CLI::App app;
+
+    int val{0};
+    // add a set of flags with default values associate with them
+    app.add_flag("-1{1},-2{2},-3{3},-4{4},-5{5},-6{6}, -7{7}, -8{8}, -9{9}", val, "compression level");
+
+    CLI11_PARSE(app, argc, argv);
+
+    std::cout << "value = " << val << std::endl;
+    return 0;
+}
diff --git a/external/CLI11/examples/enum.cpp b/external/CLI11/examples/enum.cpp
new file mode 100644
index 0000000..09b7f04
--- /dev/null
+++ b/external/CLI11/examples/enum.cpp
@@ -0,0 +1,33 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <map>
+#include <string>
+
+enum class Level : int { High, Medium, Low };
+
+int main(int argc, char **argv) {
+    CLI::App app;
+
+    Level level{Level::Low};
+    // specify string->value mappings
+    std::map<std::string, Level> map{{"high", Level::High}, {"medium", Level::Medium}, {"low", Level::Low}};
+    // CheckedTransformer translates and checks whether the results are either in one of the strings or in one of the
+    // translations already
+    app.add_option("-l,--level", level, "Level settings")
+        ->required()
+        ->transform(CLI::CheckedTransformer(map, CLI::ignore_case));
+
+    CLI11_PARSE(app, argc, argv);
+
+    // CLI11's built in enum streaming can be used outside CLI11 like this:
+    using CLI::enums::operator<<;
+    std::cout << "Enum received: " << level << std::endl;
+
+    return 0;
+}
diff --git a/external/CLI11/examples/enum_ostream.cpp b/external/CLI11/examples/enum_ostream.cpp
new file mode 100644
index 0000000..4e3e6b2
--- /dev/null
+++ b/external/CLI11/examples/enum_ostream.cpp
@@ -0,0 +1,50 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <map>
+#include <string>
+
+enum class Level : int { High, Medium, Low };
+
+// Defining operator<<() for your enum class (in this case for 'Level') overrides CLI11's enum streaming
+inline std::ostream &operator<<(std::ostream &os, const Level &level) {
+    switch(level) {
+    case Level::High:
+        os << "High";
+        break;
+    case Level::Medium:
+        os << "Medium";
+        break;
+    case Level::Low:
+        os << "Low";
+        break;
+    }
+    os << " (ft rom custom ostream)";
+    return os;
+}
+
+int main(int argc, char **argv) {
+    CLI::App app;
+
+    Level level{Level::Low};
+    // specify string->value mappings
+    std::map<std::string, Level> map{{"high", Level::High}, {"medium", Level::Medium}, {"low", Level::Low}};
+    // CheckedTransformer translates and checks whether the results are either in one of the strings or in one of the
+    // translations already
+    app.add_option("-l,--level", level, "Level settings")
+        ->required()
+        ->transform(CLI::CheckedTransformer(map, CLI::ignore_case));
+
+    CLI11_PARSE(app, argc, argv);
+
+    // CLI11's built in enum streaming can be used outside CLI11 like this:
+    using CLI::enums::operator<<;
+    std::cout << "Enum received: " << level << std::endl;
+
+    return 0;
+}
diff --git a/external/CLI11/examples/formatter.cpp b/external/CLI11/examples/formatter.cpp
new file mode 100644
index 0000000..3b2e3dc
--- /dev/null
+++ b/external/CLI11/examples/formatter.cpp
@@ -0,0 +1,37 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <memory>
+
+class MyFormatter : public CLI::Formatter {
+  public:
+    MyFormatter() : Formatter() {}
+    std::string make_option_opts(const CLI::Option *) const override { return " OPTION"; }
+};
+
+int main(int argc, char **argv) {
+    CLI::App app;
+    app.set_help_all_flag("--help-all", "Show all help");
+
+    auto fmt = std::make_shared<MyFormatter>();
+    fmt->column_width(15);
+    app.formatter(fmt);
+
+    app.add_flag("--flag", "This is a flag");
+
+    auto sub1 = app.add_subcommand("one", "Description One");
+    sub1->add_flag("--oneflag", "Some flag");
+    auto sub2 = app.add_subcommand("two", "Description Two");
+    sub2->add_flag("--twoflag", "Some other flag");
+
+    CLI11_PARSE(app, argc, argv);
+
+    std::cout << "This app was meant to show off the formatter, run with -h" << std::endl;
+
+    return 0;
+}
diff --git a/external/CLI11/examples/groups.cpp b/external/CLI11/examples/groups.cpp
new file mode 100644
index 0000000..c766d6b
--- /dev/null
+++ b/external/CLI11/examples/groups.cpp
@@ -0,0 +1,39 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <CLI/Timer.hpp>
+#include <iostream>
+#include <string>
+
+int main(int argc, char **argv) {
+    CLI::AutoTimer("This is a timer");
+
+    CLI::App app("K3Pi goofit fitter");
+
+    std::string file;
+    CLI::Option *opt = app.add_option("-f,--file,file", file, "File name")->required()->group("Important");
+
+    int count{0};
+    CLI::Option *copt = app.add_flag("-c,--count", count, "Counter")->required()->group("Important");
+
+    double value{0.0};  // = 3.14;
+    app.add_option("-d,--double", value, "Some Value")->group("Other");
+
+    try {
+        app.parse(argc, argv);
+    } catch(const CLI::ParseError &e) {
+        return app.exit(e);
+    }
+
+    std::cout << "Working on file: " << file << ", direct count: " << app.count("--file")
+              << ", opt count: " << opt->count() << std::endl;
+    std::cout << "Working on count: " << count << ", direct count: " << app.count("--count")
+              << ", opt count: " << copt->count() << std::endl;
+    std::cout << "Some value: " << value << std::endl;
+
+    return 0;
+}
diff --git a/external/CLI11/examples/inter_argument_order.cpp b/external/CLI11/examples/inter_argument_order.cpp
new file mode 100644
index 0000000..ebee397
--- /dev/null
+++ b/external/CLI11/examples/inter_argument_order.cpp
@@ -0,0 +1,51 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <algorithm>
+#include <iostream>
+#include <tuple>
+#include <vector>
+
+int main(int argc, char **argv) {
+    CLI::App app{"An app to practice mixing unlimited arguments, but still recover the original order."};
+
+    std::vector<int> foos;
+    auto foo = app.add_option("--foo,-f", foos, "Some unlimited argument");
+
+    std::vector<int> bars;
+    auto bar = app.add_option("--bar", bars, "Some unlimited argument");
+
+    app.add_flag("--z,--x", "Random other flags");
+
+    // Standard parsing lines (copy and paste in, or use CLI11_PARSE)
+    try {
+        app.parse(argc, argv);
+    } catch(const CLI::ParseError &e) {
+        return app.exit(e);
+    }
+
+    // I prefer using the back and popping
+    std::reverse(std::begin(foos), std::end(foos));
+    std::reverse(std::begin(bars), std::end(bars));
+
+    std::vector<std::pair<std::string, int>> keyval;
+    for(auto option : app.parse_order()) {
+        if(option == foo) {
+            keyval.emplace_back("foo", foos.back());
+            foos.pop_back();
+        }
+        if(option == bar) {
+            keyval.emplace_back("bar", bars.back());
+            bars.pop_back();
+        }
+    }
+
+    // Prove the vector is correct
+    for(auto &pair : keyval) {
+        std::cout << pair.first << " : " << pair.second << std::endl;
+    }
+}
diff --git a/external/CLI11/examples/json.cpp b/external/CLI11/examples/json.cpp
new file mode 100644
index 0000000..ae2595c
--- /dev/null
+++ b/external/CLI11/examples/json.cpp
@@ -0,0 +1,123 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <memory>
+#include <nlohmann/json.hpp>
+#include <string>
+#include <vector>
+
+// This example is only built on GCC 7 on Travis due to mismatch in stdlib
+// for clang (CLI11 is forgiving about mismatches, json.hpp is not)
+
+using nlohmann::json;
+
+class ConfigJSON : public CLI::Config {
+  public:
+    std::string to_config(const CLI::App *app, bool default_also, bool, std::string) const override {
+
+        json j;
+
+        for(const CLI::Option *opt : app->get_options({})) {
+
+            // Only process option with a long-name and configurable
+            if(!opt->get_lnames().empty() && opt->get_configurable()) {
+                std::string name = opt->get_lnames()[0];
+
+                // Non-flags
+                if(opt->get_type_size() != 0) {
+
+                    // If the option was found on command line
+                    if(opt->count() == 1)
+                        j[name] = opt->results().at(0);
+                    else if(opt->count() > 1)
+                        j[name] = opt->results();
+
+                    // If the option has a default and is requested by optional argument
+                    else if(default_also && !opt->get_default_str().empty())
+                        j[name] = opt->get_default_str();
+
+                    // Flag, one passed
+                } else if(opt->count() == 1) {
+                    j[name] = true;
+
+                    // Flag, multiple passed
+                } else if(opt->count() > 1) {
+                    j[name] = opt->count();
+
+                    // Flag, not present
+                } else if(opt->count() == 0 && default_also) {
+                    j[name] = false;
+                }
+            }
+        }
+
+        for(const CLI::App *subcom : app->get_subcommands({}))
+            j[subcom->get_name()] = json(to_config(subcom, default_also, false, ""));
+
+        return j.dump(4);
+    }
+
+    std::vector<CLI::ConfigItem> from_config(std::istream &input) const override {
+        json j;
+        input >> j;
+        return _from_config(j);
+    }
+
+    std::vector<CLI::ConfigItem>
+    _from_config(json j, std::string name = "", std::vector<std::string> prefix = {}) const {
+        std::vector<CLI::ConfigItem> results;
+
+        if(j.is_object()) {
+            for(json::iterator item = j.begin(); item != j.end(); ++item) {
+                auto copy_prefix = prefix;
+                if(!name.empty())
+                    copy_prefix.push_back(name);
+                auto sub_results = _from_config(*item, item.key(), copy_prefix);
+                results.insert(results.end(), sub_results.begin(), sub_results.end());
+            }
+        } else if(!name.empty()) {
+            results.emplace_back();
+            CLI::ConfigItem &res = results.back();
+            res.name = name;
+            res.parents = prefix;
+            if(j.is_boolean()) {
+                res.inputs = {j.get<bool>() ? "true" : "false"};
+            } else if(j.is_number()) {
+                std::stringstream ss;
+                ss << j.get<double>();
+                res.inputs = {ss.str()};
+            } else if(j.is_string()) {
+                res.inputs = {j.get<std::string>()};
+            } else if(j.is_array()) {
+                for(std::string ival : j)
+                    res.inputs.push_back(ival);
+            } else {
+                throw CLI::ConversionError("Failed to convert " + name);
+            }
+        } else {
+            throw CLI::ConversionError("You must make all top level values objects in json!");
+        }
+
+        return results;
+    }
+};
+
+int main(int argc, char **argv) {
+    CLI::App app;
+    app.config_formatter(std::make_shared<ConfigJSON>());
+
+    int item;
+
+    app.add_flag("--simple");
+    app.add_option("--item", item);
+    app.set_config("--config");
+
+    CLI11_PARSE(app, argc, argv);
+
+    std::cout << app.config_to_str(true, true) << std::endl;
+}
diff --git a/external/CLI11/examples/modhelp.cpp b/external/CLI11/examples/modhelp.cpp
new file mode 100644
index 0000000..9c67430
--- /dev/null
+++ b/external/CLI11/examples/modhelp.cpp
@@ -0,0 +1,35 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+
+int main(int argc, char **argv) {
+    CLI::App test{R"raw(Modify the help print so that argument values are accessible.
+Note that this will not shortcut `->required` and other similar options.)raw"};
+
+    // Remove help flag because it shortcuts all processing
+    test.set_help_flag();
+
+    // Add custom flag that activates help
+    auto help = test.add_flag("-h,--help", "Request help");
+
+    std::string some_option;
+    test.add_option("-a", some_option, "Some description");
+
+    try {
+        test.parse(argc, argv);
+        if(*help)
+            throw CLI::CallForHelp();
+    } catch(const CLI::Error &e) {
+        std::cout << "Option -a string in help: " << some_option << std::endl;
+        return test.exit(e);
+    }
+
+    std::cout << "Option -a string: " << some_option << std::endl;
+    return 0;
+}
diff --git a/external/CLI11/examples/nested.cpp b/external/CLI11/examples/nested.cpp
new file mode 100644
index 0000000..dc8323f
--- /dev/null
+++ b/external/CLI11/examples/nested.cpp
@@ -0,0 +1,30 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <string>
+
+int main(int argc, char **argv) {
+
+    CLI::App app("Vision Application");
+    app.set_help_all_flag("--help-all", "Expand all help");
+    app.add_flag("--version", "Get version");
+
+    CLI::App *cameraApp = app.add_subcommand("camera", "Configure the app camera");
+    cameraApp->require_subcommand(0, 1);  // 0 (default) or 1 camera
+
+    std::string mvcamera_config_file = "mvcamera_config.json";
+    CLI::App *mvcameraApp = cameraApp->add_subcommand("mvcamera", "MatrixVision Camera Configuration");
+    mvcameraApp->add_option("-c,--config", mvcamera_config_file, "Config filename")
+        ->capture_default_str()
+        ->check(CLI::ExistingFile);
+
+    std::string mock_camera_path;
+    CLI::App *mockcameraApp = cameraApp->add_subcommand("mock", "Mock Camera Configuration");
+    mockcameraApp->add_option("-p,--path", mock_camera_path, "Path")->required()->check(CLI::ExistingPath);
+
+    CLI11_PARSE(app, argc, argv);
+}
diff --git a/external/CLI11/examples/option_groups.cpp b/external/CLI11/examples/option_groups.cpp
new file mode 100644
index 0000000..acc6574
--- /dev/null
+++ b/external/CLI11/examples/option_groups.cpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+
+int main(int argc, char **argv) {
+
+    CLI::App app("data output specification");
+    app.set_help_all_flag("--help-all", "Expand all help");
+
+    auto format = app.add_option_group("output_format", "formatting type for output");
+    auto target = app.add_option_group("output target", "target location for the output");
+    bool csv{false};
+    bool human{false};
+    bool binary{false};
+    format->add_flag("--csv", csv, "specify the output in csv format");
+    format->add_flag("--human", human, "specify the output in human readable text format");
+    format->add_flag("--binary", binary, "specify the output in binary format");
+    // require one of the options to be selected
+    format->require_option(1);
+    std::string fileLoc;
+    std::string networkAddress;
+    target->add_option("-o,--file", fileLoc, "specify the file location of the output");
+    target->add_option("--address", networkAddress, "specify a network address to send the file");
+
+    // require at most one of the target options
+    target->require_option(0, 1);
+    CLI11_PARSE(app, argc, argv);
+
+    std::string format_type = (csv) ? std::string("CSV") : ((human) ? "human readable" : "binary");
+    std::cout << "Selected  " << format_type << "format" << std::endl;
+    if(fileLoc.empty()) {
+        std::cout << " sent to file " << fileLoc << std::endl;
+    } else if(networkAddress.empty()) {
+        std::cout << " sent over network to " << networkAddress << std::endl;
+    } else {
+        std::cout << " sent to std::cout" << std::endl;
+    }
+
+    return 0;
+}
diff --git a/external/CLI11/examples/positional_arity.cpp b/external/CLI11/examples/positional_arity.cpp
new file mode 100644
index 0000000..e9238fc
--- /dev/null
+++ b/external/CLI11/examples/positional_arity.cpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+
+int main(int argc, char **argv) {
+
+    CLI::App app("test for positional arity");
+
+    auto numbers = app.add_option_group("numbers", "specify key numbers");
+    auto files = app.add_option_group("files", "specify files");
+    int num1{-1}, num2{-1};
+    numbers->add_option("num1", num1, "first number");
+    numbers->add_option("num2", num2, "second number");
+    std::string file1, file2;
+    files->add_option("file1", file1, "first file")->required();
+    files->add_option("file2", file2, "second file");
+    // set a pre parse callback that turns the numbers group on or off depending on the number of arguments
+    app.preparse_callback([numbers](std::size_t arity) {
+        if(arity <= 2) {
+            numbers->disabled();
+        } else {
+            numbers->disabled(false);
+        }
+    });
+
+    CLI11_PARSE(app, argc, argv);
+
+    if(num1 != -1)
+        std::cout << "Num1 = " << num1 << '\n';
+
+    if(num2 != -1)
+        std::cout << "Num2 = " << num2 << '\n';
+
+    std::cout << "File 1 = " << file1 << '\n';
+    if(!file2.empty()) {
+        std::cout << "File 2 = " << file2 << '\n';
+    }
+
+    return 0;
+}
diff --git a/external/CLI11/examples/positional_validation.cpp b/external/CLI11/examples/positional_validation.cpp
new file mode 100644
index 0000000..afc03fa
--- /dev/null
+++ b/external/CLI11/examples/positional_validation.cpp
@@ -0,0 +1,37 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+
+int main(int argc, char **argv) {
+
+    CLI::App app("test for positional validation");
+
+    int num1{-1}, num2{-1};
+    app.add_option("num1", num1, "first number")->check(CLI::Number);
+    app.add_option("num2", num2, "second number")->check(CLI::Number);
+    std::string file1, file2;
+    app.add_option("file1", file1, "first file")->required();
+    app.add_option("file2", file2, "second file");
+    app.validate_positionals();
+
+    CLI11_PARSE(app, argc, argv);
+
+    if(num1 != -1)
+        std::cout << "Num1 = " << num1 << '\n';
+
+    if(num2 != -1)
+        std::cout << "Num2 = " << num2 << '\n';
+
+    std::cout << "File 1 = " << file1 << '\n';
+    if(!file2.empty()) {
+        std::cout << "File 2 = " << file2 << '\n';
+    }
+
+    return 0;
+}
diff --git a/external/CLI11/examples/prefix_command.cpp b/external/CLI11/examples/prefix_command.cpp
new file mode 100644
index 0000000..2af1a87
--- /dev/null
+++ b/external/CLI11/examples/prefix_command.cpp
@@ -0,0 +1,35 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+#include <vector>
+
+int main(int argc, char **argv) {
+
+    CLI::App app("Prefix command app");
+    app.prefix_command();
+
+    std::vector<int> vals;
+    app.add_option("--vals,-v", vals)->expected(-1);
+
+    CLI11_PARSE(app, argc, argv);
+
+    std::vector<std::string> more_comms = app.remaining();
+
+    std::cout << "Prefix";
+    for(int v : vals)
+        std::cout << ": " << v << " ";
+
+    std::cout << std::endl << "Remaining commands: ";
+
+    for(auto com : more_comms)
+        std::cout << com << " ";
+    std::cout << std::endl;
+
+    return 0;
+}
diff --git a/external/CLI11/examples/ranges.cpp b/external/CLI11/examples/ranges.cpp
new file mode 100644
index 0000000..6b4c1e3
--- /dev/null
+++ b/external/CLI11/examples/ranges.cpp
@@ -0,0 +1,41 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <vector>
+
+int main(int argc, char **argv) {
+
+    CLI::App app{"App to demonstrate exclusionary option groups."};
+
+    std::vector<int> range;
+    app.add_option("--range,-R", range, "A range")->expected(-2);
+
+    auto ogroup = app.add_option_group("min_max_step", "set the min max and step");
+    int min{0}, max{0}, step{1};
+    ogroup->add_option("--min,-m", min, "The minimum")->required();
+    ogroup->add_option("--max,-M", max, "The maximum")->required();
+    ogroup->add_option("--step,-s", step, "The step")->capture_default_str();
+
+    app.require_option(1);
+
+    CLI11_PARSE(app, argc, argv);
+
+    if(!range.empty()) {
+        if(range.size() == 2) {
+            min = range[0];
+            max = range[1];
+        }
+        if(range.size() >= 3) {
+            step = range[0];
+            min = range[1];
+            max = range[2];
+        }
+    }
+    std::cout << "range is [" << min << ':' << step << ':' << max << "]\n";
+    return 0;
+}
diff --git a/external/CLI11/examples/retired.cpp b/external/CLI11/examples/retired.cpp
new file mode 100644
index 0000000..3a18db4
--- /dev/null
+++ b/external/CLI11/examples/retired.cpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <utility>
+#include <vector>
+
+// This example shows the usage of the retired and deprecated option helper methods
+int main(int argc, char **argv) {
+
+    CLI::App app("example for retired/deprecated options");
+    std::vector<int> x;
+    auto opt1 = app.add_option("--retired_option2", x);
+
+    std::pair<int, int> y;
+    auto opt2 = app.add_option("--deprecate", y);
+
+    app.add_option("--not_deprecated", x);
+
+    // specify that a non-existing option is retired
+    CLI::retire_option(app, "--retired_option");
+
+    // specify that an existing option is retired and non-functional: this will replace the option with another that
+    // behaves the same but does nothing
+    CLI::retire_option(app, opt1);
+
+    // deprecate an existing option and specify the recommended replacement
+    CLI::deprecate_option(opt2, "--not_deprecated");
+
+    CLI11_PARSE(app, argc, argv);
+
+    if(!x.empty()) {
+        std::cout << "Retired option example: got --not_deprecated values:";
+        for(auto &xval : x) {
+            std::cout << xval << " ";
+        }
+        std::cout << '\n';
+    } else if(app.count_all() == 1) {
+        std::cout << "Retired option example: no arguments received\n";
+    }
+    return 0;
+}
diff --git a/external/CLI11/examples/shapes.cpp b/external/CLI11/examples/shapes.cpp
new file mode 100644
index 0000000..b318df0
--- /dev/null
+++ b/external/CLI11/examples/shapes.cpp
@@ -0,0 +1,56 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <vector>
+
+int main(int argc, char **argv) {
+
+    CLI::App app("load shapes");
+
+    app.set_help_all_flag("--help-all");
+    auto circle = app.add_subcommand("circle", "draw a circle")->immediate_callback();
+    double radius{0.0};
+    int circle_counter{0};
+    circle->callback([&radius, &circle_counter] {
+        ++circle_counter;
+        std::cout << "circle" << circle_counter << " with radius " << radius << std::endl;
+    });
+
+    circle->add_option("radius", radius, "the radius of the circle")->required();
+
+    auto rect = app.add_subcommand("rectangle", "draw a rectangle")->immediate_callback();
+    double edge1{0.0};
+    double edge2{0.0};
+    int rect_counter{0};
+    rect->callback([&edge1, &edge2, &rect_counter] {
+        ++rect_counter;
+        if(edge2 == 0) {
+            edge2 = edge1;
+        }
+        std::cout << "rectangle" << rect_counter << " with edges [" << edge1 << ',' << edge2 << "]" << std::endl;
+        edge2 = 0;
+    });
+
+    rect->add_option("edge1", edge1, "the first edge length of the rectangle")->required();
+    rect->add_option("edge2", edge2, "the second edge length of the rectangle");
+
+    auto tri = app.add_subcommand("triangle", "draw a rectangle")->immediate_callback();
+    std::vector<double> sides;
+    int tri_counter = 0;
+    tri->callback([&sides, &tri_counter] {
+        ++tri_counter;
+
+        std::cout << "triangle" << tri_counter << " with sides [" << CLI::detail::join(sides) << "]" << std::endl;
+    });
+
+    tri->add_option("sides", sides, "the side lengths of the triangle");
+
+    CLI11_PARSE(app, argc, argv);
+
+    return 0;
+}
diff --git a/external/CLI11/examples/simple.cpp b/external/CLI11/examples/simple.cpp
new file mode 100644
index 0000000..0f90442
--- /dev/null
+++ b/external/CLI11/examples/simple.cpp
@@ -0,0 +1,38 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+
+int main(int argc, char **argv) {
+
+    CLI::App app("K3Pi goofit fitter");
+    // add version output
+    app.set_version_flag("--version", std::string(CLI11_VERSION));
+    std::string file;
+    CLI::Option *opt = app.add_option("-f,--file,file", file, "File name");
+
+    int count{0};
+    CLI::Option *copt = app.add_option("-c,--count", count, "Counter");
+
+    int v{0};
+    CLI::Option *flag = app.add_flag("--flag", v, "Some flag that can be passed multiple times");
+
+    double value{0.0};  // = 3.14;
+    app.add_option("-d,--double", value, "Some Value");
+
+    CLI11_PARSE(app, argc, argv);
+
+    std::cout << "Working on file: " << file << ", direct count: " << app.count("--file")
+              << ", opt count: " << opt->count() << std::endl;
+    std::cout << "Working on count: " << count << ", direct count: " << app.count("--count")
+              << ", opt count: " << copt->count() << std::endl;
+    std::cout << "Received flag: " << v << " (" << flag->count() << ") times\n";
+    std::cout << "Some value: " << value << std::endl;
+
+    return 0;
+}
diff --git a/external/CLI11/examples/subcom_help.cpp b/external/CLI11/examples/subcom_help.cpp
new file mode 100644
index 0000000..89af131
--- /dev/null
+++ b/external/CLI11/examples/subcom_help.cpp
@@ -0,0 +1,21 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+
+int main(int argc, char *argv[]) {
+    CLI::App cli_global{"Demo app"};
+    auto &cli_sub = *cli_global.add_subcommand("sub", "Some subcommand");
+    std::string sub_arg;
+    cli_sub.add_option("sub_arg", sub_arg, "Argument for subcommand")->required();
+    CLI11_PARSE(cli_global, argc, argv);
+    if(cli_sub) {
+        std::cout << "Got: " << sub_arg << std::endl;
+    }
+    return 0;
+}
diff --git a/external/CLI11/examples/subcom_in_files/CMakeLists.txt b/external/CLI11/examples/subcom_in_files/CMakeLists.txt
new file mode 100644
index 0000000..74ba5dd
--- /dev/null
+++ b/external/CLI11/examples/subcom_in_files/CMakeLists.txt
@@ -0,0 +1 @@
+add_cli_exe(subcommand_main subcommand_main.cpp subcommand_a.cpp subcommand_a.hpp)
diff --git a/external/CLI11/examples/subcom_in_files/subcommand_a.cpp b/external/CLI11/examples/subcom_in_files/subcommand_a.cpp
new file mode 100644
index 0000000..6b22983
--- /dev/null
+++ b/external/CLI11/examples/subcom_in_files/subcommand_a.cpp
@@ -0,0 +1,37 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "subcommand_a.hpp"
+#include <iostream>
+#include <memory>
+
+/// Set up a subcommand and capture a shared_ptr to a struct that holds all its options.
+/// The variables of the struct are bound to the CLI options.
+/// We use a shared ptr so that the addresses of the variables remain for binding,
+/// You could return the shared pointer if you wanted to access the values in main.
+void setup_subcommand_a(CLI::App &app) {
+    // Create the option and subcommand objects.
+    auto opt = std::make_shared<SubcommandAOptions>();
+    auto sub = app.add_subcommand("subcommand_a", "performs subcommand a");
+
+    // Add options to sub, binding them to opt.
+    sub->add_option("-f,--file", opt->file, "File name")->required();
+    sub->add_flag("--with-foo", opt->with_foo, "Counter");
+
+    // Set the run function as callback to be called when this subcommand is issued.
+    sub->callback([opt]() { run_subcommand_a(*opt); });
+}
+
+/// The function that runs our code.
+/// This could also simply be in the callback lambda itself,
+/// but having a separate function is cleaner.
+void run_subcommand_a(SubcommandAOptions const &opt) {
+    // Do stuff...
+    std::cout << "Working on file: " << opt.file << std::endl;
+    if(opt.with_foo) {
+        std::cout << "Using foo!" << std::endl;
+    }
+}
diff --git a/external/CLI11/examples/subcom_in_files/subcommand_a.hpp b/external/CLI11/examples/subcom_in_files/subcommand_a.hpp
new file mode 100644
index 0000000..116160c
--- /dev/null
+++ b/external/CLI11/examples/subcom_in_files/subcommand_a.hpp
@@ -0,0 +1,23 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+#include <CLI/CLI.hpp>
+#include <string>
+
+/// Collection of all options of Subcommand A.
+struct SubcommandAOptions {
+    std::string file;
+    bool with_foo;
+};
+
+// We could manually make a few variables and use shared pointers for each; this
+// is just done this way to be nicely organized
+
+// Function declarations.
+void setup_subcommand_a(CLI::App &app);
+void run_subcommand_a(SubcommandAOptions const &opt);
diff --git a/external/CLI11/examples/subcom_in_files/subcommand_main.cpp b/external/CLI11/examples/subcom_in_files/subcommand_main.cpp
new file mode 100644
index 0000000..62b6380
--- /dev/null
+++ b/external/CLI11/examples/subcom_in_files/subcommand_main.cpp
@@ -0,0 +1,26 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "subcommand_a.hpp"
+#include <CLI/CLI.hpp>
+
+int main(int argc, char **argv) {
+    CLI::App app{"..."};
+
+    // Call the setup functions for the subcommands.
+    // They are kept alive by a shared pointer in the
+    // lambda function held by CLI11
+    setup_subcommand_a(app);
+
+    // Make sure we get at least one subcommand
+    app.require_subcommand();
+
+    // More setup if needed, i.e., other subcommands etc.
+
+    CLI11_PARSE(app, argc, argv);
+
+    return 0;
+}
diff --git a/external/CLI11/examples/subcom_partitioned.cpp b/external/CLI11/examples/subcom_partitioned.cpp
new file mode 100644
index 0000000..df48e2d
--- /dev/null
+++ b/external/CLI11/examples/subcom_partitioned.cpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <CLI/Timer.hpp>
+#include <iostream>
+#include <memory>
+#include <string>
+
+int main(int argc, char **argv) {
+    CLI::AutoTimer("This is a timer");
+
+    CLI::App app("K3Pi goofit fitter");
+
+    CLI::App_p impOpt = std::make_shared<CLI::App>("Important");
+    std::string file;
+    CLI::Option *opt = impOpt->add_option("-f,--file,file", file, "File name")->required();
+
+    int count{0};
+    CLI::Option *copt = impOpt->add_flag("-c,--count", count, "Counter")->required();
+
+    CLI::App_p otherOpt = std::make_shared<CLI::App>("Other");
+    double value{0.0};  // = 3.14;
+    otherOpt->add_option("-d,--double", value, "Some Value");
+
+    // add the subapps to the main one
+    app.add_subcommand(impOpt);
+    app.add_subcommand(otherOpt);
+
+    try {
+        app.parse(argc, argv);
+    } catch(const CLI::ParseError &e) {
+        return app.exit(e);
+    }
+
+    std::cout << "Working on file: " << file << ", direct count: " << impOpt->count("--file")
+              << ", opt count: " << opt->count() << std::endl;
+    std::cout << "Working on count: " << count << ", direct count: " << impOpt->count("--count")
+              << ", opt count: " << copt->count() << std::endl;
+    std::cout << "Some value: " << value << std::endl;
+
+    return 0;
+}
diff --git a/external/CLI11/examples/subcommands.cpp b/external/CLI11/examples/subcommands.cpp
new file mode 100644
index 0000000..68f163a
--- /dev/null
+++ b/external/CLI11/examples/subcommands.cpp
@@ -0,0 +1,35 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+
+int main(int argc, char **argv) {
+
+    CLI::App app("K3Pi goofit fitter");
+    app.set_help_all_flag("--help-all", "Expand all help");
+    app.add_flag("--random", "Some random flag");
+    CLI::App *start = app.add_subcommand("start", "A great subcommand");
+    CLI::App *stop = app.add_subcommand("stop", "Do you really want to stop?");
+    app.require_subcommand();  // 1 or more
+
+    std::string file;
+    start->add_option("-f,--file", file, "File name");
+
+    CLI::Option *s = stop->add_flag("-c,--count", "Counter");
+
+    CLI11_PARSE(app, argc, argv);
+
+    std::cout << "Working on --file from start: " << file << std::endl;
+    std::cout << "Working on --count from stop: " << s->count() << ", direct count: " << stop->count("--count")
+              << std::endl;
+    std::cout << "Count of --random flag: " << app.count("--random") << std::endl;
+    for(auto subcom : app.get_subcommands())
+        std::cout << "Subcommand: " << subcom->get_name() << std::endl;
+
+    return 0;
+}
diff --git a/external/CLI11/examples/validators.cpp b/external/CLI11/examples/validators.cpp
new file mode 100644
index 0000000..6ca8d38
--- /dev/null
+++ b/external/CLI11/examples/validators.cpp
@@ -0,0 +1,25 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+#include <iostream>
+#include <string>
+
+int main(int argc, char **argv) {
+
+    CLI::App app("Validator checker");
+
+    std::string file;
+    app.add_option("-f,--file,file", file, "File name")->check(CLI::ExistingFile);
+
+    int count{0};
+    app.add_option("-v,--value", count, "Value in range")->check(CLI::Range(3, 6));
+    CLI11_PARSE(app, argc, argv);
+
+    std::cout << "Try printing help or failing the validator" << std::endl;
+
+    return 0;
+}
diff --git a/external/CLI11/include/CLI/App.hpp b/external/CLI11/include/CLI/App.hpp
new file mode 100644
index 0000000..803f0f7
--- /dev/null
+++ b/external/CLI11/include/CLI/App.hpp
@@ -0,0 +1,3201 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <iterator>
+#include <memory>
+#include <numeric>
+#include <set>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+// CLI Library includes
+#include "ConfigFwd.hpp"
+#include "Error.hpp"
+#include "FormatterFwd.hpp"
+#include "Macros.hpp"
+#include "Option.hpp"
+#include "Split.hpp"
+#include "StringTools.hpp"
+#include "TypeTools.hpp"
+
+namespace CLI {
+// [CLI11:app_hpp:verbatim]
+
+#ifndef CLI11_PARSE
+#define CLI11_PARSE(app, argc, argv)                                                                                   \
+    try {                                                                                                              \
+        (app).parse((argc), (argv));                                                                                   \
+    } catch(const CLI::ParseError &e) {                                                                                \
+        return (app).exit(e);                                                                                          \
+    }
+#endif
+
+namespace detail {
+enum class Classifier { NONE, POSITIONAL_MARK, SHORT, LONG, WINDOWS_STYLE, SUBCOMMAND, SUBCOMMAND_TERMINATOR };
+struct AppFriend;
+}  // namespace detail
+
+namespace FailureMessage {
+std::string simple(const App *app, const Error &e);
+std::string help(const App *app, const Error &e);
+}  // namespace FailureMessage
+
+/// enumeration of modes of how to deal with extras in config files
+
+enum class config_extras_mode : char { error = 0, ignore, ignore_all, capture };
+
+class App;
+
+using App_p = std::shared_ptr<App>;
+
+class Option_group;
+/// Creates a command line program, with very few defaults.
+/** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated
+ *  add_option methods make it easy to prepare options. Remember to call `.start` before starting your
+ * program, so that the options can be evaluated and the help option doesn't accidentally run your program. */
+class App {
+    friend Option;
+    friend detail::AppFriend;
+
+  protected:
+    // This library follows the Google style guide for member names ending in underscores
+
+    /// @name Basics
+    ///@{
+
+    /// Subcommand name or program name (from parser if name is empty)
+    std::string name_{};
+
+    /// Description of the current program/subcommand
+    std::string description_{};
+
+    /// If true, allow extra arguments (ie, don't throw an error). INHERITABLE
+    bool allow_extras_{false};
+
+    /// If ignore, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE
+    /// if error error on an extra argument, and if capture feed it to the app
+    config_extras_mode allow_config_extras_{config_extras_mode::ignore};
+
+    ///  If true, return immediately on an unrecognized option (implies allow_extras) INHERITABLE
+    bool prefix_command_{false};
+
+    /// If set to true the name was automatically generated from the command line vs a user set name
+    bool has_automatic_name_{false};
+
+    /// If set to true the subcommand is required to be processed and used, ignored for main app
+    bool required_{false};
+
+    /// If set to true the subcommand is disabled and cannot be used, ignored for main app
+    bool disabled_{false};
+
+    /// Flag indicating that the pre_parse_callback has been triggered
+    bool pre_parse_called_{false};
+
+    /// Flag indicating that the callback for the subcommand should be executed immediately on parse completion which is
+    /// before help or ini files are processed. INHERITABLE
+    bool immediate_callback_{false};
+
+    /// This is a function that runs prior to the start of parsing
+    std::function<void(std::size_t)> pre_parse_callback_{};
+
+    /// This is a function that runs when parsing has finished.
+    std::function<void()> parse_complete_callback_{};
+
+    /// This is a function that runs when all processing has completed
+    std::function<void()> final_callback_{};
+
+    ///@}
+    /// @name Options
+    ///@{
+
+    /// The default values for options, customizable and changeable INHERITABLE
+    OptionDefaults option_defaults_{};
+
+    /// The list of options, stored locally
+    std::vector<Option_p> options_{};
+
+    ///@}
+    /// @name Help
+    ///@{
+
+    /// Footer to put after all options in the help output INHERITABLE
+    std::string footer_{};
+
+    /// This is a function that generates a footer to put after all other options in help output
+    std::function<std::string()> footer_callback_{};
+
+    /// A pointer to the help flag if there is one INHERITABLE
+    Option *help_ptr_{nullptr};
+
+    /// A pointer to the help all flag if there is one INHERITABLE
+    Option *help_all_ptr_{nullptr};
+
+    /// A pointer to a version flag if there is one
+    Option *version_ptr_{nullptr};
+
+    /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer)
+    std::shared_ptr<FormatterBase> formatter_{new Formatter()};
+
+    /// The error message printing function INHERITABLE
+    std::function<std::string(const App *, const Error &e)> failure_message_{FailureMessage::simple};
+
+    ///@}
+    /// @name Parsing
+    ///@{
+
+    using missing_t = std::vector<std::pair<detail::Classifier, std::string>>;
+
+    /// Pair of classifier, string for missing options. (extra detail is removed on returning from parse)
+    ///
+    /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator.
+    missing_t missing_{};
+
+    /// This is a list of pointers to options with the original parse order
+    std::vector<Option *> parse_order_{};
+
+    /// This is a list of the subcommands collected, in order
+    std::vector<App *> parsed_subcommands_{};
+
+    /// this is a list of subcommands that are exclusionary to this one
+    std::set<App *> exclude_subcommands_{};
+
+    /// This is a list of options which are exclusionary to this App, if the options were used this subcommand should
+    /// not be
+    std::set<Option *> exclude_options_{};
+
+    /// this is a list of subcommands or option groups that are required by this one, the list is not mutual,  the
+    /// listed subcommands do not require this one
+    std::set<App *> need_subcommands_{};
+
+    /// This is a list of options which are required by this app, the list is not mutual, listed options do not need the
+    /// subcommand not be
+    std::set<Option *> need_options_{};
+
+    ///@}
+    /// @name Subcommands
+    ///@{
+
+    /// Storage for subcommand list
+    std::vector<App_p> subcommands_{};
+
+    /// If true, the program name is not case sensitive INHERITABLE
+    bool ignore_case_{false};
+
+    /// If true, the program should ignore underscores INHERITABLE
+    bool ignore_underscore_{false};
+
+    /// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand.  INHERITABLE
+    bool fallthrough_{false};
+
+    /// Allow '/' for options for Windows like options. Defaults to true on Windows, false otherwise. INHERITABLE
+    bool allow_windows_style_options_{
+#ifdef _WIN32
+        true
+#else
+        false
+#endif
+    };
+    /// specify that positional arguments come at the end of the argument sequence not inheritable
+    bool positionals_at_end_{false};
+
+    enum class startup_mode : char { stable, enabled, disabled };
+    /// specify the startup mode for the app
+    /// stable=no change, enabled= startup enabled, disabled=startup disabled
+    startup_mode default_startup{startup_mode::stable};
+
+    /// if set to true the subcommand can be triggered via configuration files INHERITABLE
+    bool configurable_{false};
+
+    /// If set to true positional options are validated before assigning INHERITABLE
+    bool validate_positionals_{false};
+
+    /// indicator that the subcommand is silent and won't show up in subcommands list
+    /// This is potentially useful as a modifier subcommand
+    bool silent_{false};
+
+    /// Counts the number of times this command/subcommand was parsed
+    std::uint32_t parsed_{0U};
+
+    /// Minimum required subcommands (not inheritable!)
+    std::size_t require_subcommand_min_{0};
+
+    /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE
+    std::size_t require_subcommand_max_{0};
+
+    /// Minimum required options (not inheritable!)
+    std::size_t require_option_min_{0};
+
+    /// Max number of options allowed. 0 is unlimited (not inheritable)
+    std::size_t require_option_max_{0};
+
+    /// A pointer to the parent if this is a subcommand
+    App *parent_{nullptr};
+
+    /// The group membership INHERITABLE
+    std::string group_{"Subcommands"};
+
+    /// Alias names for the subcommand
+    std::vector<std::string> aliases_{};
+
+    ///@}
+    /// @name Config
+    ///@{
+
+    /// Pointer to the config option
+    Option *config_ptr_{nullptr};
+
+    /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer)
+    std::shared_ptr<Config> config_formatter_{new ConfigTOML()};
+
+    ///@}
+
+    /// Special private constructor for subcommand
+    App(std::string app_description, std::string app_name, App *parent)
+        : name_(std::move(app_name)), description_(std::move(app_description)), parent_(parent) {
+        // Inherit if not from a nullptr
+        if(parent_ != nullptr) {
+            if(parent_->help_ptr_ != nullptr)
+                set_help_flag(parent_->help_ptr_->get_name(false, true), parent_->help_ptr_->get_description());
+            if(parent_->help_all_ptr_ != nullptr)
+                set_help_all_flag(parent_->help_all_ptr_->get_name(false, true),
+                                  parent_->help_all_ptr_->get_description());
+
+            /// OptionDefaults
+            option_defaults_ = parent_->option_defaults_;
+
+            // INHERITABLE
+            failure_message_ = parent_->failure_message_;
+            allow_extras_ = parent_->allow_extras_;
+            allow_config_extras_ = parent_->allow_config_extras_;
+            prefix_command_ = parent_->prefix_command_;
+            immediate_callback_ = parent_->immediate_callback_;
+            ignore_case_ = parent_->ignore_case_;
+            ignore_underscore_ = parent_->ignore_underscore_;
+            fallthrough_ = parent_->fallthrough_;
+            validate_positionals_ = parent_->validate_positionals_;
+            configurable_ = parent_->configurable_;
+            allow_windows_style_options_ = parent_->allow_windows_style_options_;
+            group_ = parent_->group_;
+            footer_ = parent_->footer_;
+            formatter_ = parent_->formatter_;
+            config_formatter_ = parent_->config_formatter_;
+            require_subcommand_max_ = parent_->require_subcommand_max_;
+        }
+    }
+
+  public:
+    /// @name Basic
+    ///@{
+
+    /// Create a new program. Pass in the same arguments as main(), along with a help string.
+    explicit App(std::string app_description = "", std::string app_name = "")
+        : App(app_description, app_name, nullptr) {
+        set_help_flag("-h,--help", "Print this help message and exit");
+    }
+
+    App(const App &) = delete;
+    App &operator=(const App &) = delete;
+
+    /// virtual destructor
+    virtual ~App() = default;
+
+    /// Set a callback for execution when all parsing and processing has completed
+    ///
+    /// Due to a bug in c++11,
+    /// it is not possible to overload on std::function (fixed in c++14
+    /// and backported to c++11 on newer compilers). Use capture by reference
+    /// to get a pointer to App if needed.
+    App *callback(std::function<void()> app_callback) {
+        if(immediate_callback_) {
+            parse_complete_callback_ = std::move(app_callback);
+        } else {
+            final_callback_ = std::move(app_callback);
+        }
+        return this;
+    }
+
+    /// Set a callback for execution when all parsing and processing has completed
+    /// aliased as callback
+    App *final_callback(std::function<void()> app_callback) {
+        final_callback_ = std::move(app_callback);
+        return this;
+    }
+
+    /// Set a callback to execute when parsing has completed for the app
+    ///
+    App *parse_complete_callback(std::function<void()> pc_callback) {
+        parse_complete_callback_ = std::move(pc_callback);
+        return this;
+    }
+
+    /// Set a callback to execute prior to parsing.
+    ///
+    App *preparse_callback(std::function<void(std::size_t)> pp_callback) {
+        pre_parse_callback_ = std::move(pp_callback);
+        return this;
+    }
+
+    /// Set a name for the app (empty will use parser to set the name)
+    App *name(std::string app_name = "") {
+
+        if(parent_ != nullptr) {
+            auto oname = name_;
+            name_ = app_name;
+            auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent());
+            if(!res.empty()) {
+                name_ = oname;
+                throw(OptionAlreadyAdded(app_name + " conflicts with existing subcommand names"));
+            }
+        } else {
+            name_ = app_name;
+        }
+        has_automatic_name_ = false;
+        return this;
+    }
+
+    /// Set an alias for the app
+    App *alias(std::string app_name) {
+        if(app_name.empty() || !detail::valid_alias_name_string(app_name)) {
+            throw IncorrectConstruction("Aliases may not be empty or contain newlines or null characters");
+        }
+        if(parent_ != nullptr) {
+            aliases_.push_back(app_name);
+            auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent());
+            if(!res.empty()) {
+                aliases_.pop_back();
+                throw(OptionAlreadyAdded("alias already matches an existing subcommand: " + app_name));
+            }
+        } else {
+            aliases_.push_back(app_name);
+        }
+
+        return this;
+    }
+
+    /// Remove the error when extras are left over on the command line.
+    App *allow_extras(bool allow = true) {
+        allow_extras_ = allow;
+        return this;
+    }
+
+    /// Remove the error when extras are left over on the command line.
+    App *required(bool require = true) {
+        required_ = require;
+        return this;
+    }
+
+    /// Disable the subcommand or option group
+    App *disabled(bool disable = true) {
+        disabled_ = disable;
+        return this;
+    }
+
+    /// silence the subcommand from showing up in the processed list
+    App *silent(bool silence = true) {
+        silent_ = silence;
+        return this;
+    }
+
+    /// Set the subcommand to be disabled by default, so on clear(), at the start of each parse it is disabled
+    App *disabled_by_default(bool disable = true) {
+        if(disable) {
+            default_startup = startup_mode::disabled;
+        } else {
+            default_startup = (default_startup == startup_mode::enabled) ? startup_mode::enabled : startup_mode::stable;
+        }
+        return this;
+    }
+
+    /// Set the subcommand to be enabled by default, so on clear(), at the start of each parse it is enabled (not
+    /// disabled)
+    App *enabled_by_default(bool enable = true) {
+        if(enable) {
+            default_startup = startup_mode::enabled;
+        } else {
+            default_startup =
+                (default_startup == startup_mode::disabled) ? startup_mode::disabled : startup_mode::stable;
+        }
+        return this;
+    }
+
+    /// Set the subcommand callback to be executed immediately on subcommand completion
+    App *immediate_callback(bool immediate = true) {
+        immediate_callback_ = immediate;
+        if(immediate_callback_) {
+            if(final_callback_ && !(parse_complete_callback_)) {
+                std::swap(final_callback_, parse_complete_callback_);
+            }
+        } else if(!(final_callback_) && parse_complete_callback_) {
+            std::swap(final_callback_, parse_complete_callback_);
+        }
+        return this;
+    }
+
+    /// Set the subcommand to validate positional arguments before assigning
+    App *validate_positionals(bool validate = true) {
+        validate_positionals_ = validate;
+        return this;
+    }
+
+    /// ignore extras in config files
+    App *allow_config_extras(bool allow = true) {
+        if(allow) {
+            allow_config_extras_ = config_extras_mode::capture;
+            allow_extras_ = true;
+        } else {
+            allow_config_extras_ = config_extras_mode::error;
+        }
+        return this;
+    }
+
+    /// ignore extras in config files
+    App *allow_config_extras(config_extras_mode mode) {
+        allow_config_extras_ = mode;
+        return this;
+    }
+
+    /// Do not parse anything after the first unrecognized option and return
+    App *prefix_command(bool allow = true) {
+        prefix_command_ = allow;
+        return this;
+    }
+
+    /// Ignore case. Subcommands inherit value.
+    App *ignore_case(bool value = true) {
+        if(value && !ignore_case_) {
+            ignore_case_ = true;
+            auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
+            auto &match = _compare_subcommand_names(*this, *p);
+            if(!match.empty()) {
+                ignore_case_ = false;  // we are throwing so need to be exception invariant
+                throw OptionAlreadyAdded("ignore case would cause subcommand name conflicts: " + match);
+            }
+        }
+        ignore_case_ = value;
+        return this;
+    }
+
+    /// Allow windows style options, such as `/opt`. First matching short or long name used. Subcommands inherit
+    /// value.
+    App *allow_windows_style_options(bool value = true) {
+        allow_windows_style_options_ = value;
+        return this;
+    }
+
+    /// Specify that the positional arguments are only at the end of the sequence
+    App *positionals_at_end(bool value = true) {
+        positionals_at_end_ = value;
+        return this;
+    }
+
+    /// Specify that the subcommand can be triggered by a config file
+    App *configurable(bool value = true) {
+        configurable_ = value;
+        return this;
+    }
+
+    /// Ignore underscore. Subcommands inherit value.
+    App *ignore_underscore(bool value = true) {
+        if(value && !ignore_underscore_) {
+            ignore_underscore_ = true;
+            auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
+            auto &match = _compare_subcommand_names(*this, *p);
+            if(!match.empty()) {
+                ignore_underscore_ = false;
+                throw OptionAlreadyAdded("ignore underscore would cause subcommand name conflicts: " + match);
+            }
+        }
+        ignore_underscore_ = value;
+        return this;
+    }
+
+    /// Set the help formatter
+    App *formatter(std::shared_ptr<FormatterBase> fmt) {
+        formatter_ = fmt;
+        return this;
+    }
+
+    /// Set the help formatter
+    App *formatter_fn(std::function<std::string(const App *, std::string, AppFormatMode)> fmt) {
+        formatter_ = std::make_shared<FormatterLambda>(fmt);
+        return this;
+    }
+
+    /// Set the config formatter
+    App *config_formatter(std::shared_ptr<Config> fmt) {
+        config_formatter_ = fmt;
+        return this;
+    }
+
+    /// Check to see if this subcommand was parsed, true only if received on command line.
+    bool parsed() const { return parsed_ > 0; }
+
+    /// Get the OptionDefault object, to set option defaults
+    OptionDefaults *option_defaults() { return &option_defaults_; }
+
+    ///@}
+    /// @name Adding options
+    ///@{
+
+    /// Add an option, will automatically understand the type for common types.
+    ///
+    /// To use, create a variable with the expected type, and pass it in after the name.
+    /// After start is called, you can use count to see if the value was passed, and
+    /// the value will be initialized properly. Numbers, vectors, and strings are supported.
+    ///
+    /// ->required(), ->default, and the validators are options,
+    /// The positional options take an optional number of arguments.
+    ///
+    /// For example,
+    ///
+    ///     std::string filename;
+    ///     program.add_option("filename", filename, "description of filename");
+    ///
+    Option *add_option(std::string option_name,
+                       callback_t option_callback,
+                       std::string option_description = "",
+                       bool defaulted = false,
+                       std::function<std::string()> func = {}) {
+        Option myopt{option_name, option_description, option_callback, this};
+
+        if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) {
+               return *v == myopt;
+           }) == std::end(options_)) {
+            options_.emplace_back();
+            Option_p &option = options_.back();
+            option.reset(new Option(option_name, option_description, option_callback, this));
+
+            // Set the default string capture function
+            option->default_function(func);
+
+            // For compatibility with CLI11 1.7 and before, capture the default string here
+            if(defaulted)
+                option->capture_default_str();
+
+            // Transfer defaults to the new option
+            option_defaults_.copy_to(option.get());
+
+            // Don't bother to capture if we already did
+            if(!defaulted && option->get_always_capture_default())
+                option->capture_default_str();
+
+            return option.get();
+        }
+        // we know something matches now find what it is so we can produce more error information
+        for(auto &opt : options_) {
+            auto &matchname = opt->matching_name(myopt);
+            if(!matchname.empty()) {
+                throw(OptionAlreadyAdded("added option matched existing option name: " + matchname));
+            }
+        }
+        // this line should not be reached the above loop should trigger the throw
+        throw(OptionAlreadyAdded("added option matched existing option name"));  // LCOV_EXCL_LINE
+    }
+
+    /// Add option for assigning to a variable
+    template <typename AssignTo,
+              typename ConvertTo = AssignTo,
+              enable_if_t<!std::is_const<ConvertTo>::value, detail::enabler> = detail::dummy>
+    Option *add_option(std::string option_name,
+                       AssignTo &variable,  ///< The variable to set
+                       std::string option_description = "") {
+
+        auto fun = [&variable](const CLI::results_t &res) {  // comment for spacing
+            return detail::lexical_conversion<AssignTo, ConvertTo>(res, variable);
+        };
+
+        Option *opt = add_option(option_name, fun, option_description, false, [&variable]() {
+            return CLI::detail::checked_to_string<AssignTo, ConvertTo>(variable);
+        });
+        opt->type_name(detail::type_name<ConvertTo>());
+        // these must be actual lvalues since (std::max) sometimes is defined in terms of references and references
+        // to structs used in the evaluation can be temporary so that would cause issues.
+        auto Tcount = detail::type_count<AssignTo>::value;
+        auto XCcount = detail::type_count<ConvertTo>::value;
+        opt->type_size(detail::type_count_min<ConvertTo>::value, (std::max)(Tcount, XCcount));
+        opt->expected(detail::expected_count<ConvertTo>::value);
+        opt->run_callback_for_default();
+        return opt;
+    }
+
+    /// Add option for assigning to a variable
+    template <typename AssignTo, enable_if_t<!std::is_const<AssignTo>::value, detail::enabler> = detail::dummy>
+    Option *add_option_no_stream(std::string option_name,
+                                 AssignTo &variable,  ///< The variable to set
+                                 std::string option_description = "") {
+
+        auto fun = [&variable](const CLI::results_t &res) {  // comment for spacing
+            return detail::lexical_conversion<AssignTo, AssignTo>(res, variable);
+        };
+
+        Option *opt = add_option(option_name, fun, option_description, false, []() { return std::string{}; });
+        opt->type_name(detail::type_name<AssignTo>());
+        opt->type_size(detail::type_count_min<AssignTo>::value, detail::type_count<AssignTo>::value);
+        opt->expected(detail::expected_count<AssignTo>::value);
+        opt->run_callback_for_default();
+        return opt;
+    }
+
+    /// Add option for a callback of a specific type
+    template <typename ArgType>
+    Option *add_option_function(std::string option_name,
+                                const std::function<void(const ArgType &)> &func,  ///< the callback to execute
+                                std::string option_description = "") {
+
+        auto fun = [func](const CLI::results_t &res) {
+            ArgType variable;
+            bool result = detail::lexical_conversion<ArgType, ArgType>(res, variable);
+            if(result) {
+                func(variable);
+            }
+            return result;
+        };
+
+        Option *opt = add_option(option_name, std::move(fun), option_description, false);
+        opt->type_name(detail::type_name<ArgType>());
+        opt->type_size(detail::type_count_min<ArgType>::value, detail::type_count<ArgType>::value);
+        opt->expected(detail::expected_count<ArgType>::value);
+        return opt;
+    }
+
+    /// Add option with no description or variable assignment
+    Option *add_option(std::string option_name) {
+        return add_option(option_name, CLI::callback_t{}, std::string{}, false);
+    }
+
+    /// Add option with description but with no variable assignment or callback
+    template <typename T,
+              enable_if_t<std::is_const<T>::value && std::is_constructible<std::string, T>::value, detail::enabler> =
+                  detail::dummy>
+    Option *add_option(std::string option_name, T &option_description) {
+        return add_option(option_name, CLI::callback_t(), option_description, false);
+    }
+
+    /// Set a help flag, replace the existing one if present
+    Option *set_help_flag(std::string flag_name = "", const std::string &help_description = "") {
+        // take flag_description by const reference otherwise add_flag tries to assign to help_description
+        if(help_ptr_ != nullptr) {
+            remove_option(help_ptr_);
+            help_ptr_ = nullptr;
+        }
+
+        // Empty name will simply remove the help flag
+        if(!flag_name.empty()) {
+            help_ptr_ = add_flag(flag_name, help_description);
+            help_ptr_->configurable(false);
+        }
+
+        return help_ptr_;
+    }
+
+    /// Set a help all flag, replaced the existing one if present
+    Option *set_help_all_flag(std::string help_name = "", const std::string &help_description = "") {
+        // take flag_description by const reference otherwise add_flag tries to assign to flag_description
+        if(help_all_ptr_ != nullptr) {
+            remove_option(help_all_ptr_);
+            help_all_ptr_ = nullptr;
+        }
+
+        // Empty name will simply remove the help all flag
+        if(!help_name.empty()) {
+            help_all_ptr_ = add_flag(help_name, help_description);
+            help_all_ptr_->configurable(false);
+        }
+
+        return help_all_ptr_;
+    }
+
+    /// Set a version flag and version display string, replace the existing one if present
+    Option *set_version_flag(std::string flag_name = "",
+                             const std::string &versionString = "",
+                             const std::string &version_help = "Display program version information and exit") {
+        // take flag_description by const reference otherwise add_flag tries to assign to version_description
+        if(version_ptr_ != nullptr) {
+            remove_option(version_ptr_);
+            version_ptr_ = nullptr;
+        }
+
+        // Empty name will simply remove the version flag
+        if(!flag_name.empty()) {
+            version_ptr_ = add_flag_callback(
+                flag_name, [versionString]() { throw(CLI::CallForVersion(versionString, 0)); }, version_help);
+            version_ptr_->configurable(false);
+        }
+
+        return version_ptr_;
+    }
+    /// Generate the version string through a callback function
+    Option *set_version_flag(std::string flag_name,
+                             std::function<std::string()> vfunc,
+                             const std::string &version_help = "Display program version information and exit") {
+        if(version_ptr_ != nullptr) {
+            remove_option(version_ptr_);
+            version_ptr_ = nullptr;
+        }
+
+        // Empty name will simply remove the version flag
+        if(!flag_name.empty()) {
+            version_ptr_ = add_flag_callback(
+                flag_name, [vfunc]() { throw(CLI::CallForVersion(vfunc(), 0)); }, version_help);
+            version_ptr_->configurable(false);
+        }
+
+        return version_ptr_;
+    }
+
+  private:
+    /// Internal function for adding a flag
+    Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) {
+        Option *opt;
+        if(detail::has_default_flag_values(flag_name)) {
+            // check for default values and if it has them
+            auto flag_defaults = detail::get_default_flag_values(flag_name);
+            detail::remove_default_flag_values(flag_name);
+            opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false);
+            for(const auto &fname : flag_defaults)
+                opt->fnames_.push_back(fname.first);
+            opt->default_flag_values_ = std::move(flag_defaults);
+        } else {
+            opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false);
+        }
+        // flags cannot have positional values
+        if(opt->get_positional()) {
+            auto pos_name = opt->get_name(true);
+            remove_option(opt);
+            throw IncorrectConstruction::PositionalFlag(pos_name);
+        }
+        opt->multi_option_policy(MultiOptionPolicy::TakeLast);
+        opt->expected(0);
+        opt->required(false);
+        return opt;
+    }
+
+  public:
+    /// Add a flag with no description or variable assignment
+    Option *add_flag(std::string flag_name) { return _add_flag_internal(flag_name, CLI::callback_t(), std::string{}); }
+
+    /// Add flag with description but with no variable assignment or callback
+    /// takes a constant string,  if a variable string is passed that variable will be assigned the results from the
+    /// flag
+    template <typename T,
+              enable_if_t<std::is_const<T>::value && std::is_constructible<std::string, T>::value, detail::enabler> =
+                  detail::dummy>
+    Option *add_flag(std::string flag_name, T &flag_description) {
+        return _add_flag_internal(flag_name, CLI::callback_t(), flag_description);
+    }
+
+    /// Add option for flag with integer result - defaults to allowing multiple passings, but can be forced to one
+    /// if `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used.
+    template <typename T,
+              enable_if_t<std::is_constructible<T, std::int64_t>::value && !is_bool<T>::value, detail::enabler> =
+                  detail::dummy>
+    Option *add_flag(std::string flag_name,
+                     T &flag_count,  ///< A variable holding the count
+                     std::string flag_description = "") {
+        flag_count = 0;
+        CLI::callback_t fun = [&flag_count](const CLI::results_t &res) {
+            try {
+                detail::sum_flag_vector(res, flag_count);
+            } catch(const std::invalid_argument &) {
+                return false;
+            }
+            return true;
+        };
+        return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
+            ->multi_option_policy(MultiOptionPolicy::TakeAll);
+    }
+
+    /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes
+    /// that can be converted from a string
+    template <typename T,
+              enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value &&
+                              (!std::is_constructible<T, std::int64_t>::value || is_bool<T>::value) &&
+                              !std::is_constructible<std::function<void(int)>, T>::value,
+                          detail::enabler> = detail::dummy>
+    Option *add_flag(std::string flag_name,
+                     T &flag_result,  ///< A variable holding true if passed
+                     std::string flag_description = "") {
+
+        CLI::callback_t fun = [&flag_result](const CLI::results_t &res) {
+            return CLI::detail::lexical_cast(res[0], flag_result);
+        };
+        return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))->run_callback_for_default();
+    }
+
+    /// Vector version to capture multiple flags.
+    template <typename T,
+              enable_if_t<!std::is_assignable<std::function<void(std::int64_t)> &, T>::value, detail::enabler> =
+                  detail::dummy>
+    Option *add_flag(std::string flag_name,
+                     std::vector<T> &flag_results,  ///< A vector of values with the flag results
+                     std::string flag_description = "") {
+        CLI::callback_t fun = [&flag_results](const CLI::results_t &res) {
+            bool retval = true;
+            for(const auto &elem : res) {
+                flag_results.emplace_back();
+                retval &= detail::lexical_cast(elem, flag_results.back());
+            }
+            return retval;
+        };
+        return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
+            ->multi_option_policy(MultiOptionPolicy::TakeAll)
+            ->run_callback_for_default();
+    }
+
+    /// Add option for callback that is triggered with a true flag and takes no arguments
+    Option *add_flag_callback(std::string flag_name,
+                              std::function<void(void)> function,  ///< A function to call, void(void)
+                              std::string flag_description = "") {
+
+        CLI::callback_t fun = [function](const CLI::results_t &res) {
+            bool trigger{false};
+            auto result = CLI::detail::lexical_cast(res[0], trigger);
+            if(result && trigger) {
+                function();
+            }
+            return result;
+        };
+        return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description));
+    }
+
+    /// Add option for callback with an integer value
+    Option *add_flag_function(std::string flag_name,
+                              std::function<void(std::int64_t)> function,  ///< A function to call, void(int)
+                              std::string flag_description = "") {
+
+        CLI::callback_t fun = [function](const CLI::results_t &res) {
+            std::int64_t flag_count = 0;
+            detail::sum_flag_vector(res, flag_count);
+            function(flag_count);
+            return true;
+        };
+        return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description))
+            ->multi_option_policy(MultiOptionPolicy::TakeAll);
+    }
+
+#ifdef CLI11_CPP14
+    /// Add option for callback (C++14 or better only)
+    Option *add_flag(std::string flag_name,
+                     std::function<void(std::int64_t)> function,  ///< A function to call, void(std::int64_t)
+                     std::string flag_description = "") {
+        return add_flag_function(std::move(flag_name), std::move(function), std::move(flag_description));
+    }
+#endif
+
+    /// Set a configuration ini file option, or clear it if no name passed
+    Option *set_config(std::string option_name = "",
+                       std::string default_filename = "",
+                       const std::string &help_message = "Read an ini file",
+                       bool config_required = false) {
+
+        // Remove existing config if present
+        if(config_ptr_ != nullptr) {
+            remove_option(config_ptr_);
+            config_ptr_ = nullptr;  // need to remove the config_ptr completely
+        }
+
+        // Only add config if option passed
+        if(!option_name.empty()) {
+            config_ptr_ = add_option(option_name, help_message);
+            if(config_required) {
+                config_ptr_->required();
+            }
+            if(!default_filename.empty()) {
+                config_ptr_->default_str(std::move(default_filename));
+            }
+            config_ptr_->configurable(false);
+        }
+
+        return config_ptr_;
+    }
+
+    /// Removes an option from the App. Takes an option pointer. Returns true if found and removed.
+    bool remove_option(Option *opt) {
+        // Make sure no links exist
+        for(Option_p &op : options_) {
+            op->remove_needs(opt);
+            op->remove_excludes(opt);
+        }
+
+        if(help_ptr_ == opt)
+            help_ptr_ = nullptr;
+        if(help_all_ptr_ == opt)
+            help_all_ptr_ = nullptr;
+
+        auto iterator =
+            std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; });
+        if(iterator != std::end(options_)) {
+            options_.erase(iterator);
+            return true;
+        }
+        return false;
+    }
+
+    /// creates an option group as part of the given app
+    template <typename T = Option_group>
+    T *add_option_group(std::string group_name, std::string group_description = "") {
+        if(!detail::valid_alias_name_string(group_name)) {
+            throw IncorrectConstruction("option group names may not contain newlines or null characters");
+        }
+        auto option_group = std::make_shared<T>(std::move(group_description), group_name, this);
+        auto ptr = option_group.get();
+        // move to App_p for overload resolution on older gcc versions
+        App_p app_ptr = std::dynamic_pointer_cast<App>(option_group);
+        add_subcommand(std::move(app_ptr));
+        return ptr;
+    }
+
+    ///@}
+    /// @name Subcommands
+    ///@{
+
+    /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag
+    App *add_subcommand(std::string subcommand_name = "", std::string subcommand_description = "") {
+        if(!subcommand_name.empty() && !detail::valid_name_string(subcommand_name)) {
+            if(!detail::valid_first_char(subcommand_name[0])) {
+                throw IncorrectConstruction(
+                    "Subcommand name starts with invalid character, '!' and '-' are not allowed");
+            }
+            for(auto c : subcommand_name) {
+                if(!detail::valid_later_char(c)) {
+                    throw IncorrectConstruction(std::string("Subcommand name contains invalid character ('") + c +
+                                                "'), all characters are allowed except"
+                                                "'=',':','{','}', and ' '");
+                }
+            }
+        }
+        CLI::App_p subcom = std::shared_ptr<App>(new App(std::move(subcommand_description), subcommand_name, this));
+        return add_subcommand(std::move(subcom));
+    }
+
+    /// Add a previously created app as a subcommand
+    App *add_subcommand(CLI::App_p subcom) {
+        if(!subcom)
+            throw IncorrectConstruction("passed App is not valid");
+        auto ckapp = (name_.empty() && parent_ != nullptr) ? _get_fallthrough_parent() : this;
+        auto &mstrg = _compare_subcommand_names(*subcom, *ckapp);
+        if(!mstrg.empty()) {
+            throw(OptionAlreadyAdded("subcommand name or alias matches existing subcommand: " + mstrg));
+        }
+        subcom->parent_ = this;
+        subcommands_.push_back(std::move(subcom));
+        return subcommands_.back().get();
+    }
+
+    /// Removes a subcommand from the App. Takes a subcommand pointer. Returns true if found and removed.
+    bool remove_subcommand(App *subcom) {
+        // Make sure no links exist
+        for(App_p &sub : subcommands_) {
+            sub->remove_excludes(subcom);
+            sub->remove_needs(subcom);
+        }
+
+        auto iterator = std::find_if(
+            std::begin(subcommands_), std::end(subcommands_), [subcom](const App_p &v) { return v.get() == subcom; });
+        if(iterator != std::end(subcommands_)) {
+            subcommands_.erase(iterator);
+            return true;
+        }
+        return false;
+    }
+    /// Check to see if a subcommand is part of this command (doesn't have to be in command line)
+    /// returns the first subcommand if passed a nullptr
+    App *get_subcommand(const App *subcom) const {
+        if(subcom == nullptr)
+            throw OptionNotFound("nullptr passed");
+        for(const App_p &subcomptr : subcommands_)
+            if(subcomptr.get() == subcom)
+                return subcomptr.get();
+        throw OptionNotFound(subcom->get_name());
+    }
+
+    /// Check to see if a subcommand is part of this command (text version)
+    App *get_subcommand(std::string subcom) const {
+        auto subc = _find_subcommand(subcom, false, false);
+        if(subc == nullptr)
+            throw OptionNotFound(subcom);
+        return subc;
+    }
+    /// Get a pointer to subcommand by index
+    App *get_subcommand(int index = 0) const {
+        if(index >= 0) {
+            auto uindex = static_cast<unsigned>(index);
+            if(uindex < subcommands_.size())
+                return subcommands_[uindex].get();
+        }
+        throw OptionNotFound(std::to_string(index));
+    }
+
+    /// Check to see if a subcommand is part of this command and get a shared_ptr to it
+    CLI::App_p get_subcommand_ptr(App *subcom) const {
+        if(subcom == nullptr)
+            throw OptionNotFound("nullptr passed");
+        for(const App_p &subcomptr : subcommands_)
+            if(subcomptr.get() == subcom)
+                return subcomptr;
+        throw OptionNotFound(subcom->get_name());
+    }
+
+    /// Check to see if a subcommand is part of this command (text version)
+    CLI::App_p get_subcommand_ptr(std::string subcom) const {
+        for(const App_p &subcomptr : subcommands_)
+            if(subcomptr->check_name(subcom))
+                return subcomptr;
+        throw OptionNotFound(subcom);
+    }
+
+    /// Get an owning pointer to subcommand by index
+    CLI::App_p get_subcommand_ptr(int index = 0) const {
+        if(index >= 0) {
+            auto uindex = static_cast<unsigned>(index);
+            if(uindex < subcommands_.size())
+                return subcommands_[uindex];
+        }
+        throw OptionNotFound(std::to_string(index));
+    }
+
+    /// Check to see if an option group is part of this App
+    App *get_option_group(std::string group_name) const {
+        for(const App_p &app : subcommands_) {
+            if(app->name_.empty() && app->group_ == group_name) {
+                return app.get();
+            }
+        }
+        throw OptionNotFound(group_name);
+    }
+
+    /// No argument version of count counts the number of times this subcommand was
+    /// passed in. The main app will return 1. Unnamed subcommands will also return 1 unless
+    /// otherwise modified in a callback
+    std::size_t count() const { return parsed_; }
+
+    /// Get a count of all the arguments processed in options and subcommands, this excludes arguments which were
+    /// treated as extras.
+    std::size_t count_all() const {
+        std::size_t cnt{0};
+        for(auto &opt : options_) {
+            cnt += opt->count();
+        }
+        for(auto &sub : subcommands_) {
+            cnt += sub->count_all();
+        }
+        if(!get_name().empty()) {  // for named subcommands add the number of times the subcommand was called
+            cnt += parsed_;
+        }
+        return cnt;
+    }
+
+    /// Changes the group membership
+    App *group(std::string group_name) {
+        group_ = group_name;
+        return this;
+    }
+
+    /// The argumentless form of require subcommand requires 1 or more subcommands
+    App *require_subcommand() {
+        require_subcommand_min_ = 1;
+        require_subcommand_max_ = 0;
+        return this;
+    }
+
+    /// Require a subcommand to be given (does not affect help call)
+    /// The number required can be given. Negative values indicate maximum
+    /// number allowed (0 for any number). Max number inheritable.
+    App *require_subcommand(int value) {
+        if(value < 0) {
+            require_subcommand_min_ = 0;
+            require_subcommand_max_ = static_cast<std::size_t>(-value);
+        } else {
+            require_subcommand_min_ = static_cast<std::size_t>(value);
+            require_subcommand_max_ = static_cast<std::size_t>(value);
+        }
+        return this;
+    }
+
+    /// Explicitly control the number of subcommands required. Setting 0
+    /// for the max means unlimited number allowed. Max number inheritable.
+    App *require_subcommand(std::size_t min, std::size_t max) {
+        require_subcommand_min_ = min;
+        require_subcommand_max_ = max;
+        return this;
+    }
+
+    /// The argumentless form of require option requires 1 or more options be used
+    App *require_option() {
+        require_option_min_ = 1;
+        require_option_max_ = 0;
+        return this;
+    }
+
+    /// Require an option to be given (does not affect help call)
+    /// The number required can be given. Negative values indicate maximum
+    /// number allowed (0 for any number).
+    App *require_option(int value) {
+        if(value < 0) {
+            require_option_min_ = 0;
+            require_option_max_ = static_cast<std::size_t>(-value);
+        } else {
+            require_option_min_ = static_cast<std::size_t>(value);
+            require_option_max_ = static_cast<std::size_t>(value);
+        }
+        return this;
+    }
+
+    /// Explicitly control the number of options required. Setting 0
+    /// for the max means unlimited number allowed. Max number inheritable.
+    App *require_option(std::size_t min, std::size_t max) {
+        require_option_min_ = min;
+        require_option_max_ = max;
+        return this;
+    }
+
+    /// Stop subcommand fallthrough, so that parent commands cannot collect commands after subcommand.
+    /// Default from parent, usually set on parent.
+    App *fallthrough(bool value = true) {
+        fallthrough_ = value;
+        return this;
+    }
+
+    /// Check to see if this subcommand was parsed, true only if received on command line.
+    /// This allows the subcommand to be directly checked.
+    explicit operator bool() const { return parsed_ > 0; }
+
+    ///@}
+    /// @name Extras for subclassing
+    ///@{
+
+    /// This allows subclasses to inject code before callbacks but after parse.
+    ///
+    /// This does not run if any errors or help is thrown.
+    virtual void pre_callback() {}
+
+    ///@}
+    /// @name Parsing
+    ///@{
+    //
+    /// Reset the parsed data
+    void clear() {
+
+        parsed_ = 0;
+        pre_parse_called_ = false;
+
+        missing_.clear();
+        parsed_subcommands_.clear();
+        for(const Option_p &opt : options_) {
+            opt->clear();
+        }
+        for(const App_p &subc : subcommands_) {
+            subc->clear();
+        }
+    }
+
+    /// Parses the command line - throws errors.
+    /// This must be called after the options are in but before the rest of the program.
+    void parse(int argc, const char *const *argv) {
+        // If the name is not set, read from command line
+        if(name_.empty() || has_automatic_name_) {
+            has_automatic_name_ = true;
+            name_ = argv[0];
+        }
+
+        std::vector<std::string> args;
+        args.reserve(static_cast<std::size_t>(argc) - 1);
+        for(int i = argc - 1; i > 0; i--)
+            args.emplace_back(argv[i]);
+        parse(std::move(args));
+    }
+
+    /// Parse a single string as if it contained command line arguments.
+    /// This function splits the string into arguments then calls parse(std::vector<std::string> &)
+    /// the function takes an optional boolean argument specifying if the programName is included in the string to
+    /// process
+    void parse(std::string commandline, bool program_name_included = false) {
+
+        if(program_name_included) {
+            auto nstr = detail::split_program_name(commandline);
+            if((name_.empty()) || (has_automatic_name_)) {
+                has_automatic_name_ = true;
+                name_ = nstr.first;
+            }
+            commandline = std::move(nstr.second);
+        } else {
+            detail::trim(commandline);
+        }
+        // the next section of code is to deal with quoted arguments after an '=' or ':' for windows like operations
+        if(!commandline.empty()) {
+            commandline = detail::find_and_modify(commandline, "=", detail::escape_detect);
+            if(allow_windows_style_options_)
+                commandline = detail::find_and_modify(commandline, ":", detail::escape_detect);
+        }
+
+        auto args = detail::split_up(std::move(commandline));
+        // remove all empty strings
+        args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end());
+        std::reverse(args.begin(), args.end());
+
+        parse(std::move(args));
+    }
+
+    /// The real work is done here. Expects a reversed vector.
+    /// Changes the vector to the remaining options.
+    void parse(std::vector<std::string> &args) {
+        // Clear if parsed
+        if(parsed_ > 0)
+            clear();
+
+        // parsed_ is incremented in commands/subcommands,
+        // but placed here to make sure this is cleared when
+        // running parse after an error is thrown, even by _validate or _configure.
+        parsed_ = 1;
+        _validate();
+        _configure();
+        // set the parent as nullptr as this object should be the top now
+        parent_ = nullptr;
+        parsed_ = 0;
+
+        _parse(args);
+        run_callback();
+    }
+
+    /// The real work is done here. Expects a reversed vector.
+    void parse(std::vector<std::string> &&args) {
+        // Clear if parsed
+        if(parsed_ > 0)
+            clear();
+
+        // parsed_ is incremented in commands/subcommands,
+        // but placed here to make sure this is cleared when
+        // running parse after an error is thrown, even by _validate or _configure.
+        parsed_ = 1;
+        _validate();
+        _configure();
+        // set the parent as nullptr as this object should be the top now
+        parent_ = nullptr;
+        parsed_ = 0;
+
+        _parse(std::move(args));
+        run_callback();
+    }
+
+    void parse_from_stream(std::istream &input) {
+        if(parsed_ == 0) {
+            _validate();
+            _configure();
+            // set the parent as nullptr as this object should be the top now
+        }
+
+        _parse_stream(input);
+        run_callback();
+    }
+    /// Provide a function to print a help message. The function gets access to the App pointer and error.
+    void failure_message(std::function<std::string(const App *, const Error &e)> function) {
+        failure_message_ = function;
+    }
+
+    /// Print a nice error message and return the exit code
+    int exit(const Error &e, std::ostream &out = std::cout, std::ostream &err = std::cerr) const {
+
+        /// Avoid printing anything if this is a CLI::RuntimeError
+        if(e.get_name() == "RuntimeError")
+            return e.get_exit_code();
+
+        if(e.get_name() == "CallForHelp") {
+            out << help();
+            return e.get_exit_code();
+        }
+
+        if(e.get_name() == "CallForAllHelp") {
+            out << help("", AppFormatMode::All);
+            return e.get_exit_code();
+        }
+
+        if(e.get_name() == "CallForVersion") {
+            out << e.what() << std::endl;
+            return e.get_exit_code();
+        }
+
+        if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) {
+            if(failure_message_)
+                err << failure_message_(this, e) << std::flush;
+        }
+
+        return e.get_exit_code();
+    }
+
+    ///@}
+    /// @name Post parsing
+    ///@{
+
+    /// Counts the number of times the given option was passed.
+    std::size_t count(std::string option_name) const { return get_option(option_name)->count(); }
+
+    /// Get a subcommand pointer list to the currently selected subcommands (after parsing by default, in command
+    /// line order; use parsed = false to get the original definition list.)
+    std::vector<App *> get_subcommands() const { return parsed_subcommands_; }
+
+    /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all
+    /// subcommands (const)
+    std::vector<const App *> get_subcommands(const std::function<bool(const App *)> &filter) const {
+        std::vector<const App *> subcomms(subcommands_.size());
+        std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) {
+            return v.get();
+        });
+
+        if(filter) {
+            subcomms.erase(std::remove_if(std::begin(subcomms),
+                                          std::end(subcomms),
+                                          [&filter](const App *app) { return !filter(app); }),
+                           std::end(subcomms));
+        }
+
+        return subcomms;
+    }
+
+    /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all
+    /// subcommands
+    std::vector<App *> get_subcommands(const std::function<bool(App *)> &filter) {
+        std::vector<App *> subcomms(subcommands_.size());
+        std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) {
+            return v.get();
+        });
+
+        if(filter) {
+            subcomms.erase(
+                std::remove_if(std::begin(subcomms), std::end(subcomms), [&filter](App *app) { return !filter(app); }),
+                std::end(subcomms));
+        }
+
+        return subcomms;
+    }
+
+    /// Check to see if given subcommand was selected
+    bool got_subcommand(const App *subcom) const {
+        // get subcom needed to verify that this was a real subcommand
+        return get_subcommand(subcom)->parsed_ > 0;
+    }
+
+    /// Check with name instead of pointer to see if subcommand was selected
+    bool got_subcommand(std::string subcommand_name) const { return get_subcommand(subcommand_name)->parsed_ > 0; }
+
+    /// Sets excluded options for the subcommand
+    App *excludes(Option *opt) {
+        if(opt == nullptr) {
+            throw OptionNotFound("nullptr passed");
+        }
+        exclude_options_.insert(opt);
+        return this;
+    }
+
+    /// Sets excluded subcommands for the subcommand
+    App *excludes(App *app) {
+        if(app == nullptr) {
+            throw OptionNotFound("nullptr passed");
+        }
+        if(app == this) {
+            throw OptionNotFound("cannot self reference in needs");
+        }
+        auto res = exclude_subcommands_.insert(app);
+        // subcommand exclusion should be symmetric
+        if(res.second) {
+            app->exclude_subcommands_.insert(this);
+        }
+        return this;
+    }
+
+    App *needs(Option *opt) {
+        if(opt == nullptr) {
+            throw OptionNotFound("nullptr passed");
+        }
+        need_options_.insert(opt);
+        return this;
+    }
+
+    App *needs(App *app) {
+        if(app == nullptr) {
+            throw OptionNotFound("nullptr passed");
+        }
+        if(app == this) {
+            throw OptionNotFound("cannot self reference in needs");
+        }
+        need_subcommands_.insert(app);
+        return this;
+    }
+
+    /// Removes an option from the excludes list of this subcommand
+    bool remove_excludes(Option *opt) {
+        auto iterator = std::find(std::begin(exclude_options_), std::end(exclude_options_), opt);
+        if(iterator == std::end(exclude_options_)) {
+            return false;
+        }
+        exclude_options_.erase(iterator);
+        return true;
+    }
+
+    /// Removes a subcommand from the excludes list of this subcommand
+    bool remove_excludes(App *app) {
+        auto iterator = std::find(std::begin(exclude_subcommands_), std::end(exclude_subcommands_), app);
+        if(iterator == std::end(exclude_subcommands_)) {
+            return false;
+        }
+        auto other_app = *iterator;
+        exclude_subcommands_.erase(iterator);
+        other_app->remove_excludes(this);
+        return true;
+    }
+
+    /// Removes an option from the needs list of this subcommand
+    bool remove_needs(Option *opt) {
+        auto iterator = std::find(std::begin(need_options_), std::end(need_options_), opt);
+        if(iterator == std::end(need_options_)) {
+            return false;
+        }
+        need_options_.erase(iterator);
+        return true;
+    }
+
+    /// Removes a subcommand from the needs list of this subcommand
+    bool remove_needs(App *app) {
+        auto iterator = std::find(std::begin(need_subcommands_), std::end(need_subcommands_), app);
+        if(iterator == std::end(need_subcommands_)) {
+            return false;
+        }
+        need_subcommands_.erase(iterator);
+        return true;
+    }
+
+    ///@}
+    /// @name Help
+    ///@{
+
+    /// Set footer.
+    App *footer(std::string footer_string) {
+        footer_ = std::move(footer_string);
+        return this;
+    }
+    /// Set footer.
+    App *footer(std::function<std::string()> footer_function) {
+        footer_callback_ = std::move(footer_function);
+        return this;
+    }
+    /// Produce a string that could be read in as a config of the current values of the App. Set default_also to
+    /// include default arguments. write_descriptions will print a description for the App and for each option.
+    std::string config_to_str(bool default_also = false, bool write_description = false) const {
+        return config_formatter_->to_config(this, default_also, write_description, "");
+    }
+
+    /// Makes a help message, using the currently configured formatter
+    /// Will only do one subcommand at a time
+    std::string help(std::string prev = "", AppFormatMode mode = AppFormatMode::Normal) const {
+        if(prev.empty())
+            prev = get_name();
+        else
+            prev += " " + get_name();
+
+        // Delegate to subcommand if needed
+        auto selected_subcommands = get_subcommands();
+        if(!selected_subcommands.empty()) {
+            return selected_subcommands.at(0)->help(prev, mode);
+        }
+        return formatter_->make_help(this, prev, mode);
+    }
+
+    /// Displays a version string
+    std::string version() const {
+        std::string val;
+        if(version_ptr_ != nullptr) {
+            auto rv = version_ptr_->results();
+            version_ptr_->clear();
+            version_ptr_->add_result("true");
+            try {
+                version_ptr_->run_callback();
+            } catch(const CLI::CallForVersion &cfv) {
+                val = cfv.what();
+            }
+            version_ptr_->clear();
+            version_ptr_->add_result(rv);
+        }
+        return val;
+    }
+    ///@}
+    /// @name Getters
+    ///@{
+
+    /// Access the formatter
+    std::shared_ptr<FormatterBase> get_formatter() const { return formatter_; }
+
+    /// Access the config formatter
+    std::shared_ptr<Config> get_config_formatter() const { return config_formatter_; }
+
+    /// Access the config formatter as a configBase pointer
+    std::shared_ptr<ConfigBase> get_config_formatter_base() const {
+        // This is safer as a dynamic_cast if we have RTTI, as Config -> ConfigBase
+#if defined(__cpp_rtti) || (defined(__GXX_RTTI) && __GXX_RTTI) || (defined(_HAS_STATIC_RTTI) && (_HAS_STATIC_RTTI == 0))
+        return std::dynamic_pointer_cast<ConfigBase>(config_formatter_);
+#else
+        return std::static_pointer_cast<ConfigBase>(config_formatter_);
+#endif
+    }
+
+    /// Get the app or subcommand description
+    std::string get_description() const { return description_; }
+
+    /// Set the description of the app
+    App *description(std::string app_description) {
+        description_ = std::move(app_description);
+        return this;
+    }
+
+    /// Get the list of options (user facing function, so returns raw pointers), has optional filter function
+    std::vector<const Option *> get_options(const std::function<bool(const Option *)> filter = {}) const {
+        std::vector<const Option *> options(options_.size());
+        std::transform(std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) {
+            return val.get();
+        });
+
+        if(filter) {
+            options.erase(std::remove_if(std::begin(options),
+                                         std::end(options),
+                                         [&filter](const Option *opt) { return !filter(opt); }),
+                          std::end(options));
+        }
+
+        return options;
+    }
+
+    /// Non-const version of the above
+    std::vector<Option *> get_options(const std::function<bool(Option *)> filter = {}) {
+        std::vector<Option *> options(options_.size());
+        std::transform(std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) {
+            return val.get();
+        });
+
+        if(filter) {
+            options.erase(
+                std::remove_if(std::begin(options), std::end(options), [&filter](Option *opt) { return !filter(opt); }),
+                std::end(options));
+        }
+
+        return options;
+    }
+
+    /// Get an option by name (noexcept non-const version)
+    Option *get_option_no_throw(std::string option_name) noexcept {
+        for(Option_p &opt : options_) {
+            if(opt->check_name(option_name)) {
+                return opt.get();
+            }
+        }
+        for(auto &subc : subcommands_) {
+            // also check down into nameless subcommands
+            if(subc->get_name().empty()) {
+                auto opt = subc->get_option_no_throw(option_name);
+                if(opt != nullptr) {
+                    return opt;
+                }
+            }
+        }
+        return nullptr;
+    }
+
+    /// Get an option by name (noexcept const version)
+    const Option *get_option_no_throw(std::string option_name) const noexcept {
+        for(const Option_p &opt : options_) {
+            if(opt->check_name(option_name)) {
+                return opt.get();
+            }
+        }
+        for(const auto &subc : subcommands_) {
+            // also check down into nameless subcommands
+            if(subc->get_name().empty()) {
+                auto opt = subc->get_option_no_throw(option_name);
+                if(opt != nullptr) {
+                    return opt;
+                }
+            }
+        }
+        return nullptr;
+    }
+
+    /// Get an option by name
+    const Option *get_option(std::string option_name) const {
+        auto opt = get_option_no_throw(option_name);
+        if(opt == nullptr) {
+            throw OptionNotFound(option_name);
+        }
+        return opt;
+    }
+
+    /// Get an option by name (non-const version)
+    Option *get_option(std::string option_name) {
+        auto opt = get_option_no_throw(option_name);
+        if(opt == nullptr) {
+            throw OptionNotFound(option_name);
+        }
+        return opt;
+    }
+
+    /// Shortcut bracket operator for getting a pointer to an option
+    const Option *operator[](const std::string &option_name) const { return get_option(option_name); }
+
+    /// Shortcut bracket operator for getting a pointer to an option
+    const Option *operator[](const char *option_name) const { return get_option(option_name); }
+
+    /// Check the status of ignore_case
+    bool get_ignore_case() const { return ignore_case_; }
+
+    /// Check the status of ignore_underscore
+    bool get_ignore_underscore() const { return ignore_underscore_; }
+
+    /// Check the status of fallthrough
+    bool get_fallthrough() const { return fallthrough_; }
+
+    /// Check the status of the allow windows style options
+    bool get_allow_windows_style_options() const { return allow_windows_style_options_; }
+
+    /// Check the status of the allow windows style options
+    bool get_positionals_at_end() const { return positionals_at_end_; }
+
+    /// Check the status of the allow windows style options
+    bool get_configurable() const { return configurable_; }
+
+    /// Get the group of this subcommand
+    const std::string &get_group() const { return group_; }
+
+    /// Generate and return the footer.
+    std::string get_footer() const { return (footer_callback_) ? footer_callback_() + '\n' + footer_ : footer_; }
+
+    /// Get the required min subcommand value
+    std::size_t get_require_subcommand_min() const { return require_subcommand_min_; }
+
+    /// Get the required max subcommand value
+    std::size_t get_require_subcommand_max() const { return require_subcommand_max_; }
+
+    /// Get the required min option value
+    std::size_t get_require_option_min() const { return require_option_min_; }
+
+    /// Get the required max option value
+    std::size_t get_require_option_max() const { return require_option_max_; }
+
+    /// Get the prefix command status
+    bool get_prefix_command() const { return prefix_command_; }
+
+    /// Get the status of allow extras
+    bool get_allow_extras() const { return allow_extras_; }
+
+    /// Get the status of required
+    bool get_required() const { return required_; }
+
+    /// Get the status of disabled
+    bool get_disabled() const { return disabled_; }
+
+    /// Get the status of silence
+    bool get_silent() const { return silent_; }
+
+    /// Get the status of disabled
+    bool get_immediate_callback() const { return immediate_callback_; }
+
+    /// Get the status of disabled by default
+    bool get_disabled_by_default() const { return (default_startup == startup_mode::disabled); }
+
+    /// Get the status of disabled by default
+    bool get_enabled_by_default() const { return (default_startup == startup_mode::enabled); }
+    /// Get the status of validating positionals
+    bool get_validate_positionals() const { return validate_positionals_; }
+
+    /// Get the status of allow extras
+    config_extras_mode get_allow_config_extras() const { return allow_config_extras_; }
+
+    /// Get a pointer to the help flag.
+    Option *get_help_ptr() { return help_ptr_; }
+
+    /// Get a pointer to the help flag. (const)
+    const Option *get_help_ptr() const { return help_ptr_; }
+
+    /// Get a pointer to the help all flag. (const)
+    const Option *get_help_all_ptr() const { return help_all_ptr_; }
+
+    /// Get a pointer to the config option.
+    Option *get_config_ptr() { return config_ptr_; }
+
+    /// Get a pointer to the config option. (const)
+    const Option *get_config_ptr() const { return config_ptr_; }
+
+    /// Get a pointer to the version option.
+    Option *get_version_ptr() { return version_ptr_; }
+
+    /// Get a pointer to the version option. (const)
+    const Option *get_version_ptr() const { return version_ptr_; }
+
+    /// Get the parent of this subcommand (or nullptr if main app)
+    App *get_parent() { return parent_; }
+
+    /// Get the parent of this subcommand (or nullptr if main app) (const version)
+    const App *get_parent() const { return parent_; }
+
+    /// Get the name of the current app
+    const std::string &get_name() const { return name_; }
+
+    /// Get the aliases of the current app
+    const std::vector<std::string> &get_aliases() const { return aliases_; }
+
+    /// clear all the aliases of the current App
+    App *clear_aliases() {
+        aliases_.clear();
+        return this;
+    }
+
+    /// Get a display name for an app
+    std::string get_display_name(bool with_aliases = false) const {
+        if(name_.empty()) {
+            return std::string("[Option Group: ") + get_group() + "]";
+        }
+        if(aliases_.empty() || !with_aliases) {
+            return name_;
+        }
+        std::string dispname = name_;
+        for(const auto &lalias : aliases_) {
+            dispname.push_back(',');
+            dispname.push_back(' ');
+            dispname.append(lalias);
+        }
+        return dispname;
+    }
+
+    /// Check the name, case insensitive and underscore insensitive if set
+    bool check_name(std::string name_to_check) const {
+        std::string local_name = name_;
+        if(ignore_underscore_) {
+            local_name = detail::remove_underscore(name_);
+            name_to_check = detail::remove_underscore(name_to_check);
+        }
+        if(ignore_case_) {
+            local_name = detail::to_lower(name_);
+            name_to_check = detail::to_lower(name_to_check);
+        }
+
+        if(local_name == name_to_check) {
+            return true;
+        }
+        for(auto les : aliases_) {
+            if(ignore_underscore_) {
+                les = detail::remove_underscore(les);
+            }
+            if(ignore_case_) {
+                les = detail::to_lower(les);
+            }
+            if(les == name_to_check) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /// Get the groups available directly from this option (in order)
+    std::vector<std::string> get_groups() const {
+        std::vector<std::string> groups;
+
+        for(const Option_p &opt : options_) {
+            // Add group if it is not already in there
+            if(std::find(groups.begin(), groups.end(), opt->get_group()) == groups.end()) {
+                groups.push_back(opt->get_group());
+            }
+        }
+
+        return groups;
+    }
+
+    /// This gets a vector of pointers with the original parse order
+    const std::vector<Option *> &parse_order() const { return parse_order_; }
+
+    /// This returns the missing options from the current subcommand
+    std::vector<std::string> remaining(bool recurse = false) const {
+        std::vector<std::string> miss_list;
+        for(const std::pair<detail::Classifier, std::string> &miss : missing_) {
+            miss_list.push_back(std::get<1>(miss));
+        }
+        // Get from a subcommand that may allow extras
+        if(recurse) {
+            if(!allow_extras_) {
+                for(const auto &sub : subcommands_) {
+                    if(sub->name_.empty() && !sub->missing_.empty()) {
+                        for(const std::pair<detail::Classifier, std::string> &miss : sub->missing_) {
+                            miss_list.push_back(std::get<1>(miss));
+                        }
+                    }
+                }
+            }
+            // Recurse into subcommands
+
+            for(const App *sub : parsed_subcommands_) {
+                std::vector<std::string> output = sub->remaining(recurse);
+                std::copy(std::begin(output), std::end(output), std::back_inserter(miss_list));
+            }
+        }
+        return miss_list;
+    }
+
+    /// This returns the missing options in a form ready for processing by another command line program
+    std::vector<std::string> remaining_for_passthrough(bool recurse = false) const {
+        std::vector<std::string> miss_list = remaining(recurse);
+        std::reverse(std::begin(miss_list), std::end(miss_list));
+        return miss_list;
+    }
+
+    /// This returns the number of remaining options, minus the -- separator
+    std::size_t remaining_size(bool recurse = false) const {
+        auto remaining_options = static_cast<std::size_t>(std::count_if(
+            std::begin(missing_), std::end(missing_), [](const std::pair<detail::Classifier, std::string> &val) {
+                return val.first != detail::Classifier::POSITIONAL_MARK;
+            }));
+
+        if(recurse) {
+            for(const App_p &sub : subcommands_) {
+                remaining_options += sub->remaining_size(recurse);
+            }
+        }
+        return remaining_options;
+    }
+
+    ///@}
+
+  protected:
+    /// Check the options to make sure there are no conflicts.
+    ///
+    /// Currently checks to see if multiple positionals exist with unlimited args and checks if the min and max options
+    /// are feasible
+    void _validate() const {
+        // count the number of positional only args
+        auto pcount = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
+            return opt->get_items_expected_max() >= detail::expected_max_vector_size && !opt->nonpositional();
+        });
+        if(pcount > 1) {
+            auto pcount_req = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) {
+                return opt->get_items_expected_max() >= detail::expected_max_vector_size && !opt->nonpositional() &&
+                       opt->get_required();
+            });
+            if(pcount - pcount_req > 1) {
+                throw InvalidError(name_);
+            }
+        }
+
+        std::size_t nameless_subs{0};
+        for(const App_p &app : subcommands_) {
+            app->_validate();
+            if(app->get_name().empty())
+                ++nameless_subs;
+        }
+
+        if(require_option_min_ > 0) {
+            if(require_option_max_ > 0) {
+                if(require_option_max_ < require_option_min_) {
+                    throw(InvalidError("Required min options greater than required max options",
+                                       ExitCodes::InvalidError));
+                }
+            }
+            if(require_option_min_ > (options_.size() + nameless_subs)) {
+                throw(InvalidError("Required min options greater than number of available options",
+                                   ExitCodes::InvalidError));
+            }
+        }
+    }
+
+    /// configure subcommands to enable parsing through the current object
+    /// set the correct fallthrough and prefix for nameless subcommands and manage the automatic enable or disable
+    /// makes sure parent is set correctly
+    void _configure() {
+        if(default_startup == startup_mode::enabled) {
+            disabled_ = false;
+        } else if(default_startup == startup_mode::disabled) {
+            disabled_ = true;
+        }
+        for(const App_p &app : subcommands_) {
+            if(app->has_automatic_name_) {
+                app->name_.clear();
+            }
+            if(app->name_.empty()) {
+                app->fallthrough_ = false;  // make sure fallthrough_ is false to prevent infinite loop
+                app->prefix_command_ = false;
+            }
+            // make sure the parent is set to be this object in preparation for parse
+            app->parent_ = this;
+            app->_configure();
+        }
+    }
+
+    /// Internal function to run (App) callback, bottom up
+    void run_callback(bool final_mode = false, bool suppress_final_callback = false) {
+        pre_callback();
+        // in the main app if immediate_callback_ is set it runs the main callback before the used subcommands
+        if(!final_mode && parse_complete_callback_) {
+            parse_complete_callback_();
+        }
+        // run the callbacks for the received subcommands
+        for(App *subc : get_subcommands()) {
+            subc->run_callback(true, suppress_final_callback);
+        }
+        // now run callbacks for option_groups
+        for(auto &subc : subcommands_) {
+            if(subc->name_.empty() && subc->count_all() > 0) {
+                subc->run_callback(true, suppress_final_callback);
+            }
+        }
+
+        // finally run the main callback
+        if(final_callback_ && (parsed_ > 0) && (!suppress_final_callback)) {
+            if(!name_.empty() || count_all() > 0 || parent_ == nullptr) {
+                final_callback_();
+            }
+        }
+    }
+
+    /// Check to see if a subcommand is valid. Give up immediately if subcommand max has been reached.
+    bool _valid_subcommand(const std::string &current, bool ignore_used = true) const {
+        // Don't match if max has been reached - but still check parents
+        if(require_subcommand_max_ != 0 && parsed_subcommands_.size() >= require_subcommand_max_) {
+            return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used);
+        }
+        auto com = _find_subcommand(current, true, ignore_used);
+        if(com != nullptr) {
+            return true;
+        }
+        // Check parent if exists, else return false
+        return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used);
+    }
+
+    /// Selects a Classifier enum based on the type of the current argument
+    detail::Classifier _recognize(const std::string &current, bool ignore_used_subcommands = true) const {
+        std::string dummy1, dummy2;
+
+        if(current == "--")
+            return detail::Classifier::POSITIONAL_MARK;
+        if(_valid_subcommand(current, ignore_used_subcommands))
+            return detail::Classifier::SUBCOMMAND;
+        if(detail::split_long(current, dummy1, dummy2))
+            return detail::Classifier::LONG;
+        if(detail::split_short(current, dummy1, dummy2)) {
+            if(dummy1[0] >= '0' && dummy1[0] <= '9') {
+                if(get_option_no_throw(std::string{'-', dummy1[0]}) == nullptr) {
+                    return detail::Classifier::NONE;
+                }
+            }
+            return detail::Classifier::SHORT;
+        }
+        if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2)))
+            return detail::Classifier::WINDOWS_STYLE;
+        if((current == "++") && !name_.empty() && parent_ != nullptr)
+            return detail::Classifier::SUBCOMMAND_TERMINATOR;
+        return detail::Classifier::NONE;
+    }
+
+    // The parse function is now broken into several parts, and part of process
+
+    /// Read and process a configuration file (main app only)
+    void _process_config_file() {
+        if(config_ptr_ != nullptr) {
+            bool config_required = config_ptr_->get_required();
+            auto file_given = config_ptr_->count() > 0;
+            auto config_files = config_ptr_->as<std::vector<std::string>>();
+            if(config_files.empty() || config_files.front().empty()) {
+                if(config_required) {
+                    throw FileError::Missing("no specified config file");
+                }
+                return;
+            }
+            for(auto rit = config_files.rbegin(); rit != config_files.rend(); ++rit) {
+                const auto &config_file = *rit;
+                auto path_result = detail::check_path(config_file.c_str());
+                if(path_result == detail::path_type::file) {
+                    try {
+                        std::vector<ConfigItem> values = config_formatter_->from_file(config_file);
+                        _parse_config(values);
+                        if(!file_given) {
+                            config_ptr_->add_result(config_file);
+                        }
+                    } catch(const FileError &) {
+                        if(config_required || file_given)
+                            throw;
+                    }
+                } else if(config_required || file_given) {
+                    throw FileError::Missing(config_file);
+                }
+            }
+        }
+    }
+
+    /// Get envname options if not yet passed. Runs on *all* subcommands.
+    void _process_env() {
+        for(const Option_p &opt : options_) {
+            if(opt->count() == 0 && !opt->envname_.empty()) {
+                char *buffer = nullptr;
+                std::string ename_string;
+
+#ifdef _MSC_VER
+                // Windows version
+                std::size_t sz = 0;
+                if(_dupenv_s(&buffer, &sz, opt->envname_.c_str()) == 0 && buffer != nullptr) {
+                    ename_string = std::string(buffer);
+                    free(buffer);
+                }
+#else
+                // This also works on Windows, but gives a warning
+                buffer = std::getenv(opt->envname_.c_str());
+                if(buffer != nullptr)
+                    ename_string = std::string(buffer);
+#endif
+
+                if(!ename_string.empty()) {
+                    opt->add_result(ename_string);
+                }
+            }
+        }
+
+        for(App_p &sub : subcommands_) {
+            if(sub->get_name().empty() || !sub->parse_complete_callback_)
+                sub->_process_env();
+        }
+    }
+
+    /// Process callbacks. Runs on *all* subcommands.
+    void _process_callbacks() {
+
+        for(App_p &sub : subcommands_) {
+            // process the priority option_groups first
+            if(sub->get_name().empty() && sub->parse_complete_callback_) {
+                if(sub->count_all() > 0) {
+                    sub->_process_callbacks();
+                    sub->run_callback();
+                }
+            }
+        }
+
+        for(const Option_p &opt : options_) {
+            if((*opt) && !opt->get_callback_run()) {
+                opt->run_callback();
+            }
+        }
+        for(App_p &sub : subcommands_) {
+            if(!sub->parse_complete_callback_) {
+                sub->_process_callbacks();
+            }
+        }
+    }
+
+    /// Run help flag processing if any are found.
+    ///
+    /// The flags allow recursive calls to remember if there was a help flag on a parent.
+    void _process_help_flags(bool trigger_help = false, bool trigger_all_help = false) const {
+        const Option *help_ptr = get_help_ptr();
+        const Option *help_all_ptr = get_help_all_ptr();
+
+        if(help_ptr != nullptr && help_ptr->count() > 0)
+            trigger_help = true;
+        if(help_all_ptr != nullptr && help_all_ptr->count() > 0)
+            trigger_all_help = true;
+
+        // If there were parsed subcommands, call those. First subcommand wins if there are multiple ones.
+        if(!parsed_subcommands_.empty()) {
+            for(const App *sub : parsed_subcommands_)
+                sub->_process_help_flags(trigger_help, trigger_all_help);
+
+            // Only the final subcommand should call for help. All help wins over help.
+        } else if(trigger_all_help) {
+            throw CallForAllHelp();
+        } else if(trigger_help) {
+            throw CallForHelp();
+        }
+    }
+
+    /// Verify required options and cross requirements. Subcommands too (only if selected).
+    void _process_requirements() {
+        // check excludes
+        bool excluded{false};
+        std::string excluder;
+        for(auto &opt : exclude_options_) {
+            if(opt->count() > 0) {
+                excluded = true;
+                excluder = opt->get_name();
+            }
+        }
+        for(auto &subc : exclude_subcommands_) {
+            if(subc->count_all() > 0) {
+                excluded = true;
+                excluder = subc->get_display_name();
+            }
+        }
+        if(excluded) {
+            if(count_all() > 0) {
+                throw ExcludesError(get_display_name(), excluder);
+            }
+            // if we are excluded but didn't receive anything, just return
+            return;
+        }
+
+        // check excludes
+        bool missing_needed{false};
+        std::string missing_need;
+        for(auto &opt : need_options_) {
+            if(opt->count() == 0) {
+                missing_needed = true;
+                missing_need = opt->get_name();
+            }
+        }
+        for(auto &subc : need_subcommands_) {
+            if(subc->count_all() == 0) {
+                missing_needed = true;
+                missing_need = subc->get_display_name();
+            }
+        }
+        if(missing_needed) {
+            if(count_all() > 0) {
+                throw RequiresError(get_display_name(), missing_need);
+            }
+            // if we missing something but didn't have any options, just return
+            return;
+        }
+
+        std::size_t used_options = 0;
+        for(const Option_p &opt : options_) {
+
+            if(opt->count() != 0) {
+                ++used_options;
+            }
+            // Required but empty
+            if(opt->get_required() && opt->count() == 0) {
+                throw RequiredError(opt->get_name());
+            }
+            // Requires
+            for(const Option *opt_req : opt->needs_)
+                if(opt->count() > 0 && opt_req->count() == 0)
+                    throw RequiresError(opt->get_name(), opt_req->get_name());
+            // Excludes
+            for(const Option *opt_ex : opt->excludes_)
+                if(opt->count() > 0 && opt_ex->count() != 0)
+                    throw ExcludesError(opt->get_name(), opt_ex->get_name());
+        }
+        // check for the required number of subcommands
+        if(require_subcommand_min_ > 0) {
+            auto selected_subcommands = get_subcommands();
+            if(require_subcommand_min_ > selected_subcommands.size())
+                throw RequiredError::Subcommand(require_subcommand_min_);
+        }
+
+        // Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item.
+
+        // run this loop to check how many unnamed subcommands were actually used since they are considered options
+        // from the perspective of an App
+        for(App_p &sub : subcommands_) {
+            if(sub->disabled_)
+                continue;
+            if(sub->name_.empty() && sub->count_all() > 0) {
+                ++used_options;
+            }
+        }
+
+        if(require_option_min_ > used_options || (require_option_max_ > 0 && require_option_max_ < used_options)) {
+            auto option_list = detail::join(options_, [this](const Option_p &ptr) {
+                if(ptr.get() == help_ptr_ || ptr.get() == help_all_ptr_) {
+                    return std::string{};
+                }
+                return ptr->get_name(false, true);
+            });
+
+            auto subc_list = get_subcommands([](App *app) { return ((app->get_name().empty()) && (!app->disabled_)); });
+            if(!subc_list.empty()) {
+                option_list += "," + detail::join(subc_list, [](const App *app) { return app->get_display_name(); });
+            }
+            throw RequiredError::Option(require_option_min_, require_option_max_, used_options, option_list);
+        }
+
+        // now process the requirements for subcommands if needed
+        for(App_p &sub : subcommands_) {
+            if(sub->disabled_)
+                continue;
+            if(sub->name_.empty() && sub->required_ == false) {
+                if(sub->count_all() == 0) {
+                    if(require_option_min_ > 0 && require_option_min_ <= used_options) {
+                        continue;
+                        // if we have met the requirement and there is nothing in this option group skip checking
+                        // requirements
+                    }
+                    if(require_option_max_ > 0 && used_options >= require_option_min_) {
+                        continue;
+                        // if we have met the requirement and there is nothing in this option group skip checking
+                        // requirements
+                    }
+                }
+            }
+            if(sub->count() > 0 || sub->name_.empty()) {
+                sub->_process_requirements();
+            }
+
+            if(sub->required_ && sub->count_all() == 0) {
+                throw(CLI::RequiredError(sub->get_display_name()));
+            }
+        }
+    }
+
+    /// Process callbacks and such.
+    void _process() {
+        CLI::FileError fe("ne");
+        bool caught_error{false};
+        try {
+            // the config file might generate a FileError but that should not be processed until later in the process
+            // to allow for help, version and other errors to generate first.
+            _process_config_file();
+            // process env shouldn't throw but no reason to process it if config generated an error
+            _process_env();
+        } catch(const CLI::FileError &fe2) {
+            fe = fe2;
+            caught_error = true;
+        }
+        // callbacks and help_flags can generate exceptions which should take priority over the config file error if one
+        // exists
+        _process_callbacks();
+        _process_help_flags();
+
+        if(caught_error) {
+            throw CLI::FileError(std::move(fe));
+        }
+
+        _process_requirements();
+    }
+
+    /// Throw an error if anything is left over and should not be.
+    void _process_extras() {
+        if(!(allow_extras_ || prefix_command_)) {
+            std::size_t num_left_over = remaining_size();
+            if(num_left_over > 0) {
+                throw ExtrasError(name_, remaining(false));
+            }
+        }
+
+        for(App_p &sub : subcommands_) {
+            if(sub->count() > 0)
+                sub->_process_extras();
+        }
+    }
+
+    /// Throw an error if anything is left over and should not be.
+    /// Modifies the args to fill in the missing items before throwing.
+    void _process_extras(std::vector<std::string> &args) {
+        if(!(allow_extras_ || prefix_command_)) {
+            std::size_t num_left_over = remaining_size();
+            if(num_left_over > 0) {
+                args = remaining(false);
+                throw ExtrasError(name_, args);
+            }
+        }
+
+        for(App_p &sub : subcommands_) {
+            if(sub->count() > 0)
+                sub->_process_extras(args);
+        }
+    }
+
+    /// Internal function to recursively increment the parsed counter on the current app as well unnamed subcommands
+    void increment_parsed() {
+        ++parsed_;
+        for(App_p &sub : subcommands_) {
+            if(sub->get_name().empty())
+                sub->increment_parsed();
+        }
+    }
+    /// Internal parse function
+    void _parse(std::vector<std::string> &args) {
+        increment_parsed();
+        _trigger_pre_parse(args.size());
+        bool positional_only = false;
+
+        while(!args.empty()) {
+            if(!_parse_single(args, positional_only)) {
+                break;
+            }
+        }
+
+        if(parent_ == nullptr) {
+            _process();
+
+            // Throw error if any items are left over (depending on settings)
+            _process_extras(args);
+
+            // Convert missing (pairs) to extras (string only) ready for processing in another app
+            args = remaining_for_passthrough(false);
+        } else if(parse_complete_callback_) {
+            _process_env();
+            _process_callbacks();
+            _process_help_flags();
+            _process_requirements();
+            run_callback(false, true);
+        }
+    }
+
+    /// Internal parse function
+    void _parse(std::vector<std::string> &&args) {
+        // this can only be called by the top level in which case parent == nullptr by definition
+        // operation is simplified
+        increment_parsed();
+        _trigger_pre_parse(args.size());
+        bool positional_only = false;
+
+        while(!args.empty()) {
+            _parse_single(args, positional_only);
+        }
+        _process();
+
+        // Throw error if any items are left over (depending on settings)
+        _process_extras();
+    }
+
+    /// Internal function to parse a stream
+    void _parse_stream(std::istream &input) {
+        auto values = config_formatter_->from_config(input);
+        _parse_config(values);
+        increment_parsed();
+        _trigger_pre_parse(values.size());
+        _process();
+
+        // Throw error if any items are left over (depending on settings)
+        _process_extras();
+    }
+
+    /// Parse one config param, return false if not found in any subcommand, remove if it is
+    ///
+    /// If this has more than one dot.separated.name, go into the subcommand matching it
+    /// Returns true if it managed to find the option, if false you'll need to remove the arg manually.
+    void _parse_config(const std::vector<ConfigItem> &args) {
+        for(const ConfigItem &item : args) {
+            if(!_parse_single_config(item) && allow_config_extras_ == config_extras_mode::error)
+                throw ConfigError::Extras(item.fullname());
+        }
+    }
+
+    /// Fill in a single config option
+    bool _parse_single_config(const ConfigItem &item, std::size_t level = 0) {
+        if(level < item.parents.size()) {
+            try {
+                auto subcom = get_subcommand(item.parents.at(level));
+                auto result = subcom->_parse_single_config(item, level + 1);
+
+                return result;
+            } catch(const OptionNotFound &) {
+                return false;
+            }
+        }
+        // check for section open
+        if(item.name == "++") {
+            if(configurable_) {
+                increment_parsed();
+                _trigger_pre_parse(2);
+                if(parent_ != nullptr) {
+                    parent_->parsed_subcommands_.push_back(this);
+                }
+            }
+            return true;
+        }
+        // check for section close
+        if(item.name == "--") {
+            if(configurable_) {
+                _process_callbacks();
+                _process_requirements();
+                run_callback();
+            }
+            return true;
+        }
+        Option *op = get_option_no_throw("--" + item.name);
+        if(op == nullptr) {
+            if(item.name.size() == 1) {
+                op = get_option_no_throw("-" + item.name);
+            }
+        }
+        if(op == nullptr) {
+            op = get_option_no_throw(item.name);
+        }
+        if(op == nullptr) {
+            // If the option was not present
+            if(get_allow_config_extras() == config_extras_mode::capture)
+                // Should we worry about classifying the extras properly?
+                missing_.emplace_back(detail::Classifier::NONE, item.fullname());
+            return false;
+        }
+
+        if(!op->get_configurable()) {
+            if(get_allow_config_extras() == config_extras_mode::ignore_all) {
+                return false;
+            }
+            throw ConfigError::NotConfigurable(item.fullname());
+        }
+
+        if(op->empty()) {
+            // Flag parsing
+            if(op->get_expected_min() == 0) {
+                auto res = config_formatter_->to_flag(item);
+                res = op->get_flag_value(item.name, res);
+
+                op->add_result(res);
+
+            } else {
+                op->add_result(item.inputs);
+                op->run_callback();
+            }
+        }
+
+        return true;
+    }
+
+    /// Parse "one" argument (some may eat more than one), delegate to parent if fails, add to missing if missing
+    /// from main return false if the parse has failed and needs to return to parent
+    bool _parse_single(std::vector<std::string> &args, bool &positional_only) {
+        bool retval = true;
+        detail::Classifier classifier = positional_only ? detail::Classifier::NONE : _recognize(args.back());
+        switch(classifier) {
+        case detail::Classifier::POSITIONAL_MARK:
+            args.pop_back();
+            positional_only = true;
+            if((!_has_remaining_positionals()) && (parent_ != nullptr)) {
+                retval = false;
+            } else {
+                _move_to_missing(classifier, "--");
+            }
+            break;
+        case detail::Classifier::SUBCOMMAND_TERMINATOR:
+            // treat this like a positional mark if in the parent app
+            args.pop_back();
+            retval = false;
+            break;
+        case detail::Classifier::SUBCOMMAND:
+            retval = _parse_subcommand(args);
+            break;
+        case detail::Classifier::LONG:
+        case detail::Classifier::SHORT:
+        case detail::Classifier::WINDOWS_STYLE:
+            // If already parsed a subcommand, don't accept options_
+            _parse_arg(args, classifier);
+            break;
+        case detail::Classifier::NONE:
+            // Probably a positional or something for a parent (sub)command
+            retval = _parse_positional(args, false);
+            if(retval && positionals_at_end_) {
+                positional_only = true;
+            }
+            break;
+            // LCOV_EXCL_START
+        default:
+            throw HorribleError("unrecognized classifier (you should not see this!)");
+            // LCOV_EXCL_STOP
+        }
+        return retval;
+    }
+
+    /// Count the required remaining positional arguments
+    std::size_t _count_remaining_positionals(bool required_only = false) const {
+        std::size_t retval = 0;
+        for(const Option_p &opt : options_) {
+            if(opt->get_positional() && (!required_only || opt->get_required())) {
+                if(opt->get_items_expected_min() > 0 &&
+                   static_cast<int>(opt->count()) < opt->get_items_expected_min()) {
+                    retval += static_cast<std::size_t>(opt->get_items_expected_min()) - opt->count();
+                }
+            }
+        }
+        return retval;
+    }
+
+    /// Count the required remaining positional arguments
+    bool _has_remaining_positionals() const {
+        for(const Option_p &opt : options_) {
+            if(opt->get_positional() && ((static_cast<int>(opt->count()) < opt->get_items_expected_min()))) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /// Parse a positional, go up the tree to check
+    /// @param haltOnSubcommand if set to true the operation will not process subcommands merely return false
+    /// Return true if the positional was used false otherwise
+    bool _parse_positional(std::vector<std::string> &args, bool haltOnSubcommand) {
+
+        const std::string &positional = args.back();
+
+        if(positionals_at_end_) {
+            // deal with the case of required arguments at the end which should take precedence over other arguments
+            auto arg_rem = args.size();
+            auto remreq = _count_remaining_positionals(true);
+            if(arg_rem <= remreq) {
+                for(const Option_p &opt : options_) {
+                    if(opt->get_positional() && opt->required_) {
+                        if(static_cast<int>(opt->count()) < opt->get_items_expected_min()) {
+                            if(validate_positionals_) {
+                                std::string pos = positional;
+                                pos = opt->_validate(pos, 0);
+                                if(!pos.empty()) {
+                                    continue;
+                                }
+                            }
+                            opt->add_result(positional);
+                            parse_order_.push_back(opt.get());
+                            args.pop_back();
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        for(const Option_p &opt : options_) {
+            // Eat options, one by one, until done
+            if(opt->get_positional() &&
+               (static_cast<int>(opt->count()) < opt->get_items_expected_min() || opt->get_allow_extra_args())) {
+                if(validate_positionals_) {
+                    std::string pos = positional;
+                    pos = opt->_validate(pos, 0);
+                    if(!pos.empty()) {
+                        continue;
+                    }
+                }
+                opt->add_result(positional);
+                parse_order_.push_back(opt.get());
+                args.pop_back();
+                return true;
+            }
+        }
+
+        for(auto &subc : subcommands_) {
+            if((subc->name_.empty()) && (!subc->disabled_)) {
+                if(subc->_parse_positional(args, false)) {
+                    if(!subc->pre_parse_called_) {
+                        subc->_trigger_pre_parse(args.size());
+                    }
+                    return true;
+                }
+            }
+        }
+        // let the parent deal with it if possible
+        if(parent_ != nullptr && fallthrough_)
+            return _get_fallthrough_parent()->_parse_positional(args, static_cast<bool>(parse_complete_callback_));
+
+        /// Try to find a local subcommand that is repeated
+        auto com = _find_subcommand(args.back(), true, false);
+        if(com != nullptr && (require_subcommand_max_ == 0 || require_subcommand_max_ > parsed_subcommands_.size())) {
+            if(haltOnSubcommand) {
+                return false;
+            }
+            args.pop_back();
+            com->_parse(args);
+            return true;
+        }
+        /// now try one last gasp at subcommands that have been executed before, go to root app and try to find a
+        /// subcommand in a broader way, if one exists let the parent deal with it
+        auto parent_app = (parent_ != nullptr) ? _get_fallthrough_parent() : this;
+        com = parent_app->_find_subcommand(args.back(), true, false);
+        if(com != nullptr && (com->parent_->require_subcommand_max_ == 0 ||
+                              com->parent_->require_subcommand_max_ > com->parent_->parsed_subcommands_.size())) {
+            return false;
+        }
+
+        if(positionals_at_end_) {
+            throw CLI::ExtrasError(name_, args);
+        }
+        /// If this is an option group don't deal with it
+        if(parent_ != nullptr && name_.empty()) {
+            return false;
+        }
+        /// We are out of other options this goes to missing
+        _move_to_missing(detail::Classifier::NONE, positional);
+        args.pop_back();
+        if(prefix_command_) {
+            while(!args.empty()) {
+                _move_to_missing(detail::Classifier::NONE, args.back());
+                args.pop_back();
+            }
+        }
+
+        return true;
+    }
+
+    /// Locate a subcommand by name with two conditions, should disabled subcommands be ignored, and should used
+    /// subcommands be ignored
+    App *_find_subcommand(const std::string &subc_name, bool ignore_disabled, bool ignore_used) const noexcept {
+        for(const App_p &com : subcommands_) {
+            if(com->disabled_ && ignore_disabled)
+                continue;
+            if(com->get_name().empty()) {
+                auto subc = com->_find_subcommand(subc_name, ignore_disabled, ignore_used);
+                if(subc != nullptr) {
+                    return subc;
+                }
+            }
+            if(com->check_name(subc_name)) {
+                if((!*com) || !ignore_used)
+                    return com.get();
+            }
+        }
+        return nullptr;
+    }
+
+    /// Parse a subcommand, modify args and continue
+    ///
+    /// Unlike the others, this one will always allow fallthrough
+    /// return true if the subcommand was processed false otherwise
+    bool _parse_subcommand(std::vector<std::string> &args) {
+        if(_count_remaining_positionals(/* required */ true) > 0) {
+            _parse_positional(args, false);
+            return true;
+        }
+        auto com = _find_subcommand(args.back(), true, true);
+        if(com != nullptr) {
+            args.pop_back();
+            if(!com->silent_) {
+                parsed_subcommands_.push_back(com);
+            }
+            com->_parse(args);
+            auto parent_app = com->parent_;
+            while(parent_app != this) {
+                parent_app->_trigger_pre_parse(args.size());
+                if(!com->silent_) {
+                    parent_app->parsed_subcommands_.push_back(com);
+                }
+                parent_app = parent_app->parent_;
+            }
+            return true;
+        }
+
+        if(parent_ == nullptr)
+            throw HorribleError("Subcommand " + args.back() + " missing");
+        return false;
+    }
+
+    /// Parse a short (false) or long (true) argument, must be at the top of the list
+    /// return true if the argument was processed or false if nothing was done
+    bool _parse_arg(std::vector<std::string> &args, detail::Classifier current_type) {
+
+        std::string current = args.back();
+
+        std::string arg_name;
+        std::string value;
+        std::string rest;
+
+        switch(current_type) {
+        case detail::Classifier::LONG:
+            if(!detail::split_long(current, arg_name, value))
+                throw HorribleError("Long parsed but missing (you should not see this):" + args.back());
+            break;
+        case detail::Classifier::SHORT:
+            if(!detail::split_short(current, arg_name, rest))
+                throw HorribleError("Short parsed but missing! You should not see this");
+            break;
+        case detail::Classifier::WINDOWS_STYLE:
+            if(!detail::split_windows_style(current, arg_name, value))
+                throw HorribleError("windows option parsed but missing! You should not see this");
+            break;
+        case detail::Classifier::SUBCOMMAND:
+        case detail::Classifier::SUBCOMMAND_TERMINATOR:
+        case detail::Classifier::POSITIONAL_MARK:
+        case detail::Classifier::NONE:
+        default:
+            throw HorribleError("parsing got called with invalid option! You should not see this");
+        }
+
+        auto op_ptr =
+            std::find_if(std::begin(options_), std::end(options_), [arg_name, current_type](const Option_p &opt) {
+                if(current_type == detail::Classifier::LONG)
+                    return opt->check_lname(arg_name);
+                if(current_type == detail::Classifier::SHORT)
+                    return opt->check_sname(arg_name);
+                // this will only get called for detail::Classifier::WINDOWS_STYLE
+                return opt->check_lname(arg_name) || opt->check_sname(arg_name);
+            });
+
+        // Option not found
+        if(op_ptr == std::end(options_)) {
+            for(auto &subc : subcommands_) {
+                if(subc->name_.empty() && !subc->disabled_) {
+                    if(subc->_parse_arg(args, current_type)) {
+                        if(!subc->pre_parse_called_) {
+                            subc->_trigger_pre_parse(args.size());
+                        }
+                        return true;
+                    }
+                }
+            }
+            // If a subcommand, try the main command
+            if(parent_ != nullptr && fallthrough_)
+                return _get_fallthrough_parent()->_parse_arg(args, current_type);
+            // don't capture missing if this is a nameless subcommand
+            if(parent_ != nullptr && name_.empty()) {
+                return false;
+            }
+            // Otherwise, add to missing
+            args.pop_back();
+            _move_to_missing(current_type, current);
+            return true;
+        }
+
+        args.pop_back();
+
+        // Get a reference to the pointer to make syntax bearable
+        Option_p &op = *op_ptr;
+        /// if we require a separator add it here
+        if(op->get_inject_separator()) {
+            if(!op->results().empty() && !op->results().back().empty()) {
+                op->add_result(std::string{});
+            }
+        }
+        if(op->get_trigger_on_parse() && op->current_option_state_ == Option::option_state::callback_run) {
+            op->clear();
+        }
+        int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min());
+        int max_num = op->get_items_expected_max();
+        // check container like options to limit the argument size to a single type if the allow_extra_flags argument is
+        // set. 16 is somewhat arbitrary (needs to be at least 4)
+        if(max_num >= detail::expected_max_vector_size / 16 && !op->get_allow_extra_args()) {
+            auto tmax = op->get_type_size_max();
+            max_num = detail::checked_multiply(tmax, op->get_expected_min()) ? tmax : detail::expected_max_vector_size;
+        }
+        // Make sure we always eat the minimum for unlimited vectors
+        int collected = 0;     // total number of arguments collected
+        int result_count = 0;  // local variable for number of results in a single arg string
+        // deal with purely flag like things
+        if(max_num == 0) {
+            auto res = op->get_flag_value(arg_name, value);
+            op->add_result(res);
+            parse_order_.push_back(op.get());
+        } else if(!value.empty()) {  // --this=value
+            op->add_result(value, result_count);
+            parse_order_.push_back(op.get());
+            collected += result_count;
+            // -Trest
+        } else if(!rest.empty()) {
+            op->add_result(rest, result_count);
+            parse_order_.push_back(op.get());
+            rest = "";
+            collected += result_count;
+        }
+
+        // gather the minimum number of arguments
+        while(min_num > collected && !args.empty()) {
+            std::string current_ = args.back();
+            args.pop_back();
+            op->add_result(current_, result_count);
+            parse_order_.push_back(op.get());
+            collected += result_count;
+        }
+
+        if(min_num > collected) {  // if we have run out of arguments and the minimum was not met
+            throw ArgumentMismatch::TypedAtLeast(op->get_name(), min_num, op->get_type_name());
+        }
+
+        if(max_num > collected || op->get_allow_extra_args()) {  // we allow optional arguments
+            auto remreqpos = _count_remaining_positionals(true);
+            // we have met the minimum now optionally check up to the maximum
+            while((collected < max_num || op->get_allow_extra_args()) && !args.empty() &&
+                  _recognize(args.back(), false) == detail::Classifier::NONE) {
+                // If any required positionals remain, don't keep eating
+                if(remreqpos >= args.size()) {
+                    break;
+                }
+
+                op->add_result(args.back(), result_count);
+                parse_order_.push_back(op.get());
+                args.pop_back();
+                collected += result_count;
+            }
+
+            // Allow -- to end an unlimited list and "eat" it
+            if(!args.empty() && _recognize(args.back()) == detail::Classifier::POSITIONAL_MARK)
+                args.pop_back();
+            // optional flag that didn't receive anything now get the default value
+            if(min_num == 0 && max_num > 0 && collected == 0) {
+                auto res = op->get_flag_value(arg_name, std::string{});
+                op->add_result(res);
+                parse_order_.push_back(op.get());
+            }
+        }
+
+        // if we only partially completed a type then add an empty string for later processing
+        if(min_num > 0 && op->get_type_size_max() != min_num && (collected % op->get_type_size_max()) != 0) {
+            op->add_result(std::string{});
+        }
+        if(op->get_trigger_on_parse()) {
+            op->run_callback();
+        }
+        if(!rest.empty()) {
+            rest = "-" + rest;
+            args.push_back(rest);
+        }
+        return true;
+    }
+
+    /// Trigger the pre_parse callback if needed
+    void _trigger_pre_parse(std::size_t remaining_args) {
+        if(!pre_parse_called_) {
+            pre_parse_called_ = true;
+            if(pre_parse_callback_) {
+                pre_parse_callback_(remaining_args);
+            }
+        } else if(immediate_callback_) {
+            if(!name_.empty()) {
+                auto pcnt = parsed_;
+                auto extras = std::move(missing_);
+                clear();
+                parsed_ = pcnt;
+                pre_parse_called_ = true;
+                missing_ = std::move(extras);
+            }
+        }
+    }
+
+    /// Get the appropriate parent to fallthrough to which is the first one that has a name or the main app
+    App *_get_fallthrough_parent() {
+        if(parent_ == nullptr) {
+            throw(HorribleError("No Valid parent"));
+        }
+        auto fallthrough_parent = parent_;
+        while((fallthrough_parent->parent_ != nullptr) && (fallthrough_parent->get_name().empty())) {
+            fallthrough_parent = fallthrough_parent->parent_;
+        }
+        return fallthrough_parent;
+    }
+
+    /// Helper function to run through all possible comparisons of subcommand names to check there is no overlap
+    const std::string &_compare_subcommand_names(const App &subcom, const App &base) const {
+        static const std::string estring;
+        if(subcom.disabled_) {
+            return estring;
+        }
+        for(auto &subc : base.subcommands_) {
+            if(subc.get() != &subcom) {
+                if(subc->disabled_) {
+                    continue;
+                }
+                if(!subcom.get_name().empty()) {
+                    if(subc->check_name(subcom.get_name())) {
+                        return subcom.get_name();
+                    }
+                }
+                if(!subc->get_name().empty()) {
+                    if(subcom.check_name(subc->get_name())) {
+                        return subc->get_name();
+                    }
+                }
+                for(const auto &les : subcom.aliases_) {
+                    if(subc->check_name(les)) {
+                        return les;
+                    }
+                }
+                // this loop is needed in case of ignore_underscore or ignore_case on one but not the other
+                for(const auto &les : subc->aliases_) {
+                    if(subcom.check_name(les)) {
+                        return les;
+                    }
+                }
+                // if the subcommand is an option group we need to check deeper
+                if(subc->get_name().empty()) {
+                    auto &cmpres = _compare_subcommand_names(subcom, *subc);
+                    if(!cmpres.empty()) {
+                        return cmpres;
+                    }
+                }
+                // if the test subcommand is an option group we need to check deeper
+                if(subcom.get_name().empty()) {
+                    auto &cmpres = _compare_subcommand_names(*subc, subcom);
+                    if(!cmpres.empty()) {
+                        return cmpres;
+                    }
+                }
+            }
+        }
+        return estring;
+    }
+    /// Helper function to place extra values in the most appropriate position
+    void _move_to_missing(detail::Classifier val_type, const std::string &val) {
+        if(allow_extras_ || subcommands_.empty()) {
+            missing_.emplace_back(val_type, val);
+            return;
+        }
+        // allow extra arguments to be places in an option group if it is allowed there
+        for(auto &subc : subcommands_) {
+            if(subc->name_.empty() && subc->allow_extras_) {
+                subc->missing_.emplace_back(val_type, val);
+                return;
+            }
+        }
+        // if we haven't found any place to put them yet put them in missing
+        missing_.emplace_back(val_type, val);
+    }
+
+  public:
+    /// function that could be used by subclasses of App to shift options around into subcommands
+    void _move_option(Option *opt, App *app) {
+        if(opt == nullptr) {
+            throw OptionNotFound("the option is NULL");
+        }
+        // verify that the give app is actually a subcommand
+        bool found = false;
+        for(auto &subc : subcommands_) {
+            if(app == subc.get()) {
+                found = true;
+            }
+        }
+        if(!found) {
+            throw OptionNotFound("The Given app is not a subcommand");
+        }
+
+        if((help_ptr_ == opt) || (help_all_ptr_ == opt))
+            throw OptionAlreadyAdded("cannot move help options");
+
+        if(config_ptr_ == opt)
+            throw OptionAlreadyAdded("cannot move config file options");
+
+        auto iterator =
+            std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; });
+        if(iterator != std::end(options_)) {
+            const auto &opt_p = *iterator;
+            if(std::find_if(std::begin(app->options_), std::end(app->options_), [&opt_p](const Option_p &v) {
+                   return (*v == *opt_p);
+               }) == std::end(app->options_)) {
+                // only erase after the insertion was successful
+                app->options_.push_back(std::move(*iterator));
+                options_.erase(iterator);
+            } else {
+                throw OptionAlreadyAdded("option was not located: " + opt->get_name());
+            }
+        } else {
+            throw OptionNotFound("could not locate the given Option");
+        }
+    }
+};  // namespace CLI
+
+/// Extension of App to better manage groups of options
+class Option_group : public App {
+  public:
+    Option_group(std::string group_description, std::string group_name, App *parent)
+        : App(std::move(group_description), "", parent) {
+        group(group_name);
+        // option groups should have automatic fallthrough
+    }
+    using App::add_option;
+    /// Add an existing option to the Option_group
+    Option *add_option(Option *opt) {
+        if(get_parent() == nullptr) {
+            throw OptionNotFound("Unable to locate the specified option");
+        }
+        get_parent()->_move_option(opt, this);
+        return opt;
+    }
+    /// Add an existing option to the Option_group
+    void add_options(Option *opt) { add_option(opt); }
+    /// Add a bunch of options to the group
+    template <typename... Args> void add_options(Option *opt, Args... args) {
+        add_option(opt);
+        add_options(args...);
+    }
+    using App::add_subcommand;
+    /// Add an existing subcommand to be a member of an option_group
+    App *add_subcommand(App *subcom) {
+        App_p subc = subcom->get_parent()->get_subcommand_ptr(subcom);
+        subc->get_parent()->remove_subcommand(subcom);
+        add_subcommand(std::move(subc));
+        return subcom;
+    }
+};
+/// Helper function to enable one option group/subcommand when another is used
+inline void TriggerOn(App *trigger_app, App *app_to_enable) {
+    app_to_enable->enabled_by_default(false);
+    app_to_enable->disabled_by_default();
+    trigger_app->preparse_callback([app_to_enable](std::size_t) { app_to_enable->disabled(false); });
+}
+
+/// Helper function to enable one option group/subcommand when another is used
+inline void TriggerOn(App *trigger_app, std::vector<App *> apps_to_enable) {
+    for(auto &app : apps_to_enable) {
+        app->enabled_by_default(false);
+        app->disabled_by_default();
+    }
+
+    trigger_app->preparse_callback([apps_to_enable](std::size_t) {
+        for(auto &app : apps_to_enable) {
+            app->disabled(false);
+        }
+    });
+}
+
+/// Helper function to disable one option group/subcommand when another is used
+inline void TriggerOff(App *trigger_app, App *app_to_enable) {
+    app_to_enable->disabled_by_default(false);
+    app_to_enable->enabled_by_default();
+    trigger_app->preparse_callback([app_to_enable](std::size_t) { app_to_enable->disabled(); });
+}
+
+/// Helper function to disable one option group/subcommand when another is used
+inline void TriggerOff(App *trigger_app, std::vector<App *> apps_to_enable) {
+    for(auto &app : apps_to_enable) {
+        app->disabled_by_default(false);
+        app->enabled_by_default();
+    }
+
+    trigger_app->preparse_callback([apps_to_enable](std::size_t) {
+        for(auto &app : apps_to_enable) {
+            app->disabled();
+        }
+    });
+}
+
+/// Helper function to mark an option as deprecated
+inline void deprecate_option(Option *opt, const std::string &replacement = "") {
+    Validator deprecate_warning{[opt, replacement](std::string &) {
+                                    std::cout << opt->get_name() << " is deprecated please use '" << replacement
+                                              << "' instead\n";
+                                    return std::string();
+                                },
+                                "DEPRECATED"};
+    deprecate_warning.application_index(0);
+    opt->check(deprecate_warning);
+    if(!replacement.empty()) {
+        opt->description(opt->get_description() + " DEPRECATED: please use '" + replacement + "' instead");
+    }
+}
+
+/// Helper function to mark an option as deprecated
+inline void deprecate_option(App *app, const std::string &option_name, const std::string &replacement = "") {
+    auto opt = app->get_option(option_name);
+    deprecate_option(opt, replacement);
+}
+
+/// Helper function to mark an option as deprecated
+inline void deprecate_option(App &app, const std::string &option_name, const std::string &replacement = "") {
+    auto opt = app.get_option(option_name);
+    deprecate_option(opt, replacement);
+}
+
+/// Helper function to mark an option as retired
+inline void retire_option(App *app, Option *opt) {
+    App temp;
+    auto option_copy = temp.add_option(opt->get_name(false, true))
+                           ->type_size(opt->get_type_size_min(), opt->get_type_size_max())
+                           ->expected(opt->get_expected_min(), opt->get_expected_max())
+                           ->allow_extra_args(opt->get_allow_extra_args());
+
+    app->remove_option(opt);
+    auto opt2 = app->add_option(option_copy->get_name(false, true), "option has been retired and has no effect")
+                    ->type_name("RETIRED")
+                    ->default_str("RETIRED")
+                    ->type_size(option_copy->get_type_size_min(), option_copy->get_type_size_max())
+                    ->expected(option_copy->get_expected_min(), option_copy->get_expected_max())
+                    ->allow_extra_args(option_copy->get_allow_extra_args());
+
+    Validator retired_warning{[opt2](std::string &) {
+                                  std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
+                                  return std::string();
+                              },
+                              ""};
+    retired_warning.application_index(0);
+    opt2->check(retired_warning);
+}
+
+/// Helper function to mark an option as retired
+inline void retire_option(App &app, Option *opt) { retire_option(&app, opt); }
+
+/// Helper function to mark an option as retired
+inline void retire_option(App *app, const std::string &option_name) {
+
+    auto opt = app->get_option_no_throw(option_name);
+    if(opt != nullptr) {
+        retire_option(app, opt);
+        return;
+    }
+    auto opt2 = app->add_option(option_name, "option has been retired and has no effect")
+                    ->type_name("RETIRED")
+                    ->expected(0, 1)
+                    ->default_str("RETIRED");
+    Validator retired_warning{[opt2](std::string &) {
+                                  std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n";
+                                  return std::string();
+                              },
+                              ""};
+    retired_warning.application_index(0);
+    opt2->check(retired_warning);
+}
+
+/// Helper function to mark an option as retired
+inline void retire_option(App &app, const std::string &option_name) { retire_option(&app, option_name); }
+
+namespace FailureMessage {
+
+/// Printout a clean, simple message on error (the default in CLI11 1.5+)
+inline std::string simple(const App *app, const Error &e) {
+    std::string header = std::string(e.what()) + "\n";
+    std::vector<std::string> names;
+
+    // Collect names
+    if(app->get_help_ptr() != nullptr)
+        names.push_back(app->get_help_ptr()->get_name());
+
+    if(app->get_help_all_ptr() != nullptr)
+        names.push_back(app->get_help_all_ptr()->get_name());
+
+    // If any names found, suggest those
+    if(!names.empty())
+        header += "Run with " + detail::join(names, " or ") + " for more information.\n";
+
+    return header;
+}
+
+/// Printout the full help string on error (if this fn is set, the old default for CLI11)
+inline std::string help(const App *app, const Error &e) {
+    std::string header = std::string("ERROR: ") + e.get_name() + ": " + e.what() + "\n";
+    header += app->help();
+    return header;
+}
+
+}  // namespace FailureMessage
+
+namespace detail {
+/// This class is simply to allow tests access to App's protected functions
+struct AppFriend {
+#ifdef CLI11_CPP14
+
+    /// Wrap _parse_short, perfectly forward arguments and return
+    template <typename... Args> static decltype(auto) parse_arg(App *app, Args &&...args) {
+        return app->_parse_arg(std::forward<Args>(args)...);
+    }
+
+    /// Wrap _parse_subcommand, perfectly forward arguments and return
+    template <typename... Args> static decltype(auto) parse_subcommand(App *app, Args &&...args) {
+        return app->_parse_subcommand(std::forward<Args>(args)...);
+    }
+#else
+    /// Wrap _parse_short, perfectly forward arguments and return
+    template <typename... Args>
+    static auto parse_arg(App *app, Args &&...args) ->
+        typename std::result_of<decltype (&App::_parse_arg)(App, Args...)>::type {
+        return app->_parse_arg(std::forward<Args>(args)...);
+    }
+
+    /// Wrap _parse_subcommand, perfectly forward arguments and return
+    template <typename... Args>
+    static auto parse_subcommand(App *app, Args &&...args) ->
+        typename std::result_of<decltype (&App::_parse_subcommand)(App, Args...)>::type {
+        return app->_parse_subcommand(std::forward<Args>(args)...);
+    }
+#endif
+    /// Wrap the fallthrough parent function to make sure that is working correctly
+    static App *get_fallthrough_parent(App *app) { return app->_get_fallthrough_parent(); }
+};
+}  // namespace detail
+
+// [CLI11:app_hpp:end]
+}  // namespace CLI
diff --git a/external/CLI11/include/CLI/CLI.hpp b/external/CLI11/include/CLI/CLI.hpp
new file mode 100644
index 0000000..990ba40
--- /dev/null
+++ b/external/CLI11/include/CLI/CLI.hpp
@@ -0,0 +1,36 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// CLI Library includes
+// Order is important for combiner script
+
+#include "Version.hpp"
+
+#include "Macros.hpp"
+
+#include "StringTools.hpp"
+
+#include "Error.hpp"
+
+#include "TypeTools.hpp"
+
+#include "Split.hpp"
+
+#include "ConfigFwd.hpp"
+
+#include "Validators.hpp"
+
+#include "FormatterFwd.hpp"
+
+#include "Option.hpp"
+
+#include "App.hpp"
+
+#include "Config.hpp"
+
+#include "Formatter.hpp"
diff --git a/external/CLI11/include/CLI/Config.hpp b/external/CLI11/include/CLI/Config.hpp
new file mode 100644
index 0000000..57944ec
--- /dev/null
+++ b/external/CLI11/include/CLI/Config.hpp
@@ -0,0 +1,396 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:set]
+
+#include "App.hpp"
+#include "ConfigFwd.hpp"
+#include "StringTools.hpp"
+
+namespace CLI {
+// [CLI11:config_hpp:verbatim]
+namespace detail {
+
+inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'') {
+    if(arg.empty()) {
+        return std::string(2, stringQuote);
+    }
+    // some specifically supported strings
+    if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") {
+        return arg;
+    }
+    // floating point conversion can convert some hex codes, but don't try that here
+    if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) {
+        double val;
+        if(detail::lexical_cast(arg, val)) {
+            return arg;
+        }
+    }
+    // just quote a single non numeric character
+    if(arg.size() == 1) {
+        return std::string(1, characterQuote) + arg + characterQuote;
+    }
+    // handle hex, binary or octal arguments
+    if(arg.front() == '0') {
+        if(arg[1] == 'x') {
+            if(std::all_of(arg.begin() + 2, arg.end(), [](char x) {
+                   return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f');
+               })) {
+                return arg;
+            }
+        } else if(arg[1] == 'o') {
+            if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) {
+                return arg;
+            }
+        } else if(arg[1] == 'b') {
+            if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) {
+                return arg;
+            }
+        }
+    }
+    if(arg.find_first_of(stringQuote) == std::string::npos) {
+        return std::string(1, stringQuote) + arg + stringQuote;
+    } else {
+        return characterQuote + arg + characterQuote;
+    }
+}
+
+/// Comma separated join, adds quotes if needed
+inline std::string ini_join(const std::vector<std::string> &args,
+                            char sepChar = ',',
+                            char arrayStart = '[',
+                            char arrayEnd = ']',
+                            char stringQuote = '"',
+                            char characterQuote = '\'') {
+    std::string joined;
+    if(args.size() > 1 && arrayStart != '\0') {
+        joined.push_back(arrayStart);
+    }
+    std::size_t start = 0;
+    for(const auto &arg : args) {
+        if(start++ > 0) {
+            joined.push_back(sepChar);
+            if(isspace(sepChar) == 0) {
+                joined.push_back(' ');
+            }
+        }
+        joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote));
+    }
+    if(args.size() > 1 && arrayEnd != '\0') {
+        joined.push_back(arrayEnd);
+    }
+    return joined;
+}
+
+inline std::vector<std::string> generate_parents(const std::string &section, std::string &name, char parentSeparator) {
+    std::vector<std::string> parents;
+    if(detail::to_lower(section) != "default") {
+        if(section.find(parentSeparator) != std::string::npos) {
+            parents = detail::split(section, parentSeparator);
+        } else {
+            parents = {section};
+        }
+    }
+    if(name.find(parentSeparator) != std::string::npos) {
+        std::vector<std::string> plist = detail::split(name, parentSeparator);
+        name = plist.back();
+        detail::remove_quotes(name);
+        plist.pop_back();
+        parents.insert(parents.end(), plist.begin(), plist.end());
+    }
+
+    // clean up quotes on the parents
+    for(auto &parent : parents) {
+        detail::remove_quotes(parent);
+    }
+    return parents;
+}
+
+/// assuming non default segments do a check on the close and open of the segments in a configItem structure
+inline void
+checkParentSegments(std::vector<ConfigItem> &output, const std::string &currentSection, char parentSeparator) {
+
+    std::string estring;
+    auto parents = detail::generate_parents(currentSection, estring, parentSeparator);
+    if(!output.empty() && output.back().name == "--") {
+        std::size_t msize = (parents.size() > 1U) ? parents.size() : 2;
+        while(output.back().parents.size() >= msize) {
+            output.push_back(output.back());
+            output.back().parents.pop_back();
+        }
+
+        if(parents.size() > 1) {
+            std::size_t common = 0;
+            std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1);
+            for(std::size_t ii = 0; ii < mpair; ++ii) {
+                if(output.back().parents[ii] != parents[ii]) {
+                    break;
+                }
+                ++common;
+            }
+            if(common == mpair) {
+                output.pop_back();
+            } else {
+                while(output.back().parents.size() > common + 1) {
+                    output.push_back(output.back());
+                    output.back().parents.pop_back();
+                }
+            }
+            for(std::size_t ii = common; ii < parents.size() - 1; ++ii) {
+                output.emplace_back();
+                output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
+                output.back().name = "++";
+            }
+        }
+    } else if(parents.size() > 1) {
+        for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) {
+            output.emplace_back();
+            output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1);
+            output.back().name = "++";
+        }
+    }
+
+    // insert a section end which is just an empty items_buffer
+    output.emplace_back();
+    output.back().parents = std::move(parents);
+    output.back().name = "++";
+}
+}  // namespace detail
+
+inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const {
+    std::string line;
+    std::string currentSection = "default";
+    std::string previousSection = "default";
+    std::vector<ConfigItem> output;
+    bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ',');
+    bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd;
+    bool inSection{false};
+    char aStart = (isINIArray) ? '[' : arrayStart;
+    char aEnd = (isINIArray) ? ']' : arrayEnd;
+    char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator;
+    int currentSectionIndex{0};
+    while(getline(input, line)) {
+        std::vector<std::string> items_buffer;
+        std::string name;
+
+        detail::trim(line);
+        std::size_t len = line.length();
+        // lines have to be at least 3 characters to have any meaning to CLI just skip the rest
+        if(len < 3) {
+            continue;
+        }
+        if(line.front() == '[' && line.back() == ']') {
+            if(currentSection != "default") {
+                // insert a section end which is just an empty items_buffer
+                output.emplace_back();
+                output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
+                output.back().name = "--";
+            }
+            currentSection = line.substr(1, len - 2);
+            // deal with double brackets for TOML
+            if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') {
+                currentSection = currentSection.substr(1, currentSection.size() - 2);
+            }
+            if(detail::to_lower(currentSection) == "default") {
+                currentSection = "default";
+            } else {
+                detail::checkParentSegments(output, currentSection, parentSeparatorChar);
+            }
+            inSection = false;
+            if(currentSection == previousSection) {
+                ++currentSectionIndex;
+            } else {
+                currentSectionIndex = 0;
+                previousSection = currentSection;
+            }
+            continue;
+        }
+
+        // comment lines
+        if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
+            continue;
+        }
+
+        // Find = in string, split and recombine
+        auto pos = line.find(valueDelimiter);
+        if(pos != std::string::npos) {
+            name = detail::trim_copy(line.substr(0, pos));
+            std::string item = detail::trim_copy(line.substr(pos + 1));
+            auto cloc = item.find(commentChar);
+            if(cloc != std::string::npos) {
+                item.erase(cloc, std::string::npos);
+                detail::trim(item);
+            }
+            if(item.size() > 1 && item.front() == aStart) {
+                for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) {
+                    detail::trim(multiline);
+                    item += multiline;
+                }
+                items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
+            } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) {
+                items_buffer = detail::split_up(item, aSep);
+            } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) {
+                items_buffer = detail::split_up(item);
+            } else {
+                items_buffer = {item};
+            }
+        } else {
+            name = detail::trim_copy(line);
+            auto cloc = name.find(commentChar);
+            if(cloc != std::string::npos) {
+                name.erase(cloc, std::string::npos);
+                detail::trim(name);
+            }
+
+            items_buffer = {"true"};
+        }
+        if(name.find(parentSeparatorChar) == std::string::npos) {
+            detail::remove_quotes(name);
+        }
+        // clean up quotes on the items
+        for(auto &it : items_buffer) {
+            detail::remove_quotes(it);
+        }
+
+        std::vector<std::string> parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
+        if(parents.size() > maximumLayers) {
+            continue;
+        }
+        if(!configSection.empty() && !inSection) {
+            if(parents.empty() || parents.front() != configSection) {
+                continue;
+            }
+            if(configIndex >= 0 && currentSectionIndex != configIndex) {
+                continue;
+            }
+            parents.erase(parents.begin());
+            inSection = true;
+        }
+        if(!output.empty() && name == output.back().name && parents == output.back().parents) {
+            output.back().inputs.insert(output.back().inputs.end(), items_buffer.begin(), items_buffer.end());
+        } else {
+            output.emplace_back();
+            output.back().parents = std::move(parents);
+            output.back().name = std::move(name);
+            output.back().inputs = std::move(items_buffer);
+        }
+    }
+    if(currentSection != "default") {
+        // insert a section end which is just an empty items_buffer
+        std::string ename;
+        output.emplace_back();
+        output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar);
+        output.back().name = "--";
+        while(output.back().parents.size() > 1) {
+            output.push_back(output.back());
+            output.back().parents.pop_back();
+        }
+    }
+    return output;
+}
+
+inline std::string
+ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const {
+    std::stringstream out;
+    std::string commentLead;
+    commentLead.push_back(commentChar);
+    commentLead.push_back(' ');
+
+    std::vector<std::string> groups = app->get_groups();
+    bool defaultUsed = false;
+    groups.insert(groups.begin(), std::string("Options"));
+    if(write_description && (app->get_configurable() || app->get_parent() == nullptr || app->get_name().empty())) {
+        out << commentLead << detail::fix_newlines(commentLead, app->get_description()) << '\n';
+    }
+    for(auto &group : groups) {
+        if(group == "Options" || group.empty()) {
+            if(defaultUsed) {
+                continue;
+            }
+            defaultUsed = true;
+        }
+        if(write_description && group != "Options" && !group.empty()) {
+            out << '\n' << commentLead << group << " Options\n";
+        }
+        for(const Option *opt : app->get_options({})) {
+
+            // Only process options that are configurable
+            if(opt->get_configurable()) {
+                if(opt->get_group() != group) {
+                    if(!(group == "Options" && opt->get_group().empty())) {
+                        continue;
+                    }
+                }
+                std::string name = prefix + opt->get_single_name();
+                std::string value = detail::ini_join(
+                    opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote);
+
+                if(value.empty() && default_also) {
+                    if(!opt->get_default_str().empty()) {
+                        value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote);
+                    } else if(opt->get_expected_min() == 0) {
+                        value = "false";
+                    } else if(opt->get_run_callback_for_default()) {
+                        value = "\"\"";  // empty string default value
+                    }
+                }
+
+                if(!value.empty()) {
+                    if(write_description && opt->has_description()) {
+                        out << '\n';
+                        out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
+                    }
+                    out << name << valueDelimiter << value << '\n';
+                }
+            }
+        }
+    }
+    auto subcommands = app->get_subcommands({});
+    for(const App *subcom : subcommands) {
+        if(subcom->get_name().empty()) {
+            if(write_description && !subcom->get_group().empty()) {
+                out << '\n' << commentLead << subcom->get_group() << " Options\n";
+            }
+            out << to_config(subcom, default_also, write_description, prefix);
+        }
+    }
+
+    for(const App *subcom : subcommands) {
+        if(!subcom->get_name().empty()) {
+            if(subcom->get_configurable() && app->got_subcommand(subcom)) {
+                if(!prefix.empty() || app->get_parent() == nullptr) {
+                    out << '[' << prefix << subcom->get_name() << "]\n";
+                } else {
+                    std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name();
+                    auto p = app->get_parent();
+                    while(p->get_parent() != nullptr) {
+                        subname = p->get_name() + parentSeparatorChar + subname;
+                        p = p->get_parent();
+                    }
+                    out << '[' << subname << "]\n";
+                }
+                out << to_config(subcom, default_also, write_description, "");
+            } else {
+                out << to_config(
+                    subcom, default_also, write_description, prefix + subcom->get_name() + parentSeparatorChar);
+            }
+        }
+    }
+
+    return out.str();
+}
+
+// [CLI11:config_hpp:end]
+}  // namespace CLI
diff --git a/external/CLI11/include/CLI/ConfigFwd.hpp b/external/CLI11/include/CLI/ConfigFwd.hpp
new file mode 100644
index 0000000..ef2ac34
--- /dev/null
+++ b/external/CLI11/include/CLI/ConfigFwd.hpp
@@ -0,0 +1,182 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+// [CLI11:public_includes:end]
+
+#include "Error.hpp"
+#include "StringTools.hpp"
+
+namespace CLI {
+// [CLI11:config_fwd_hpp:verbatim]
+
+class App;
+
+/// Holds values to load into Options
+struct ConfigItem {
+    /// This is the list of parents
+    std::vector<std::string> parents{};
+
+    /// This is the name
+    std::string name{};
+
+    /// Listing of inputs
+    std::vector<std::string> inputs{};
+
+    /// The list of parents and name joined by "."
+    std::string fullname() const {
+        std::vector<std::string> tmp = parents;
+        tmp.emplace_back(name);
+        return detail::join(tmp, ".");
+    }
+};
+
+/// This class provides a converter for configuration files.
+class Config {
+  protected:
+    std::vector<ConfigItem> items{};
+
+  public:
+    /// Convert an app into a configuration
+    virtual std::string to_config(const App *, bool, bool, std::string) const = 0;
+
+    /// Convert a configuration into an app
+    virtual std::vector<ConfigItem> from_config(std::istream &) const = 0;
+
+    /// Get a flag value
+    virtual std::string to_flag(const ConfigItem &item) const {
+        if(item.inputs.size() == 1) {
+            return item.inputs.at(0);
+        }
+        throw ConversionError::TooManyInputsFlag(item.fullname());
+    }
+
+    /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure
+    std::vector<ConfigItem> from_file(const std::string &name) {
+        std::ifstream input{name};
+        if(!input.good())
+            throw FileError::Missing(name);
+
+        return from_config(input);
+    }
+
+    /// Virtual destructor
+    virtual ~Config() = default;
+};
+
+/// This converter works with INI/TOML files; to write INI files use ConfigINI
+class ConfigBase : public Config {
+  protected:
+    /// the character used for comments
+    char commentChar = '#';
+    /// the character used to start an array '\0' is a default to not use
+    char arrayStart = '[';
+    /// the character used to end an array '\0' is a default to not use
+    char arrayEnd = ']';
+    /// the character used to separate elements in an array
+    char arraySeparator = ',';
+    /// the character used separate the name from the value
+    char valueDelimiter = '=';
+    /// the character to use around strings
+    char stringQuote = '"';
+    /// the character to use around single characters
+    char characterQuote = '\'';
+    /// the maximum number of layers to allow
+    uint8_t maximumLayers{255};
+    /// the separator used to separator parent layers
+    char parentSeparatorChar{'.'};
+    /// Specify the configuration index to use for arrayed sections
+    int16_t configIndex{-1};
+    /// Specify the configuration section that should be used
+    std::string configSection{};
+
+  public:
+    std::string
+    to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override;
+
+    std::vector<ConfigItem> from_config(std::istream &input) const override;
+    /// Specify the configuration for comment characters
+    ConfigBase *comment(char cchar) {
+        commentChar = cchar;
+        return this;
+    }
+    /// Specify the start and end characters for an array
+    ConfigBase *arrayBounds(char aStart, char aEnd) {
+        arrayStart = aStart;
+        arrayEnd = aEnd;
+        return this;
+    }
+    /// Specify the delimiter character for an array
+    ConfigBase *arrayDelimiter(char aSep) {
+        arraySeparator = aSep;
+        return this;
+    }
+    /// Specify the delimiter between a name and value
+    ConfigBase *valueSeparator(char vSep) {
+        valueDelimiter = vSep;
+        return this;
+    }
+    /// Specify the quote characters used around strings and characters
+    ConfigBase *quoteCharacter(char qString, char qChar) {
+        stringQuote = qString;
+        characterQuote = qChar;
+        return this;
+    }
+    /// Specify the maximum number of parents
+    ConfigBase *maxLayers(uint8_t layers) {
+        maximumLayers = layers;
+        return this;
+    }
+    /// Specify the separator to use for parent layers
+    ConfigBase *parentSeparator(char sep) {
+        parentSeparatorChar = sep;
+        return this;
+    }
+    /// get a reference to the configuration section
+    std::string &sectionRef() { return configSection; }
+    /// get the section
+    const std::string &section() const { return configSection; }
+    /// specify a particular section of the configuration file to use
+    ConfigBase *section(const std::string &sectionName) {
+        configSection = sectionName;
+        return this;
+    }
+
+    /// get a reference to the configuration index
+    int16_t &indexRef() { return configIndex; }
+    /// get the section index
+    int16_t index() const { return configIndex; }
+    /// specify a particular index in the section to use (-1) for all sections to use
+    ConfigBase *index(int16_t sectionIndex) {
+        configIndex = sectionIndex;
+        return this;
+    }
+};
+
+/// the default Config is the TOML file format
+using ConfigTOML = ConfigBase;
+
+/// ConfigINI generates a "standard" INI compliant output
+class ConfigINI : public ConfigTOML {
+
+  public:
+    ConfigINI() {
+        commentChar = ';';
+        arrayStart = '\0';
+        arrayEnd = '\0';
+        arraySeparator = ' ';
+        valueDelimiter = '=';
+    }
+};
+// [CLI11:config_fwd_hpp:end]
+}  // namespace CLI
diff --git a/external/CLI11/include/CLI/Error.hpp b/external/CLI11/include/CLI/Error.hpp
new file mode 100644
index 0000000..de3122b
--- /dev/null
+++ b/external/CLI11/include/CLI/Error.hpp
@@ -0,0 +1,351 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <exception>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+// CLI library includes
+#include "StringTools.hpp"
+
+namespace CLI {
+// [CLI11:error_hpp:verbatim]
+
+// Use one of these on all error classes.
+// These are temporary and are undef'd at the end of this file.
+#define CLI11_ERROR_DEF(parent, name)                                                                                  \
+  protected:                                                                                                           \
+    name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {}   \
+    name(std::string ename, std::string msg, ExitCodes exit_code)                                                      \
+        : parent(std::move(ename), std::move(msg), exit_code) {}                                                       \
+                                                                                                                       \
+  public:                                                                                                              \
+    name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {}                           \
+    name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {}
+
+// This is added after the one above if a class is used directly and builds its own message
+#define CLI11_ERROR_SIMPLE(name)                                                                                       \
+    explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {}
+
+/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut,
+/// int values from e.get_error_code().
+enum class ExitCodes {
+    Success = 0,
+    IncorrectConstruction = 100,
+    BadNameString,
+    OptionAlreadyAdded,
+    FileError,
+    ConversionError,
+    ValidationError,
+    RequiredError,
+    RequiresError,
+    ExcludesError,
+    ExtrasError,
+    ConfigError,
+    InvalidError,
+    HorribleError,
+    OptionNotFound,
+    ArgumentMismatch,
+    BaseClass = 127
+};
+
+// Error definitions
+
+/// @defgroup error_group Errors
+/// @brief Errors thrown by CLI11
+///
+/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors.
+/// @{
+
+/// All errors derive from this one
+class Error : public std::runtime_error {
+    int actual_exit_code;
+    std::string error_name{"Error"};
+
+  public:
+    int get_exit_code() const { return actual_exit_code; }
+
+    std::string get_name() const { return error_name; }
+
+    Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass))
+        : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {}
+
+    Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast<int>(exit_code)) {}
+};
+
+// Note: Using Error::Error constructors does not work on GCC 4.7
+
+/// Construction errors (not in parsing)
+class ConstructionError : public Error {
+    CLI11_ERROR_DEF(Error, ConstructionError)
+};
+
+/// Thrown when an option is set to conflicting values (non-vector and multi args, for example)
+class IncorrectConstruction : public ConstructionError {
+    CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction)
+    CLI11_ERROR_SIMPLE(IncorrectConstruction)
+    static IncorrectConstruction PositionalFlag(std::string name) {
+        return IncorrectConstruction(name + ": Flags cannot be positional");
+    }
+    static IncorrectConstruction Set0Opt(std::string name) {
+        return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead");
+    }
+    static IncorrectConstruction SetFlag(std::string name) {
+        return IncorrectConstruction(name + ": Cannot set an expected number for flags");
+    }
+    static IncorrectConstruction ChangeNotVector(std::string name) {
+        return IncorrectConstruction(name + ": You can only change the expected arguments for vectors");
+    }
+    static IncorrectConstruction AfterMultiOpt(std::string name) {
+        return IncorrectConstruction(
+            name + ": You can't change expected arguments after you've changed the multi option policy!");
+    }
+    static IncorrectConstruction MissingOption(std::string name) {
+        return IncorrectConstruction("Option " + name + " is not defined");
+    }
+    static IncorrectConstruction MultiOptionPolicy(std::string name) {
+        return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options");
+    }
+};
+
+/// Thrown on construction of a bad name
+class BadNameString : public ConstructionError {
+    CLI11_ERROR_DEF(ConstructionError, BadNameString)
+    CLI11_ERROR_SIMPLE(BadNameString)
+    static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); }
+    static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); }
+    static BadNameString DashesOnly(std::string name) {
+        return BadNameString("Must have a name, not just dashes: " + name);
+    }
+    static BadNameString MultiPositionalNames(std::string name) {
+        return BadNameString("Only one positional name allowed, remove: " + name);
+    }
+};
+
+/// Thrown when an option already exists
+class OptionAlreadyAdded : public ConstructionError {
+    CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded)
+    explicit OptionAlreadyAdded(std::string name)
+        : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {}
+    static OptionAlreadyAdded Requires(std::string name, std::string other) {
+        return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded);
+    }
+    static OptionAlreadyAdded Excludes(std::string name, std::string other) {
+        return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded);
+    }
+};
+
+// Parsing errors
+
+/// Anything that can error in Parse
+class ParseError : public Error {
+    CLI11_ERROR_DEF(Error, ParseError)
+};
+
+// Not really "errors"
+
+/// This is a successful completion on parsing, supposed to exit
+class Success : public ParseError {
+    CLI11_ERROR_DEF(ParseError, Success)
+    Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {}
+};
+
+/// -h or --help on command line
+class CallForHelp : public Success {
+    CLI11_ERROR_DEF(Success, CallForHelp)
+    CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
+};
+
+/// Usually something like --help-all on command line
+class CallForAllHelp : public Success {
+    CLI11_ERROR_DEF(Success, CallForAllHelp)
+    CallForAllHelp()
+        : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {}
+};
+
+/// -v or --version on command line
+class CallForVersion : public Success {
+    CLI11_ERROR_DEF(Success, CallForVersion)
+    CallForVersion()
+        : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {}
+};
+
+/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code.
+class RuntimeError : public ParseError {
+    CLI11_ERROR_DEF(ParseError, RuntimeError)
+    explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {}
+};
+
+/// Thrown when parsing an INI file and it is missing
+class FileError : public ParseError {
+    CLI11_ERROR_DEF(ParseError, FileError)
+    CLI11_ERROR_SIMPLE(FileError)
+    static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); }
+};
+
+/// Thrown when conversion call back fails, such as when an int fails to coerce to a string
+class ConversionError : public ParseError {
+    CLI11_ERROR_DEF(ParseError, ConversionError)
+    CLI11_ERROR_SIMPLE(ConversionError)
+    ConversionError(std::string member, std::string name)
+        : ConversionError("The value " + member + " is not an allowed value for " + name) {}
+    ConversionError(std::string name, std::vector<std::string> results)
+        : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {}
+    static ConversionError TooManyInputsFlag(std::string name) {
+        return ConversionError(name + ": too many inputs for a flag");
+    }
+    static ConversionError TrueFalse(std::string name) {
+        return ConversionError(name + ": Should be true/false or a number");
+    }
+};
+
+/// Thrown when validation of results fails
+class ValidationError : public ParseError {
+    CLI11_ERROR_DEF(ParseError, ValidationError)
+    CLI11_ERROR_SIMPLE(ValidationError)
+    explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {}
+};
+
+/// Thrown when a required option is missing
+class RequiredError : public ParseError {
+    CLI11_ERROR_DEF(ParseError, RequiredError)
+    explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {}
+    static RequiredError Subcommand(std::size_t min_subcom) {
+        if(min_subcom == 1) {
+            return RequiredError("A subcommand");
+        }
+        return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands",
+                             ExitCodes::RequiredError);
+    }
+    static RequiredError
+    Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) {
+        if((min_option == 1) && (max_option == 1) && (used == 0))
+            return RequiredError("Exactly 1 option from [" + option_list + "]");
+        if((min_option == 1) && (max_option == 1) && (used > 1)) {
+            return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) +
+                                     " were given",
+                                 ExitCodes::RequiredError);
+        }
+        if((min_option == 1) && (used == 0))
+            return RequiredError("At least 1 option from [" + option_list + "]");
+        if(used < min_option) {
+            return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " +
+                                     std::to_string(used) + "were given from [" + option_list + "]",
+                                 ExitCodes::RequiredError);
+        }
+        if(max_option == 1)
+            return RequiredError("Requires at most 1 options be given from [" + option_list + "]",
+                                 ExitCodes::RequiredError);
+
+        return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " +
+                                 std::to_string(used) + "were given from [" + option_list + "]",
+                             ExitCodes::RequiredError);
+    }
+};
+
+/// Thrown when the wrong number of arguments has been received
+class ArgumentMismatch : public ParseError {
+    CLI11_ERROR_DEF(ParseError, ArgumentMismatch)
+    CLI11_ERROR_SIMPLE(ArgumentMismatch)
+    ArgumentMismatch(std::string name, int expected, std::size_t received)
+        : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name +
+                                           ", got " + std::to_string(received))
+                                        : ("Expected at least " + std::to_string(-expected) + " arguments to " + name +
+                                           ", got " + std::to_string(received)),
+                           ExitCodes::ArgumentMismatch) {}
+
+    static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) {
+        return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " +
+                                std::to_string(received));
+    }
+    static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) {
+        return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " +
+                                std::to_string(received));
+    }
+    static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) {
+        return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing");
+    }
+    static ArgumentMismatch FlagOverride(std::string name) {
+        return ArgumentMismatch(name + " was given a disallowed flag override");
+    }
+};
+
+/// Thrown when a requires option is missing
+class RequiresError : public ParseError {
+    CLI11_ERROR_DEF(ParseError, RequiresError)
+    RequiresError(std::string curname, std::string subname)
+        : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {}
+};
+
+/// Thrown when an excludes option is present
+class ExcludesError : public ParseError {
+    CLI11_ERROR_DEF(ParseError, ExcludesError)
+    ExcludesError(std::string curname, std::string subname)
+        : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {}
+};
+
+/// Thrown when too many positionals or options are found
+class ExtrasError : public ParseError {
+    CLI11_ERROR_DEF(ParseError, ExtrasError)
+    explicit ExtrasError(std::vector<std::string> args)
+        : ExtrasError((args.size() > 1 ? "The following arguments were not expected: "
+                                       : "The following argument was not expected: ") +
+                          detail::rjoin(args, " "),
+                      ExitCodes::ExtrasError) {}
+    ExtrasError(const std::string &name, std::vector<std::string> args)
+        : ExtrasError(name,
+                      (args.size() > 1 ? "The following arguments were not expected: "
+                                       : "The following argument was not expected: ") +
+                          detail::rjoin(args, " "),
+                      ExitCodes::ExtrasError) {}
+};
+
+/// Thrown when extra values are found in an INI file
+class ConfigError : public ParseError {
+    CLI11_ERROR_DEF(ParseError, ConfigError)
+    CLI11_ERROR_SIMPLE(ConfigError)
+    static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); }
+    static ConfigError NotConfigurable(std::string item) {
+        return ConfigError(item + ": This option is not allowed in a configuration file");
+    }
+};
+
+/// Thrown when validation fails before parsing
+class InvalidError : public ParseError {
+    CLI11_ERROR_DEF(ParseError, InvalidError)
+    explicit InvalidError(std::string name)
+        : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) {
+    }
+};
+
+/// This is just a safety check to verify selection and parsing match - you should not ever see it
+/// Strings are directly added to this error, but again, it should never be seen.
+class HorribleError : public ParseError {
+    CLI11_ERROR_DEF(ParseError, HorribleError)
+    CLI11_ERROR_SIMPLE(HorribleError)
+};
+
+// After parsing
+
+/// Thrown when counting a non-existent option
+class OptionNotFound : public Error {
+    CLI11_ERROR_DEF(Error, OptionNotFound)
+    explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {}
+};
+
+#undef CLI11_ERROR_DEF
+#undef CLI11_ERROR_SIMPLE
+
+/// @}
+
+// [CLI11:error_hpp:end]
+}  // namespace CLI
diff --git a/external/CLI11/include/CLI/Formatter.hpp b/external/CLI11/include/CLI/Formatter.hpp
new file mode 100644
index 0000000..e45aa25
--- /dev/null
+++ b/external/CLI11/include/CLI/Formatter.hpp
@@ -0,0 +1,292 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <string>
+#include <vector>
+// [CLI11:public_includes:end]
+
+#include "App.hpp"
+#include "FormatterFwd.hpp"
+
+namespace CLI {
+// [CLI11:formatter_hpp:verbatim]
+
+inline std::string
+Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const {
+    std::stringstream out;
+
+    out << "\n" << group << ":\n";
+    for(const Option *opt : opts) {
+        out << make_option(opt, is_positional);
+    }
+
+    return out.str();
+}
+
+inline std::string Formatter::make_positionals(const App *app) const {
+    std::vector<const Option *> opts =
+        app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); });
+
+    if(opts.empty())
+        return std::string();
+
+    return make_group(get_label("Positionals"), true, opts);
+}
+
+inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) const {
+    std::stringstream out;
+    std::vector<std::string> groups = app->get_groups();
+
+    // Options
+    for(const std::string &group : groups) {
+        std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) {
+            return opt->get_group() == group                     // Must be in the right group
+                   && opt->nonpositional()                       // Must not be a positional
+                   && (mode != AppFormatMode::Sub                // If mode is Sub, then
+                       || (app->get_help_ptr() != opt            // Ignore help pointer
+                           && app->get_help_all_ptr() != opt));  // Ignore help all pointer
+        });
+        if(!group.empty() && !opts.empty()) {
+            out << make_group(group, false, opts);
+
+            if(group != groups.back())
+                out << "\n";
+        }
+    }
+
+    return out.str();
+}
+
+inline std::string Formatter::make_description(const App *app) const {
+    std::string desc = app->get_description();
+    auto min_options = app->get_require_option_min();
+    auto max_options = app->get_require_option_max();
+    if(app->get_required()) {
+        desc += " REQUIRED ";
+    }
+    if((max_options == min_options) && (min_options > 0)) {
+        if(min_options == 1) {
+            desc += " \n[Exactly 1 of the following options is required]";
+        } else {
+            desc += " \n[Exactly " + std::to_string(min_options) + "options from the following list are required]";
+        }
+    } else if(max_options > 0) {
+        if(min_options > 0) {
+            desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) +
+                    " of the follow options are required]";
+        } else {
+            desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]";
+        }
+    } else if(min_options > 0) {
+        desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]";
+    }
+    return (!desc.empty()) ? desc + "\n" : std::string{};
+}
+
+inline std::string Formatter::make_usage(const App *app, std::string name) const {
+    std::stringstream out;
+
+    out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name;
+
+    std::vector<std::string> groups = app->get_groups();
+
+    // Print an Options badge if any options exist
+    std::vector<const Option *> non_pos_options =
+        app->get_options([](const Option *opt) { return opt->nonpositional(); });
+    if(!non_pos_options.empty())
+        out << " [" << get_label("OPTIONS") << "]";
+
+    // Positionals need to be listed here
+    std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); });
+
+    // Print out positionals if any are left
+    if(!positionals.empty()) {
+        // Convert to help names
+        std::vector<std::string> positional_names(positionals.size());
+        std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) {
+            return make_option_usage(opt);
+        });
+
+        out << " " << detail::join(positional_names, " ");
+    }
+
+    // Add a marker if subcommands are expected or optional
+    if(!app->get_subcommands(
+               [](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); })
+            .empty()) {
+        out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "")
+            << get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
+                                                                                                        : "SUBCOMMANDS")
+            << (app->get_require_subcommand_min() == 0 ? "]" : "");
+    }
+
+    out << std::endl;
+
+    return out.str();
+}
+
+inline std::string Formatter::make_footer(const App *app) const {
+    std::string footer = app->get_footer();
+    if(footer.empty()) {
+        return std::string{};
+    }
+    return footer + "\n";
+}
+
+inline std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const {
+
+    // This immediately forwards to the make_expanded method. This is done this way so that subcommands can
+    // have overridden formatters
+    if(mode == AppFormatMode::Sub)
+        return make_expanded(app);
+
+    std::stringstream out;
+    if((app->get_name().empty()) && (app->get_parent() != nullptr)) {
+        if(app->get_group() != "Subcommands") {
+            out << app->get_group() << ':';
+        }
+    }
+
+    out << make_description(app);
+    out << make_usage(app, name);
+    out << make_positionals(app);
+    out << make_groups(app, mode);
+    out << make_subcommands(app, mode);
+    out << '\n' << make_footer(app);
+
+    return out.str();
+}
+
+inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const {
+    std::stringstream out;
+
+    std::vector<const App *> subcommands = app->get_subcommands({});
+
+    // Make a list in definition order of the groups seen
+    std::vector<std::string> subcmd_groups_seen;
+    for(const App *com : subcommands) {
+        if(com->get_name().empty()) {
+            if(!com->get_group().empty()) {
+                out << make_expanded(com);
+            }
+            continue;
+        }
+        std::string group_key = com->get_group();
+        if(!group_key.empty() &&
+           std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) {
+               return detail::to_lower(a) == detail::to_lower(group_key);
+           }) == subcmd_groups_seen.end())
+            subcmd_groups_seen.push_back(group_key);
+    }
+
+    // For each group, filter out and print subcommands
+    for(const std::string &group : subcmd_groups_seen) {
+        out << "\n" << group << ":\n";
+        std::vector<const App *> subcommands_group = app->get_subcommands(
+            [&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); });
+        for(const App *new_com : subcommands_group) {
+            if(new_com->get_name().empty())
+                continue;
+            if(mode != AppFormatMode::All) {
+                out << make_subcommand(new_com);
+            } else {
+                out << new_com->help(new_com->get_name(), AppFormatMode::Sub);
+                out << "\n";
+            }
+        }
+    }
+
+    return out.str();
+}
+
+inline std::string Formatter::make_subcommand(const App *sub) const {
+    std::stringstream out;
+    detail::format_help(out, sub->get_display_name(true), sub->get_description(), column_width_);
+    return out.str();
+}
+
+inline std::string Formatter::make_expanded(const App *sub) const {
+    std::stringstream out;
+    out << sub->get_display_name(true) << "\n";
+
+    out << make_description(sub);
+    if(sub->get_name().empty() && !sub->get_aliases().empty()) {
+        detail::format_aliases(out, sub->get_aliases(), column_width_ + 2);
+    }
+    out << make_positionals(sub);
+    out << make_groups(sub, AppFormatMode::Sub);
+    out << make_subcommands(sub, AppFormatMode::Sub);
+
+    // Drop blank spaces
+    std::string tmp = detail::find_and_replace(out.str(), "\n\n", "\n");
+    tmp = tmp.substr(0, tmp.size() - 1);  // Remove the final '\n'
+
+    // Indent all but the first line (the name)
+    return detail::find_and_replace(tmp, "\n", "\n  ") + "\n";
+}
+
+inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const {
+    if(is_positional)
+        return opt->get_name(true, false);
+
+    return opt->get_name(false, true);
+}
+
+inline std::string Formatter::make_option_opts(const Option *opt) const {
+    std::stringstream out;
+
+    if(!opt->get_option_text().empty()) {
+        out << " " << opt->get_option_text();
+    } else {
+        if(opt->get_type_size() != 0) {
+            if(!opt->get_type_name().empty())
+                out << " " << get_label(opt->get_type_name());
+            if(!opt->get_default_str().empty())
+                out << "=" << opt->get_default_str();
+            if(opt->get_expected_max() == detail::expected_max_vector_size)
+                out << " ...";
+            else if(opt->get_expected_min() > 1)
+                out << " x " << opt->get_expected();
+
+            if(opt->get_required())
+                out << " " << get_label("REQUIRED");
+        }
+        if(!opt->get_envname().empty())
+            out << " (" << get_label("Env") << ":" << opt->get_envname() << ")";
+        if(!opt->get_needs().empty()) {
+            out << " " << get_label("Needs") << ":";
+            for(const Option *op : opt->get_needs())
+                out << " " << op->get_name();
+        }
+        if(!opt->get_excludes().empty()) {
+            out << " " << get_label("Excludes") << ":";
+            for(const Option *op : opt->get_excludes())
+                out << " " << op->get_name();
+        }
+    }
+    return out.str();
+}
+
+inline std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); }
+
+inline std::string Formatter::make_option_usage(const Option *opt) const {
+    // Note that these are positionals usages
+    std::stringstream out;
+    out << make_option_name(opt, true);
+    if(opt->get_expected_max() >= detail::expected_max_vector_size)
+        out << "...";
+    else if(opt->get_expected_max() > 1)
+        out << "(" << opt->get_expected() << "x)";
+
+    return opt->get_required() ? out.str() : "[" + out.str() + "]";
+}
+
+// [CLI11:formatter_hpp:end]
+}  // namespace CLI
diff --git a/external/CLI11/include/CLI/FormatterFwd.hpp b/external/CLI11/include/CLI/FormatterFwd.hpp
new file mode 100644
index 0000000..728926c
--- /dev/null
+++ b/external/CLI11/include/CLI/FormatterFwd.hpp
@@ -0,0 +1,184 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+#include "StringTools.hpp"
+
+namespace CLI {
+// [CLI11:formatter_fwd_hpp:verbatim]
+
+class Option;
+class App;
+
+/// This enum signifies the type of help requested
+///
+/// This is passed in by App; all user classes must accept this as
+/// the second argument.
+
+enum class AppFormatMode {
+    Normal,  ///< The normal, detailed help
+    All,     ///< A fully expanded help
+    Sub,     ///< Used when printed as part of expanded subcommand
+};
+
+/// This is the minimum requirements to run a formatter.
+///
+/// A user can subclass this is if they do not care at all
+/// about the structure in CLI::Formatter.
+class FormatterBase {
+  protected:
+    /// @name Options
+    ///@{
+
+    /// The width of the first column
+    std::size_t column_width_{30};
+
+    /// @brief The required help printout labels (user changeable)
+    /// Values are Needs, Excludes, etc.
+    std::map<std::string, std::string> labels_{};
+
+    ///@}
+    /// @name Basic
+    ///@{
+
+  public:
+    FormatterBase() = default;
+    FormatterBase(const FormatterBase &) = default;
+    FormatterBase(FormatterBase &&) = default;
+
+    /// Adding a destructor in this form to work around bug in GCC 4.7
+    virtual ~FormatterBase() noexcept {}  // NOLINT(modernize-use-equals-default)
+
+    /// This is the key method that puts together help
+    virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0;
+
+    ///@}
+    /// @name Setters
+    ///@{
+
+    /// Set the "REQUIRED" label
+    void label(std::string key, std::string val) { labels_[key] = val; }
+
+    /// Set the column width
+    void column_width(std::size_t val) { column_width_ = val; }
+
+    ///@}
+    /// @name Getters
+    ///@{
+
+    /// Get the current value of a name (REQUIRED, etc.)
+    std::string get_label(std::string key) const {
+        if(labels_.find(key) == labels_.end())
+            return key;
+        else
+            return labels_.at(key);
+    }
+
+    /// Get the current column width
+    std::size_t get_column_width() const { return column_width_; }
+
+    ///@}
+};
+
+/// This is a specialty override for lambda functions
+class FormatterLambda final : public FormatterBase {
+    using funct_t = std::function<std::string(const App *, std::string, AppFormatMode)>;
+
+    /// The lambda to hold and run
+    funct_t lambda_;
+
+  public:
+    /// Create a FormatterLambda with a lambda function
+    explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {}
+
+    /// Adding a destructor (mostly to make GCC 4.7 happy)
+    ~FormatterLambda() noexcept override {}  // NOLINT(modernize-use-equals-default)
+
+    /// This will simply call the lambda function
+    std::string make_help(const App *app, std::string name, AppFormatMode mode) const override {
+        return lambda_(app, name, mode);
+    }
+};
+
+/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few
+/// overridable methods, to be highly customizable with minimal effort.
+class Formatter : public FormatterBase {
+  public:
+    Formatter() = default;
+    Formatter(const Formatter &) = default;
+    Formatter(Formatter &&) = default;
+
+    /// @name Overridables
+    ///@{
+
+    /// This prints out a group of options with title
+    ///
+    virtual std::string make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const;
+
+    /// This prints out just the positionals "group"
+    virtual std::string make_positionals(const App *app) const;
+
+    /// This prints out all the groups of options
+    std::string make_groups(const App *app, AppFormatMode mode) const;
+
+    /// This prints out all the subcommands
+    virtual std::string make_subcommands(const App *app, AppFormatMode mode) const;
+
+    /// This prints out a subcommand
+    virtual std::string make_subcommand(const App *sub) const;
+
+    /// This prints out a subcommand in help-all
+    virtual std::string make_expanded(const App *sub) const;
+
+    /// This prints out all the groups of options
+    virtual std::string make_footer(const App *app) const;
+
+    /// This displays the description line
+    virtual std::string make_description(const App *app) const;
+
+    /// This displays the usage line
+    virtual std::string make_usage(const App *app, std::string name) const;
+
+    /// This puts everything together
+    std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override;
+
+    ///@}
+    /// @name Options
+    ///@{
+
+    /// This prints out an option help line, either positional or optional form
+    virtual std::string make_option(const Option *opt, bool is_positional) const {
+        std::stringstream out;
+        detail::format_help(
+            out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_);
+        return out.str();
+    }
+
+    /// @brief This is the name part of an option, Default: left column
+    virtual std::string make_option_name(const Option *, bool) const;
+
+    /// @brief This is the options part of the name, Default: combined into left column
+    virtual std::string make_option_opts(const Option *) const;
+
+    /// @brief This is the description. Default: Right column, on new line if left column too large
+    virtual std::string make_option_desc(const Option *) const;
+
+    /// @brief This is used to print the name on the USAGE line
+    virtual std::string make_option_usage(const Option *opt) const;
+
+    ///@}
+};
+
+// [CLI11:formatter_fwd_hpp:end]
+}  // namespace CLI
diff --git a/external/CLI11/include/CLI/Macros.hpp b/external/CLI11/include/CLI/Macros.hpp
new file mode 100644
index 0000000..f228c91
--- /dev/null
+++ b/external/CLI11/include/CLI/Macros.hpp
@@ -0,0 +1,44 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:macros_hpp:verbatim]
+
+// The following version macro is very similar to the one in pybind11
+#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER)
+#if __cplusplus >= 201402L
+#define CLI11_CPP14
+#if __cplusplus >= 201703L
+#define CLI11_CPP17
+#if __cplusplus > 201703L
+#define CLI11_CPP20
+#endif
+#endif
+#endif
+#elif defined(_MSC_VER) && __cplusplus == 199711L
+// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented)
+// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer
+#if _MSVC_LANG >= 201402L
+#define CLI11_CPP14
+#if _MSVC_LANG > 201402L && _MSC_VER >= 1910
+#define CLI11_CPP17
+#if __MSVC_LANG > 201703L && _MSC_VER >= 1910
+#define CLI11_CPP20
+#endif
+#endif
+#endif
+#endif
+
+#if defined(CLI11_CPP14)
+#define CLI11_DEPRECATED(reason) [[deprecated(reason)]]
+#elif defined(_MSC_VER)
+#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason))
+#else
+#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason)))
+#endif
+
+// [CLI11:macros_hpp:end]
diff --git a/external/CLI11/include/CLI/Option.hpp b/external/CLI11/include/CLI/Option.hpp
new file mode 100644
index 0000000..25a6760
--- /dev/null
+++ b/external/CLI11/include/CLI/Option.hpp
@@ -0,0 +1,1348 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <set>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+#include "Error.hpp"
+#include "Macros.hpp"
+#include "Split.hpp"
+#include "StringTools.hpp"
+#include "Validators.hpp"
+
+namespace CLI {
+// [CLI11:option_hpp:verbatim]
+
+using results_t = std::vector<std::string>;
+/// callback function definition
+using callback_t = std::function<bool(const results_t &)>;
+
+class Option;
+class App;
+
+using Option_p = std::unique_ptr<Option>;
+/// Enumeration of the multiOption Policy selection
+enum class MultiOptionPolicy : char {
+    Throw,      //!< Throw an error if any extra arguments were given
+    TakeLast,   //!< take only the last Expected number of arguments
+    TakeFirst,  //!< take only the first Expected number of arguments
+    Join,       //!< merge all the arguments together into a single string via the delimiter character default('\n')
+    TakeAll     //!< just get all the passed argument regardless
+};
+
+/// This is the CRTP base class for Option and OptionDefaults. It was designed this way
+/// to share parts of the class; an OptionDefaults can copy to an Option.
+template <typename CRTP> class OptionBase {
+    friend App;
+
+  protected:
+    /// The group membership
+    std::string group_ = std::string("Options");
+
+    /// True if this is a required option
+    bool required_{false};
+
+    /// Ignore the case when matching (option, not value)
+    bool ignore_case_{false};
+
+    /// Ignore underscores when matching (option, not value)
+    bool ignore_underscore_{false};
+
+    /// Allow this option to be given in a configuration file
+    bool configurable_{true};
+
+    /// Disable overriding flag values with '=value'
+    bool disable_flag_override_{false};
+
+    /// Specify a delimiter character for vector arguments
+    char delimiter_{'\0'};
+
+    /// Automatically capture default value
+    bool always_capture_default_{false};
+
+    /// Policy for handling multiple arguments beyond the expected Max
+    MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw};
+
+    /// Copy the contents to another similar class (one based on OptionBase)
+    template <typename T> void copy_to(T *other) const {
+        other->group(group_);
+        other->required(required_);
+        other->ignore_case(ignore_case_);
+        other->ignore_underscore(ignore_underscore_);
+        other->configurable(configurable_);
+        other->disable_flag_override(disable_flag_override_);
+        other->delimiter(delimiter_);
+        other->always_capture_default(always_capture_default_);
+        other->multi_option_policy(multi_option_policy_);
+    }
+
+  public:
+    // setters
+
+    /// Changes the group membership
+    CRTP *group(const std::string &name) {
+        if(!detail::valid_alias_name_string(name)) {
+            throw IncorrectConstruction("Group names may not contain newlines or null characters");
+        }
+        group_ = name;
+        return static_cast<CRTP *>(this);
+    }
+
+    /// Set the option as required
+    CRTP *required(bool value = true) {
+        required_ = value;
+        return static_cast<CRTP *>(this);
+    }
+
+    /// Support Plumbum term
+    CRTP *mandatory(bool value = true) { return required(value); }
+
+    CRTP *always_capture_default(bool value = true) {
+        always_capture_default_ = value;
+        return static_cast<CRTP *>(this);
+    }
+
+    // Getters
+
+    /// Get the group of this option
+    const std::string &get_group() const { return group_; }
+
+    /// True if this is a required option
+    bool get_required() const { return required_; }
+
+    /// The status of ignore case
+    bool get_ignore_case() const { return ignore_case_; }
+
+    /// The status of ignore_underscore
+    bool get_ignore_underscore() const { return ignore_underscore_; }
+
+    /// The status of configurable
+    bool get_configurable() const { return configurable_; }
+
+    /// The status of configurable
+    bool get_disable_flag_override() const { return disable_flag_override_; }
+
+    /// Get the current delimiter char
+    char get_delimiter() const { return delimiter_; }
+
+    /// Return true if this will automatically capture the default value for help printing
+    bool get_always_capture_default() const { return always_capture_default_; }
+
+    /// The status of the multi option policy
+    MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; }
+
+    // Shortcuts for multi option policy
+
+    /// Set the multi option policy to take last
+    CRTP *take_last() {
+        auto self = static_cast<CRTP *>(this);
+        self->multi_option_policy(MultiOptionPolicy::TakeLast);
+        return self;
+    }
+
+    /// Set the multi option policy to take last
+    CRTP *take_first() {
+        auto self = static_cast<CRTP *>(this);
+        self->multi_option_policy(MultiOptionPolicy::TakeFirst);
+        return self;
+    }
+
+    /// Set the multi option policy to take all arguments
+    CRTP *take_all() {
+        auto self = static_cast<CRTP *>(this);
+        self->multi_option_policy(MultiOptionPolicy::TakeAll);
+        return self;
+    }
+
+    /// Set the multi option policy to join
+    CRTP *join() {
+        auto self = static_cast<CRTP *>(this);
+        self->multi_option_policy(MultiOptionPolicy::Join);
+        return self;
+    }
+
+    /// Set the multi option policy to join with a specific delimiter
+    CRTP *join(char delim) {
+        auto self = static_cast<CRTP *>(this);
+        self->delimiter_ = delim;
+        self->multi_option_policy(MultiOptionPolicy::Join);
+        return self;
+    }
+
+    /// Allow in a configuration file
+    CRTP *configurable(bool value = true) {
+        configurable_ = value;
+        return static_cast<CRTP *>(this);
+    }
+
+    /// Allow in a configuration file
+    CRTP *delimiter(char value = '\0') {
+        delimiter_ = value;
+        return static_cast<CRTP *>(this);
+    }
+};
+
+/// This is a version of OptionBase that only supports setting values,
+/// for defaults. It is stored as the default option in an App.
+class OptionDefaults : public OptionBase<OptionDefaults> {
+  public:
+    OptionDefaults() = default;
+
+    // Methods here need a different implementation if they are Option vs. OptionDefault
+
+    /// Take the last argument if given multiple times
+    OptionDefaults *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
+        multi_option_policy_ = value;
+        return this;
+    }
+
+    /// Ignore the case of the option name
+    OptionDefaults *ignore_case(bool value = true) {
+        ignore_case_ = value;
+        return this;
+    }
+
+    /// Ignore underscores in the option name
+    OptionDefaults *ignore_underscore(bool value = true) {
+        ignore_underscore_ = value;
+        return this;
+    }
+
+    /// Disable overriding flag values with an '=<value>' segment
+    OptionDefaults *disable_flag_override(bool value = true) {
+        disable_flag_override_ = value;
+        return this;
+    }
+
+    /// set a delimiter character to split up single arguments to treat as multiple inputs
+    OptionDefaults *delimiter(char value = '\0') {
+        delimiter_ = value;
+        return this;
+    }
+};
+
+class Option : public OptionBase<Option> {
+    friend App;
+
+  protected:
+    /// @name Names
+    ///@{
+
+    /// A list of the short names (`-a`) without the leading dashes
+    std::vector<std::string> snames_{};
+
+    /// A list of the long names (`--long`) without the leading dashes
+    std::vector<std::string> lnames_{};
+
+    /// A list of the flag names with the appropriate default value, the first part of the pair should be duplicates of
+    /// what is in snames or lnames but will trigger a particular response on a flag
+    std::vector<std::pair<std::string, std::string>> default_flag_values_{};
+
+    /// a list of flag names with specified default values;
+    std::vector<std::string> fnames_{};
+
+    /// A positional name
+    std::string pname_{};
+
+    /// If given, check the environment for this option
+    std::string envname_{};
+
+    ///@}
+    /// @name Help
+    ///@{
+
+    /// The description for help strings
+    std::string description_{};
+
+    /// A human readable default value, either manually set, captured, or captured by default
+    std::string default_str_{};
+
+    /// If given, replace the text that describes the option type and usage in the help text
+    std::string option_text_{};
+
+    /// A human readable type value, set when App creates this
+    ///
+    /// This is a lambda function so "types" can be dynamic, such as when a set prints its contents.
+    std::function<std::string()> type_name_{[]() { return std::string(); }};
+
+    /// Run this function to capture a default (ignore if empty)
+    std::function<std::string()> default_function_{};
+
+    ///@}
+    /// @name Configuration
+    ///@{
+
+    /// The number of arguments that make up one option. max is the nominal type size, min is the minimum number of
+    /// strings
+    int type_size_max_{1};
+    /// The minimum number of arguments an option should be expecting
+    int type_size_min_{1};
+
+    /// The minimum number of expected values
+    int expected_min_{1};
+    /// The maximum number of expected values
+    int expected_max_{1};
+
+    /// A list of Validators to run on each value parsed
+    std::vector<Validator> validators_{};
+
+    /// A list of options that are required with this option
+    std::set<Option *> needs_{};
+
+    /// A list of options that are excluded with this option
+    std::set<Option *> excludes_{};
+
+    ///@}
+    /// @name Other
+    ///@{
+
+    /// link back up to the parent App for fallthrough
+    App *parent_{nullptr};
+
+    /// Options store a callback to do all the work
+    callback_t callback_{};
+
+    ///@}
+    /// @name Parsing results
+    ///@{
+
+    /// complete Results of parsing
+    results_t results_{};
+    /// results after reduction
+    results_t proc_results_{};
+    /// enumeration for the option state machine
+    enum class option_state : char {
+        parsing = 0,       //!< The option is currently collecting parsed results
+        validated = 2,     //!< the results have been validated
+        reduced = 4,       //!< a subset of results has been generated
+        callback_run = 6,  //!< the callback has been executed
+    };
+    /// Whether the callback has run (needed for INI parsing)
+    option_state current_option_state_{option_state::parsing};
+    /// Specify that extra args beyond type_size_max should be allowed
+    bool allow_extra_args_{false};
+    /// Specify that the option should act like a flag vs regular option
+    bool flag_like_{false};
+    /// Control option to run the callback to set the default
+    bool run_callback_for_default_{false};
+    /// flag indicating a separator needs to be injected after each argument call
+    bool inject_separator_{false};
+    /// flag indicating that the option should trigger the validation and callback chain on each result when loaded
+    bool trigger_on_result_{false};
+    /// flag indicating that the option should force the callback regardless if any results present
+    bool force_callback_{false};
+    ///@}
+
+    /// Making an option by hand is not defined, it must be made by the App class
+    Option(std::string option_name, std::string option_description, callback_t callback, App *parent)
+        : description_(std::move(option_description)), parent_(parent), callback_(std::move(callback)) {
+        std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name));
+    }
+
+  public:
+    /// @name Basic
+    ///@{
+
+    Option(const Option &) = delete;
+    Option &operator=(const Option &) = delete;
+
+    /// Count the total number of times an option was passed
+    std::size_t count() const { return results_.size(); }
+
+    /// True if the option was not passed
+    bool empty() const { return results_.empty(); }
+
+    /// This bool operator returns true if any arguments were passed or the option callback is forced
+    explicit operator bool() const { return !empty() || force_callback_; }
+
+    /// Clear the parsed results (mostly for testing)
+    void clear() {
+        results_.clear();
+        current_option_state_ = option_state::parsing;
+    }
+
+    ///@}
+    /// @name Setting options
+    ///@{
+
+    /// Set the number of expected arguments
+    Option *expected(int value) {
+        if(value < 0) {
+            expected_min_ = -value;
+            if(expected_max_ < expected_min_) {
+                expected_max_ = expected_min_;
+            }
+            allow_extra_args_ = true;
+            flag_like_ = false;
+        } else if(value == detail::expected_max_vector_size) {
+            expected_min_ = 1;
+            expected_max_ = detail::expected_max_vector_size;
+            allow_extra_args_ = true;
+            flag_like_ = false;
+        } else {
+            expected_min_ = value;
+            expected_max_ = value;
+            flag_like_ = (expected_min_ == 0);
+        }
+        return this;
+    }
+
+    /// Set the range of expected arguments
+    Option *expected(int value_min, int value_max) {
+        if(value_min < 0) {
+            value_min = -value_min;
+        }
+
+        if(value_max < 0) {
+            value_max = detail::expected_max_vector_size;
+        }
+        if(value_max < value_min) {
+            expected_min_ = value_max;
+            expected_max_ = value_min;
+        } else {
+            expected_max_ = value_max;
+            expected_min_ = value_min;
+        }
+
+        return this;
+    }
+    /// Set the value of allow_extra_args which allows extra value arguments on the flag or option to be included
+    /// with each instance
+    Option *allow_extra_args(bool value = true) {
+        allow_extra_args_ = value;
+        return this;
+    }
+    /// Get the current value of allow extra args
+    bool get_allow_extra_args() const { return allow_extra_args_; }
+    /// Set the value of trigger_on_parse which specifies that the option callback should be triggered on every parse
+    Option *trigger_on_parse(bool value = true) {
+        trigger_on_result_ = value;
+        return this;
+    }
+    /// The status of trigger on parse
+    bool get_trigger_on_parse() const { return trigger_on_result_; }
+
+    /// Set the value of force_callback
+    Option *force_callback(bool value = true) {
+        force_callback_ = value;
+        return this;
+    }
+    /// The status of force_callback
+    bool get_force_callback() const { return force_callback_; }
+
+    /// Set the value of run_callback_for_default which controls whether the callback function should be called to set
+    /// the default This is controlled automatically but could be manipulated by the user.
+    Option *run_callback_for_default(bool value = true) {
+        run_callback_for_default_ = value;
+        return this;
+    }
+    /// Get the current value of run_callback_for_default
+    bool get_run_callback_for_default() const { return run_callback_for_default_; }
+
+    /// Adds a Validator with a built in type name
+    Option *check(Validator validator, const std::string &validator_name = "") {
+        validator.non_modifying();
+        validators_.push_back(std::move(validator));
+        if(!validator_name.empty())
+            validators_.back().name(validator_name);
+        return this;
+    }
+
+    /// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay).
+    Option *check(std::function<std::string(const std::string &)> Validator,
+                  std::string Validator_description = "",
+                  std::string Validator_name = "") {
+        validators_.emplace_back(Validator, std::move(Validator_description), std::move(Validator_name));
+        validators_.back().non_modifying();
+        return this;
+    }
+
+    /// Adds a transforming Validator with a built in type name
+    Option *transform(Validator Validator, const std::string &Validator_name = "") {
+        validators_.insert(validators_.begin(), std::move(Validator));
+        if(!Validator_name.empty())
+            validators_.front().name(Validator_name);
+        return this;
+    }
+
+    /// Adds a Validator-like function that can change result
+    Option *transform(const std::function<std::string(std::string)> &func,
+                      std::string transform_description = "",
+                      std::string transform_name = "") {
+        validators_.insert(validators_.begin(),
+                           Validator(
+                               [func](std::string &val) {
+                                   val = func(val);
+                                   return std::string{};
+                               },
+                               std::move(transform_description),
+                               std::move(transform_name)));
+
+        return this;
+    }
+
+    /// Adds a user supplied function to run on each item passed in (communicate though lambda capture)
+    Option *each(const std::function<void(std::string)> &func) {
+        validators_.emplace_back(
+            [func](std::string &inout) {
+                func(inout);
+                return std::string{};
+            },
+            std::string{});
+        return this;
+    }
+    /// Get a named Validator
+    Validator *get_validator(const std::string &Validator_name = "") {
+        for(auto &Validator : validators_) {
+            if(Validator_name == Validator.get_name()) {
+                return &Validator;
+            }
+        }
+        if((Validator_name.empty()) && (!validators_.empty())) {
+            return &(validators_.front());
+        }
+        throw OptionNotFound(std::string{"Validator "} + Validator_name + " Not Found");
+    }
+
+    /// Get a Validator by index NOTE: this may not be the order of definition
+    Validator *get_validator(int index) {
+        // This is an signed int so that it is not equivalent to a pointer.
+        if(index >= 0 && index < static_cast<int>(validators_.size())) {
+            return &(validators_[static_cast<decltype(validators_)::size_type>(index)]);
+        }
+        throw OptionNotFound("Validator index is not valid");
+    }
+
+    /// Sets required options
+    Option *needs(Option *opt) {
+        if(opt != this) {
+            needs_.insert(opt);
+        }
+        return this;
+    }
+
+    /// Can find a string if needed
+    template <typename T = App> Option *needs(std::string opt_name) {
+        auto opt = static_cast<T *>(parent_)->get_option_no_throw(opt_name);
+        if(opt == nullptr) {
+            throw IncorrectConstruction::MissingOption(opt_name);
+        }
+        return needs(opt);
+    }
+
+    /// Any number supported, any mix of string and Opt
+    template <typename A, typename B, typename... ARG> Option *needs(A opt, B opt1, ARG... args) {
+        needs(opt);
+        return needs(opt1, args...);
+    }
+
+    /// Remove needs link from an option. Returns true if the option really was in the needs list.
+    bool remove_needs(Option *opt) {
+        auto iterator = std::find(std::begin(needs_), std::end(needs_), opt);
+
+        if(iterator == std::end(needs_)) {
+            return false;
+        }
+        needs_.erase(iterator);
+        return true;
+    }
+
+    /// Sets excluded options
+    Option *excludes(Option *opt) {
+        if(opt == this) {
+            throw(IncorrectConstruction("and option cannot exclude itself"));
+        }
+        excludes_.insert(opt);
+
+        // Help text should be symmetric - excluding a should exclude b
+        opt->excludes_.insert(this);
+
+        // Ignoring the insert return value, excluding twice is now allowed.
+        // (Mostly to allow both directions to be excluded by user, even though the library does it for you.)
+
+        return this;
+    }
+
+    /// Can find a string if needed
+    template <typename T = App> Option *excludes(std::string opt_name) {
+        auto opt = static_cast<T *>(parent_)->get_option_no_throw(opt_name);
+        if(opt == nullptr) {
+            throw IncorrectConstruction::MissingOption(opt_name);
+        }
+        return excludes(opt);
+    }
+
+    /// Any number supported, any mix of string and Opt
+    template <typename A, typename B, typename... ARG> Option *excludes(A opt, B opt1, ARG... args) {
+        excludes(opt);
+        return excludes(opt1, args...);
+    }
+
+    /// Remove needs link from an option. Returns true if the option really was in the needs list.
+    bool remove_excludes(Option *opt) {
+        auto iterator = std::find(std::begin(excludes_), std::end(excludes_), opt);
+
+        if(iterator == std::end(excludes_)) {
+            return false;
+        }
+        excludes_.erase(iterator);
+        return true;
+    }
+
+    /// Sets environment variable to read if no option given
+    Option *envname(std::string name) {
+        envname_ = std::move(name);
+        return this;
+    }
+
+    /// Ignore case
+    ///
+    /// The template hides the fact that we don't have the definition of App yet.
+    /// You are never expected to add an argument to the template here.
+    template <typename T = App> Option *ignore_case(bool value = true) {
+        if(!ignore_case_ && value) {
+            ignore_case_ = value;
+            auto *parent = static_cast<T *>(parent_);
+            for(const Option_p &opt : parent->options_) {
+                if(opt.get() == this) {
+                    continue;
+                }
+                auto &omatch = opt->matching_name(*this);
+                if(!omatch.empty()) {
+                    ignore_case_ = false;
+                    throw OptionAlreadyAdded("adding ignore case caused a name conflict with " + omatch);
+                }
+            }
+        } else {
+            ignore_case_ = value;
+        }
+        return this;
+    }
+
+    /// Ignore underscores in the option names
+    ///
+    /// The template hides the fact that we don't have the definition of App yet.
+    /// You are never expected to add an argument to the template here.
+    template <typename T = App> Option *ignore_underscore(bool value = true) {
+
+        if(!ignore_underscore_ && value) {
+            ignore_underscore_ = value;
+            auto *parent = static_cast<T *>(parent_);
+            for(const Option_p &opt : parent->options_) {
+                if(opt.get() == this) {
+                    continue;
+                }
+                auto &omatch = opt->matching_name(*this);
+                if(!omatch.empty()) {
+                    ignore_underscore_ = false;
+                    throw OptionAlreadyAdded("adding ignore underscore caused a name conflict with " + omatch);
+                }
+            }
+        } else {
+            ignore_underscore_ = value;
+        }
+        return this;
+    }
+
+    /// Take the last argument if given multiple times (or another policy)
+    Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) {
+        if(value != multi_option_policy_) {
+            if(multi_option_policy_ == MultiOptionPolicy::Throw && expected_max_ == detail::expected_max_vector_size &&
+               expected_min_ > 1) {  // this bizarre condition is to maintain backwards compatibility
+                                     // with the previous behavior of expected_ with vectors
+                expected_max_ = expected_min_;
+            }
+            multi_option_policy_ = value;
+            current_option_state_ = option_state::parsing;
+        }
+        return this;
+    }
+
+    /// Disable flag overrides values, e.g. --flag=<value> is not allowed
+    Option *disable_flag_override(bool value = true) {
+        disable_flag_override_ = value;
+        return this;
+    }
+    ///@}
+    /// @name Accessors
+    ///@{
+
+    /// The number of arguments the option expects
+    int get_type_size() const { return type_size_min_; }
+
+    /// The minimum number of arguments the option expects
+    int get_type_size_min() const { return type_size_min_; }
+    /// The maximum number of arguments the option expects
+    int get_type_size_max() const { return type_size_max_; }
+
+    /// Return the inject_separator flag
+    int get_inject_separator() const { return inject_separator_; }
+
+    /// The environment variable associated to this value
+    std::string get_envname() const { return envname_; }
+
+    /// The set of options needed
+    std::set<Option *> get_needs() const { return needs_; }
+
+    /// The set of options excluded
+    std::set<Option *> get_excludes() const { return excludes_; }
+
+    /// The default value (for help printing)
+    std::string get_default_str() const { return default_str_; }
+
+    /// Get the callback function
+    callback_t get_callback() const { return callback_; }
+
+    /// Get the long names
+    const std::vector<std::string> &get_lnames() const { return lnames_; }
+
+    /// Get the short names
+    const std::vector<std::string> &get_snames() const { return snames_; }
+
+    /// Get the flag names with specified default values
+    const std::vector<std::string> &get_fnames() const { return fnames_; }
+    /// Get a single name for the option, first of lname, pname, sname, envname
+    const std::string &get_single_name() const {
+        if(!lnames_.empty()) {
+            return lnames_[0];
+        }
+        if(!pname_.empty()) {
+            return pname_;
+        }
+        if(!snames_.empty()) {
+            return snames_[0];
+        }
+        return envname_;
+    }
+    /// The number of times the option expects to be included
+    int get_expected() const { return expected_min_; }
+
+    /// The number of times the option expects to be included
+    int get_expected_min() const { return expected_min_; }
+    /// The max number of times the option expects to be included
+    int get_expected_max() const { return expected_max_; }
+
+    /// The total min number of expected  string values to be used
+    int get_items_expected_min() const { return type_size_min_ * expected_min_; }
+
+    /// Get the maximum number of items expected to be returned and used for the callback
+    int get_items_expected_max() const {
+        int t = type_size_max_;
+        return detail::checked_multiply(t, expected_max_) ? t : detail::expected_max_vector_size;
+    }
+    /// The total min number of expected  string values to be used
+    int get_items_expected() const { return get_items_expected_min(); }
+
+    /// True if the argument can be given directly
+    bool get_positional() const { return pname_.length() > 0; }
+
+    /// True if option has at least one non-positional name
+    bool nonpositional() const { return (snames_.size() + lnames_.size()) > 0; }
+
+    /// True if option has description
+    bool has_description() const { return description_.length() > 0; }
+
+    /// Get the description
+    const std::string &get_description() const { return description_; }
+
+    /// Set the description
+    Option *description(std::string option_description) {
+        description_ = std::move(option_description);
+        return this;
+    }
+
+    Option *option_text(std::string text) {
+        option_text_ = std::move(text);
+        return this;
+    }
+
+    const std::string &get_option_text() const { return option_text_; }
+
+    ///@}
+    /// @name Help tools
+    ///@{
+
+    /// \brief Gets a comma separated list of names.
+    /// Will include / prefer the positional name if positional is true.
+    /// If all_options is false, pick just the most descriptive name to show.
+    /// Use `get_name(true)` to get the positional name (replaces `get_pname`)
+    std::string get_name(bool positional = false,  ///< Show the positional name
+                         bool all_options = false  ///< Show every option
+    ) const {
+        if(get_group().empty())
+            return {};  // Hidden
+
+        if(all_options) {
+
+            std::vector<std::string> name_list;
+
+            /// The all list will never include a positional unless asked or that's the only name.
+            if((positional && (!pname_.empty())) || (snames_.empty() && lnames_.empty())) {
+                name_list.push_back(pname_);
+            }
+            if((get_items_expected() == 0) && (!fnames_.empty())) {
+                for(const std::string &sname : snames_) {
+                    name_list.push_back("-" + sname);
+                    if(check_fname(sname)) {
+                        name_list.back() += "{" + get_flag_value(sname, "") + "}";
+                    }
+                }
+
+                for(const std::string &lname : lnames_) {
+                    name_list.push_back("--" + lname);
+                    if(check_fname(lname)) {
+                        name_list.back() += "{" + get_flag_value(lname, "") + "}";
+                    }
+                }
+            } else {
+                for(const std::string &sname : snames_)
+                    name_list.push_back("-" + sname);
+
+                for(const std::string &lname : lnames_)
+                    name_list.push_back("--" + lname);
+            }
+
+            return detail::join(name_list);
+        }
+
+        // This returns the positional name no matter what
+        if(positional)
+            return pname_;
+
+        // Prefer long name
+        if(!lnames_.empty())
+            return std::string(2, '-') + lnames_[0];
+
+        // Or short name if no long name
+        if(!snames_.empty())
+            return std::string(1, '-') + snames_[0];
+
+        // If positional is the only name, it's okay to use that
+        return pname_;
+    }
+
+    ///@}
+    /// @name Parser tools
+    ///@{
+
+    /// Process the callback
+    void run_callback() {
+        if(force_callback_ && results_.empty()) {
+            add_result(default_str_);
+        }
+        if(current_option_state_ == option_state::parsing) {
+            _validate_results(results_);
+            current_option_state_ = option_state::validated;
+        }
+
+        if(current_option_state_ < option_state::reduced) {
+            _reduce_results(proc_results_, results_);
+            current_option_state_ = option_state::reduced;
+        }
+        if(current_option_state_ >= option_state::reduced) {
+            current_option_state_ = option_state::callback_run;
+            if(!(callback_)) {
+                return;
+            }
+            const results_t &send_results = proc_results_.empty() ? results_ : proc_results_;
+            bool local_result = callback_(send_results);
+
+            if(!local_result)
+                throw ConversionError(get_name(), results_);
+        }
+    }
+
+    /// If options share any of the same names, find it
+    const std::string &matching_name(const Option &other) const {
+        static const std::string estring;
+        for(const std::string &sname : snames_)
+            if(other.check_sname(sname))
+                return sname;
+        for(const std::string &lname : lnames_)
+            if(other.check_lname(lname))
+                return lname;
+
+        if(ignore_case_ ||
+           ignore_underscore_) {  // We need to do the inverse, in case we are ignore_case or ignore underscore
+            for(const std::string &sname : other.snames_)
+                if(check_sname(sname))
+                    return sname;
+            for(const std::string &lname : other.lnames_)
+                if(check_lname(lname))
+                    return lname;
+        }
+        return estring;
+    }
+    /// If options share any of the same names, they are equal (not counting positional)
+    bool operator==(const Option &other) const { return !matching_name(other).empty(); }
+
+    /// Check a name. Requires "-" or "--" for short / long, supports positional name
+    bool check_name(const std::string &name) const {
+
+        if(name.length() > 2 && name[0] == '-' && name[1] == '-')
+            return check_lname(name.substr(2));
+        if(name.length() > 1 && name.front() == '-')
+            return check_sname(name.substr(1));
+        if(!pname_.empty()) {
+            std::string local_pname = pname_;
+            std::string local_name = name;
+            if(ignore_underscore_) {
+                local_pname = detail::remove_underscore(local_pname);
+                local_name = detail::remove_underscore(local_name);
+            }
+            if(ignore_case_) {
+                local_pname = detail::to_lower(local_pname);
+                local_name = detail::to_lower(local_name);
+            }
+            if(local_name == local_pname) {
+                return true;
+            }
+        }
+
+        if(!envname_.empty()) {
+            // this needs to be the original since envname_ shouldn't match on case insensitivity
+            return (name == envname_);
+        }
+        return false;
+    }
+
+    /// Requires "-" to be removed from string
+    bool check_sname(std::string name) const {
+        return (detail::find_member(std::move(name), snames_, ignore_case_) >= 0);
+    }
+
+    /// Requires "--" to be removed from string
+    bool check_lname(std::string name) const {
+        return (detail::find_member(std::move(name), lnames_, ignore_case_, ignore_underscore_) >= 0);
+    }
+
+    /// Requires "--" to be removed from string
+    bool check_fname(std::string name) const {
+        if(fnames_.empty()) {
+            return false;
+        }
+        return (detail::find_member(std::move(name), fnames_, ignore_case_, ignore_underscore_) >= 0);
+    }
+
+    /// Get the value that goes for a flag, nominally gets the default value but allows for overrides if not
+    /// disabled
+    std::string get_flag_value(const std::string &name, std::string input_value) const {
+        static const std::string trueString{"true"};
+        static const std::string falseString{"false"};
+        static const std::string emptyString{"{}"};
+        // check for disable flag override_
+        if(disable_flag_override_) {
+            if(!((input_value.empty()) || (input_value == emptyString))) {
+                auto default_ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
+                if(default_ind >= 0) {
+                    // We can static cast this to std::size_t because it is more than 0 in this block
+                    if(default_flag_values_[static_cast<std::size_t>(default_ind)].second != input_value) {
+                        throw(ArgumentMismatch::FlagOverride(name));
+                    }
+                } else {
+                    if(input_value != trueString) {
+                        throw(ArgumentMismatch::FlagOverride(name));
+                    }
+                }
+            }
+        }
+        auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_);
+        if((input_value.empty()) || (input_value == emptyString)) {
+            if(flag_like_) {
+                return (ind < 0) ? trueString : default_flag_values_[static_cast<std::size_t>(ind)].second;
+            } else {
+                return (ind < 0) ? default_str_ : default_flag_values_[static_cast<std::size_t>(ind)].second;
+            }
+        }
+        if(ind < 0) {
+            return input_value;
+        }
+        if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) {
+            try {
+                auto val = detail::to_flag_value(input_value);
+                return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val));
+            } catch(const std::invalid_argument &) {
+                return input_value;
+            }
+        } else {
+            return input_value;
+        }
+    }
+
+    /// Puts a result at the end
+    Option *add_result(std::string s) {
+        _add_result(std::move(s), results_);
+        current_option_state_ = option_state::parsing;
+        return this;
+    }
+
+    /// Puts a result at the end and get a count of the number of arguments actually added
+    Option *add_result(std::string s, int &results_added) {
+        results_added = _add_result(std::move(s), results_);
+        current_option_state_ = option_state::parsing;
+        return this;
+    }
+
+    /// Puts a result at the end
+    Option *add_result(std::vector<std::string> s) {
+        current_option_state_ = option_state::parsing;
+        for(auto &str : s) {
+            _add_result(std::move(str), results_);
+        }
+        return this;
+    }
+
+    /// Get the current complete results set
+    const results_t &results() const { return results_; }
+
+    /// Get a copy of the results
+    results_t reduced_results() const {
+        results_t res = proc_results_.empty() ? results_ : proc_results_;
+        if(current_option_state_ < option_state::reduced) {
+            if(current_option_state_ == option_state::parsing) {
+                res = results_;
+                _validate_results(res);
+            }
+            if(!res.empty()) {
+                results_t extra;
+                _reduce_results(extra, res);
+                if(!extra.empty()) {
+                    res = std::move(extra);
+                }
+            }
+        }
+        return res;
+    }
+
+    /// Get the results as a specified type
+    template <typename T> void results(T &output) const {
+        bool retval;
+        if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) {
+            const results_t &res = (proc_results_.empty()) ? results_ : proc_results_;
+            retval = detail::lexical_conversion<T, T>(res, output);
+        } else {
+            results_t res;
+            if(results_.empty()) {
+                if(!default_str_.empty()) {
+                    // _add_results takes an rvalue only
+                    _add_result(std::string(default_str_), res);
+                    _validate_results(res);
+                    results_t extra;
+                    _reduce_results(extra, res);
+                    if(!extra.empty()) {
+                        res = std::move(extra);
+                    }
+                } else {
+                    res.emplace_back();
+                }
+            } else {
+                res = reduced_results();
+            }
+            retval = detail::lexical_conversion<T, T>(res, output);
+        }
+        if(!retval) {
+            throw ConversionError(get_name(), results_);
+        }
+    }
+
+    /// Return the results as the specified type
+    template <typename T> T as() const {
+        T output;
+        results(output);
+        return output;
+    }
+
+    /// See if the callback has been run already
+    bool get_callback_run() const { return (current_option_state_ == option_state::callback_run); }
+
+    ///@}
+    /// @name Custom options
+    ///@{
+
+    /// Set the type function to run when displayed on this option
+    Option *type_name_fn(std::function<std::string()> typefun) {
+        type_name_ = std::move(typefun);
+        return this;
+    }
+
+    /// Set a custom option typestring
+    Option *type_name(std::string typeval) {
+        type_name_fn([typeval]() { return typeval; });
+        return this;
+    }
+
+    /// Set a custom option size
+    Option *type_size(int option_type_size) {
+        if(option_type_size < 0) {
+            // this section is included for backwards compatibility
+            type_size_max_ = -option_type_size;
+            type_size_min_ = -option_type_size;
+            expected_max_ = detail::expected_max_vector_size;
+        } else {
+            type_size_max_ = option_type_size;
+            if(type_size_max_ < detail::expected_max_vector_size) {
+                type_size_min_ = option_type_size;
+            } else {
+                inject_separator_ = true;
+            }
+            if(type_size_max_ == 0)
+                required_ = false;
+        }
+        return this;
+    }
+    /// Set a custom option type size range
+    Option *type_size(int option_type_size_min, int option_type_size_max) {
+        if(option_type_size_min < 0 || option_type_size_max < 0) {
+            // this section is included for backwards compatibility
+            expected_max_ = detail::expected_max_vector_size;
+            option_type_size_min = (std::abs)(option_type_size_min);
+            option_type_size_max = (std::abs)(option_type_size_max);
+        }
+
+        if(option_type_size_min > option_type_size_max) {
+            type_size_max_ = option_type_size_min;
+            type_size_min_ = option_type_size_max;
+        } else {
+            type_size_min_ = option_type_size_min;
+            type_size_max_ = option_type_size_max;
+        }
+        if(type_size_max_ == 0) {
+            required_ = false;
+        }
+        if(type_size_max_ >= detail::expected_max_vector_size) {
+            inject_separator_ = true;
+        }
+        return this;
+    }
+
+    /// Set the value of the separator injection flag
+    void inject_separator(bool value = true) { inject_separator_ = value; }
+
+    /// Set a capture function for the default. Mostly used by App.
+    Option *default_function(const std::function<std::string()> &func) {
+        default_function_ = func;
+        return this;
+    }
+
+    /// Capture the default value from the original value (if it can be captured)
+    Option *capture_default_str() {
+        if(default_function_) {
+            default_str_ = default_function_();
+        }
+        return this;
+    }
+
+    /// Set the default value string representation (does not change the contained value)
+    Option *default_str(std::string val) {
+        default_str_ = std::move(val);
+        return this;
+    }
+
+    /// Set the default value and validate the results and run the callback if appropriate to set the value into the
+    /// bound value only available for types that can be converted to a string
+    template <typename X> Option *default_val(const X &val) {
+        std::string val_str = detail::to_string(val);
+        auto old_option_state = current_option_state_;
+        results_t old_results{std::move(results_)};
+        results_.clear();
+        try {
+            add_result(val_str);
+            // if trigger_on_result_ is set the callback already ran
+            if(run_callback_for_default_ && !trigger_on_result_) {
+                run_callback();  // run callback sets the state we need to reset it again
+                current_option_state_ = option_state::parsing;
+            } else {
+                _validate_results(results_);
+                current_option_state_ = old_option_state;
+            }
+        } catch(const CLI::Error &) {
+            // this should be done
+            results_ = std::move(old_results);
+            current_option_state_ = old_option_state;
+            throw;
+        }
+        results_ = std::move(old_results);
+        default_str_ = std::move(val_str);
+        return this;
+    }
+
+    /// Get the full typename for this option
+    std::string get_type_name() const {
+        std::string full_type_name = type_name_();
+        if(!validators_.empty()) {
+            for(auto &Validator : validators_) {
+                std::string vtype = Validator.get_description();
+                if(!vtype.empty()) {
+                    full_type_name += ":" + vtype;
+                }
+            }
+        }
+        return full_type_name;
+    }
+
+  private:
+    /// Run the results through the Validators
+    void _validate_results(results_t &res) const {
+        // Run the Validators (can change the string)
+        if(!validators_.empty()) {
+            if(type_size_max_ > 1) {  // in this context index refers to the index in the type
+                int index = 0;
+                if(get_items_expected_max() < static_cast<int>(res.size()) &&
+                   multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
+                    // create a negative index for the earliest ones
+                    index = get_items_expected_max() - static_cast<int>(res.size());
+                }
+
+                for(std::string &result : res) {
+                    if(detail::is_separator(result) && type_size_max_ != type_size_min_ && index >= 0) {
+                        index = 0;  // reset index for variable size chunks
+                        continue;
+                    }
+                    auto err_msg = _validate(result, (index >= 0) ? (index % type_size_max_) : index);
+                    if(!err_msg.empty())
+                        throw ValidationError(get_name(), err_msg);
+                    ++index;
+                }
+            } else {
+                int index = 0;
+                if(expected_max_ < static_cast<int>(res.size()) &&
+                   multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) {
+                    // create a negative index for the earliest ones
+                    index = expected_max_ - static_cast<int>(res.size());
+                }
+                for(std::string &result : res) {
+                    auto err_msg = _validate(result, index);
+                    ++index;
+                    if(!err_msg.empty())
+                        throw ValidationError(get_name(), err_msg);
+                }
+            }
+        }
+    }
+
+    /** reduce the results in accordance with the MultiOptionPolicy
+    @param[out] res results are assigned to res if there if they are different
+    */
+    void _reduce_results(results_t &res, const results_t &original) const {
+
+        // max num items expected or length of vector, always at least 1
+        // Only valid for a trimming policy
+
+        res.clear();
+        // Operation depends on the policy setting
+        switch(multi_option_policy_) {
+        case MultiOptionPolicy::TakeAll:
+            break;
+        case MultiOptionPolicy::TakeLast: {
+            // Allow multi-option sizes (including 0)
+            std::size_t trim_size = std::min<std::size_t>(
+                static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
+            if(original.size() != trim_size) {
+                res.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end());
+            }
+        } break;
+        case MultiOptionPolicy::TakeFirst: {
+            std::size_t trim_size = std::min<std::size_t>(
+                static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size());
+            if(original.size() != trim_size) {
+                res.assign(original.begin(), original.begin() + static_cast<results_t::difference_type>(trim_size));
+            }
+        } break;
+        case MultiOptionPolicy::Join:
+            if(results_.size() > 1) {
+                res.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_)));
+            }
+            break;
+        case MultiOptionPolicy::Throw:
+        default: {
+            auto num_min = static_cast<std::size_t>(get_items_expected_min());
+            auto num_max = static_cast<std::size_t>(get_items_expected_max());
+            if(num_min == 0) {
+                num_min = 1;
+            }
+            if(num_max == 0) {
+                num_max = 1;
+            }
+            if(original.size() < num_min) {
+                throw ArgumentMismatch::AtLeast(get_name(), static_cast<int>(num_min), original.size());
+            }
+            if(original.size() > num_max) {
+                throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size());
+            }
+            break;
+        }
+        }
+    }
+
+    // Run a result through the Validators
+    std::string _validate(std::string &result, int index) const {
+        std::string err_msg;
+        if(result.empty() && expected_min_ == 0) {
+            // an empty with nothing expected is allowed
+            return err_msg;
+        }
+        for(const auto &vali : validators_) {
+            auto v = vali.get_application_index();
+            if(v == -1 || v == index) {
+                try {
+                    err_msg = vali(result);
+                } catch(const ValidationError &err) {
+                    err_msg = err.what();
+                }
+                if(!err_msg.empty())
+                    break;
+            }
+        }
+
+        return err_msg;
+    }
+
+    /// Add a single result to the result set, taking into account delimiters
+    int _add_result(std::string &&result, std::vector<std::string> &res) const {
+        int result_count = 0;
+        if(allow_extra_args_ && !result.empty() && result.front() == '[' &&
+           result.back() == ']') {  // this is now a vector string likely from the default or user entry
+            result.pop_back();
+
+            for(auto &var : CLI::detail::split(result.substr(1), ',')) {
+                if(!var.empty()) {
+                    result_count += _add_result(std::move(var), res);
+                }
+            }
+            return result_count;
+        }
+        if(delimiter_ == '\0') {
+            res.push_back(std::move(result));
+            ++result_count;
+        } else {
+            if((result.find_first_of(delimiter_) != std::string::npos)) {
+                for(const auto &var : CLI::detail::split(result, delimiter_)) {
+                    if(!var.empty()) {
+                        res.push_back(var);
+                        ++result_count;
+                    }
+                }
+            } else {
+                res.push_back(std::move(result));
+                ++result_count;
+            }
+        }
+        return result_count;
+    }
+};  // namespace CLI
+
+// [CLI11:option_hpp:end]
+}  // namespace CLI
diff --git a/external/CLI11/include/CLI/Split.hpp b/external/CLI11/include/CLI/Split.hpp
new file mode 100644
index 0000000..83ef572
--- /dev/null
+++ b/external/CLI11/include/CLI/Split.hpp
@@ -0,0 +1,143 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+#include "Error.hpp"
+#include "StringTools.hpp"
+
+namespace CLI {
+// [CLI11:split_hpp:verbatim]
+
+namespace detail {
+
+// Returns false if not a short option. Otherwise, sets opt name and rest and returns true
+inline bool split_short(const std::string &current, std::string &name, std::string &rest) {
+    if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) {
+        name = current.substr(1, 1);
+        rest = current.substr(2);
+        return true;
+    }
+    return false;
+}
+
+// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true
+inline bool split_long(const std::string &current, std::string &name, std::string &value) {
+    if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) {
+        auto loc = current.find_first_of('=');
+        if(loc != std::string::npos) {
+            name = current.substr(2, loc - 2);
+            value = current.substr(loc + 1);
+        } else {
+            name = current.substr(2);
+            value = "";
+        }
+        return true;
+    }
+    return false;
+}
+
+// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true
+inline bool split_windows_style(const std::string &current, std::string &name, std::string &value) {
+    if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) {
+        auto loc = current.find_first_of(':');
+        if(loc != std::string::npos) {
+            name = current.substr(1, loc - 1);
+            value = current.substr(loc + 1);
+        } else {
+            name = current.substr(1);
+            value = "";
+        }
+        return true;
+    }
+    return false;
+}
+
+// Splits a string into multiple long and short names
+inline std::vector<std::string> split_names(std::string current) {
+    std::vector<std::string> output;
+    std::size_t val;
+    while((val = current.find(",")) != std::string::npos) {
+        output.push_back(trim_copy(current.substr(0, val)));
+        current = current.substr(val + 1);
+    }
+    output.push_back(trim_copy(current));
+    return output;
+}
+
+/// extract default flag values either {def} or starting with a !
+inline std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) {
+    std::vector<std::string> flags = split_names(str);
+    flags.erase(std::remove_if(flags.begin(),
+                               flags.end(),
+                               [](const std::string &name) {
+                                   return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) &&
+                                                                 (name.back() == '}')) ||
+                                                                (name[0] == '!'))));
+                               }),
+                flags.end());
+    std::vector<std::pair<std::string, std::string>> output;
+    output.reserve(flags.size());
+    for(auto &flag : flags) {
+        auto def_start = flag.find_first_of('{');
+        std::string defval = "false";
+        if((def_start != std::string::npos) && (flag.back() == '}')) {
+            defval = flag.substr(def_start + 1);
+            defval.pop_back();
+            flag.erase(def_start, std::string::npos);
+        }
+        flag.erase(0, flag.find_first_not_of("-!"));
+        output.emplace_back(flag, defval);
+    }
+    return output;
+}
+
+/// Get a vector of short names, one of long names, and a single name
+inline std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>
+get_names(const std::vector<std::string> &input) {
+
+    std::vector<std::string> short_names;
+    std::vector<std::string> long_names;
+    std::string pos_name;
+
+    for(std::string name : input) {
+        if(name.length() == 0) {
+            continue;
+        }
+        if(name.length() > 1 && name[0] == '-' && name[1] != '-') {
+            if(name.length() == 2 && valid_first_char(name[1]))
+                short_names.emplace_back(1, name[1]);
+            else
+                throw BadNameString::OneCharName(name);
+        } else if(name.length() > 2 && name.substr(0, 2) == "--") {
+            name = name.substr(2);
+            if(valid_name_string(name))
+                long_names.push_back(name);
+            else
+                throw BadNameString::BadLongName(name);
+        } else if(name == "-" || name == "--") {
+            throw BadNameString::DashesOnly(name);
+        } else {
+            if(pos_name.length() > 0)
+                throw BadNameString::MultiPositionalNames(name);
+            pos_name = name;
+        }
+    }
+
+    return std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>(
+        short_names, long_names, pos_name);
+}
+
+}  // namespace detail
+// [CLI11:split_hpp:end]
+}  // namespace CLI
diff --git a/external/CLI11/include/CLI/StringTools.hpp b/external/CLI11/include/CLI/StringTools.hpp
new file mode 100644
index 0000000..4718aed
--- /dev/null
+++ b/external/CLI11/include/CLI/StringTools.hpp
@@ -0,0 +1,430 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <algorithm>
+#include <iomanip>
+#include <locale>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <vector>
+// [CLI11:public_includes:end]
+
+namespace CLI {
+
+// [CLI11:string_tools_hpp:verbatim]
+
+/// Include the items in this namespace to get free conversion of enums to/from streams.
+/// (This is available inside CLI as well, so CLI11 will use this without a using statement).
+namespace enums {
+
+/// output streaming for enumerations
+template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
+std::ostream &operator<<(std::ostream &in, const T &item) {
+    // make sure this is out of the detail namespace otherwise it won't be found when needed
+    return in << static_cast<typename std::underlying_type<T>::type>(item);
+}
+
+}  // namespace enums
+
+/// Export to CLI namespace
+using enums::operator<<;
+
+namespace detail {
+/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not
+/// produce overflow for some expected uses
+constexpr int expected_max_vector_size{1 << 29};
+// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
+/// Split a string by a delim
+inline std::vector<std::string> split(const std::string &s, char delim) {
+    std::vector<std::string> elems;
+    // Check to see if empty string, give consistent result
+    if(s.empty()) {
+        elems.emplace_back();
+    } else {
+        std::stringstream ss;
+        ss.str(s);
+        std::string item;
+        while(std::getline(ss, item, delim)) {
+            elems.push_back(item);
+        }
+    }
+    return elems;
+}
+
+/// Simple function to join a string
+template <typename T> std::string join(const T &v, std::string delim = ",") {
+    std::ostringstream s;
+    auto beg = std::begin(v);
+    auto end = std::end(v);
+    if(beg != end)
+        s << *beg++;
+    while(beg != end) {
+        s << delim << *beg++;
+    }
+    return s.str();
+}
+
+/// Simple function to join a string from processed elements
+template <typename T,
+          typename Callable,
+          typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type>
+std::string join(const T &v, Callable func, std::string delim = ",") {
+    std::ostringstream s;
+    auto beg = std::begin(v);
+    auto end = std::end(v);
+    auto loc = s.tellp();
+    while(beg != end) {
+        auto nloc = s.tellp();
+        if(nloc > loc) {
+            s << delim;
+            loc = nloc;
+        }
+        s << func(*beg++);
+    }
+    return s.str();
+}
+
+/// Join a string in reverse order
+template <typename T> std::string rjoin(const T &v, std::string delim = ",") {
+    std::ostringstream s;
+    for(std::size_t start = 0; start < v.size(); start++) {
+        if(start > 0)
+            s << delim;
+        s << v[v.size() - start - 1];
+    }
+    return s.str();
+}
+
+// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string
+
+/// Trim whitespace from left of string
+inline std::string &ltrim(std::string &str) {
+    auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
+    str.erase(str.begin(), it);
+    return str;
+}
+
+/// Trim anything from left of string
+inline std::string &ltrim(std::string &str, const std::string &filter) {
+    auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
+    str.erase(str.begin(), it);
+    return str;
+}
+
+/// Trim whitespace from right of string
+inline std::string &rtrim(std::string &str) {
+    auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
+    str.erase(it.base(), str.end());
+    return str;
+}
+
+/// Trim anything from right of string
+inline std::string &rtrim(std::string &str, const std::string &filter) {
+    auto it =
+        std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
+    str.erase(it.base(), str.end());
+    return str;
+}
+
+/// Trim whitespace from string
+inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); }
+
+/// Trim anything from string
+inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); }
+
+/// Make a copy of the string and then trim it
+inline std::string trim_copy(const std::string &str) {
+    std::string s = str;
+    return trim(s);
+}
+
+/// remove quotes at the front and back of a string either '"' or '\''
+inline std::string &remove_quotes(std::string &str) {
+    if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) {
+        if(str.front() == str.back()) {
+            str.pop_back();
+            str.erase(str.begin(), str.begin() + 1);
+        }
+    }
+    return str;
+}
+
+/// Add a leader to the beginning of all new lines (nothing is added
+/// at the start of the first line). `"; "` would be for ini files
+///
+/// Can't use Regex, or this would be a subs.
+inline std::string fix_newlines(const std::string &leader, std::string input) {
+    std::string::size_type n = 0;
+    while(n != std::string::npos && n < input.size()) {
+        n = input.find('\n', n);
+        if(n != std::string::npos) {
+            input = input.substr(0, n + 1) + leader + input.substr(n + 1);
+            n += leader.size();
+        }
+    }
+    return input;
+}
+
+/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered)
+inline std::string trim_copy(const std::string &str, const std::string &filter) {
+    std::string s = str;
+    return trim(s, filter);
+}
+/// Print a two part "help" string
+inline std::ostream &format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) {
+    name = "  " + name;
+    out << std::setw(static_cast<int>(wid)) << std::left << name;
+    if(!description.empty()) {
+        if(name.length() >= wid)
+            out << "\n" << std::setw(static_cast<int>(wid)) << "";
+        for(const char c : description) {
+            out.put(c);
+            if(c == '\n') {
+                out << std::setw(static_cast<int>(wid)) << "";
+            }
+        }
+    }
+    out << "\n";
+    return out;
+}
+
+/// Print subcommand aliases
+inline std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid) {
+    if(!aliases.empty()) {
+        out << std::setw(static_cast<int>(wid)) << "     aliases: ";
+        bool front = true;
+        for(const auto &alias : aliases) {
+            if(!front) {
+                out << ", ";
+            } else {
+                front = false;
+            }
+            out << detail::fix_newlines("              ", alias);
+        }
+        out << "\n";
+    }
+    return out;
+}
+
+/// Verify the first character of an option
+/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with
+template <typename T> bool valid_first_char(T c) { return ((c != '-') && (c != '!') && (c != ' ') && c != '\n'); }
+
+/// Verify following characters of an option
+template <typename T> bool valid_later_char(T c) {
+    // = and : are value separators, { has special meaning for option defaults,
+    // and \n would just be annoying to deal with in many places allowing space here has too much potential for
+    // inadvertent entry errors and bugs
+    return ((c != '=') && (c != ':') && (c != '{') && (c != ' ') && c != '\n');
+}
+
+/// Verify an option/subcommand name
+inline bool valid_name_string(const std::string &str) {
+    if(str.empty() || !valid_first_char(str[0])) {
+        return false;
+    }
+    auto e = str.end();
+    for(auto c = str.begin() + 1; c != e; ++c)
+        if(!valid_later_char(*c))
+            return false;
+    return true;
+}
+
+/// Verify an app name
+inline bool valid_alias_name_string(const std::string &str) {
+    static const std::string badChars(std::string("\n") + '\0');
+    return (str.find_first_of(badChars) == std::string::npos);
+}
+
+/// check if a string is a container segment separator (empty or "%%")
+inline bool is_separator(const std::string &str) {
+    static const std::string sep("%%");
+    return (str.empty() || str == sep);
+}
+
+/// Verify that str consists of letters only
+inline bool isalpha(const std::string &str) {
+    return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); });
+}
+
+/// Return a lower case version of a string
+inline std::string to_lower(std::string str) {
+    std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) {
+        return std::tolower(x, std::locale());
+    });
+    return str;
+}
+
+/// remove underscores from a string
+inline std::string remove_underscore(std::string str) {
+    str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str));
+    return str;
+}
+
+/// Find and replace a substring with another substring
+inline std::string find_and_replace(std::string str, std::string from, std::string to) {
+
+    std::size_t start_pos = 0;
+
+    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
+        str.replace(start_pos, from.length(), to);
+        start_pos += to.length();
+    }
+
+    return str;
+}
+
+/// check if the flag definitions has possible false flags
+inline bool has_default_flag_values(const std::string &flags) {
+    return (flags.find_first_of("{!") != std::string::npos);
+}
+
+inline void remove_default_flag_values(std::string &flags) {
+    auto loc = flags.find_first_of('{', 2);
+    while(loc != std::string::npos) {
+        auto finish = flags.find_first_of("},", loc + 1);
+        if((finish != std::string::npos) && (flags[finish] == '}')) {
+            flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),
+                        flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);
+        }
+        loc = flags.find_first_of('{', loc + 1);
+    }
+    flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
+}
+
+/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores
+inline std::ptrdiff_t find_member(std::string name,
+                                  const std::vector<std::string> names,
+                                  bool ignore_case = false,
+                                  bool ignore_underscore = false) {
+    auto it = std::end(names);
+    if(ignore_case) {
+        if(ignore_underscore) {
+            name = detail::to_lower(detail::remove_underscore(name));
+            it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
+                return detail::to_lower(detail::remove_underscore(local_name)) == name;
+            });
+        } else {
+            name = detail::to_lower(name);
+            it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
+                return detail::to_lower(local_name) == name;
+            });
+        }
+
+    } else if(ignore_underscore) {
+        name = detail::remove_underscore(name);
+        it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
+            return detail::remove_underscore(local_name) == name;
+        });
+    } else {
+        it = std::find(std::begin(names), std::end(names), name);
+    }
+
+    return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
+}
+
+/// Find a trigger string and call a modify callable function that takes the current string and starting position of the
+/// trigger and returns the position in the string to search for the next trigger string
+template <typename Callable> inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) {
+    std::size_t start_pos = 0;
+    while((start_pos = str.find(trigger, start_pos)) != std::string::npos) {
+        start_pos = modify(str, start_pos);
+    }
+    return str;
+}
+
+/// Split a string '"one two" "three"' into 'one two', 'three'
+/// Quote characters can be ` ' or "
+inline std::vector<std::string> split_up(std::string str, char delimiter = '\0') {
+
+    const std::string delims("\'\"`");
+    auto find_ws = [delimiter](char ch) {
+        return (delimiter == '\0') ? (std::isspace<char>(ch, std::locale()) != 0) : (ch == delimiter);
+    };
+    trim(str);
+
+    std::vector<std::string> output;
+    bool embeddedQuote = false;
+    char keyChar = ' ';
+    while(!str.empty()) {
+        if(delims.find_first_of(str[0]) != std::string::npos) {
+            keyChar = str[0];
+            auto end = str.find_first_of(keyChar, 1);
+            while((end != std::string::npos) && (str[end - 1] == '\\')) {  // deal with escaped quotes
+                end = str.find_first_of(keyChar, end + 1);
+                embeddedQuote = true;
+            }
+            if(end != std::string::npos) {
+                output.push_back(str.substr(1, end - 1));
+                if(end + 2 < str.size()) {
+                    str = str.substr(end + 2);
+                } else {
+                    str.clear();
+                }
+
+            } else {
+                output.push_back(str.substr(1));
+                str = "";
+            }
+        } else {
+            auto it = std::find_if(std::begin(str), std::end(str), find_ws);
+            if(it != std::end(str)) {
+                std::string value = std::string(str.begin(), it);
+                output.push_back(value);
+                str = std::string(it + 1, str.end());
+            } else {
+                output.push_back(str);
+                str = "";
+            }
+        }
+        // transform any embedded quotes into the regular character
+        if(embeddedQuote) {
+            output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
+            embeddedQuote = false;
+        }
+        trim(str);
+    }
+    return output;
+}
+
+/// This function detects an equal or colon followed by an escaped quote after an argument
+/// then modifies the string to replace the equality with a space.  This is needed
+/// to allow the split up function to work properly and is intended to be used with the find_and_modify function
+/// the return value is the offset+1 which is required by the find_and_modify function.
+inline std::size_t escape_detect(std::string &str, std::size_t offset) {
+    auto next = str[offset + 1];
+    if((next == '\"') || (next == '\'') || (next == '`')) {
+        auto astart = str.find_last_of("-/ \"\'`", offset - 1);
+        if(astart != std::string::npos) {
+            if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
+                str[offset] = ' ';  // interpret this as a space so the split_up works properly
+        }
+    }
+    return offset + 1;
+}
+
+/// Add quotes if the string contains spaces
+inline std::string &add_quotes_if_needed(std::string &str) {
+    if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) {
+        char quote = str.find('"') < str.find('\'') ? '\'' : '"';
+        if(str.find(' ') != std::string::npos) {
+            str.insert(0, 1, quote);
+            str.append(1, quote);
+        }
+    }
+    return str;
+}
+
+}  // namespace detail
+
+// [CLI11:string_tools_hpp:end]
+
+}  // namespace CLI
diff --git a/external/CLI11/include/CLI/Timer.hpp b/external/CLI11/include/CLI/Timer.hpp
new file mode 100644
index 0000000..429ca02
--- /dev/null
+++ b/external/CLI11/include/CLI/Timer.hpp
@@ -0,0 +1,134 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// On GCC < 4.8, the following define is often missing. Due to the
+// fact that this library only uses sleep_for, this should be safe
+#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5 && __GNUC_MINOR__ < 8
+#define _GLIBCXX_USE_NANOSLEEP
+#endif
+
+#include <array>
+#include <chrono>
+#include <functional>
+#include <iostream>
+#include <string>
+#include <utility>
+
+namespace CLI {
+
+/// This is a simple timer with pretty printing. Creating the timer starts counting.
+class Timer {
+  protected:
+    /// This is a typedef to make clocks easier to use
+    using clock = std::chrono::steady_clock;
+
+    /// This typedef is for points in time
+    using time_point = std::chrono::time_point<clock>;
+
+    /// This is the type of a printing function, you can make your own
+    using time_print_t = std::function<std::string(std::string, std::string)>;
+
+    /// This is the title of the timer
+    std::string title_;
+
+    /// This is the function that is used to format most of the timing message
+    time_print_t time_print_;
+
+    /// This is the starting point (when the timer was created)
+    time_point start_;
+
+    /// This is the number of times cycles (print divides by this number)
+    std::size_t cycles{1};
+
+  public:
+    /// Standard print function, this one is set by default
+    static std::string Simple(std::string title, std::string time) { return title + ": " + time; }
+
+    /// This is a fancy print function with --- headers
+    static std::string Big(std::string title, std::string time) {
+        return std::string("-----------------------------------------\n") + "| " + title + " | Time = " + time + "\n" +
+               "-----------------------------------------";
+    }
+
+  public:
+    /// Standard constructor, can set title and print function
+    explicit Timer(std::string title = "Timer", time_print_t time_print = Simple)
+        : title_(std::move(title)), time_print_(std::move(time_print)), start_(clock::now()) {}
+
+    /// Time a function by running it multiple times. Target time is the len to target.
+    std::string time_it(std::function<void()> f, double target_time = 1) {
+        time_point start = start_;
+        double total_time;
+
+        start_ = clock::now();
+        std::size_t n = 0;
+        do {
+            f();
+            std::chrono::duration<double> elapsed = clock::now() - start_;
+            total_time = elapsed.count();
+        } while(n++ < 100u && total_time < target_time);
+
+        std::string out = make_time_str(total_time / static_cast<double>(n)) + " for " + std::to_string(n) + " tries";
+        start_ = start;
+        return out;
+    }
+
+    /// This formats the numerical value for the time string
+    std::string make_time_str() const {
+        time_point stop = clock::now();
+        std::chrono::duration<double> elapsed = stop - start_;
+        double time = elapsed.count() / static_cast<double>(cycles);
+        return make_time_str(time);
+    }
+
+    // LCOV_EXCL_START
+    /// This prints out a time string from a time
+    std::string make_time_str(double time) const {
+        auto print_it = [](double x, std::string unit) {
+            const unsigned int buffer_length = 50;
+            std::array<char, buffer_length> buffer;
+            std::snprintf(buffer.data(), buffer_length, "%.5g", x);
+            return buffer.data() + std::string(" ") + unit;
+        };
+
+        if(time < .000001)
+            return print_it(time * 1000000000, "ns");
+        else if(time < .001)
+            return print_it(time * 1000000, "us");
+        else if(time < 1)
+            return print_it(time * 1000, "ms");
+        else
+            return print_it(time, "s");
+    }
+    // LCOV_EXCL_STOP
+
+    /// This is the main function, it creates a string
+    std::string to_string() const { return time_print_(title_, make_time_str()); }
+
+    /// Division sets the number of cycles to divide by (no graphical change)
+    Timer &operator/(std::size_t val) {
+        cycles = val;
+        return *this;
+    }
+};
+
+/// This class prints out the time upon destruction
+class AutoTimer : public Timer {
+  public:
+    /// Reimplementing the constructor is required in GCC 4.7
+    explicit AutoTimer(std::string title = "Timer", time_print_t time_print = Simple) : Timer(title, time_print) {}
+    // GCC 4.7 does not support using inheriting constructors.
+
+    /// This destructor prints the string
+    ~AutoTimer() { std::cout << to_string() << std::endl; }
+};
+
+}  // namespace CLI
+
+/// This prints out the time if shifted into a std::cout like stream.
+inline std::ostream &operator<<(std::ostream &in, const CLI::Timer &timer) { return in << timer.to_string(); }
diff --git a/external/CLI11/include/CLI/TypeTools.hpp b/external/CLI11/include/CLI/TypeTools.hpp
new file mode 100644
index 0000000..0fa2299
--- /dev/null
+++ b/external/CLI11/include/CLI/TypeTools.hpp
@@ -0,0 +1,1548 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:public_includes:set]
+#include <cstdint>
+#include <exception>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+#include "StringTools.hpp"
+
+namespace CLI {
+// [CLI11:type_tools_hpp:verbatim]
+
+// Type tools
+
+// Utilities for type enabling
+namespace detail {
+// Based generally on https://rmf.io/cxx11/almost-static-if
+/// Simple empty scoped class
+enum class enabler {};
+
+/// An instance to use in EnableIf
+constexpr enabler dummy = {};
+}  // namespace detail
+
+/// A copy of enable_if_t from C++14, compatible with C++11.
+///
+/// We could check to see if C++14 is being used, but it does not hurt to redefine this
+/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h)
+/// It is not in the std namespace anyway, so no harm done.
+template <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type;
+
+/// A copy of std::void_t from C++17 (helper for C++11 and C++14)
+template <typename... Ts> struct make_void { using type = void; };
+
+/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine
+template <typename... Ts> using void_t = typename make_void<Ts...>::type;
+
+/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine
+template <bool B, class T, class F> using conditional_t = typename std::conditional<B, T, F>::type;
+
+/// Check to see if something is bool (fail check by default)
+template <typename T> struct is_bool : std::false_type {};
+
+/// Check to see if something is bool (true if actually a bool)
+template <> struct is_bool<bool> : std::true_type {};
+
+/// Check to see if something is a shared pointer
+template <typename T> struct is_shared_ptr : std::false_type {};
+
+/// Check to see if something is a shared pointer (True if really a shared pointer)
+template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
+
+/// Check to see if something is a shared pointer (True if really a shared pointer)
+template <typename T> struct is_shared_ptr<const std::shared_ptr<T>> : std::true_type {};
+
+/// Check to see if something is copyable pointer
+template <typename T> struct is_copyable_ptr {
+    static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value;
+};
+
+/// This can be specialized to override the type deduction for IsMember.
+template <typename T> struct IsMemberType { using type = T; };
+
+/// The main custom type needed here is const char * should be a string.
+template <> struct IsMemberType<const char *> { using type = std::string; };
+
+namespace detail {
+
+// These are utilities for IsMember and other transforming objects
+
+/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that
+/// pointer_traits<T> be valid.
+
+/// not a pointer
+template <typename T, typename Enable = void> struct element_type { using type = T; };
+
+template <typename T> struct element_type<T, typename std::enable_if<is_copyable_ptr<T>::value>::type> {
+    using type = typename std::pointer_traits<T>::element_type;
+};
+
+/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of
+/// the container
+template <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; };
+
+/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing.
+template <typename T, typename _ = void> struct pair_adaptor : std::false_type {
+    using value_type = typename T::value_type;
+    using first_type = typename std::remove_const<value_type>::type;
+    using second_type = typename std::remove_const<value_type>::type;
+
+    /// Get the first value (really just the underlying value)
+    template <typename Q> static auto first(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) {
+        return std::forward<Q>(pair_value);
+    }
+    /// Get the second value (really just the underlying value)
+    template <typename Q> static auto second(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) {
+        return std::forward<Q>(pair_value);
+    }
+};
+
+/// Adaptor for map-like structure (true version, must have key_type and mapped_type).
+/// This wraps a mapped container in a few utilities access it in a general way.
+template <typename T>
+struct pair_adaptor<
+    T,
+    conditional_t<false, void_t<typename T::value_type::first_type, typename T::value_type::second_type>, void>>
+    : std::true_type {
+    using value_type = typename T::value_type;
+    using first_type = typename std::remove_const<typename value_type::first_type>::type;
+    using second_type = typename std::remove_const<typename value_type::second_type>::type;
+
+    /// Get the first value (really just the underlying value)
+    template <typename Q> static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward<Q>(pair_value))) {
+        return std::get<0>(std::forward<Q>(pair_value));
+    }
+    /// Get the second value (really just the underlying value)
+    template <typename Q> static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward<Q>(pair_value))) {
+        return std::get<1>(std::forward<Q>(pair_value));
+    }
+};
+
+// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning
+// in the unevaluated context even if the function that was using this wasn't used.  The standard says narrowing in
+// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts.  It is a
+// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out.
+// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be
+// suppressed
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnarrowing"
+#endif
+// check for constructibility from a specific type and copy assignable used in the parse detection
+template <typename T, typename C> class is_direct_constructible {
+    template <typename TT, typename CC>
+    static auto test(int, std::true_type) -> decltype(
+// NVCC warns about narrowing conversions here
+#ifdef __CUDACC__
+#pragma diag_suppress 2361
+#endif
+        TT { std::declval<CC>() }
+#ifdef __CUDACC__
+#pragma diag_default 2361
+#endif
+        ,
+        std::is_move_assignable<TT>());
+
+    template <typename TT, typename CC> static auto test(int, std::false_type) -> std::false_type;
+
+    template <typename, typename> static auto test(...) -> std::false_type;
+
+  public:
+    static constexpr bool value = decltype(test<T, C>(0, typename std::is_constructible<T, C>::type()))::value;
+};
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+// Check for output streamability
+// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream
+
+template <typename T, typename S = std::ostringstream> class is_ostreamable {
+    template <typename TT, typename SS>
+    static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type());
+
+    template <typename, typename> static auto test(...) -> std::false_type;
+
+  public:
+    static constexpr bool value = decltype(test<T, S>(0))::value;
+};
+
+/// Check for input streamability
+template <typename T, typename S = std::istringstream> class is_istreamable {
+    template <typename TT, typename SS>
+    static auto test(int) -> decltype(std::declval<SS &>() >> std::declval<TT &>(), std::true_type());
+
+    template <typename, typename> static auto test(...) -> std::false_type;
+
+  public:
+    static constexpr bool value = decltype(test<T, S>(0))::value;
+};
+
+/// Check for complex
+template <typename T> class is_complex {
+    template <typename TT>
+    static auto test(int) -> decltype(std::declval<TT>().real(), std::declval<TT>().imag(), std::true_type());
+
+    template <typename> static auto test(...) -> std::false_type;
+
+  public:
+    static constexpr bool value = decltype(test<T>(0))::value;
+};
+
+/// Templated operation to get a value from a stream
+template <typename T, enable_if_t<is_istreamable<T>::value, detail::enabler> = detail::dummy>
+bool from_stream(const std::string &istring, T &obj) {
+    std::istringstream is;
+    is.str(istring);
+    is >> obj;
+    return !is.fail() && !is.rdbuf()->in_avail();
+}
+
+template <typename T, enable_if_t<!is_istreamable<T>::value, detail::enabler> = detail::dummy>
+bool from_stream(const std::string & /*istring*/, T & /*obj*/) {
+    return false;
+}
+
+// check to see if an object is a mutable container (fail by default)
+template <typename T, typename _ = void> struct is_mutable_container : std::false_type {};
+
+/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and
+/// end methods and an insert function.  And for our purposes we exclude std::string and types that can be constructed
+/// from a std::string
+template <typename T>
+struct is_mutable_container<
+    T,
+    conditional_t<false,
+                  void_t<typename T::value_type,
+                         decltype(std::declval<T>().end()),
+                         decltype(std::declval<T>().clear()),
+                         decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(),
+                                                           std::declval<const typename T::value_type &>()))>,
+                  void>>
+    : public conditional_t<std::is_constructible<T, std::string>::value, std::false_type, std::true_type> {};
+
+// check to see if an object is a mutable container (fail by default)
+template <typename T, typename _ = void> struct is_readable_container : std::false_type {};
+
+/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end
+/// methods and an insert function.  And for our purposes we exclude std::string and types that can be constructed from
+/// a std::string
+template <typename T>
+struct is_readable_container<
+    T,
+    conditional_t<false, void_t<decltype(std::declval<T>().end()), decltype(std::declval<T>().begin())>, void>>
+    : public std::true_type {};
+
+// check to see if an object is a wrapper (fail by default)
+template <typename T, typename _ = void> struct is_wrapper : std::false_type {};
+
+// check if an object is a wrapper (it has a value_type defined)
+template <typename T>
+struct is_wrapper<T, conditional_t<false, void_t<typename T::value_type>, void>> : public std::true_type {};
+
+// Check for tuple like types, as in classes with a tuple_size type trait
+template <typename S> class is_tuple_like {
+    template <typename SS>
+    // static auto test(int)
+    //     -> decltype(std::conditional<(std::tuple_size<SS>::value > 0), std::true_type, std::false_type>::type());
+    static auto test(int) -> decltype(std::tuple_size<typename std::decay<SS>::type>::value, std::true_type{});
+    template <typename> static auto test(...) -> std::false_type;
+
+  public:
+    static constexpr bool value = decltype(test<S>(0))::value;
+};
+
+/// Convert an object to a string (directly forward if this can become a string)
+template <typename T, enable_if_t<std::is_convertible<T, std::string>::value, detail::enabler> = detail::dummy>
+auto to_string(T &&value) -> decltype(std::forward<T>(value)) {
+    return std::forward<T>(value);
+}
+
+/// Construct a string from the object
+template <typename T,
+          enable_if_t<std::is_constructible<std::string, T>::value && !std::is_convertible<T, std::string>::value,
+                      detail::enabler> = detail::dummy>
+std::string to_string(const T &value) {
+    return std::string(value);
+}
+
+/// Convert an object to a string (streaming must be supported for that type)
+template <typename T,
+          enable_if_t<!std::is_convertible<std::string, T>::value && !std::is_constructible<std::string, T>::value &&
+                          is_ostreamable<T>::value,
+                      detail::enabler> = detail::dummy>
+std::string to_string(T &&value) {
+    std::stringstream stream;
+    stream << value;
+    return stream.str();
+}
+
+/// If conversion is not supported, return an empty string (streaming is not supported for that type)
+template <typename T,
+          enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
+                          !is_readable_container<typename std::remove_const<T>::type>::value,
+                      detail::enabler> = detail::dummy>
+std::string to_string(T &&) {
+    return std::string{};
+}
+
+/// convert a readable container to a string
+template <typename T,
+          enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value &&
+                          is_readable_container<T>::value,
+                      detail::enabler> = detail::dummy>
+std::string to_string(T &&variable) {
+    std::vector<std::string> defaults;
+    auto cval = variable.begin();
+    auto end = variable.end();
+    while(cval != end) {
+        defaults.emplace_back(CLI::detail::to_string(*cval));
+        ++cval;
+    }
+    return std::string("[" + detail::join(defaults) + "]");
+}
+
+/// special template overload
+template <typename T1,
+          typename T2,
+          typename T,
+          enable_if_t<std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
+auto checked_to_string(T &&value) -> decltype(to_string(std::forward<T>(value))) {
+    return to_string(std::forward<T>(value));
+}
+
+/// special template overload
+template <typename T1,
+          typename T2,
+          typename T,
+          enable_if_t<!std::is_same<T1, T2>::value, detail::enabler> = detail::dummy>
+std::string checked_to_string(T &&) {
+    return std::string{};
+}
+/// get a string as a convertible value for arithmetic types
+template <typename T, enable_if_t<std::is_arithmetic<T>::value, detail::enabler> = detail::dummy>
+std::string value_string(const T &value) {
+    return std::to_string(value);
+}
+/// get a string as a convertible value for enumerations
+template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy>
+std::string value_string(const T &value) {
+    return std::to_string(static_cast<typename std::underlying_type<T>::type>(value));
+}
+/// for other types just use the regular to_string function
+template <typename T,
+          enable_if_t<!std::is_enum<T>::value && !std::is_arithmetic<T>::value, detail::enabler> = detail::dummy>
+auto value_string(const T &value) -> decltype(to_string(value)) {
+    return to_string(value);
+}
+
+/// template to get the underlying value type if it exists or use a default
+template <typename T, typename def, typename Enable = void> struct wrapped_type { using type = def; };
+
+/// Type size for regular object types that do not look like a tuple
+template <typename T, typename def> struct wrapped_type<T, def, typename std::enable_if<is_wrapper<T>::value>::type> {
+    using type = typename T::value_type;
+};
+
+/// This will only trigger for actual void type
+template <typename T, typename Enable = void> struct type_count_base { static const int value{0}; };
+
+/// Type size for regular object types that do not look like a tuple
+template <typename T>
+struct type_count_base<T,
+                       typename std::enable_if<!is_tuple_like<T>::value && !is_mutable_container<T>::value &&
+                                               !std::is_void<T>::value>::type> {
+    static constexpr int value{1};
+};
+
+/// the base tuple size
+template <typename T>
+struct type_count_base<T, typename std::enable_if<is_tuple_like<T>::value && !is_mutable_container<T>::value>::type> {
+    static constexpr int value{std::tuple_size<T>::value};
+};
+
+/// Type count base for containers is the type_count_base of the individual element
+template <typename T> struct type_count_base<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
+    static constexpr int value{type_count_base<typename T::value_type>::value};
+};
+
+/// Set of overloads to get the type size of an object
+
+/// forward declare the subtype_count structure
+template <typename T> struct subtype_count;
+
+/// forward declare the subtype_count_min structure
+template <typename T> struct subtype_count_min;
+
+/// This will only trigger for actual void type
+template <typename T, typename Enable = void> struct type_count { static const int value{0}; };
+
+/// Type size for regular object types that do not look like a tuple
+template <typename T>
+struct type_count<T,
+                  typename std::enable_if<!is_wrapper<T>::value && !is_tuple_like<T>::value && !is_complex<T>::value &&
+                                          !std::is_void<T>::value>::type> {
+    static constexpr int value{1};
+};
+
+/// Type size for complex since it sometimes looks like a wrapper
+template <typename T> struct type_count<T, typename std::enable_if<is_complex<T>::value>::type> {
+    static constexpr int value{2};
+};
+
+/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes)
+template <typename T> struct type_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
+    static constexpr int value{subtype_count<typename T::value_type>::value};
+};
+
+/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes)
+template <typename T>
+struct type_count<T,
+                  typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value &&
+                                          !is_mutable_container<T>::value>::type> {
+    static constexpr int value{type_count<typename T::value_type>::value};
+};
+
+/// 0 if the index > tuple size
+template <typename T, std::size_t I>
+constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size() {
+    return 0;
+}
+
+/// Recursively generate the tuple type name
+template <typename T, std::size_t I>
+    constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size() {
+    return subtype_count<typename std::tuple_element<I, T>::type>::value + tuple_type_size<T, I + 1>();
+}
+
+/// Get the type size of the sum of type sizes for all the individual tuple types
+template <typename T> struct type_count<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
+    static constexpr int value{tuple_type_size<T, 0>()};
+};
+
+/// definition of subtype count
+template <typename T> struct subtype_count {
+    static constexpr int value{is_mutable_container<T>::value ? expected_max_vector_size : type_count<T>::value};
+};
+
+/// This will only trigger for actual void type
+template <typename T, typename Enable = void> struct type_count_min { static const int value{0}; };
+
+/// Type size for regular object types that do not look like a tuple
+template <typename T>
+struct type_count_min<
+    T,
+    typename std::enable_if<!is_mutable_container<T>::value && !is_tuple_like<T>::value && !is_wrapper<T>::value &&
+                            !is_complex<T>::value && !std::is_void<T>::value>::type> {
+    static constexpr int value{type_count<T>::value};
+};
+
+/// Type size for complex since it sometimes looks like a wrapper
+template <typename T> struct type_count_min<T, typename std::enable_if<is_complex<T>::value>::type> {
+    static constexpr int value{1};
+};
+
+/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes)
+template <typename T>
+struct type_count_min<
+    T,
+    typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value>::type> {
+    static constexpr int value{subtype_count_min<typename T::value_type>::value};
+};
+
+/// 0 if the index > tuple size
+template <typename T, std::size_t I>
+constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size_min() {
+    return 0;
+}
+
+/// Recursively generate the tuple type name
+template <typename T, std::size_t I>
+    constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size_min() {
+    return subtype_count_min<typename std::tuple_element<I, T>::type>::value + tuple_type_size_min<T, I + 1>();
+}
+
+/// Get the type size of the sum of type sizes for all the individual tuple types
+template <typename T> struct type_count_min<T, typename std::enable_if<is_tuple_like<T>::value>::type> {
+    static constexpr int value{tuple_type_size_min<T, 0>()};
+};
+
+/// definition of subtype count
+template <typename T> struct subtype_count_min {
+    static constexpr int value{is_mutable_container<T>::value
+                                   ? ((type_count<T>::value < expected_max_vector_size) ? type_count<T>::value : 0)
+                                   : type_count_min<T>::value};
+};
+
+/// This will only trigger for actual void type
+template <typename T, typename Enable = void> struct expected_count { static const int value{0}; };
+
+/// For most types the number of expected items is 1
+template <typename T>
+struct expected_count<T,
+                      typename std::enable_if<!is_mutable_container<T>::value && !is_wrapper<T>::value &&
+                                              !std::is_void<T>::value>::type> {
+    static constexpr int value{1};
+};
+/// number of expected items in a vector
+template <typename T> struct expected_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
+    static constexpr int value{expected_max_vector_size};
+};
+
+/// number of expected items in a vector
+template <typename T>
+struct expected_count<T, typename std::enable_if<!is_mutable_container<T>::value && is_wrapper<T>::value>::type> {
+    static constexpr int value{expected_count<typename T::value_type>::value};
+};
+
+// Enumeration of the different supported categorizations of objects
+enum class object_category : int {
+    char_value = 1,
+    integral_value = 2,
+    unsigned_integral = 4,
+    enumeration = 6,
+    boolean_value = 8,
+    floating_point = 10,
+    number_constructible = 12,
+    double_constructible = 14,
+    integer_constructible = 16,
+    // string like types
+    string_assignable = 23,
+    string_constructible = 24,
+    other = 45,
+    // special wrapper or container types
+    wrapper_value = 50,
+    complex_number = 60,
+    tuple_value = 70,
+    container_value = 80,
+
+};
+
+/// Set of overloads to classify an object according to type
+
+/// some type that is not otherwise recognized
+template <typename T, typename Enable = void> struct classify_object {
+    static constexpr object_category value{object_category::other};
+};
+
+/// Signed integers
+template <typename T>
+struct classify_object<
+    T,
+    typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, char>::value && std::is_signed<T>::value &&
+                            !is_bool<T>::value && !std::is_enum<T>::value>::type> {
+    static constexpr object_category value{object_category::integral_value};
+};
+
+/// Unsigned integers
+template <typename T>
+struct classify_object<T,
+                       typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value &&
+                                               !std::is_same<T, char>::value && !is_bool<T>::value>::type> {
+    static constexpr object_category value{object_category::unsigned_integral};
+};
+
+/// single character values
+template <typename T>
+struct classify_object<T, typename std::enable_if<std::is_same<T, char>::value && !std::is_enum<T>::value>::type> {
+    static constexpr object_category value{object_category::char_value};
+};
+
+/// Boolean values
+template <typename T> struct classify_object<T, typename std::enable_if<is_bool<T>::value>::type> {
+    static constexpr object_category value{object_category::boolean_value};
+};
+
+/// Floats
+template <typename T> struct classify_object<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
+    static constexpr object_category value{object_category::floating_point};
+};
+
+/// String and similar direct assignment
+template <typename T>
+struct classify_object<T,
+                       typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
+                                               std::is_assignable<T &, std::string>::value>::type> {
+    static constexpr object_category value{object_category::string_assignable};
+};
+
+/// String and similar constructible and copy assignment
+template <typename T>
+struct classify_object<
+    T,
+    typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
+                            !std::is_assignable<T &, std::string>::value && (type_count<T>::value == 1) &&
+                            std::is_constructible<T, std::string>::value>::type> {
+    static constexpr object_category value{object_category::string_constructible};
+};
+
+/// Enumerations
+template <typename T> struct classify_object<T, typename std::enable_if<std::is_enum<T>::value>::type> {
+    static constexpr object_category value{object_category::enumeration};
+};
+
+template <typename T> struct classify_object<T, typename std::enable_if<is_complex<T>::value>::type> {
+    static constexpr object_category value{object_category::complex_number};
+};
+
+/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point,
+/// vectors, and enumerations
+template <typename T> struct uncommon_type {
+    using type = typename std::conditional<!std::is_floating_point<T>::value && !std::is_integral<T>::value &&
+                                               !std::is_assignable<T &, std::string>::value &&
+                                               !std::is_constructible<T, std::string>::value && !is_complex<T>::value &&
+                                               !is_mutable_container<T>::value && !std::is_enum<T>::value,
+                                           std::true_type,
+                                           std::false_type>::type;
+    static constexpr bool value = type::value;
+};
+
+/// wrapper type
+template <typename T>
+struct classify_object<T,
+                       typename std::enable_if<(!is_mutable_container<T>::value && is_wrapper<T>::value &&
+                                                !is_tuple_like<T>::value && uncommon_type<T>::value)>::type> {
+    static constexpr object_category value{object_category::wrapper_value};
+};
+
+/// Assignable from double or int
+template <typename T>
+struct classify_object<T,
+                       typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
+                                               !is_wrapper<T>::value && is_direct_constructible<T, double>::value &&
+                                               is_direct_constructible<T, int>::value>::type> {
+    static constexpr object_category value{object_category::number_constructible};
+};
+
+/// Assignable from int
+template <typename T>
+struct classify_object<T,
+                       typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
+                                               !is_wrapper<T>::value && !is_direct_constructible<T, double>::value &&
+                                               is_direct_constructible<T, int>::value>::type> {
+    static constexpr object_category value{object_category::integer_constructible};
+};
+
+/// Assignable from double
+template <typename T>
+struct classify_object<T,
+                       typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 &&
+                                               !is_wrapper<T>::value && is_direct_constructible<T, double>::value &&
+                                               !is_direct_constructible<T, int>::value>::type> {
+    static constexpr object_category value{object_category::double_constructible};
+};
+
+/// Tuple type
+template <typename T>
+struct classify_object<
+    T,
+    typename std::enable_if<is_tuple_like<T>::value &&
+                            ((type_count<T>::value >= 2 && !is_wrapper<T>::value) ||
+                             (uncommon_type<T>::value && !is_direct_constructible<T, double>::value &&
+                              !is_direct_constructible<T, int>::value))>::type> {
+    static constexpr object_category value{object_category::tuple_value};
+    // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be
+    // constructed from just the first element so tuples of <string, int,int> can be constructed from a string, which
+    // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2
+    // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out
+    // those cases that are caught by other object classifications
+};
+
+/// container type
+template <typename T> struct classify_object<T, typename std::enable_if<is_mutable_container<T>::value>::type> {
+    static constexpr object_category value{object_category::container_value};
+};
+
+// Type name print
+
+/// Was going to be based on
+///  http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template
+/// But this is cleaner and works better in this case
+
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::char_value, detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+    return "CHAR";
+}
+
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::integral_value ||
+                          classify_object<T>::value == object_category::integer_constructible,
+                      detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+    return "INT";
+}
+
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::unsigned_integral, detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+    return "UINT";
+}
+
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::floating_point ||
+                          classify_object<T>::value == object_category::number_constructible ||
+                          classify_object<T>::value == object_category::double_constructible,
+                      detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+    return "FLOAT";
+}
+
+/// Print name for enumeration types
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+    return "ENUM";
+}
+
+/// Print name for enumeration types
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::boolean_value, detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+    return "BOOLEAN";
+}
+
+/// Print name for enumeration types
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::complex_number, detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+    return "COMPLEX";
+}
+
+/// Print for all other types
+template <typename T,
+          enable_if_t<classify_object<T>::value >= object_category::string_assignable &&
+                          classify_object<T>::value <= object_category::other,
+                      detail::enabler> = detail::dummy>
+constexpr const char *type_name() {
+    return "TEXT";
+}
+/// typename for tuple value
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value >= 2,
+                      detail::enabler> = detail::dummy>
+std::string type_name();  // forward declaration
+
+/// Generate type name for a wrapper or container value
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::container_value ||
+                          classify_object<T>::value == object_category::wrapper_value,
+                      detail::enabler> = detail::dummy>
+std::string type_name();  // forward declaration
+
+/// Print name for single element tuple types
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value == 1,
+                      detail::enabler> = detail::dummy>
+inline std::string type_name() {
+    return type_name<typename std::decay<typename std::tuple_element<0, T>::type>::type>();
+}
+
+/// Empty string if the index > tuple size
+template <typename T, std::size_t I>
+inline typename std::enable_if<I == type_count_base<T>::value, std::string>::type tuple_name() {
+    return std::string{};
+}
+
+/// Recursively generate the tuple type name
+template <typename T, std::size_t I>
+inline typename std::enable_if<(I < type_count_base<T>::value), std::string>::type tuple_name() {
+    std::string str = std::string(type_name<typename std::decay<typename std::tuple_element<I, T>::type>::type>()) +
+                      ',' + tuple_name<T, I + 1>();
+    if(str.back() == ',')
+        str.pop_back();
+    return str;
+}
+
+/// Print type name for tuples with 2 or more elements
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value >= 2,
+                      detail::enabler>>
+inline std::string type_name() {
+    auto tname = std::string(1, '[') + tuple_name<T, 0>();
+    tname.push_back(']');
+    return tname;
+}
+
+/// get the type name for a type that has a value_type member
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::container_value ||
+                          classify_object<T>::value == object_category::wrapper_value,
+                      detail::enabler>>
+inline std::string type_name() {
+    return type_name<typename T::value_type>();
+}
+
+// Lexical cast
+
+/// Convert to an unsigned integral
+template <typename T, enable_if_t<std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
+bool integral_conversion(const std::string &input, T &output) noexcept {
+    if(input.empty()) {
+        return false;
+    }
+    char *val = nullptr;
+    std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0);
+    output = static_cast<T>(output_ll);
+    return val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll;
+}
+
+/// Convert to a signed integral
+template <typename T, enable_if_t<std::is_signed<T>::value, detail::enabler> = detail::dummy>
+bool integral_conversion(const std::string &input, T &output) noexcept {
+    if(input.empty()) {
+        return false;
+    }
+    char *val = nullptr;
+    std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0);
+    output = static_cast<T>(output_ll);
+    return val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll;
+}
+
+/// Convert a flag into an integer value  typically binary flags
+inline std::int64_t to_flag_value(std::string val) {
+    static const std::string trueString("true");
+    static const std::string falseString("false");
+    if(val == trueString) {
+        return 1;
+    }
+    if(val == falseString) {
+        return -1;
+    }
+    val = detail::to_lower(val);
+    std::int64_t ret;
+    if(val.size() == 1) {
+        if(val[0] >= '1' && val[0] <= '9') {
+            return (static_cast<std::int64_t>(val[0]) - '0');
+        }
+        switch(val[0]) {
+        case '0':
+        case 'f':
+        case 'n':
+        case '-':
+            ret = -1;
+            break;
+        case 't':
+        case 'y':
+        case '+':
+            ret = 1;
+            break;
+        default:
+            throw std::invalid_argument("unrecognized character");
+        }
+        return ret;
+    }
+    if(val == trueString || val == "on" || val == "yes" || val == "enable") {
+        ret = 1;
+    } else if(val == falseString || val == "off" || val == "no" || val == "disable") {
+        ret = -1;
+    } else {
+        ret = std::stoll(val);
+    }
+    return ret;
+}
+
+/// Integer conversion
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::integral_value ||
+                          classify_object<T>::value == object_category::unsigned_integral,
+                      detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    return integral_conversion(input, output);
+}
+
+/// char values
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::char_value, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    if(input.size() == 1) {
+        output = static_cast<T>(input[0]);
+        return true;
+    }
+    return integral_conversion(input, output);
+}
+
+/// Boolean values
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::boolean_value, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    try {
+        auto out = to_flag_value(input);
+        output = (out > 0);
+        return true;
+    } catch(const std::invalid_argument &) {
+        return false;
+    } catch(const std::out_of_range &) {
+        // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still
+        // valid all we care about the sign
+        output = (input[0] != '-');
+        return true;
+    }
+}
+
+/// Floats
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::floating_point, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    if(input.empty()) {
+        return false;
+    }
+    char *val = nullptr;
+    auto output_ld = std::strtold(input.c_str(), &val);
+    output = static_cast<T>(output_ld);
+    return val == (input.c_str() + input.size());
+}
+
+/// complex
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::complex_number, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    using XC = typename wrapped_type<T, double>::type;
+    XC x{0.0}, y{0.0};
+    auto str1 = input;
+    bool worked = false;
+    auto nloc = str1.find_last_of("+-");
+    if(nloc != std::string::npos && nloc > 0) {
+        worked = detail::lexical_cast(str1.substr(0, nloc), x);
+        str1 = str1.substr(nloc);
+        if(str1.back() == 'i' || str1.back() == 'j')
+            str1.pop_back();
+        worked = worked && detail::lexical_cast(str1, y);
+    } else {
+        if(str1.back() == 'i' || str1.back() == 'j') {
+            str1.pop_back();
+            worked = detail::lexical_cast(str1, y);
+            x = XC{0};
+        } else {
+            worked = detail::lexical_cast(str1, x);
+            y = XC{0};
+        }
+    }
+    if(worked) {
+        output = T{x, y};
+        return worked;
+    }
+    return from_stream(input, output);
+}
+
+/// String and similar direct assignment
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::string_assignable, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    output = input;
+    return true;
+}
+
+/// String and similar constructible and copy assignment
+template <
+    typename T,
+    enable_if_t<classify_object<T>::value == object_category::string_constructible, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    output = T(input);
+    return true;
+}
+
+/// Enumerations
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    typename std::underlying_type<T>::type val;
+    if(!integral_conversion(input, val)) {
+        return false;
+    }
+    output = static_cast<T>(val);
+    return true;
+}
+
+/// wrapper types
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::wrapper_value &&
+                          std::is_assignable<T &, typename T::value_type>::value,
+                      detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    typename T::value_type val;
+    if(lexical_cast(input, val)) {
+        output = val;
+        return true;
+    }
+    return from_stream(input, output);
+}
+
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::wrapper_value &&
+                          !std::is_assignable<T &, typename T::value_type>::value && std::is_assignable<T &, T>::value,
+                      detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    typename T::value_type val;
+    if(lexical_cast(input, val)) {
+        output = T{val};
+        return true;
+    }
+    return from_stream(input, output);
+}
+
+/// Assignable from double or int
+template <
+    typename T,
+    enable_if_t<classify_object<T>::value == object_category::number_constructible, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    int val;
+    if(integral_conversion(input, val)) {
+        output = T(val);
+        return true;
+    } else {
+        double dval;
+        if(lexical_cast(input, dval)) {
+            output = T{dval};
+            return true;
+        }
+    }
+    return from_stream(input, output);
+}
+
+/// Assignable from int
+template <
+    typename T,
+    enable_if_t<classify_object<T>::value == object_category::integer_constructible, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    int val;
+    if(integral_conversion(input, val)) {
+        output = T(val);
+        return true;
+    }
+    return from_stream(input, output);
+}
+
+/// Assignable from double
+template <
+    typename T,
+    enable_if_t<classify_object<T>::value == object_category::double_constructible, detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    double val;
+    if(lexical_cast(input, val)) {
+        output = T{val};
+        return true;
+    }
+    return from_stream(input, output);
+}
+
+/// Non-string convertible from an int
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::other && std::is_assignable<T &, int>::value,
+                      detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    int val;
+    if(integral_conversion(input, val)) {
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4800)
+#endif
+        // with Atomic<XX> this could produce a warning due to the conversion but if atomic gets here it is an old style
+        // so will most likely still work
+        output = val;
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+        return true;
+    }
+    // LCOV_EXCL_START
+    // This version of cast is only used for odd cases in an older compilers the fail over
+    // from_stream is tested elsewhere an not relevant for coverage here
+    return from_stream(input, output);
+    // LCOV_EXCL_STOP
+}
+
+/// Non-string parsable by a stream
+template <typename T,
+          enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value,
+                      detail::enabler> = detail::dummy>
+bool lexical_cast(const std::string &input, T &output) {
+    static_assert(is_istreamable<T>::value,
+                  "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it "
+                  "is convertible from another type use the add_option<T, XC>(...) with XC being the known type");
+    return from_stream(input, output);
+}
+
+/// Assign a value through lexical cast operations
+/// Strings can be empty so we need to do a little different
+template <typename AssignTo,
+          typename ConvertTo,
+          enable_if_t<std::is_same<AssignTo, ConvertTo>::value &&
+                          (classify_object<AssignTo>::value == object_category::string_assignable ||
+                           classify_object<AssignTo>::value == object_category::string_constructible),
+                      detail::enabler> = detail::dummy>
+bool lexical_assign(const std::string &input, AssignTo &output) {
+    return lexical_cast(input, output);
+}
+
+/// Assign a value through lexical cast operations
+template <typename AssignTo,
+          typename ConvertTo,
+          enable_if_t<std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, AssignTo>::value &&
+                          classify_object<AssignTo>::value != object_category::string_assignable &&
+                          classify_object<AssignTo>::value != object_category::string_constructible,
+                      detail::enabler> = detail::dummy>
+bool lexical_assign(const std::string &input, AssignTo &output) {
+    if(input.empty()) {
+        output = AssignTo{};
+        return true;
+    }
+
+    return lexical_cast(input, output);
+}
+
+/// Assign a value through lexical cast operations
+template <typename AssignTo,
+          typename ConvertTo,
+          enable_if_t<std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, AssignTo>::value &&
+                          classify_object<AssignTo>::value == object_category::wrapper_value,
+                      detail::enabler> = detail::dummy>
+bool lexical_assign(const std::string &input, AssignTo &output) {
+    if(input.empty()) {
+        typename AssignTo::value_type emptyVal{};
+        output = emptyVal;
+        return true;
+    }
+    return lexical_cast(input, output);
+}
+
+/// Assign a value through lexical cast operations for int compatible values
+/// mainly for atomic operations on some compilers
+template <typename AssignTo,
+          typename ConvertTo,
+          enable_if_t<std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, AssignTo>::value &&
+                          classify_object<AssignTo>::value != object_category::wrapper_value &&
+                          std::is_assignable<AssignTo &, int>::value,
+                      detail::enabler> = detail::dummy>
+bool lexical_assign(const std::string &input, AssignTo &output) {
+    if(input.empty()) {
+        output = 0;
+        return true;
+    }
+    int val;
+    if(lexical_cast(input, val)) {
+        output = val;
+        return true;
+    }
+    return false;
+}
+
+/// Assign a value converted from a string in lexical cast to the output value directly
+template <typename AssignTo,
+          typename ConvertTo,
+          enable_if_t<!std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, ConvertTo &>::value,
+                      detail::enabler> = detail::dummy>
+bool lexical_assign(const std::string &input, AssignTo &output) {
+    ConvertTo val{};
+    bool parse_result = (!input.empty()) ? lexical_cast<ConvertTo>(input, val) : true;
+    if(parse_result) {
+        output = val;
+    }
+    return parse_result;
+}
+
+/// Assign a value from a lexical cast through constructing a value and move assigning it
+template <
+    typename AssignTo,
+    typename ConvertTo,
+    enable_if_t<!std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, ConvertTo &>::value &&
+                    std::is_move_assignable<AssignTo>::value,
+                detail::enabler> = detail::dummy>
+bool lexical_assign(const std::string &input, AssignTo &output) {
+    ConvertTo val{};
+    bool parse_result = input.empty() ? true : lexical_cast<ConvertTo>(input, val);
+    if(parse_result) {
+        output = AssignTo(val);  // use () form of constructor to allow some implicit conversions
+    }
+    return parse_result;
+}
+
+/// primary lexical conversion operation, 1 string to 1 type of some kind
+template <typename AssignTo,
+          typename ConvertTo,
+          enable_if_t<classify_object<ConvertTo>::value <= object_category::other &&
+                          classify_object<AssignTo>::value <= object_category::wrapper_value,
+                      detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+    return lexical_assign<AssignTo, ConvertTo>(strings[0], output);
+}
+
+/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element
+/// constructor
+template <typename AssignTo,
+          typename ConvertTo,
+          enable_if_t<(type_count<AssignTo>::value <= 2) && expected_count<AssignTo>::value == 1 &&
+                          is_tuple_like<ConvertTo>::value && type_count_base<ConvertTo>::value == 2,
+                      detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+    // the remove const is to handle pair types coming from a container
+    typename std::remove_const<typename std::tuple_element<0, ConvertTo>::type>::type v1;
+    typename std::tuple_element<1, ConvertTo>::type v2;
+    bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[0], v1);
+    if(strings.size() > 1) {
+        retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[1], v2);
+    }
+    if(retval) {
+        output = AssignTo{v1, v2};
+    }
+    return retval;
+}
+
+/// Lexical conversion of a container types of single elements
+template <class AssignTo,
+          class ConvertTo,
+          enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
+                          type_count<ConvertTo>::value == 1,
+                      detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+    output.erase(output.begin(), output.end());
+    for(const auto &elem : strings) {
+        typename AssignTo::value_type out;
+        bool retval = lexical_assign<typename AssignTo::value_type, typename ConvertTo::value_type>(elem, out);
+        if(!retval) {
+            return false;
+        }
+        output.insert(output.end(), std::move(out));
+    }
+    return (!output.empty());
+}
+
+/// Lexical conversion for complex types
+template <class AssignTo, class ConvertTo, enable_if_t<is_complex<ConvertTo>::value, detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) {
+
+    if(strings.size() >= 2 && !strings[1].empty()) {
+        using XC2 = typename wrapped_type<ConvertTo, double>::type;
+        XC2 x{0.0}, y{0.0};
+        auto str1 = strings[1];
+        if(str1.back() == 'i' || str1.back() == 'j') {
+            str1.pop_back();
+        }
+        auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y);
+        if(worked) {
+            output = ConvertTo{x, y};
+        }
+        return worked;
+    } else {
+        return lexical_assign<AssignTo, ConvertTo>(strings[0], output);
+    }
+}
+
+/// Conversion to a vector type using a particular single type as the conversion type
+template <class AssignTo,
+          class ConvertTo,
+          enable_if_t<is_mutable_container<AssignTo>::value && (expected_count<ConvertTo>::value == 1) &&
+                          (type_count<ConvertTo>::value == 1),
+                      detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+    bool retval = true;
+    output.clear();
+    output.reserve(strings.size());
+    for(const auto &elem : strings) {
+
+        output.emplace_back();
+        retval = retval && lexical_assign<typename AssignTo::value_type, ConvertTo>(elem, output.back());
+    }
+    return (!output.empty()) && retval;
+}
+
+// forward declaration
+
+/// Lexical conversion of a container types with conversion type of two elements
+template <class AssignTo,
+          class ConvertTo,
+          enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
+                          type_count_base<ConvertTo>::value == 2,
+                      detail::enabler> = detail::dummy>
+bool lexical_conversion(std::vector<std::string> strings, AssignTo &output);
+
+/// Lexical conversion of a vector types with type_size >2 forward declaration
+template <class AssignTo,
+          class ConvertTo,
+          enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
+                          type_count_base<ConvertTo>::value != 2 &&
+                          ((type_count<ConvertTo>::value > 2) ||
+                           (type_count<ConvertTo>::value > type_count_base<ConvertTo>::value)),
+                      detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output);
+
+/// Conversion for tuples
+template <class AssignTo,
+          class ConvertTo,
+          enable_if_t<is_tuple_like<AssignTo>::value && is_tuple_like<ConvertTo>::value &&
+                          (type_count_base<ConvertTo>::value != type_count<ConvertTo>::value ||
+                           type_count<ConvertTo>::value > 2),
+                      detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output);  // forward declaration
+
+/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large
+/// tuple
+template <typename AssignTo,
+          typename ConvertTo,
+          enable_if_t<!is_tuple_like<AssignTo>::value && !is_mutable_container<AssignTo>::value &&
+                          classify_object<ConvertTo>::value != object_category::wrapper_value &&
+                          (is_mutable_container<ConvertTo>::value || type_count<ConvertTo>::value > 2),
+                      detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+
+    if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) {
+        ConvertTo val;
+        auto retval = lexical_conversion<ConvertTo, ConvertTo>(strings, val);
+        output = AssignTo{val};
+        return retval;
+    }
+    output = AssignTo{};
+    return true;
+}
+
+/// function template for converting tuples if the static Index is greater than the tuple size
+template <class AssignTo, class ConvertTo, std::size_t I>
+inline typename std::enable_if<(I >= type_count_base<AssignTo>::value), bool>::type
+tuple_conversion(const std::vector<std::string> &, AssignTo &) {
+    return true;
+}
+
+/// Conversion of a tuple element where the type size ==1 and not a mutable container
+template <class AssignTo, class ConvertTo>
+inline typename std::enable_if<!is_mutable_container<ConvertTo>::value && type_count<ConvertTo>::value == 1, bool>::type
+tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
+    auto retval = lexical_assign<AssignTo, ConvertTo>(strings[0], output);
+    strings.erase(strings.begin());
+    return retval;
+}
+
+/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container
+template <class AssignTo, class ConvertTo>
+inline typename std::enable_if<!is_mutable_container<ConvertTo>::value && (type_count<ConvertTo>::value > 1) &&
+                                   type_count<ConvertTo>::value == type_count_min<ConvertTo>::value,
+                               bool>::type
+tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
+    auto retval = lexical_conversion<AssignTo, ConvertTo>(strings, output);
+    strings.erase(strings.begin(), strings.begin() + type_count<ConvertTo>::value);
+    return retval;
+}
+
+/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes
+template <class AssignTo, class ConvertTo>
+inline typename std::enable_if<is_mutable_container<ConvertTo>::value ||
+                                   type_count<ConvertTo>::value != type_count_min<ConvertTo>::value,
+                               bool>::type
+tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) {
+
+    std::size_t index{subtype_count_min<ConvertTo>::value};
+    const std::size_t mx_count{subtype_count<ConvertTo>::value};
+    const std::size_t mx{(std::max)(mx_count, strings.size())};
+
+    while(index < mx) {
+        if(is_separator(strings[index])) {
+            break;
+        }
+        ++index;
+    }
+    bool retval = lexical_conversion<AssignTo, ConvertTo>(
+        std::vector<std::string>(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index)), output);
+    strings.erase(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index) + 1);
+    return retval;
+}
+
+/// Tuple conversion operation
+template <class AssignTo, class ConvertTo, std::size_t I>
+inline typename std::enable_if<(I < type_count_base<AssignTo>::value), bool>::type
+tuple_conversion(std::vector<std::string> strings, AssignTo &output) {
+    bool retval = true;
+    using ConvertToElement = typename std::
+        conditional<is_tuple_like<ConvertTo>::value, typename std::tuple_element<I, ConvertTo>::type, ConvertTo>::type;
+    if(!strings.empty()) {
+        retval = retval && tuple_type_conversion<typename std::tuple_element<I, AssignTo>::type, ConvertToElement>(
+                               strings, std::get<I>(output));
+    }
+    retval = retval && tuple_conversion<AssignTo, ConvertTo, I + 1>(std::move(strings), output);
+    return retval;
+}
+
+/// Lexical conversion of a container types with tuple elements of size 2
+template <class AssignTo,
+          class ConvertTo,
+          enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
+                          type_count_base<ConvertTo>::value == 2,
+                      detail::enabler>>
+bool lexical_conversion(std::vector<std::string> strings, AssignTo &output) {
+    output.clear();
+    while(!strings.empty()) {
+
+        typename std::remove_const<typename std::tuple_element<0, typename ConvertTo::value_type>::type>::type v1;
+        typename std::tuple_element<1, typename ConvertTo::value_type>::type v2;
+        bool retval = tuple_type_conversion<decltype(v1), decltype(v1)>(strings, v1);
+        if(!strings.empty()) {
+            retval = retval && tuple_type_conversion<decltype(v2), decltype(v2)>(strings, v2);
+        }
+        if(retval) {
+            output.insert(output.end(), typename AssignTo::value_type{v1, v2});
+        } else {
+            return false;
+        }
+    }
+    return (!output.empty());
+}
+
+/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2
+template <class AssignTo,
+          class ConvertTo,
+          enable_if_t<is_tuple_like<AssignTo>::value && is_tuple_like<ConvertTo>::value &&
+                          (type_count_base<ConvertTo>::value != type_count<ConvertTo>::value ||
+                           type_count<ConvertTo>::value > 2),
+                      detail::enabler>>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+    static_assert(
+        !is_tuple_like<ConvertTo>::value || type_count_base<AssignTo>::value == type_count_base<ConvertTo>::value,
+        "if the conversion type is defined as a tuple it must be the same size as the type you are converting to");
+    return tuple_conversion<AssignTo, ConvertTo, 0>(strings, output);
+}
+
+/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1
+template <class AssignTo,
+          class ConvertTo,
+          enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value &&
+                          type_count_base<ConvertTo>::value != 2 &&
+                          ((type_count<ConvertTo>::value > 2) ||
+                           (type_count<ConvertTo>::value > type_count_base<ConvertTo>::value)),
+                      detail::enabler>>
+bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) {
+    bool retval = true;
+    output.clear();
+    std::vector<std::string> temp;
+    std::size_t ii{0};
+    std::size_t icount{0};
+    std::size_t xcm{type_count<ConvertTo>::value};
+    auto ii_max = strings.size();
+    while(ii < ii_max) {
+        temp.push_back(strings[ii]);
+        ++ii;
+        ++icount;
+        if(icount == xcm || is_separator(temp.back()) || ii == ii_max) {
+            if(static_cast<int>(xcm) > type_count_min<ConvertTo>::value && is_separator(temp.back())) {
+                temp.pop_back();
+            }
+            typename AssignTo::value_type temp_out;
+            retval = retval &&
+                     lexical_conversion<typename AssignTo::value_type, typename ConvertTo::value_type>(temp, temp_out);
+            temp.clear();
+            if(!retval) {
+                return false;
+            }
+            output.insert(output.end(), std::move(temp_out));
+            icount = 0;
+        }
+    }
+    return retval;
+}
+
+/// conversion for wrapper types
+template <typename AssignTo,
+          class ConvertTo,
+          enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value &&
+                          std::is_assignable<ConvertTo &, ConvertTo>::value,
+                      detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) {
+    if(strings.empty() || strings.front().empty()) {
+        output = ConvertTo{};
+        return true;
+    }
+    typename ConvertTo::value_type val;
+    if(lexical_conversion<typename ConvertTo::value_type, typename ConvertTo::value_type>(strings, val)) {
+        output = ConvertTo{val};
+        return true;
+    }
+    return false;
+}
+
+/// conversion for wrapper types
+template <typename AssignTo,
+          class ConvertTo,
+          enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value &&
+                          !std::is_assignable<AssignTo &, ConvertTo>::value,
+                      detail::enabler> = detail::dummy>
+bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) {
+    using ConvertType = typename ConvertTo::value_type;
+    if(strings.empty() || strings.front().empty()) {
+        output = ConvertType{};
+        return true;
+    }
+    ConvertType val;
+    if(lexical_conversion<typename ConvertTo::value_type, typename ConvertTo::value_type>(strings, val)) {
+        output = val;
+        return true;
+    }
+    return false;
+}
+
+/// Sum a vector of flag representations
+/// The flag vector produces a series of strings in a vector,  simple true is represented by a "1",  simple false is
+/// by
+/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
+/// common true and false strings then uses stoll to convert the rest for summing
+template <typename T, enable_if_t<std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
+void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
+    std::int64_t count{0};
+    for(auto &flag : flags) {
+        count += detail::to_flag_value(flag);
+    }
+    output = (count > 0) ? static_cast<T>(count) : T{0};
+}
+
+/// Sum a vector of flag representations
+/// The flag vector produces a series of strings in a vector,  simple true is represented by a "1",  simple false is
+/// by
+/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
+/// common true and false strings then uses stoll to convert the rest for summing
+template <typename T, enable_if_t<std::is_signed<T>::value, detail::enabler> = detail::dummy>
+void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
+    std::int64_t count{0};
+    for(auto &flag : flags) {
+        count += detail::to_flag_value(flag);
+    }
+    output = static_cast<T>(count);
+}
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4800)
+#endif
+// with Atomic<XX> this could produce a warning due to the conversion but if atomic gets here it is an old style so will
+// most likely still work
+
+/// Sum a vector of flag representations
+/// The flag vector produces a series of strings in a vector,  simple true is represented by a "1",  simple false is
+/// by
+/// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most
+/// common true and false strings then uses stoll to convert the rest for summing
+template <typename T,
+          enable_if_t<!std::is_signed<T>::value && !std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
+void sum_flag_vector(const std::vector<std::string> &flags, T &output) {
+    std::int64_t count{0};
+    for(auto &flag : flags) {
+        count += detail::to_flag_value(flag);
+    }
+    std::string out = detail::to_string(count);
+    lexical_cast(out, output);
+}
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+}  // namespace detail
+// [CLI11:type_tools_hpp:end]
+}  // namespace CLI
diff --git a/external/CLI11/include/CLI/Validators.hpp b/external/CLI11/include/CLI/Validators.hpp
new file mode 100644
index 0000000..03eb77b
--- /dev/null
+++ b/external/CLI11/include/CLI/Validators.hpp
@@ -0,0 +1,1142 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+#include "Macros.hpp"
+#include "StringTools.hpp"
+#include "TypeTools.hpp"
+
+// [CLI11:public_includes:set]
+#include <cmath>
+#include <cstdint>
+#include <functional>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+// [CLI11:public_includes:end]
+
+// [CLI11:validators_hpp_filesystem:verbatim]
+
+// C standard library
+// Only needed for existence checking
+#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM
+#if __has_include(<filesystem>)
+// Filesystem cannot be used if targeting macOS < 10.15
+#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500
+#define CLI11_HAS_FILESYSTEM 0
+#else
+#include <filesystem>
+#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703
+#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9
+#define CLI11_HAS_FILESYSTEM 1
+#elif defined(__GLIBCXX__)
+// if we are using gcc and Version <9 default to no filesystem
+#define CLI11_HAS_FILESYSTEM 0
+#else
+#define CLI11_HAS_FILESYSTEM 1
+#endif
+#else
+#define CLI11_HAS_FILESYSTEM 0
+#endif
+#endif
+#endif
+#endif
+
+#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
+#include <filesystem>  // NOLINT(build/include)
+#else
+#include <sys/stat.h>
+#include <sys/types.h>
+#endif
+
+// [CLI11:validators_hpp_filesystem:end]
+
+namespace CLI {
+// [CLI11:validators_hpp:verbatim]
+
+class Option;
+
+/// @defgroup validator_group Validators
+
+/// @brief Some validators that are provided
+///
+/// These are simple `std::string(const std::string&)` validators that are useful. They return
+/// a string if the validation fails. A custom struct is provided, as well, with the same user
+/// semantics, but with the ability to provide a new type name.
+/// @{
+
+///
+class Validator {
+  protected:
+    /// This is the description function, if empty the description_ will be used
+    std::function<std::string()> desc_function_{[]() { return std::string{}; }};
+
+    /// This is the base function that is to be called.
+    /// Returns a string error message if validation fails.
+    std::function<std::string(std::string &)> func_{[](std::string &) { return std::string{}; }};
+    /// The name for search purposes of the Validator
+    std::string name_{};
+    /// A Validator will only apply to an indexed value (-1 is all elements)
+    int application_index_ = -1;
+    /// Enable for Validator to allow it to be disabled if need be
+    bool active_{true};
+    /// specify that a validator should not modify the input
+    bool non_modifying_{false};
+
+  public:
+    Validator() = default;
+    /// Construct a Validator with just the description string
+    explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {}
+    /// Construct Validator from basic information
+    Validator(std::function<std::string(std::string &)> op, std::string validator_desc, std::string validator_name = "")
+        : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)),
+          name_(std::move(validator_name)) {}
+    /// Set the Validator operation function
+    Validator &operation(std::function<std::string(std::string &)> op) {
+        func_ = std::move(op);
+        return *this;
+    }
+    /// This is the required operator for a Validator - provided to help
+    /// users (CLI11 uses the member `func` directly)
+    std::string operator()(std::string &str) const {
+        std::string retstring;
+        if(active_) {
+            if(non_modifying_) {
+                std::string value = str;
+                retstring = func_(value);
+            } else {
+                retstring = func_(str);
+            }
+        }
+        return retstring;
+    }
+
+    /// This is the required operator for a Validator - provided to help
+    /// users (CLI11 uses the member `func` directly)
+    std::string operator()(const std::string &str) const {
+        std::string value = str;
+        return (active_) ? func_(value) : std::string{};
+    }
+
+    /// Specify the type string
+    Validator &description(std::string validator_desc) {
+        desc_function_ = [validator_desc]() { return validator_desc; };
+        return *this;
+    }
+    /// Specify the type string
+    Validator description(std::string validator_desc) const {
+        Validator newval(*this);
+        newval.desc_function_ = [validator_desc]() { return validator_desc; };
+        return newval;
+    }
+    /// Generate type description information for the Validator
+    std::string get_description() const {
+        if(active_) {
+            return desc_function_();
+        }
+        return std::string{};
+    }
+    /// Specify the type string
+    Validator &name(std::string validator_name) {
+        name_ = std::move(validator_name);
+        return *this;
+    }
+    /// Specify the type string
+    Validator name(std::string validator_name) const {
+        Validator newval(*this);
+        newval.name_ = std::move(validator_name);
+        return newval;
+    }
+    /// Get the name of the Validator
+    const std::string &get_name() const { return name_; }
+    /// Specify whether the Validator is active or not
+    Validator &active(bool active_val = true) {
+        active_ = active_val;
+        return *this;
+    }
+    /// Specify whether the Validator is active or not
+    Validator active(bool active_val = true) const {
+        Validator newval(*this);
+        newval.active_ = active_val;
+        return newval;
+    }
+
+    /// Specify whether the Validator can be modifying or not
+    Validator &non_modifying(bool no_modify = true) {
+        non_modifying_ = no_modify;
+        return *this;
+    }
+    /// Specify the application index of a validator
+    Validator &application_index(int app_index) {
+        application_index_ = app_index;
+        return *this;
+    }
+    /// Specify the application index of a validator
+    Validator application_index(int app_index) const {
+        Validator newval(*this);
+        newval.application_index_ = app_index;
+        return newval;
+    }
+    /// Get the current value of the application index
+    int get_application_index() const { return application_index_; }
+    /// Get a boolean if the validator is active
+    bool get_active() const { return active_; }
+
+    /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input
+    bool get_modifying() const { return !non_modifying_; }
+
+    /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the
+    /// same.
+    Validator operator&(const Validator &other) const {
+        Validator newval;
+
+        newval._merge_description(*this, other, " AND ");
+
+        // Give references (will make a copy in lambda function)
+        const std::function<std::string(std::string & filename)> &f1 = func_;
+        const std::function<std::string(std::string & filename)> &f2 = other.func_;
+
+        newval.func_ = [f1, f2](std::string &input) {
+            std::string s1 = f1(input);
+            std::string s2 = f2(input);
+            if(!s1.empty() && !s2.empty())
+                return std::string("(") + s1 + ") AND (" + s2 + ")";
+            else
+                return s1 + s2;
+        };
+
+        newval.active_ = (active_ & other.active_);
+        newval.application_index_ = application_index_;
+        return newval;
+    }
+
+    /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the
+    /// same.
+    Validator operator|(const Validator &other) const {
+        Validator newval;
+
+        newval._merge_description(*this, other, " OR ");
+
+        // Give references (will make a copy in lambda function)
+        const std::function<std::string(std::string &)> &f1 = func_;
+        const std::function<std::string(std::string &)> &f2 = other.func_;
+
+        newval.func_ = [f1, f2](std::string &input) {
+            std::string s1 = f1(input);
+            std::string s2 = f2(input);
+            if(s1.empty() || s2.empty())
+                return std::string();
+
+            return std::string("(") + s1 + ") OR (" + s2 + ")";
+        };
+        newval.active_ = (active_ & other.active_);
+        newval.application_index_ = application_index_;
+        return newval;
+    }
+
+    /// Create a validator that fails when a given validator succeeds
+    Validator operator!() const {
+        Validator newval;
+        const std::function<std::string()> &dfunc1 = desc_function_;
+        newval.desc_function_ = [dfunc1]() {
+            auto str = dfunc1();
+            return (!str.empty()) ? std::string("NOT ") + str : std::string{};
+        };
+        // Give references (will make a copy in lambda function)
+        const std::function<std::string(std::string & res)> &f1 = func_;
+
+        newval.func_ = [f1, dfunc1](std::string &test) -> std::string {
+            std::string s1 = f1(test);
+            if(s1.empty()) {
+                return std::string("check ") + dfunc1() + " succeeded improperly";
+            }
+            return std::string{};
+        };
+        newval.active_ = active_;
+        newval.application_index_ = application_index_;
+        return newval;
+    }
+
+  private:
+    void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger) {
+
+        const std::function<std::string()> &dfunc1 = val1.desc_function_;
+        const std::function<std::string()> &dfunc2 = val2.desc_function_;
+
+        desc_function_ = [=]() {
+            std::string f1 = dfunc1();
+            std::string f2 = dfunc2();
+            if((f1.empty()) || (f2.empty())) {
+                return f1 + f2;
+            }
+            return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')';
+        };
+    }
+};  // namespace CLI
+
+/// Class wrapping some of the accessors of Validator
+class CustomValidator : public Validator {
+  public:
+};
+// The implementation of the built in validators is using the Validator class;
+// the user is only expected to use the const (static) versions (since there's no setup).
+// Therefore, this is in detail.
+namespace detail {
+
+/// CLI enumeration of different file types
+enum class path_type { nonexistent, file, directory };
+
+#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
+/// get the type of the path from a file name
+inline path_type check_path(const char *file) noexcept {
+    std::error_code ec;
+    auto stat = std::filesystem::status(file, ec);
+    if(ec) {
+        return path_type::nonexistent;
+    }
+    switch(stat.type()) {
+    case std::filesystem::file_type::none:
+    case std::filesystem::file_type::not_found:
+        return path_type::nonexistent;
+    case std::filesystem::file_type::directory:
+        return path_type::directory;
+    case std::filesystem::file_type::symlink:
+    case std::filesystem::file_type::block:
+    case std::filesystem::file_type::character:
+    case std::filesystem::file_type::fifo:
+    case std::filesystem::file_type::socket:
+    case std::filesystem::file_type::regular:
+    case std::filesystem::file_type::unknown:
+    default:
+        return path_type::file;
+    }
+}
+#else
+/// get the type of the path from a file name
+inline path_type check_path(const char *file) noexcept {
+#if defined(_MSC_VER)
+    struct __stat64 buffer;
+    if(_stat64(file, &buffer) == 0) {
+        return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
+    }
+#else
+    struct stat buffer;
+    if(stat(file, &buffer) == 0) {
+        return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
+    }
+#endif
+    return path_type::nonexistent;
+}
+#endif
+/// Check for an existing file (returns error message if check fails)
+class ExistingFileValidator : public Validator {
+  public:
+    ExistingFileValidator() : Validator("FILE") {
+        func_ = [](std::string &filename) {
+            auto path_result = check_path(filename.c_str());
+            if(path_result == path_type::nonexistent) {
+                return "File does not exist: " + filename;
+            }
+            if(path_result == path_type::directory) {
+                return "File is actually a directory: " + filename;
+            }
+            return std::string();
+        };
+    }
+};
+
+/// Check for an existing directory (returns error message if check fails)
+class ExistingDirectoryValidator : public Validator {
+  public:
+    ExistingDirectoryValidator() : Validator("DIR") {
+        func_ = [](std::string &filename) {
+            auto path_result = check_path(filename.c_str());
+            if(path_result == path_type::nonexistent) {
+                return "Directory does not exist: " + filename;
+            }
+            if(path_result == path_type::file) {
+                return "Directory is actually a file: " + filename;
+            }
+            return std::string();
+        };
+    }
+};
+
+/// Check for an existing path
+class ExistingPathValidator : public Validator {
+  public:
+    ExistingPathValidator() : Validator("PATH(existing)") {
+        func_ = [](std::string &filename) {
+            auto path_result = check_path(filename.c_str());
+            if(path_result == path_type::nonexistent) {
+                return "Path does not exist: " + filename;
+            }
+            return std::string();
+        };
+    }
+};
+
+/// Check for an non-existing path
+class NonexistentPathValidator : public Validator {
+  public:
+    NonexistentPathValidator() : Validator("PATH(non-existing)") {
+        func_ = [](std::string &filename) {
+            auto path_result = check_path(filename.c_str());
+            if(path_result != path_type::nonexistent) {
+                return "Path already exists: " + filename;
+            }
+            return std::string();
+        };
+    }
+};
+
+/// Validate the given string is a legal ipv4 address
+class IPV4Validator : public Validator {
+  public:
+    IPV4Validator() : Validator("IPV4") {
+        func_ = [](std::string &ip_addr) {
+            auto result = CLI::detail::split(ip_addr, '.');
+            if(result.size() != 4) {
+                return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')';
+            }
+            int num;
+            for(const auto &var : result) {
+                bool retval = detail::lexical_cast(var, num);
+                if(!retval) {
+                    return std::string("Failed parsing number (") + var + ')';
+                }
+                if(num < 0 || num > 255) {
+                    return std::string("Each IP number must be between 0 and 255 ") + var;
+                }
+            }
+            return std::string();
+        };
+    }
+};
+
+}  // namespace detail
+
+// Static is not needed here, because global const implies static.
+
+/// Check for existing file (returns error message if check fails)
+const detail::ExistingFileValidator ExistingFile;
+
+/// Check for an existing directory (returns error message if check fails)
+const detail::ExistingDirectoryValidator ExistingDirectory;
+
+/// Check for an existing path
+const detail::ExistingPathValidator ExistingPath;
+
+/// Check for an non-existing path
+const detail::NonexistentPathValidator NonexistentPath;
+
+/// Check for an IP4 address
+const detail::IPV4Validator ValidIPV4;
+
+/// Validate the input as a particular type
+template <typename DesiredType> class TypeValidator : public Validator {
+  public:
+    explicit TypeValidator(const std::string &validator_name) : Validator(validator_name) {
+        func_ = [](std::string &input_string) {
+            auto val = DesiredType();
+            if(!detail::lexical_cast(input_string, val)) {
+                return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>();
+            }
+            return std::string();
+        };
+    }
+    TypeValidator() : TypeValidator(detail::type_name<DesiredType>()) {}
+};
+
+/// Check for a number
+const TypeValidator<double> Number("NUMBER");
+
+/// Produce a range (factory). Min and max are inclusive.
+class Range : public Validator {
+  public:
+    /// This produces a range with min and max inclusive.
+    ///
+    /// Note that the constructor is templated, but the struct is not, so C++17 is not
+    /// needed to provide nice syntax for Range(a,b).
+    template <typename T>
+    Range(T min_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) {
+        if(validator_name.empty()) {
+            std::stringstream out;
+            out << detail::type_name<T>() << " in [" << min_val << " - " << max_val << "]";
+            description(out.str());
+        }
+
+        func_ = [min_val, max_val](std::string &input) {
+            T val;
+            bool converted = detail::lexical_cast(input, val);
+            if((!converted) || (val < min_val || val > max_val))
+                return std::string("Value ") + input + " not in range " + std::to_string(min_val) + " to " +
+                       std::to_string(max_val);
+
+            return std::string{};
+        };
+    }
+
+    /// Range of one value is 0 to value
+    template <typename T>
+    explicit Range(T max_val, const std::string &validator_name = std::string{})
+        : Range(static_cast<T>(0), max_val, validator_name) {}
+};
+
+/// Check for a non negative number
+const Range NonNegativeNumber((std::numeric_limits<double>::max)(), "NONNEGATIVE");
+
+/// Check for a positive valued number (val>0.0), min() her is the smallest positive number
+const Range PositiveNumber((std::numeric_limits<double>::min)(), (std::numeric_limits<double>::max)(), "POSITIVE");
+
+/// Produce a bounded range (factory). Min and max are inclusive.
+class Bound : public Validator {
+  public:
+    /// This bounds a value with min and max inclusive.
+    ///
+    /// Note that the constructor is templated, but the struct is not, so C++17 is not
+    /// needed to provide nice syntax for Range(a,b).
+    template <typename T> Bound(T min_val, T max_val) {
+        std::stringstream out;
+        out << detail::type_name<T>() << " bounded to [" << min_val << " - " << max_val << "]";
+        description(out.str());
+
+        func_ = [min_val, max_val](std::string &input) {
+            T val;
+            bool converted = detail::lexical_cast(input, val);
+            if(!converted) {
+                return std::string("Value ") + input + " could not be converted";
+            }
+            if(val < min_val)
+                input = detail::to_string(min_val);
+            else if(val > max_val)
+                input = detail::to_string(max_val);
+
+            return std::string{};
+        };
+    }
+
+    /// Range of one value is 0 to value
+    template <typename T> explicit Bound(T max_val) : Bound(static_cast<T>(0), max_val) {}
+};
+
+namespace detail {
+template <typename T,
+          enable_if_t<is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
+auto smart_deref(T value) -> decltype(*value) {
+    return *value;
+}
+
+template <
+    typename T,
+    enable_if_t<!is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy>
+typename std::remove_reference<T>::type &smart_deref(T &value) {
+    return value;
+}
+/// Generate a string representation of a set
+template <typename T> std::string generate_set(const T &set) {
+    using element_t = typename detail::element_type<T>::type;
+    using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type;  // the type of the object pair
+    std::string out(1, '{');
+    out.append(detail::join(
+        detail::smart_deref(set),
+        [](const iteration_type_t &v) { return detail::pair_adaptor<element_t>::first(v); },
+        ","));
+    out.push_back('}');
+    return out;
+}
+
+/// Generate a string representation of a map
+template <typename T> std::string generate_map(const T &map, bool key_only = false) {
+    using element_t = typename detail::element_type<T>::type;
+    using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type;  // the type of the object pair
+    std::string out(1, '{');
+    out.append(detail::join(
+        detail::smart_deref(map),
+        [key_only](const iteration_type_t &v) {
+            std::string res{detail::to_string(detail::pair_adaptor<element_t>::first(v))};
+
+            if(!key_only) {
+                res.append("->");
+                res += detail::to_string(detail::pair_adaptor<element_t>::second(v));
+            }
+            return res;
+        },
+        ","));
+    out.push_back('}');
+    return out;
+}
+
+template <typename C, typename V> struct has_find {
+    template <typename CC, typename VV>
+    static auto test(int) -> decltype(std::declval<CC>().find(std::declval<VV>()), std::true_type());
+    template <typename, typename> static auto test(...) -> decltype(std::false_type());
+
+    static const auto value = decltype(test<C, V>(0))::value;
+    using type = std::integral_constant<bool, value>;
+};
+
+/// A search function
+template <typename T, typename V, enable_if_t<!has_find<T, V>::value, detail::enabler> = detail::dummy>
+auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
+    using element_t = typename detail::element_type<T>::type;
+    auto &setref = detail::smart_deref(set);
+    auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) {
+        return (detail::pair_adaptor<element_t>::first(v) == val);
+    });
+    return {(it != std::end(setref)), it};
+}
+
+/// A search function that uses the built in find function
+template <typename T, typename V, enable_if_t<has_find<T, V>::value, detail::enabler> = detail::dummy>
+auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
+    auto &setref = detail::smart_deref(set);
+    auto it = setref.find(val);
+    return {(it != std::end(setref)), it};
+}
+
+/// A search function with a filter function
+template <typename T, typename V>
+auto search(const T &set, const V &val, const std::function<V(V)> &filter_function)
+    -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> {
+    using element_t = typename detail::element_type<T>::type;
+    // do the potentially faster first search
+    auto res = search(set, val);
+    if((res.first) || (!(filter_function))) {
+        return res;
+    }
+    // if we haven't found it do the longer linear search with all the element translations
+    auto &setref = detail::smart_deref(set);
+    auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) {
+        V a{detail::pair_adaptor<element_t>::first(v)};
+        a = filter_function(a);
+        return (a == val);
+    });
+    return {(it != std::end(setref)), it};
+}
+
+// the following suggestion was made by Nikita Ofitserov(@himikof)
+// done in templates to prevent compiler warnings on negation of unsigned numbers
+
+/// Do a check for overflow on signed numbers
+template <typename T>
+inline typename std::enable_if<std::is_signed<T>::value, T>::type overflowCheck(const T &a, const T &b) {
+    if((a > 0) == (b > 0)) {
+        return ((std::numeric_limits<T>::max)() / (std::abs)(a) < (std::abs)(b));
+    } else {
+        return ((std::numeric_limits<T>::min)() / (std::abs)(a) > -(std::abs)(b));
+    }
+}
+/// Do a check for overflow on unsigned numbers
+template <typename T>
+inline typename std::enable_if<!std::is_signed<T>::value, T>::type overflowCheck(const T &a, const T &b) {
+    return ((std::numeric_limits<T>::max)() / a < b);
+}
+
+/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise.
+template <typename T> typename std::enable_if<std::is_integral<T>::value, bool>::type checked_multiply(T &a, T b) {
+    if(a == 0 || b == 0 || a == 1 || b == 1) {
+        a *= b;
+        return true;
+    }
+    if(a == (std::numeric_limits<T>::min)() || b == (std::numeric_limits<T>::min)()) {
+        return false;
+    }
+    if(overflowCheck(a, b)) {
+        return false;
+    }
+    a *= b;
+    return true;
+}
+
+/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise.
+template <typename T>
+typename std::enable_if<std::is_floating_point<T>::value, bool>::type checked_multiply(T &a, T b) {
+    T c = a * b;
+    if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) {
+        return false;
+    }
+    a = c;
+    return true;
+}
+
+}  // namespace detail
+/// Verify items are in a set
+class IsMember : public Validator {
+  public:
+    using filter_fn_t = std::function<std::string(std::string)>;
+
+    /// This allows in-place construction using an initializer list
+    template <typename T, typename... Args>
+    IsMember(std::initializer_list<T> values, Args &&...args)
+        : IsMember(std::vector<T>(values), std::forward<Args>(args)...) {}
+
+    /// This checks to see if an item is in a set (empty function)
+    template <typename T> explicit IsMember(T &&set) : IsMember(std::forward<T>(set), nullptr) {}
+
+    /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
+    /// both sides of the comparison before computing the comparison.
+    template <typename T, typename F> explicit IsMember(T set, F filter_function) {
+
+        // Get the type of the contained item - requires a container have ::value_type
+        // if the type does not have first_type and second_type, these are both value_type
+        using element_t = typename detail::element_type<T>::type;             // Removes (smart) pointers if needed
+        using item_t = typename detail::pair_adaptor<element_t>::first_type;  // Is value_type if not a map
+
+        using local_item_t = typename IsMemberType<item_t>::type;  // This will convert bad types to good ones
+                                                                   // (const char * to std::string)
+
+        // Make a local copy of the filter function, using a std::function if not one already
+        std::function<local_item_t(local_item_t)> filter_fn = filter_function;
+
+        // This is the type name for help, it will take the current version of the set contents
+        desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); };
+
+        // This is the function that validates
+        // It stores a copy of the set pointer-like, so shared_ptr will stay alive
+        func_ = [set, filter_fn](std::string &input) {
+            local_item_t b;
+            if(!detail::lexical_cast(input, b)) {
+                throw ValidationError(input);  // name is added later
+            }
+            if(filter_fn) {
+                b = filter_fn(b);
+            }
+            auto res = detail::search(set, b, filter_fn);
+            if(res.first) {
+                // Make sure the version in the input string is identical to the one in the set
+                if(filter_fn) {
+                    input = detail::value_string(detail::pair_adaptor<element_t>::first(*(res.second)));
+                }
+
+                // Return empty error string (success)
+                return std::string{};
+            }
+
+            // If you reach this point, the result was not found
+            return input + " not in " + detail::generate_set(detail::smart_deref(set));
+        };
+    }
+
+    /// You can pass in as many filter functions as you like, they nest (string only currently)
+    template <typename T, typename... Args>
+    IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
+        : IsMember(
+              std::forward<T>(set),
+              [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
+              other...) {}
+};
+
+/// definition of the default transformation object
+template <typename T> using TransformPairs = std::vector<std::pair<std::string, T>>;
+
+/// Translate named items to other or a value set
+class Transformer : public Validator {
+  public:
+    using filter_fn_t = std::function<std::string(std::string)>;
+
+    /// This allows in-place construction
+    template <typename... Args>
+    Transformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
+        : Transformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
+
+    /// direct map of std::string to std::string
+    template <typename T> explicit Transformer(T &&mapping) : Transformer(std::forward<T>(mapping), nullptr) {}
+
+    /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
+    /// both sides of the comparison before computing the comparison.
+    template <typename T, typename F> explicit Transformer(T mapping, F filter_function) {
+
+        static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
+                      "mapping must produce value pairs");
+        // Get the type of the contained item - requires a container have ::value_type
+        // if the type does not have first_type and second_type, these are both value_type
+        using element_t = typename detail::element_type<T>::type;             // Removes (smart) pointers if needed
+        using item_t = typename detail::pair_adaptor<element_t>::first_type;  // Is value_type if not a map
+        using local_item_t = typename IsMemberType<item_t>::type;             // Will convert bad types to good ones
+                                                                              // (const char * to std::string)
+
+        // Make a local copy of the filter function, using a std::function if not one already
+        std::function<local_item_t(local_item_t)> filter_fn = filter_function;
+
+        // This is the type name for help, it will take the current version of the set contents
+        desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); };
+
+        func_ = [mapping, filter_fn](std::string &input) {
+            local_item_t b;
+            if(!detail::lexical_cast(input, b)) {
+                return std::string();
+                // there is no possible way we can match anything in the mapping if we can't convert so just return
+            }
+            if(filter_fn) {
+                b = filter_fn(b);
+            }
+            auto res = detail::search(mapping, b, filter_fn);
+            if(res.first) {
+                input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
+            }
+            return std::string{};
+        };
+    }
+
+    /// You can pass in as many filter functions as you like, they nest
+    template <typename T, typename... Args>
+    Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
+        : Transformer(
+              std::forward<T>(mapping),
+              [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
+              other...) {}
+};
+
+/// translate named items to other or a value set
+class CheckedTransformer : public Validator {
+  public:
+    using filter_fn_t = std::function<std::string(std::string)>;
+
+    /// This allows in-place construction
+    template <typename... Args>
+    CheckedTransformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args)
+        : CheckedTransformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {}
+
+    /// direct map of std::string to std::string
+    template <typename T> explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {}
+
+    /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter
+    /// both sides of the comparison before computing the comparison.
+    template <typename T, typename F> explicit CheckedTransformer(T mapping, F filter_function) {
+
+        static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value,
+                      "mapping must produce value pairs");
+        // Get the type of the contained item - requires a container have ::value_type
+        // if the type does not have first_type and second_type, these are both value_type
+        using element_t = typename detail::element_type<T>::type;             // Removes (smart) pointers if needed
+        using item_t = typename detail::pair_adaptor<element_t>::first_type;  // Is value_type if not a map
+        using local_item_t = typename IsMemberType<item_t>::type;             // Will convert bad types to good ones
+                                                                              // (const char * to std::string)
+        using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type;  // the type of the object pair
+
+        // Make a local copy of the filter function, using a std::function if not one already
+        std::function<local_item_t(local_item_t)> filter_fn = filter_function;
+
+        auto tfunc = [mapping]() {
+            std::string out("value in ");
+            out += detail::generate_map(detail::smart_deref(mapping)) + " OR {";
+            out += detail::join(
+                detail::smart_deref(mapping),
+                [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor<element_t>::second(v)); },
+                ",");
+            out.push_back('}');
+            return out;
+        };
+
+        desc_function_ = tfunc;
+
+        func_ = [mapping, tfunc, filter_fn](std::string &input) {
+            local_item_t b;
+            bool converted = detail::lexical_cast(input, b);
+            if(converted) {
+                if(filter_fn) {
+                    b = filter_fn(b);
+                }
+                auto res = detail::search(mapping, b, filter_fn);
+                if(res.first) {
+                    input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second));
+                    return std::string{};
+                }
+            }
+            for(const auto &v : detail::smart_deref(mapping)) {
+                auto output_string = detail::value_string(detail::pair_adaptor<element_t>::second(v));
+                if(output_string == input) {
+                    return std::string();
+                }
+            }
+
+            return "Check " + input + " " + tfunc() + " FAILED";
+        };
+    }
+
+    /// You can pass in as many filter functions as you like, they nest
+    template <typename T, typename... Args>
+    CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other)
+        : CheckedTransformer(
+              std::forward<T>(mapping),
+              [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); },
+              other...) {}
+};
+
+/// Helper function to allow ignore_case to be passed to IsMember or Transform
+inline std::string ignore_case(std::string item) { return detail::to_lower(item); }
+
+/// Helper function to allow ignore_underscore to be passed to IsMember or Transform
+inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); }
+
+/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform
+inline std::string ignore_space(std::string item) {
+    item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item));
+    item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item));
+    return item;
+}
+
+/// Multiply a number by a factor using given mapping.
+/// Can be used to write transforms for SIZE or DURATION inputs.
+///
+/// Example:
+///   With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}`
+///   one can recognize inputs like "100", "12kb", "100 MB",
+///   that will be automatically transformed to 100, 14448, 104857600.
+///
+/// Output number type matches the type in the provided mapping.
+/// Therefore, if it is required to interpret real inputs like "0.42 s",
+/// the mapping should be of a type <string, float> or <string, double>.
+class AsNumberWithUnit : public Validator {
+  public:
+    /// Adjust AsNumberWithUnit behavior.
+    /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched.
+    /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError
+    ///   if UNIT_REQUIRED is set and unit literal is not found.
+    enum Options {
+        CASE_SENSITIVE = 0,
+        CASE_INSENSITIVE = 1,
+        UNIT_OPTIONAL = 0,
+        UNIT_REQUIRED = 2,
+        DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL
+    };
+
+    template <typename Number>
+    explicit AsNumberWithUnit(std::map<std::string, Number> mapping,
+                              Options opts = DEFAULT,
+                              const std::string &unit_name = "UNIT") {
+        description(generate_description<Number>(unit_name, opts));
+        validate_mapping(mapping, opts);
+
+        // transform function
+        func_ = [mapping, opts](std::string &input) -> std::string {
+            Number num;
+
+            detail::rtrim(input);
+            if(input.empty()) {
+                throw ValidationError("Input is empty");
+            }
+
+            // Find split position between number and prefix
+            auto unit_begin = input.end();
+            while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) {
+                --unit_begin;
+            }
+
+            std::string unit{unit_begin, input.end()};
+            input.resize(static_cast<std::size_t>(std::distance(input.begin(), unit_begin)));
+            detail::trim(input);
+
+            if(opts & UNIT_REQUIRED && unit.empty()) {
+                throw ValidationError("Missing mandatory unit");
+            }
+            if(opts & CASE_INSENSITIVE) {
+                unit = detail::to_lower(unit);
+            }
+            if(unit.empty()) {
+                if(!detail::lexical_cast(input, num)) {
+                    throw ValidationError(std::string("Value ") + input + " could not be converted to " +
+                                          detail::type_name<Number>());
+                }
+                // No need to modify input if no unit passed
+                return {};
+            }
+
+            // find corresponding factor
+            auto it = mapping.find(unit);
+            if(it == mapping.end()) {
+                throw ValidationError(unit +
+                                      " unit not recognized. "
+                                      "Allowed values: " +
+                                      detail::generate_map(mapping, true));
+            }
+
+            if(!input.empty()) {
+                bool converted = detail::lexical_cast(input, num);
+                if(!converted) {
+                    throw ValidationError(std::string("Value ") + input + " could not be converted to " +
+                                          detail::type_name<Number>());
+                }
+                // perform safe multiplication
+                bool ok = detail::checked_multiply(num, it->second);
+                if(!ok) {
+                    throw ValidationError(detail::to_string(num) + " multiplied by " + unit +
+                                          " factor would cause number overflow. Use smaller value.");
+                }
+            } else {
+                num = static_cast<Number>(it->second);
+            }
+
+            input = detail::to_string(num);
+
+            return {};
+        };
+    }
+
+  private:
+    /// Check that mapping contains valid units.
+    /// Update mapping for CASE_INSENSITIVE mode.
+    template <typename Number> static void validate_mapping(std::map<std::string, Number> &mapping, Options opts) {
+        for(auto &kv : mapping) {
+            if(kv.first.empty()) {
+                throw ValidationError("Unit must not be empty.");
+            }
+            if(!detail::isalpha(kv.first)) {
+                throw ValidationError("Unit must contain only letters.");
+            }
+        }
+
+        // make all units lowercase if CASE_INSENSITIVE
+        if(opts & CASE_INSENSITIVE) {
+            std::map<std::string, Number> lower_mapping;
+            for(auto &kv : mapping) {
+                auto s = detail::to_lower(kv.first);
+                if(lower_mapping.count(s)) {
+                    throw ValidationError(std::string("Several matching lowercase unit representations are found: ") +
+                                          s);
+                }
+                lower_mapping[detail::to_lower(kv.first)] = kv.second;
+            }
+            mapping = std::move(lower_mapping);
+        }
+    }
+
+    /// Generate description like this: NUMBER [UNIT]
+    template <typename Number> static std::string generate_description(const std::string &name, Options opts) {
+        std::stringstream out;
+        out << detail::type_name<Number>() << ' ';
+        if(opts & UNIT_REQUIRED) {
+            out << name;
+        } else {
+            out << '[' << name << ']';
+        }
+        return out.str();
+    }
+};
+
+/// Converts a human-readable size string (with unit literal) to uin64_t size.
+/// Example:
+///   "100" => 100
+///   "1 b" => 100
+///   "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024)
+///   "10 KB" => 10240
+///   "10 kb" => 10240
+///   "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024)
+///   "10kb" => 10240
+///   "2 MB" => 2097152
+///   "2 EiB" => 2^61 // Units up to exibyte are supported
+class AsSizeValue : public AsNumberWithUnit {
+  public:
+    using result_t = std::uint64_t;
+
+    /// If kb_is_1000 is true,
+    /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024
+    /// (same applies to higher order units as well).
+    /// Otherwise, interpret all literals as factors of 1024.
+    /// The first option is formally correct, but
+    /// the second interpretation is more wide-spread
+    /// (see https://en.wikipedia.org/wiki/Binary_prefix).
+    explicit AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) {
+        if(kb_is_1000) {
+            description("SIZE [b, kb(=1000b), kib(=1024b), ...]");
+        } else {
+            description("SIZE [b, kb(=1024b), ...]");
+        }
+    }
+
+  private:
+    /// Get <size unit, factor> mapping
+    static std::map<std::string, result_t> init_mapping(bool kb_is_1000) {
+        std::map<std::string, result_t> m;
+        result_t k_factor = kb_is_1000 ? 1000 : 1024;
+        result_t ki_factor = 1024;
+        result_t k = 1;
+        result_t ki = 1;
+        m["b"] = 1;
+        for(std::string p : {"k", "m", "g", "t", "p", "e"}) {
+            k *= k_factor;
+            ki *= ki_factor;
+            m[p] = k;
+            m[p + "b"] = k;
+            m[p + "i"] = ki;
+            m[p + "ib"] = ki;
+        }
+        return m;
+    }
+
+    /// Cache calculated mapping
+    static std::map<std::string, result_t> get_mapping(bool kb_is_1000) {
+        if(kb_is_1000) {
+            static auto m = init_mapping(true);
+            return m;
+        } else {
+            static auto m = init_mapping(false);
+            return m;
+        }
+    }
+};
+
+namespace detail {
+/// Split a string into a program name and command line arguments
+/// the string is assumed to contain a file name followed by other arguments
+/// the return value contains is a pair with the first argument containing the program name and the second
+/// everything else.
+inline std::pair<std::string, std::string> split_program_name(std::string commandline) {
+    // try to determine the programName
+    std::pair<std::string, std::string> vals;
+    trim(commandline);
+    auto esp = commandline.find_first_of(' ', 1);
+    while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) {
+        esp = commandline.find_first_of(' ', esp + 1);
+        if(esp == std::string::npos) {
+            // if we have reached the end and haven't found a valid file just assume the first argument is the
+            // program name
+            if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') {
+                bool embeddedQuote = false;
+                auto keyChar = commandline[0];
+                auto end = commandline.find_first_of(keyChar, 1);
+                while((end != std::string::npos) && (commandline[end - 1] == '\\')) {  // deal with escaped quotes
+                    end = commandline.find_first_of(keyChar, end + 1);
+                    embeddedQuote = true;
+                }
+                if(end != std::string::npos) {
+                    vals.first = commandline.substr(1, end - 1);
+                    esp = end + 1;
+                    if(embeddedQuote) {
+                        vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar));
+                    }
+                } else {
+                    esp = commandline.find_first_of(' ', 1);
+                }
+            } else {
+                esp = commandline.find_first_of(' ', 1);
+            }
+
+            break;
+        }
+    }
+    if(vals.first.empty()) {
+        vals.first = commandline.substr(0, esp);
+        rtrim(vals.first);
+    }
+
+    // strip the program name
+    vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{};
+    ltrim(vals.second);
+    return vals;
+}
+
+}  // namespace detail
+/// @}
+
+// [CLI11:validators_hpp:end]
+}  // namespace CLI
diff --git a/external/CLI11/include/CLI/Version.hpp b/external/CLI11/include/CLI/Version.hpp
new file mode 100644
index 0000000..4bc79a7
--- /dev/null
+++ b/external/CLI11/include/CLI/Version.hpp
@@ -0,0 +1,16 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+// [CLI11:version_hpp:verbatim]
+
+#define CLI11_VERSION_MAJOR 2
+#define CLI11_VERSION_MINOR 1
+#define CLI11_VERSION_PATCH 2
+#define CLI11_VERSION "2.1.2"
+
+// [CLI11:version_hpp:end]
diff --git a/external/CLI11/meson.build b/external/CLI11/meson.build
new file mode 100644
index 0000000..8b36c2a
--- /dev/null
+++ b/external/CLI11/meson.build
@@ -0,0 +1,11 @@
+project('CLI11', ['cpp'],
+  version         : run_command(find_program('scripts/ExtractVersion.py')).stdout().strip(),
+  default_options : ['cpp_std=c++11']
+)
+
+CLI11_inc = include_directories(['include'])
+
+CLI11_dep = declare_dependency(
+  include_directories : CLI11_inc,
+  version             : meson.project_version(),
+)
diff --git a/external/CLI11/scripts/ExtractVersion.py b/external/CLI11/scripts/ExtractVersion.py
new file mode 100755
index 0000000..adfd18d
--- /dev/null
+++ b/external/CLI11/scripts/ExtractVersion.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python3
+
+import os
+import re
+
+base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
+config_h = os.path.join(base_path, "include", "CLI", "Version.hpp")
+data = {"MAJOR": 0, "MINOR": 0, "PATCH": 0}
+reg = re.compile(r"^\s*#define\s+CLI11_VERSION_([A-Z]+)\s+([0-9]+).*$")
+
+with open(config_h, "r") as fp:
+    for l in fp:
+        m = reg.match(l)
+        if m:
+            data[m.group(1)] = int(m.group(2))
+
+print("{}.{}.{}".format(data["MAJOR"], data["MINOR"], data["PATCH"]))
diff --git a/external/CLI11/scripts/MakeSingleHeader.py b/external/CLI11/scripts/MakeSingleHeader.py
new file mode 100755
index 0000000..42e0ee7
--- /dev/null
+++ b/external/CLI11/scripts/MakeSingleHeader.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python
+
+from __future__ import print_function, unicode_literals
+
+import os
+import re
+from argparse import ArgumentParser
+from subprocess import Popen, PIPE
+import warnings
+
+tag_str = r"""
+^                  # Begin of line
+[/\s]+             # Whitespace or comment // chars
+\[                 # A literal [
+{tag}:             # The tag
+(?P<name>[\w_]+)   # name: group name
+:                  # Colon
+(?P<action>[\w_]+) # action: type of include
+\]                 # A literal ]
+\s*                # Whitespace
+$                  # End of a line
+
+(?P<content>.*)    # All
+
+^                  # Begin of line
+[/\s]+             # Whitespace or comment // chars
+\[                 # A literal [
+{tag}:             # The tag
+(?P=name)          # Repeated name
+:                  # Colon
+end                # Literal "end"
+\]                 # A literal ]
+\s*                # Whitespace
+$                  # End of a line
+"""
+
+DIR = os.path.dirname(os.path.abspath(__file__))
+
+
+class HeaderGroups(dict):
+    def __init__(self, tag):
+        """
+        A dictionary that also can read headers given a tag expression.
+
+        TODO: might have gone overboard on this one, could maybe be two functions.
+        """
+        self.re_matcher = re.compile(
+            tag_str.format(tag=tag), re.MULTILINE | re.DOTALL | re.VERBOSE
+        )
+        super(HeaderGroups, self).__init__()
+
+    def read_header(self, filename):
+        """
+        Read a header file in and add items to the dict, based on the item's action.
+        """
+        with open(filename) as f:
+            inner = f.read()
+
+        matches = self.re_matcher.findall(inner)
+
+        if not matches:
+            warnings.warn(
+                "Failed to find any matches in {filename}".format(filename=filename)
+            )
+
+        for name, action, content in matches:
+            if action == "verbatim":
+                assert (
+                    name not in self
+                ), "{name} read in more than once! Quitting.".format(name=name)
+                self[name] = content
+            elif action == "set":
+                self[name] = self.get(name, set()) | set(content.strip().splitlines())
+            else:
+                raise RuntimeError("Action not understood, must be verbatim or set")
+
+    def post_process(self):
+        """
+        Turn sets into multiple line strings.
+        """
+        for key in self:
+            if isinstance(self[key], set):
+                self[key] = "\n".join(self[key])
+
+
+def make_header(output, main_header, files, tag, namespace, macro=None, version=None):
+    """
+    Makes a single header given a main header template and a list of files.
+    """
+    groups = HeaderGroups(tag)
+
+    # Set tag if possible to class variable
+    try:
+        proc = Popen(
+            ["git", "describe", "--tags", "--always"], cwd=str(DIR), stdout=PIPE
+        )
+        out, _ = proc.communicate()
+        groups["git"] = out.decode("utf-8").strip() if proc.returncode == 0 else ""
+    except OSError:
+        groups["git"] = ""
+
+    for f in files:
+        groups.read_header(f)
+
+    groups["namespace"] = namespace
+    groups["version"] = version or groups["git"]
+
+    groups.post_process()
+
+    with open(main_header) as f:
+        single_header = f.read().format(**groups)
+
+    if macro is not None:
+        before, after = macro
+        print("Converting macros", before, "->", after)
+        single_header.replace(before, after)
+
+    if output is not None:
+        with open(output, "w") as f:
+            f.write(single_header)
+
+        print("Created", output)
+    else:
+        print(single_header)
+
+
+if __name__ == "__main__":
+    parser = ArgumentParser(
+        usage="Convert source to single header include. Can optionally add namespace and search-replace replacements (for macros)."
+    )
+    parser.add_argument("--output", default=None, help="Single header file output")
+    parser.add_argument(
+        "--main",
+        default="CLI11.hpp.in",
+        help="The main include file that defines the other files",
+    )
+    parser.add_argument("files", nargs="*", help="The header files")
+    parser.add_argument("--namespace", default="CLI", help="Set the namespace")
+    parser.add_argument("--tag", default="CLI11", help="Tag to look up")
+    parser.add_argument(
+        "--macro", nargs=2, help="Replaces OLD_PREFIX_ with NEW_PREFIX_"
+    )
+    parser.add_argument("--version", help="Include this version in the generated file")
+    args = parser.parse_args()
+
+    make_header(
+        args.output,
+        args.main,
+        args.files,
+        args.tag,
+        args.namespace,
+        args.macro,
+        args.version,
+    )
diff --git a/external/CLI11/scripts/check_style.sh b/external/CLI11/scripts/check_style.sh
new file mode 100755
index 0000000..a51bc7f
--- /dev/null
+++ b/external/CLI11/scripts/check_style.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env sh
+set -evx
+
+clang-format --version
+
+git ls-files -- '*.cpp' '*.hpp' | xargs clang-format -sort-includes -i -style=file
+
+git diff --exit-code --color
+
+set +evx
diff --git a/external/CLI11/scripts/check_style_docker.sh b/external/CLI11/scripts/check_style_docker.sh
new file mode 100755
index 0000000..3defb24
--- /dev/null
+++ b/external/CLI11/scripts/check_style_docker.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env sh
+
+# Also good but untagged: CLANG_FORMAT=unibeautify/clang-format
+# This might provide more control in the future: silkeh/clang:8 (etc)
+CLANG_FORMAT=saschpe/clang-format:5.0.1
+
+set -evx
+
+docker run --rm ${CLANG_FORMAT} --version
+docker run --rm --user $(id -u):$(id -g) -v "$(pwd)":/workdir -w /workdir ${CLANG_FORMAT} -style=file -sort-includes -i $(git ls-files -- '*.cpp' '*.hpp')
+
+git diff --exit-code --color
+
+set +evx
diff --git a/external/CLI11/scripts/clang-format-pre-commit b/external/CLI11/scripts/clang-format-pre-commit
new file mode 100755
index 0000000..96e178f
--- /dev/null
+++ b/external/CLI11/scripts/clang-format-pre-commit
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+
+# To use:
+# ln -s scripts/clang-format.hook .git/hooks/pre-commit
+
+# Based loosely on https://github.com/andrewseidl/githook-clang-format
+
+format_file() {
+    file="${1}"
+    case "$file" in
+    *.hpp | *.cpp | .c | *.cc | *.cu | *.h )
+        if [ -f "${1}" ] ; then
+            clang-format -i -style=file -sort-includes "${1}"
+            if git diff-files --quiet -- "${1}" ; then
+                echo "Already nicely formatted: ${1}"
+            else
+                git add "${1}"
+                echo "Reformatting file: ${1}"
+            fi
+        fi
+        ;;
+    *)
+        ;;
+    esac
+}
+
+case "${1}" in
+  --about )
+    echo "Runs clang-format on source files"
+    ;;
+  * )
+    for file in `git diff-index --cached --name-only HEAD` ; do
+      format_file "${file}"
+    done
+    ;;
+esac
diff --git a/external/CLI11/scripts/mdlint_style.rb b/external/CLI11/scripts/mdlint_style.rb
new file mode 100644
index 0000000..6fca85b
--- /dev/null
+++ b/external/CLI11/scripts/mdlint_style.rb
@@ -0,0 +1,8 @@
+all
+
+exclude_rule 'MD013'  # Line length
+exclude_rule 'MD033'  # Inline HTML
+exclude_rule 'MD034'  # Bare URL (for now)
+
+rule 'MD026', punctuation: '.,;:!'  # Trailing punctuation in header (& in this case)
+rule 'MD029', style: :ordered
diff --git a/external/CLI11/test_package/CMakeLists.txt b/external/CLI11/test_package/CMakeLists.txt
new file mode 100644
index 0000000..48f6d99
--- /dev/null
+++ b/external/CLI11/test_package/CMakeLists.txt
@@ -0,0 +1,16 @@
+project(PackageTest CXX)
+cmake_minimum_required(VERSION 3.1)
+
+include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
+conan_basic_setup()
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_EXTENSIONS OFF)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+message(STATUS "${CMAKE_PREFIX_PATH}")
+
+find_package(CLI11 CONFIG REQUIRED)
+
+add_executable(example example.cpp)
+target_link_libraries(example CLI11::CLI11)
diff --git a/external/CLI11/test_package/conanfile.py b/external/CLI11/test_package/conanfile.py
new file mode 100644
index 0000000..4c5c028
--- /dev/null
+++ b/external/CLI11/test_package/conanfile.py
@@ -0,0 +1,21 @@
+from conans import ConanFile, CMake, tools
+import os
+
+
+class HelloTestConan(ConanFile):
+    settings = "os", "compiler", "build_type", "arch"
+    generators = "cmake"
+
+    def build(self):
+        cmake = CMake(self)
+        cmake.configure()
+        cmake.build()
+
+    def imports(self):
+        self.copy("*.dll", dst="bin", src="bin")
+        self.copy("*.dylib*", dst="bin", src="lib")
+
+    def test(self):
+        if not tools.cross_building(self.settings):
+            os.chdir("bin")
+            self.run(".%sexample" % os.sep)
diff --git a/external/CLI11/test_package/example.cpp b/external/CLI11/test_package/example.cpp
new file mode 100644
index 0000000..464cf75
--- /dev/null
+++ b/external/CLI11/test_package/example.cpp
@@ -0,0 +1,20 @@
+// This file is a "Hello, world!" CLI11 program
+
+#include "CLI/CLI.hpp"
+
+#include <iostream>
+
+int main(int argc, char **argv) {
+
+    CLI::App app("Some nice description");
+
+    int x = 0;
+    app.add_option("-x", x, "an integer value")->capture_default_str();
+
+    bool flag;
+    app.add_flag("-f,--flag", flag, "a flag option");
+
+    CLI11_PARSE(app, argc, argv);
+
+    return 0;
+}
diff --git a/external/CLI11/tests/.syntastic_cpp_config b/external/CLI11/tests/.syntastic_cpp_config
new file mode 100644
index 0000000..67ad583
--- /dev/null
+++ b/external/CLI11/tests/.syntastic_cpp_config
@@ -0,0 +1 @@
+- I../ build / googletest - src / googletest / include / -I../ build / googletest - src / googlemock / include /
diff --git a/external/CLI11/tests/AppTest.cpp b/external/CLI11/tests/AppTest.cpp
new file mode 100644
index 0000000..d425b41
--- /dev/null
+++ b/external/CLI11/tests/AppTest.cpp
@@ -0,0 +1,2310 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+#include <complex>
+#include <cstdint>
+#include <cstdlib>
+
+TEST_CASE_METHOD(TApp, "OneFlagShort", "[app]") {
+    app.add_flag("-c,--count");
+    args = {"-c"};
+    run();
+    CHECK(app.count("-c") == 1u);
+    CHECK(app.count("--count") == 1u);
+}
+
+TEST_CASE_METHOD(TApp, "OneFlagShortValues", "[app]") {
+    app.add_flag("-c{v1},--count{v2}");
+    args = {"-c"};
+    run();
+    CHECK(app.count("-c") == 1u);
+    CHECK(app.count("--count") == 1u);
+    auto v = app["-c"]->results();
+    CHECK("v1" == v[0]);
+
+    CHECK_THROWS_AS(app["--invalid"], CLI::OptionNotFound);
+}
+
+TEST_CASE_METHOD(TApp, "OneFlagShortValuesAs", "[app]") {
+    auto flg = app.add_flag("-c{1},--count{2}");
+    args = {"-c"};
+    run();
+    auto opt = app["-c"];
+    CHECK(1 == opt->as<int>());
+    args = {"--count"};
+    run();
+    CHECK(2 == opt->as<int>());
+    flg->take_first();
+    args = {"-c", "--count"};
+    run();
+    CHECK(1 == opt->as<int>());
+    flg->take_last();
+    CHECK(2 == opt->as<int>());
+    flg->multi_option_policy(CLI::MultiOptionPolicy::Throw);
+    CHECK_THROWS_AS(opt->as<int>(), CLI::ArgumentMismatch);
+    flg->multi_option_policy(CLI::MultiOptionPolicy::TakeAll);
+    auto vec = opt->as<std::vector<int>>();
+    CHECK(1 == vec[0]);
+    CHECK(2 == vec[1]);
+    flg->multi_option_policy(CLI::MultiOptionPolicy::Join);
+    CHECK("1\n2" == opt->as<std::string>());
+    flg->delimiter(',');
+    CHECK("1,2" == opt->as<std::string>());
+}
+
+TEST_CASE_METHOD(TApp, "OneFlagShortWindows", "[app]") {
+    app.add_flag("-c,--count");
+    args = {"/c"};
+    app.allow_windows_style_options();
+    run();
+    CHECK(app.count("-c") == 1u);
+    CHECK(app.count("--count") == 1u);
+}
+
+TEST_CASE_METHOD(TApp, "WindowsLongShortMix1", "[app]") {
+    app.allow_windows_style_options();
+
+    auto a = app.add_flag("-c");
+    auto b = app.add_flag("--c");
+    args = {"/c"};
+    run();
+    CHECK(a->count() == 1u);
+    CHECK(b->count() == 0u);
+}
+
+TEST_CASE_METHOD(TApp, "WindowsLongShortMix2", "[app]") {
+    app.allow_windows_style_options();
+
+    auto a = app.add_flag("--c");
+    auto b = app.add_flag("-c");
+    args = {"/c"};
+    run();
+    CHECK(a->count() == 1u);
+    CHECK(b->count() == 0u);
+}
+
+TEST_CASE_METHOD(TApp, "CountNonExist", "[app]") {
+    app.add_flag("-c,--count");
+    args = {"-c"};
+    run();
+    CHECK_THROWS_AS(app.count("--nonexist"), CLI::OptionNotFound);
+}
+
+TEST_CASE_METHOD(TApp, "OneFlagLong", "[app]") {
+    app.add_flag("-c,--count");
+    args = {"--count"};
+    run();
+    CHECK(app.count("-c") == 1u);
+    CHECK(app.count("--count") == 1u);
+}
+
+TEST_CASE_METHOD(TApp, "DashedOptions", "[app]") {
+    app.add_flag("-c");
+    app.add_flag("--q");
+    app.add_flag("--this,--that");
+
+    args = {"-c", "--q", "--this", "--that"};
+    run();
+    CHECK(app.count("-c") == 1u);
+    CHECK(app.count("--q") == 1u);
+    CHECK(app.count("--this") == 2u);
+    CHECK(app.count("--that") == 2u);
+}
+
+TEST_CASE_METHOD(TApp, "DashedOptionsSingleString", "[app]") {
+    app.add_flag("-c");
+    app.add_flag("--q");
+    app.add_flag("--this,--that");
+
+    app.parse("-c --q --this --that");
+    CHECK(app.count("-c") == 1u);
+    CHECK(app.count("--q") == 1u);
+    CHECK(app.count("--this") == 2u);
+    CHECK(app.count("--that") == 2u);
+}
+
+TEST_CASE_METHOD(TApp, "StrangeFlagNames", "[app]") {
+    app.add_flag("-=");
+    app.add_flag("--t\tt");
+    app.add_flag("-{");
+    CHECK_THROWS_AS(app.add_flag("--t t"), CLI::ConstructionError);
+    args = {"-=", "--t\tt"};
+    run();
+    CHECK(app.count("-=") == 1u);
+    CHECK(app.count("--t\tt") == 1u);
+}
+
+TEST_CASE_METHOD(TApp, "RequireOptionsError", "[app]") {
+    using Catch::Matchers::Contains;
+
+    app.add_flag("-c");
+    app.add_flag("--q");
+    app.add_flag("--this,--that");
+    app.set_help_flag("-h,--help");
+    app.set_help_all_flag("--help_all");
+    app.require_option(1, 2);
+    try {
+        app.parse("-c --q --this --that");
+    } catch(const CLI::RequiredError &re) {
+        CHECK_THAT(re.what(), !Contains("-h,--help"));
+        CHECK_THAT(re.what(), !Contains("help_all"));
+    }
+
+    CHECK_NOTHROW(app.parse("-c --q"));
+    CHECK_NOTHROW(app.parse("-c --this --that"));
+}
+
+TEST_CASE_METHOD(TApp, "BoolFlagOverride", "[app]") {
+    bool val{false};
+    auto flg = app.add_flag("--this,--that", val);
+
+    app.parse("--this");
+    CHECK(val);
+    app.parse("--this=false");
+    CHECK(!val);
+    flg->disable_flag_override(true);
+    app.parse("--this");
+    CHECK(val);
+    // this is allowed since the matching string is the default
+    app.parse("--this=true");
+    CHECK(val);
+
+    CHECK_THROWS_AS(app.parse("--this=false"), CLI::ArgumentMismatch);
+    // try a string that specifies 'use default val'
+    CHECK_NOTHROW(app.parse("--this={}"));
+}
+
+TEST_CASE_METHOD(TApp, "OneFlagRef", "[app]") {
+    int ref{0};
+    app.add_flag("-c,--count", ref);
+    args = {"--count"};
+    run();
+    CHECK(app.count("-c") == 1u);
+    CHECK(app.count("--count") == 1u);
+    CHECK(ref == 1);
+}
+
+TEST_CASE_METHOD(TApp, "OneFlagRefValue", "[app]") {
+    int ref{0};
+    app.add_flag("-c,--count", ref);
+    args = {"--count=7"};
+    run();
+    CHECK(app.count("-c") == 1u);
+    CHECK(app.count("--count") == 1u);
+    CHECK(ref == 7);
+}
+
+TEST_CASE_METHOD(TApp, "OneFlagRefValueFalse", "[app]") {
+    int ref{0};
+    auto flg = app.add_flag("-c,--count", ref);
+    args = {"--count=false"};
+    run();
+    CHECK(app.count("-c") == 1u);
+    CHECK(app.count("--count") == 1u);
+    CHECK(ref == -1);
+
+    CHECK(!flg->check_fname("c"));
+    args = {"--count=0"};
+    run();
+    CHECK(app.count("-c") == 1u);
+    CHECK(app.count("--count") == 1u);
+    CHECK(ref == -1);
+
+    args = {"--count=happy"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "FlagNegation", "[app]") {
+    int ref{0};
+    auto flg = app.add_flag("-c,--count,--ncount{false}", ref);
+    args = {"--count", "-c", "--ncount"};
+    CHECK(!flg->check_fname("count"));
+    CHECK(flg->check_fname("ncount"));
+    run();
+    CHECK(app.count("-c") == 3u);
+    CHECK(app.count("--count") == 3u);
+    CHECK(app.count("--ncount") == 3u);
+    CHECK(ref == 1);
+}
+
+TEST_CASE_METHOD(TApp, "FlagNegationShortcutNotation", "[app]") {
+    int ref{0};
+    app.add_flag("-c,--count{true},!--ncount", ref);
+    args = {"--count=TRUE", "-c", "--ncount"};
+    run();
+    CHECK(app.count("-c") == 3u);
+    CHECK(app.count("--count") == 3u);
+    CHECK(app.count("--ncount") == 3u);
+    CHECK(ref == 1);
+}
+
+TEST_CASE_METHOD(TApp, "FlagNegationShortcutNotationInvalid", "[app]") {
+    int ref{0};
+    app.add_flag("-c,--count,!--ncount", ref);
+    args = {"--ncount=happy"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "OneString", "[app]") {
+    std::string str;
+    app.add_option("-s,--string", str);
+    args = {"--string", "mystring"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--string") == 1u);
+    CHECK("mystring" == str);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringWindowsStyle", "[app]") {
+    std::string str;
+    app.add_option("-s,--string", str);
+    args = {"/string", "mystring"};
+    app.allow_windows_style_options();
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--string") == 1u);
+    CHECK("mystring" == str);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringSingleStringInput", "[app]") {
+    std::string str;
+    app.add_option("-s,--string", str);
+
+    app.parse("--string mystring");
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--string") == 1u);
+    CHECK("mystring" == str);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringEqualVersion", "[app]") {
+    std::string str;
+    app.add_option("-s,--string", str);
+    args = {"--string=mystring"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--string") == 1u);
+    CHECK("mystring" == str);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringEqualVersionWindowsStyle", "[app]") {
+    std::string str;
+    app.add_option("-s,--string", str);
+    args = {"/string:mystring"};
+    app.allow_windows_style_options();
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--string") == 1u);
+    CHECK("mystring" == str);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleString", "[app]") {
+    std::string str;
+    app.add_option("-s,--string", str);
+    app.parse("--string=mystring");
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--string") == 1u);
+    CHECK("mystring" == str);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuoted", "[app]") {
+    std::string str;
+    app.add_option("-s,--string", str);
+    app.parse(R"raw(--string="this is my quoted string")raw");
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--string") == 1u);
+    CHECK("this is my quoted string" == str);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedMultiple", "[app]") {
+    std::string str, str2, str3;
+    app.add_option("-s,--string", str);
+    app.add_option("-t,--tstr", str2);
+    app.add_option("-m,--mstr", str3);
+    app.parse(R"raw(--string="this is my quoted string" -t 'qstring 2' -m=`"quoted string"`)raw");
+    CHECK("this is my quoted string" == str);
+    CHECK("qstring 2" == str2);
+    CHECK("\"quoted string\"" == str3);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringEmbeddedEqual", "[app]") {
+    std::string str, str2, str3;
+    app.add_option("-s,--string", str);
+    app.add_option("-t,--tstr", str2);
+    app.add_option("-m,--mstr", str3);
+    app.parse(R"raw(--string="app=\"test1 b\" test2=\"frogs\"" -t 'qstring 2' -m=`"quoted string"`)raw");
+    CHECK("app=\"test1 b\" test2=\"frogs\"" == str);
+    CHECK("qstring 2" == str2);
+    CHECK("\"quoted string\"" == str3);
+
+    app.parse(R"raw(--string="app='test1 b' test2='frogs'" -t 'qstring 2' -m=`"quoted string"`)raw");
+    CHECK("app='test1 b' test2='frogs'" == str);
+    CHECK("qstring 2" == str2);
+    CHECK("\"quoted string\"" == str3);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringEmbeddedEqualWindowsStyle", "[app]") {
+    std::string str, str2, str3;
+    app.add_option("-s,--string", str);
+    app.add_option("-t,--tstr", str2);
+    app.add_option("--mstr", str3);
+    app.allow_windows_style_options();
+    app.parse(R"raw(/string:"app:\"test1 b\" test2:\"frogs\"" /t 'qstring 2' /mstr:`"quoted string"`)raw");
+    CHECK("app:\"test1 b\" test2:\"frogs\"" == str);
+    CHECK("qstring 2" == str2);
+    CHECK("\"quoted string\"" == str3);
+
+    app.parse(R"raw(/string:"app:'test1 b' test2:'frogs'" /t 'qstring 2' /mstr:`"quoted string"`)raw");
+    CHECK("app:'test1 b' test2:'frogs'" == str);
+    CHECK("qstring 2" == str2);
+    CHECK("\"quoted string\"" == str3);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedMultipleMixedStyle", "[app]") {
+    std::string str, str2, str3;
+    app.add_option("-s,--string", str);
+    app.add_option("-t,--tstr", str2);
+    app.add_option("-m,--mstr", str3);
+    app.allow_windows_style_options();
+    app.parse(R"raw(/string:"this is my quoted string" /t 'qstring 2' -m=`"quoted string"`)raw");
+    CHECK("this is my quoted string" == str);
+    CHECK("qstring 2" == str2);
+    CHECK("\"quoted string\"" == str3);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedMultipleInMiddle", "[app]") {
+    std::string str, str2, str3;
+    app.add_option("-s,--string", str);
+    app.add_option("-t,--tstr", str2);
+    app.add_option("-m,--mstr", str3);
+    app.parse(R"raw(--string="this is my quoted string" -t "qst\"ring 2" -m=`"quoted string"`)raw");
+    CHECK("this is my quoted string" == str);
+    CHECK("qst\"ring 2" == str2);
+    CHECK("\"quoted string\"" == str3);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedEscapedCharacters", "[app]") {
+    std::string str, str2, str3;
+    app.add_option("-s,--string", str);
+    app.add_option("-t,--tstr", str2);
+    app.add_option("-m,--mstr", str3);
+    app.parse(R"raw(--string="this is my \"quoted\" string" -t 'qst\'ring 2' -m=`"quoted\` string"`")raw");
+    CHECK("this is my \"quoted\" string" == str);
+    CHECK("qst\'ring 2" == str2);
+    CHECK("\"quoted` string\"" == str3);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedMultipleWithEqual", "[app]") {
+    std::string str, str2, str3, str4;
+    app.add_option("-s,--string", str);
+    app.add_option("-t,--tstr", str2);
+    app.add_option("-m,--mstr", str3);
+    app.add_option("-j,--jstr", str4);
+    app.parse(R"raw(--string="this is my quoted string" -t 'qstring 2' -m=`"quoted string"` --jstr=Unquoted)raw");
+    CHECK("this is my quoted string" == str);
+    CHECK("qstring 2" == str2);
+    CHECK("\"quoted string\"" == str3);
+    CHECK("Unquoted" == str4);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedMultipleWithEqualAndProgram", "[app]") {
+    std::string str, str2, str3, str4;
+    app.add_option("-s,--string", str);
+    app.add_option("-t,--tstr", str2);
+    app.add_option("-m,--mstr", str3);
+    app.add_option("-j,--jstr", str4);
+    app.parse(
+        R"raw(program --string="this is my quoted string" -t 'qstring 2' -m=`"quoted string"` --jstr=Unquoted)raw",
+        true);
+    CHECK("this is my quoted string" == str);
+    CHECK("qstring 2" == str2);
+    CHECK("\"quoted string\"" == str3);
+    CHECK("Unquoted" == str4);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringFlagLike", "[app]") {
+    std::string str{"something"};
+    app.add_option("-s,--string", str)->expected(0, 1);
+    args = {"--string"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--string") == 1u);
+    CHECK(str.empty());
+}
+
+TEST_CASE_METHOD(TApp, "OneIntFlagLike", "[app]") {
+    int val{0};
+    auto opt = app.add_option("-i", val)->expected(0, 1);
+    args = {"-i"};
+    run();
+    CHECK(app.count("-i") == 1u);
+    opt->default_str("7");
+    run();
+    CHECK(7 == val);
+
+    opt->default_val(9);
+    run();
+    CHECK(9 == val);
+}
+
+TEST_CASE_METHOD(TApp, "TogetherInt", "[app]") {
+    int i{0};
+    app.add_option("-i,--int", i);
+    args = {"-i4"};
+    run();
+    CHECK(app.count("--int") == 1u);
+    CHECK(app.count("-i") == 1u);
+    CHECK(4 == i);
+    CHECK("4" == app["-i"]->as<std::string>());
+    CHECK(4.0 == app["--int"]->as<double>());
+}
+
+TEST_CASE_METHOD(TApp, "SepInt", "[app]") {
+    int i{0};
+    app.add_option("-i,--int", i);
+    args = {"-i", "4"};
+    run();
+    CHECK(app.count("--int") == 1u);
+    CHECK(app.count("-i") == 1u);
+    CHECK(4 == i);
+}
+
+TEST_CASE_METHOD(TApp, "DefaultStringAgain", "[app]") {
+    std::string str = "previous";
+    app.add_option("-s,--string", str);
+    run();
+    CHECK(app.count("-s") == 0u);
+    CHECK(app.count("--string") == 0u);
+    CHECK("previous" == str);
+}
+
+TEST_CASE_METHOD(TApp, "DefaultStringAgainEmpty", "[app]") {
+    std::string str = "previous";
+    app.add_option("-s,--string", str);
+    app.parse("   ");
+    CHECK(app.count("-s") == 0u);
+    CHECK(app.count("--string") == 0u);
+    CHECK("previous" == str);
+}
+
+TEST_CASE_METHOD(TApp, "DualOptions", "[app]") {
+
+    std::string str = "previous";
+    std::vector<std::string> vstr = {"previous"};
+    std::vector<std::string> ans = {"one", "two"};
+    app.add_option("-s,--string", str);
+    app.add_option("-v,--vector", vstr);
+
+    args = {"--vector=one", "--vector=two"};
+    run();
+    CHECK(vstr == ans);
+
+    args = {"--string=one", "--string=two"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "LotsOfFlags", "[app]") {
+
+    app.add_flag("-a");
+    app.add_flag("-A");
+    app.add_flag("-b");
+
+    args = {"-a", "-b", "-aA"};
+    run();
+    CHECK(app.count("-a") == 2u);
+    CHECK(app.count("-b") == 1u);
+    CHECK(app.count("-A") == 1u);
+    CHECK(4u == app.count_all());
+}
+
+TEST_CASE_METHOD(TApp, "NumberFlags", "[app]") {
+
+    int val{0};
+    app.add_flag("-1{1},-2{2},-3{3},-4{4},-5{5},-6{6}, -7{7}, -8{8}, -9{9}", val);
+
+    args = {"-7"};
+    run();
+    CHECK(app.count("-1") == 1u);
+    CHECK(7 == val);
+}
+
+TEST_CASE_METHOD(TApp, "DisableFlagOverrideTest", "[app]") {
+
+    int val{0};
+    auto opt = app.add_flag("--1{1},--2{2},--3{3},--4{4},--5{5},--6{6}, --7{7}, --8{8}, --9{9}", val);
+    CHECK(!opt->get_disable_flag_override());
+    opt->disable_flag_override();
+    args = {"--7=5"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+    CHECK(opt->get_disable_flag_override());
+    opt->disable_flag_override(false);
+    CHECK(!opt->get_disable_flag_override());
+    CHECK_NOTHROW(run());
+    CHECK(5 == val);
+    opt->disable_flag_override();
+    args = {"--7=7"};
+    CHECK_NOTHROW(run());
+}
+
+TEST_CASE_METHOD(TApp, "LotsOfFlagsSingleString", "[app]") {
+
+    app.add_flag("-a");
+    app.add_flag("-A");
+    app.add_flag("-b");
+
+    app.parse("-a -b -aA");
+    CHECK(app.count("-a") == 2u);
+    CHECK(app.count("-b") == 1u);
+    CHECK(app.count("-A") == 1u);
+}
+
+TEST_CASE_METHOD(TApp, "LotsOfFlagsSingleStringExtraSpace", "[app]") {
+
+    app.add_flag("-a");
+    app.add_flag("-A");
+    app.add_flag("-b");
+
+    app.parse("  -a    -b    -aA   ");
+    CHECK(app.count("-a") == 2u);
+    CHECK(app.count("-b") == 1u);
+    CHECK(app.count("-A") == 1u);
+}
+
+TEST_CASE_METHOD(TApp, "SingleArgVector", "[app]") {
+
+    std::vector<std::string> channels;
+    std::vector<std::string> iargs;
+    std::string path;
+    app.add_option("-c", channels)->type_size(1)->allow_extra_args(false);
+    app.add_option("args", iargs);
+    app.add_option("-p", path);
+
+    app.parse("-c t1 -c t2 -c t3 a1 a2 a3 a4 -p happy");
+    CHECK(channels.size() == 3u);
+    CHECK(iargs.size() == 4u);
+    CHECK("happy" == path);
+
+    app.parse("-c t1 a1 -c t2 -c t3 a2 a3 a4 -p happy");
+    CHECK(channels.size() == 3u);
+    CHECK(iargs.size() == 4u);
+    CHECK("happy" == path);
+}
+
+TEST_CASE_METHOD(TApp, "StrangeOptionNames", "[app]") {
+    app.add_option("-:");
+    app.add_option("--t\tt");
+    app.add_option("--{}");
+    app.add_option("--:)");
+    CHECK_THROWS_AS(app.add_option("--t t"), CLI::ConstructionError);
+    args = {"-:)", "--{}", "5"};
+    run();
+    CHECK(app.count("-:") == 1u);
+    CHECK(app.count("--{}") == 1u);
+    CHECK(app["-:"]->as<char>() == ')');
+    CHECK(app["--{}"]->as<int>() == 5);
+}
+
+TEST_CASE_METHOD(TApp, "FlagLikeOption", "[app]") {
+    bool val{false};
+    auto opt = app.add_option("--flag", val)->type_size(0)->default_str("true");
+    args = {"--flag"};
+    run();
+    CHECK(app.count("--flag") == 1u);
+    CHECK(val);
+    val = false;
+    opt->type_size(0, 0);  // should be the same as above
+    CHECK(0 == opt->get_type_size_min());
+    CHECK(0 == opt->get_type_size_max());
+    run();
+    CHECK(app.count("--flag") == 1u);
+    CHECK(val);
+}
+
+TEST_CASE_METHOD(TApp, "FlagLikeIntOption", "[app]") {
+    int val{-47};
+    auto opt = app.add_option("--flag", val)->expected(0, 1);
+    // normally some default value should be set, but this test is for some paths in the validators checks to skip
+    // validation on empty string if nothing is expected
+    opt->check(CLI::PositiveNumber);
+    args = {"--flag"};
+    CHECK(opt->as<std::string>().empty());
+    run();
+    CHECK(app.count("--flag") == 1u);
+    CHECK(-47 != val);
+    args = {"--flag", "12"};
+    run();
+
+    CHECK(12 == val);
+    args.clear();
+    run();
+    CHECK(opt->as<std::string>().empty());
+}
+
+TEST_CASE_METHOD(TApp, "BoolOnlyFlag", "[app]") {
+    bool bflag{false};
+    app.add_flag("-b", bflag)->multi_option_policy(CLI::MultiOptionPolicy::Throw);
+
+    args = {"-b"};
+    REQUIRE_NOTHROW(run());
+    CHECK(bflag);
+
+    args = {"-b", "-b"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "ShortOpts", "[app]") {
+
+    std::uint64_t funnyint{0};
+    std::string someopt;
+    app.add_flag("-z", funnyint);
+    app.add_option("-y", someopt);
+
+    args = {
+        "-zzyzyz",
+    };
+
+    run();
+
+    CHECK(app.count("-z") == 2u);
+    CHECK(app.count("-y") == 1u);
+    CHECK(funnyint == std::uint64_t{2});
+    CHECK(someopt == "zyz");
+    CHECK(3u == app.count_all());
+}
+
+TEST_CASE_METHOD(TApp, "TwoParamTemplateOpts", "[app]") {
+
+    double funnyint{0.0};
+    auto opt = app.add_option<double, unsigned int>("-y", funnyint);
+
+    args = {"-y", "32"};
+
+    run();
+
+    CHECK(funnyint == 32.0);
+
+    args = {"-y", "32.3"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-y", "-19"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    opt->capture_default_str();
+    CHECK(opt->get_default_str().empty());
+}
+
+TEST_CASE_METHOD(TApp, "DefaultOpts", "[app]") {
+
+    int i{3};
+    std::string s = "HI";
+
+    app.add_option("-i,i", i);
+    app.add_option("-s,s", s)->capture_default_str();  //  Used to be different
+
+    args = {"-i2", "9"};
+
+    run();
+
+    CHECK(app.count("i") == 1u);
+    CHECK(app.count("-s") == 1u);
+    CHECK(i == 2);
+    CHECK(s == "9");
+}
+
+TEST_CASE_METHOD(TApp, "TakeLastOpt", "[app]") {
+
+    std::string str;
+    app.add_option("--str", str)->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
+
+    args = {"--str=one", "--str=two"};
+
+    run();
+
+    CHECK("two" == str);
+}
+
+TEST_CASE_METHOD(TApp, "TakeLastOpt2", "[app]") {
+
+    std::string str;
+    app.add_option("--str", str)->take_last();
+
+    args = {"--str=one", "--str=two"};
+
+    run();
+
+    CHECK("two" == str);
+}
+
+TEST_CASE_METHOD(TApp, "TakeFirstOpt", "[app]") {
+
+    std::string str;
+    app.add_option("--str", str)->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst);
+
+    args = {"--str=one", "--str=two"};
+
+    run();
+
+    CHECK("one" == str);
+}
+
+TEST_CASE_METHOD(TApp, "TakeFirstOpt2", "[app]") {
+
+    std::string str;
+    app.add_option("--str", str)->take_first();
+
+    args = {"--str=one", "--str=two"};
+
+    run();
+
+    CHECK("one" == str);
+}
+
+TEST_CASE_METHOD(TApp, "JoinOpt", "[app]") {
+
+    std::string str;
+    app.add_option("--str", str)->multi_option_policy(CLI::MultiOptionPolicy::Join);
+
+    args = {"--str=one", "--str=two"};
+
+    run();
+
+    CHECK("one\ntwo" == str);
+}
+
+TEST_CASE_METHOD(TApp, "JoinOpt2", "[app]") {
+
+    std::string str;
+    app.add_option("--str", str)->join();
+
+    args = {"--str=one", "--str=two"};
+
+    run();
+
+    CHECK("one\ntwo" == str);
+}
+
+TEST_CASE_METHOD(TApp, "TakeLastOptMulti", "[app]") {
+    std::vector<int> vals;
+    app.add_option("--long", vals)->expected(2)->take_last();
+
+    args = {"--long", "1", "2", "3"};
+
+    run();
+
+    CHECK(std::vector<int>({2, 3}) == vals);
+}
+
+TEST_CASE_METHOD(TApp, "TakeLastOptMulti_alternative_path", "[app]") {
+    std::vector<int> vals;
+    app.add_option("--long", vals)->expected(2, -1)->take_last();
+
+    args = {"--long", "1", "2", "3"};
+
+    run();
+
+    CHECK(std::vector<int>({2, 3}) == vals);
+}
+
+TEST_CASE_METHOD(TApp, "TakeLastOptMultiCheck", "[app]") {
+    std::vector<int> vals;
+    auto opt = app.add_option("--long", vals)->expected(-2)->take_last();
+
+    opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0));
+    opt->check((!CLI::PositiveNumber).application_index(1));
+    args = {"--long", "-1", "2", "-3"};
+
+    CHECK_NOTHROW(run());
+
+    CHECK(std::vector<int>({2, -3}) == vals);
+}
+
+TEST_CASE_METHOD(TApp, "TakeFirstOptMulti", "[app]") {
+    std::vector<int> vals;
+    app.add_option("--long", vals)->expected(2)->take_first();
+
+    args = {"--long", "1", "2", "3"};
+
+    run();
+
+    CHECK(std::vector<int>({1, 2}) == vals);
+}
+
+TEST_CASE_METHOD(TApp, "ComplexOptMulti", "[app]") {
+    std::complex<double> val;
+    app.add_option("--long", val)->take_first()->allow_extra_args();
+
+    args = {"--long", "1", "2", "3", "4"};
+
+    run();
+
+    CHECK(1 == Approx(val.real()));
+    CHECK(2 == Approx(val.imag()));
+}
+
+TEST_CASE_METHOD(TApp, "MissingValueNonRequiredOpt", "[app]") {
+    int count{0};
+    app.add_option("-c,--count", count);
+
+    args = {"-c"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+
+    args = {"--count"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "MissingValueMoreThan", "[app]") {
+    std::vector<int> vals1;
+    std::vector<int> vals2;
+    app.add_option("-v", vals1)->expected(-2);
+    app.add_option("--vals", vals2)->expected(-2);
+
+    args = {"-v", "2"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+
+    args = {"--vals", "4"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "NoMissingValueMoreThan", "[app]") {
+    std::vector<int> vals1;
+    std::vector<int> vals2;
+    app.add_option("-v", vals1)->expected(-2);
+    app.add_option("--vals", vals2)->expected(-2);
+
+    args = {"-v", "2", "3", "4"};
+    run();
+    CHECK(std::vector<int>({2, 3, 4}) == vals1);
+
+    args = {"--vals", "2", "3", "4"};
+    run();
+    CHECK(std::vector<int>({2, 3, 4}) == vals2);
+}
+
+TEST_CASE_METHOD(TApp, "NotRequiredOptsSingle", "[app]") {
+
+    std::string str;
+    app.add_option("--str", str);
+
+    args = {"--str"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "NotRequiredOptsSingleShort", "[app]") {
+
+    std::string str;
+    app.add_option("-s", str);
+
+    args = {"-s"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "RequiredOptsSingle", "[app]") {
+
+    std::string str;
+    app.add_option("--str", str)->required();
+
+    args = {"--str"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "RequiredOptsSingleShort", "[app]") {
+
+    std::string str;
+    app.add_option("-s", str)->required();
+
+    args = {"-s"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "RequiredOptsDouble", "[app]") {
+
+    std::vector<std::string> strs;
+    app.add_option("--str", strs)->required()->expected(2);
+
+    args = {"--str", "one"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+
+    args = {"--str", "one", "two"};
+
+    run();
+
+    CHECK(std::vector<std::string>({"one", "two"}) == strs);
+}
+
+TEST_CASE_METHOD(TApp, "RequiredOptsDoubleShort", "[app]") {
+
+    std::vector<std::string> strs;
+    app.add_option("-s", strs)->required()->expected(2);
+
+    args = {"-s", "one"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+
+    args = {"-s", "one", "-s", "one", "-s", "one"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "RequiredOptsDoubleNeg", "[app]") {
+    std::vector<std::string> strs;
+    app.add_option("-s", strs)->required()->expected(-2);
+
+    args = {"-s", "one"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+
+    args = {"-s", "one", "two", "-s", "three"};
+
+    REQUIRE_NOTHROW(run());
+    CHECK(std::vector<std::string>({"one", "two", "three"}) == strs);
+
+    args = {"-s", "one", "two"};
+    REQUIRE_NOTHROW(run());
+    CHECK(std::vector<std::string>({"one", "two"}) == strs);
+}
+
+// This makes sure unlimited option priority is
+// correct for space vs. no space #90
+TEST_CASE_METHOD(TApp, "PositionalNoSpace", "[app]") {
+    std::vector<std::string> options;
+    std::string foo, bar;
+
+    app.add_option("-O", options);
+    app.add_option("foo", foo)->required();
+    app.add_option("bar", bar)->required();
+
+    args = {"-O", "Test", "param1", "param2"};
+    run();
+
+    CHECK(1u == options.size());
+    CHECK("Test" == options.at(0));
+
+    args = {"-OTest", "param1", "param2"};
+    run();
+
+    CHECK(1u == options.size());
+    CHECK("Test" == options.at(0));
+}
+
+// Tests positionals at end
+TEST_CASE_METHOD(TApp, "PositionalAtEnd", "[app]") {
+    std::string options;
+    std::string foo;
+
+    app.add_option("-O", options);
+    app.add_option("foo", foo);
+    app.positionals_at_end();
+    CHECK(app.get_positionals_at_end());
+    args = {"-O", "Test", "param1"};
+    run();
+
+    CHECK("Test" == options);
+    CHECK("param1" == foo);
+
+    args = {"param2", "-O", "Test"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+// Tests positionals at end
+TEST_CASE_METHOD(TApp, "RequiredPositionals", "[app]") {
+    std::vector<std::string> sources;
+    std::string dest;
+    app.add_option("src", sources);
+    app.add_option("dest", dest)->required();
+    app.positionals_at_end();
+
+    args = {"1", "2", "3"};
+    run();
+
+    CHECK(2u == sources.size());
+    CHECK("3" == dest);
+
+    args = {"a"};
+    sources.clear();
+    run();
+
+    CHECK(0u == sources.size());
+    CHECK("a" == dest);
+}
+
+TEST_CASE_METHOD(TApp, "RequiredPositionalVector", "[app]") {
+    std::string d1;
+    std::string d2;
+    std::string d3;
+    std::vector<std::string> sources;
+
+    app.add_option("dest1", d1);
+    app.add_option("dest2", d2);
+    app.add_option("dest3", d3);
+    app.add_option("src", sources)->required();
+
+    app.positionals_at_end();
+
+    args = {"1", "2", "3"};
+    run();
+
+    CHECK(1u == sources.size());
+    CHECK("1" == d1);
+    CHECK("2" == d2);
+    CHECK(d3.empty());
+    args = {"a"};
+    sources.clear();
+    run();
+
+    CHECK(1u == sources.size());
+}
+
+// Tests positionals at end
+TEST_CASE_METHOD(TApp, "RequiredPositionalValidation", "[app]") {
+    std::vector<std::string> sources;
+    int dest;  // required
+    std::string d2;
+    app.add_option("src", sources);
+    app.add_option("dest", dest)->required()->check(CLI::PositiveNumber);
+    app.add_option("dest2", d2)->required();
+    app.positionals_at_end()->validate_positionals();
+
+    args = {"1", "2", "string", "3"};
+    run();
+
+    CHECK(2u == sources.size());
+    CHECK(3 == dest);
+    CHECK("string" == d2);
+}
+
+// Tests positionals at end
+TEST_CASE_METHOD(TApp, "PositionalValidation", "[app]") {
+    std::string options;
+    std::string foo;
+
+    app.add_option("bar", options)->check(CLI::Number.name("valbar"));
+    // disable the check on foo
+    app.add_option("foo", foo)->check(CLI::Number.active(false));
+    app.validate_positionals();
+    args = {"1", "param1"};
+    run();
+
+    CHECK("1" == options);
+    CHECK("param1" == foo);
+
+    args = {"param1", "1"};
+    CHECK_NOTHROW(run());
+
+    CHECK("1" == options);
+    CHECK("param1" == foo);
+
+    CHECK(nullptr != app.get_option("bar")->get_validator("valbar"));
+}
+
+TEST_CASE_METHOD(TApp, "PositionalNoSpaceLong", "[app]") {
+    std::vector<std::string> options;
+    std::string foo, bar;
+
+    app.add_option("--option", options);
+    app.add_option("foo", foo)->required();
+    app.add_option("bar", bar)->required();
+
+    args = {"--option", "Test", "param1", "param2"};
+    run();
+
+    CHECK(1u == options.size());
+    CHECK("Test" == options.at(0));
+
+    args = {"--option=Test", "param1", "param2"};
+    run();
+
+    CHECK(1u == options.size());
+    CHECK("Test" == options.at(0));
+}
+
+TEST_CASE_METHOD(TApp, "RequiredOptsUnlimited", "[app]") {
+
+    std::vector<std::string> strs;
+    app.add_option("--str", strs)->required();
+
+    args = {"--str"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+
+    args = {"--str", "one", "--str", "two"};
+    run();
+    CHECK(std::vector<std::string>({"one", "two"}) == strs);
+
+    args = {"--str", "one", "two"};
+    run();
+    CHECK(std::vector<std::string>({"one", "two"}) == strs);
+
+    // It's better to feed a hungry option than to feed allow_extras
+    app.allow_extras();
+    run();
+    CHECK(std::vector<std::string>({"one", "two"}) == strs);
+    CHECK(std::vector<std::string>({}) == app.remaining());
+
+    app.allow_extras(false);
+    std::vector<std::string> remain;
+    auto popt = app.add_option("positional", remain);
+    run();
+    CHECK(std::vector<std::string>({"one", "two"}) == strs);
+    CHECK(std::vector<std::string>() == remain);
+
+    args = {"--str", "one", "--", "two"};
+
+    run();
+    CHECK(std::vector<std::string>({"one"}) == strs);
+    CHECK(std::vector<std::string>({"two"}) == remain);
+
+    args = {"one", "--str", "two"};
+
+    run();
+    CHECK(std::vector<std::string>({"two"}) == strs);
+    CHECK(std::vector<std::string>({"one"}) == remain);
+
+    args = {"--str", "one", "two"};
+    popt->required();
+    run();
+    CHECK(std::vector<std::string>({"one"}) == strs);
+    CHECK(std::vector<std::string>({"two"}) == remain);
+}
+
+TEST_CASE_METHOD(TApp, "RequiredOptsUnlimitedShort", "[app]") {
+
+    std::vector<std::string> strs;
+    app.add_option("-s", strs)->required();
+
+    args = {"-s"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+
+    args = {"-s", "one", "-s", "two"};
+    run();
+    CHECK(std::vector<std::string>({"one", "two"}) == strs);
+
+    args = {"-s", "one", "two"};
+    run();
+    CHECK(std::vector<std::string>({"one", "two"}) == strs);
+
+    // It's better to feed a hungry option than to feed allow_extras
+    app.allow_extras();
+    run();
+    CHECK(std::vector<std::string>({"one", "two"}) == strs);
+    CHECK(std::vector<std::string>({}) == app.remaining());
+
+    app.allow_extras(false);
+    std::vector<std::string> remain;
+    app.add_option("positional", remain);
+    run();
+    CHECK(std::vector<std::string>({"one", "two"}) == strs);
+    CHECK(std::vector<std::string>() == remain);
+
+    args = {"-s", "one", "--", "two"};
+
+    run();
+    CHECK(std::vector<std::string>({"one"}) == strs);
+    CHECK(std::vector<std::string>({"two"}) == remain);
+
+    args = {"one", "-s", "two"};
+
+    run();
+    CHECK(std::vector<std::string>({"two"}) == strs);
+    CHECK(std::vector<std::string>({"one"}) == remain);
+}
+
+TEST_CASE_METHOD(TApp, "OptsUnlimitedEnd", "[app]") {
+    std::vector<std::string> strs;
+    app.add_option("-s,--str", strs);
+    app.allow_extras();
+
+    args = {"one", "-s", "two", "three", "--", "four"};
+
+    run();
+
+    CHECK(std::vector<std::string>({"two", "three"}) == strs);
+    CHECK(std::vector<std::string>({"one", "four"}) == app.remaining());
+}
+
+TEST_CASE_METHOD(TApp, "RequireOptPriority", "[app]") {
+
+    std::vector<std::string> strs;
+    app.add_option("--str", strs);
+    std::vector<std::string> remain;
+    app.add_option("positional", remain)->expected(2)->required();
+
+    args = {"--str", "one", "two", "three"};
+    run();
+
+    CHECK(std::vector<std::string>({"one"}) == strs);
+    CHECK(std::vector<std::string>({"two", "three"}) == remain);
+
+    args = {"two", "three", "--str", "one", "four"};
+    run();
+
+    CHECK(std::vector<std::string>({"one", "four"}) == strs);
+    CHECK(std::vector<std::string>({"two", "three"}) == remain);
+}
+
+TEST_CASE_METHOD(TApp, "RequireOptPriorityShort", "[app]") {
+
+    std::vector<std::string> strs;
+    app.add_option("-s", strs)->required();
+    std::vector<std::string> remain;
+    app.add_option("positional", remain)->expected(2)->required();
+
+    args = {"-s", "one", "two", "three"};
+    run();
+
+    CHECK(std::vector<std::string>({"one"}) == strs);
+    CHECK(std::vector<std::string>({"two", "three"}) == remain);
+
+    args = {"two", "three", "-s", "one", "four"};
+    run();
+
+    CHECK(std::vector<std::string>({"one", "four"}) == strs);
+    CHECK(std::vector<std::string>({"two", "three"}) == remain);
+}
+
+TEST_CASE_METHOD(TApp, "NotRequiredExpectedDouble", "[app]") {
+
+    std::vector<std::string> strs;
+    app.add_option("--str", strs)->expected(2);
+
+    args = {"--str", "one"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "NotRequiredExpectedDoubleShort", "[app]") {
+
+    std::vector<std::string> strs;
+    app.add_option("-s", strs)->expected(2);
+
+    args = {"-s", "one"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "RequiredFlags", "[app]") {
+    app.add_flag("-a")->required();
+    app.add_flag("-b")->mandatory();  // Alternate term
+
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"-a"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"-b"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"-a", "-b"};
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "CallbackFlags", "[app]") {
+
+    std::int64_t value{0};
+
+    auto func = [&value](std::int64_t x) { value = x; };
+
+    app.add_flag_function("-v", func);
+
+    run();
+    CHECK(0u == value);
+
+    args = {"-v"};
+    run();
+    CHECK(1u == value);
+
+    args = {"-vv"};
+    run();
+    CHECK(2u == value);
+
+    CHECK_THROWS_AS(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
+}
+
+TEST_CASE_METHOD(TApp, "CallbackFlagsFalse", "[app]") {
+    std::int64_t value = 0;
+
+    auto func = [&value](std::int64_t x) { value = x; };
+
+    app.add_flag_function("-v,-f{false},--val,--fval{false}", func);
+
+    run();
+    CHECK(0 == value);
+
+    args = {"-f"};
+    run();
+    CHECK(-1 == value);
+
+    args = {"-vfv"};
+    run();
+    CHECK(1 == value);
+
+    args = {"--fval"};
+    run();
+    CHECK(-1 == value);
+
+    args = {"--fval=2"};
+    run();
+    CHECK(-2 == value);
+
+    CHECK_THROWS_AS(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
+}
+
+TEST_CASE_METHOD(TApp, "CallbackFlagsFalseShortcut", "[app]") {
+    std::int64_t value = 0;
+
+    auto func = [&value](std::int64_t x) { value = x; };
+
+    app.add_flag_function("-v,!-f,--val,!--fval", func);
+
+    run();
+    CHECK(0 == value);
+
+    args = {"-f"};
+    run();
+    CHECK(-1 == value);
+
+    args = {"-vfv"};
+    run();
+    CHECK(1 == value);
+
+    args = {"--fval"};
+    run();
+    CHECK(-1 == value);
+
+    args = {"--fval=2"};
+    run();
+    CHECK(-2 == value);
+
+    CHECK_THROWS_AS(app.add_flag_function("hi", func), CLI::IncorrectConstruction);
+}
+
+#if __cplusplus >= 201402L || _MSC_VER >= 1900
+TEST_CASE_METHOD(TApp, "CallbackFlagsAuto", "[app]") {
+
+    std::int64_t value{0};
+
+    auto func = [&value](std::int64_t x) { value = x; };
+
+    app.add_flag("-v", func);
+
+    run();
+    CHECK(0u == value);
+
+    args = {"-v"};
+    run();
+    CHECK(1u == value);
+
+    args = {"-vv"};
+    run();
+    CHECK(2u == value);
+
+    CHECK_THROWS_AS(app.add_flag("hi", func), CLI::IncorrectConstruction);
+}
+#endif
+
+TEST_CASE_METHOD(TApp, "Positionals", "[app]") {
+
+    std::string posit1;
+    std::string posit2;
+    app.add_option("posit1", posit1);
+    app.add_option("posit2", posit2);
+
+    args = {"thing1", "thing2"};
+
+    run();
+
+    CHECK(app.count("posit1") == 1u);
+    CHECK(app.count("posit2") == 1u);
+    CHECK(posit1 == "thing1");
+    CHECK(posit2 == "thing2");
+}
+
+TEST_CASE_METHOD(TApp, "ForcedPositional", "[app]") {
+    std::vector<std::string> posit;
+    auto one = app.add_flag("--one");
+    app.add_option("posit", posit);
+
+    args = {"--one", "two", "three"};
+    run();
+    std::vector<std::string> answers1 = {"two", "three"};
+    CHECK(one->count());
+    CHECK(posit == answers1);
+
+    args = {"--", "--one", "two", "three"};
+    std::vector<std::string> answers2 = {"--one", "two", "three"};
+    run();
+
+    CHECK(!one->count());
+    CHECK(posit == answers2);
+}
+
+TEST_CASE_METHOD(TApp, "MixedPositionals", "[app]") {
+
+    int positional_int{0};
+    std::string positional_string;
+    app.add_option("posit1,--posit1", positional_int, "");
+    app.add_option("posit2,--posit2", positional_string, "");
+
+    args = {"--posit2", "thing2", "7"};
+
+    run();
+
+    CHECK(app.count("posit2") == 1u);
+    CHECK(app.count("--posit1") == 1u);
+    CHECK(positional_int == 7);
+    CHECK(positional_string == "thing2");
+}
+
+TEST_CASE_METHOD(TApp, "BigPositional", "[app]") {
+    std::vector<std::string> vec;
+    app.add_option("pos", vec);
+
+    args = {"one"};
+
+    run();
+    CHECK(vec == args);
+
+    args = {"one", "two"};
+    run();
+
+    CHECK(vec == args);
+}
+
+TEST_CASE_METHOD(TApp, "Reset", "[app]") {
+
+    app.add_flag("--simple");
+    double doub{0.0};
+    app.add_option("-d,--double", doub);
+
+    args = {"--simple", "--double", "1.2"};
+
+    run();
+
+    CHECK(app.count("--simple") == 1u);
+    CHECK(app.count("-d") == 1u);
+    CHECK(doub == Approx(1.2));
+
+    app.clear();
+
+    CHECK(app.count("--simple") == 0u);
+    CHECK(app.count("-d") == 0u);
+
+    run();
+
+    CHECK(app.count("--simple") == 1u);
+    CHECK(app.count("-d") == 1u);
+    CHECK(doub == Approx(1.2));
+}
+
+TEST_CASE_METHOD(TApp, "RemoveOption", "[app]") {
+    app.add_flag("--one");
+    auto opt = app.add_flag("--two");
+
+    CHECK(app.remove_option(opt));
+    CHECK(!app.remove_option(opt));
+
+    args = {"--two"};
+
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(TApp, "RemoveNeedsLinks", "[app]") {
+    auto one = app.add_flag("--one");
+    auto two = app.add_flag("--two");
+
+    two->needs(one);
+    one->needs(two);
+
+    CHECK(app.remove_option(one));
+
+    args = {"--two"};
+
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "RemoveExcludesLinks", "[app]") {
+    auto one = app.add_flag("--one");
+    auto two = app.add_flag("--two");
+
+    two->excludes(one);
+    one->excludes(two);
+
+    CHECK(app.remove_option(one));
+
+    args = {"--two"};
+
+    run();  // Mostly hoping it does not crash
+}
+
+TEST_CASE_METHOD(TApp, "FileNotExists", "[app]") {
+    std::string myfile{"TestNonFileNotUsed.txt"};
+    REQUIRE_NOTHROW(CLI::NonexistentPath(myfile));
+
+    std::string filename;
+    auto opt = app.add_option("--file", filename)->check(CLI::NonexistentPath, "path_check");
+    args = {"--file", myfile};
+
+    run();
+    CHECK(filename == myfile);
+
+    bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a'));  // create file
+    CHECK(ok);
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+    // deactivate the check, so it should run now
+    opt->get_validator("path_check")->active(false);
+    CHECK_NOTHROW(run());
+    std::remove(myfile.c_str());
+    CHECK(!CLI::ExistingFile(myfile).empty());
+}
+
+TEST_CASE_METHOD(TApp, "FileExists", "[app]") {
+    std::string myfile{"TestNonFileNotUsed.txt"};
+    CHECK(!CLI::ExistingFile(myfile).empty());
+
+    std::string filename = "Failed";
+    app.add_option("--file", filename)->check(CLI::ExistingFile);
+    args = {"--file", myfile};
+
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a'));  // create file
+    CHECK(ok);
+    run();
+    CHECK(filename == myfile);
+
+    std::remove(myfile.c_str());
+    CHECK(!CLI::ExistingFile(myfile).empty());
+}
+
+TEST_CASE_METHOD(TApp, "NotFileExists", "[app]") {
+    std::string myfile{"TestNonFileNotUsed.txt"};
+    CHECK(!CLI::ExistingFile(myfile).empty());
+
+    std::string filename = "Failed";
+    app.add_option("--file", filename)->check(!CLI::ExistingFile);
+    args = {"--file", myfile};
+
+    CHECK_NOTHROW(run());
+
+    bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a'));  // create file
+    CHECK(ok);
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    std::remove(myfile.c_str());
+    CHECK(!CLI::ExistingFile(myfile).empty());
+}
+
+TEST_CASE_METHOD(TApp, "DefaultedResult", "[app]") {
+    std::string sval = "NA";
+    int ival{0};
+    auto opts = app.add_option("--string", sval)->capture_default_str();
+    auto optv = app.add_option("--val", ival);
+    args = {};
+    run();
+    CHECK("NA" == sval);
+    std::string nString;
+    opts->results(nString);
+    CHECK("NA" == nString);
+    int newIval;
+    // CHECK_THROWS_AS (optv->results(newIval), CLI::ConversionError);
+    optv->default_str("442");
+    optv->results(newIval);
+    CHECK(442 == newIval);
+}
+
+TEST_CASE_METHOD(TApp, "OriginalOrder", "[app]") {
+    std::vector<int> st1;
+    CLI::Option *op1 = app.add_option("-a", st1);
+    std::vector<int> st2;
+    CLI::Option *op2 = app.add_option("-b", st2);
+
+    args = {"-a", "1", "-b", "2", "-a3", "-a", "4"};
+
+    run();
+
+    CHECK(std::vector<int>({1, 3, 4}) == st1);
+    CHECK(std::vector<int>({2}) == st2);
+
+    CHECK(std::vector<CLI::Option *>({op1, op2, op1, op1}) == app.parse_order());
+}
+
+TEST_CASE_METHOD(TApp, "NeedsFlags", "[app]") {
+    CLI::Option *opt = app.add_flag("-s,--string");
+    app.add_flag("--both")->needs(opt);
+
+    run();
+
+    args = {"-s"};
+    run();
+
+    args = {"-s", "--both"};
+    run();
+
+    args = {"--both"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    CHECK_NOTHROW(opt->needs(opt));
+}
+
+TEST_CASE_METHOD(TApp, "ExcludesFlags", "[app]") {
+    CLI::Option *opt = app.add_flag("-s,--string");
+    app.add_flag("--nostr")->excludes(opt);
+
+    run();
+
+    args = {"-s"};
+    run();
+
+    args = {"--nostr"};
+    run();
+
+    args = {"--nostr", "-s"};
+    CHECK_THROWS_AS(run(), CLI::ExcludesError);
+
+    args = {"--string", "--nostr"};
+    CHECK_THROWS_AS(run(), CLI::ExcludesError);
+
+    CHECK_THROWS_AS(opt->excludes(opt), CLI::IncorrectConstruction);
+}
+
+TEST_CASE_METHOD(TApp, "ExcludesMixedFlags", "[app]") {
+    CLI::Option *opt1 = app.add_flag("--opt1");
+    app.add_flag("--opt2");
+    CLI::Option *opt3 = app.add_flag("--opt3");
+    app.add_flag("--no")->excludes(opt1, "--opt2", opt3);
+
+    run();
+
+    args = {"--no"};
+    run();
+
+    args = {"--opt2"};
+    run();
+
+    args = {"--no", "--opt1"};
+    CHECK_THROWS_AS(run(), CLI::ExcludesError);
+
+    args = {"--no", "--opt2"};
+    CHECK_THROWS_AS(run(), CLI::ExcludesError);
+}
+
+TEST_CASE_METHOD(TApp, "NeedsMultiFlags", "[app]") {
+    CLI::Option *opt1 = app.add_flag("--opt1");
+    CLI::Option *opt2 = app.add_flag("--opt2");
+    CLI::Option *opt3 = app.add_flag("--opt3");
+    app.add_flag("--optall")->needs(opt1, opt2, opt3);
+
+    run();
+
+    args = {"--opt1"};
+    run();
+
+    args = {"--opt2"};
+    run();
+
+    args = {"--optall"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"--optall", "--opt1"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"--optall", "--opt2", "--opt1"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"--optall", "--opt1", "--opt2", "--opt3"};
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "NeedsMixedFlags", "[app]") {
+    CLI::Option *opt1 = app.add_flag("--opt1");
+    app.add_flag("--opt2");
+    app.add_flag("--opt3");
+    app.add_flag("--optall")->needs(opt1, "--opt2", "--opt3");
+
+    run();
+
+    args = {"--opt1"};
+    run();
+
+    args = {"--opt2"};
+    run();
+
+    args = {"--optall"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"--optall", "--opt1"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"--optall", "--opt2", "--opt1"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"--optall", "--opt1", "--opt2", "--opt3"};
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "NeedsChainedFlags", "[app]") {
+    CLI::Option *opt1 = app.add_flag("--opt1");
+    CLI::Option *opt2 = app.add_flag("--opt2")->needs(opt1);
+    app.add_flag("--opt3")->needs(opt2);
+
+    run();
+
+    args = {"--opt1"};
+    run();
+
+    args = {"--opt2"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"--opt3"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"--opt3", "--opt2"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"--opt3", "--opt1"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"--opt2", "--opt1"};
+    run();
+
+    args = {"--opt1", "--opt2", "--opt3"};
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "Env", "[app]") {
+
+    put_env("CLI11_TEST_ENV_TMP", "2");
+
+    int val{1};
+    CLI::Option *vopt = app.add_option("--tmp", val)->envname("CLI11_TEST_ENV_TMP");
+
+    run();
+
+    CHECK(val == 2);
+    CHECK(vopt->count() == 1u);
+
+    vopt->required();
+    run();
+
+    unset_env("CLI11_TEST_ENV_TMP");
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+}
+
+// curiously check if an environmental only option works
+TEST_CASE_METHOD(TApp, "EnvOnly", "[app]") {
+
+    put_env("CLI11_TEST_ENV_TMP", "2");
+
+    int val{1};
+    CLI::Option *vopt = app.add_option("", val)->envname("CLI11_TEST_ENV_TMP");
+
+    run();
+
+    CHECK(val == 2);
+    CHECK(vopt->count() == 1u);
+
+    vopt->required();
+    run();
+
+    unset_env("CLI11_TEST_ENV_TMP");
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+}
+
+TEST_CASE_METHOD(TApp, "RangeInt", "[app]") {
+    int x{0};
+    app.add_option("--one", x)->check(CLI::Range(3, 6));
+
+    args = {"--one=1"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--one=7"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--one=3"};
+    run();
+
+    args = {"--one=5"};
+    run();
+
+    args = {"--one=6"};
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "RangeDouble", "[app]") {
+
+    double x{0.0};
+    /// Note that this must be a double in Range, too
+    app.add_option("--one", x)->check(CLI::Range(3.0, 6.0));
+
+    args = {"--one=1"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--one=7"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--one=3"};
+    run();
+
+    args = {"--one=5"};
+    run();
+
+    args = {"--one=6"};
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "typeCheck", "[app]") {
+
+    /// Note that this must be a double in Range, too
+    app.add_option("--one")->check(CLI::TypeValidator<unsigned int>());
+
+    args = {"--one=1"};
+    CHECK_NOTHROW(run());
+
+    args = {"--one=-7"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--one=error"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--one=4.568"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+// Check to make sure programmatic access to left over is available
+TEST_CASE_METHOD(TApp, "AllowExtras", "[app]") {
+
+    app.allow_extras();
+
+    bool val{true};
+    app.add_flag("-f", val);
+
+    args = {"-x", "-f"};
+
+    REQUIRE_NOTHROW(run());
+    CHECK(val);
+    CHECK(std::vector<std::string>({"-x"}) == app.remaining());
+}
+
+TEST_CASE_METHOD(TApp, "AllowExtrasOrder", "[app]") {
+
+    app.allow_extras();
+
+    args = {"-x", "-f"};
+    REQUIRE_NOTHROW(run());
+    CHECK(std::vector<std::string>({"-x", "-f"}) == app.remaining());
+
+    std::vector<std::string> left_over = app.remaining();
+    app.parse(left_over);
+    CHECK(std::vector<std::string>({"-f", "-x"}) == app.remaining());
+    CHECK(left_over == app.remaining_for_passthrough());
+}
+
+TEST_CASE_METHOD(TApp, "AllowExtrasCascade", "[app]") {
+
+    app.allow_extras();
+
+    args = {"-x", "45", "-f", "27"};
+    REQUIRE_NOTHROW(run());
+    CHECK(std::vector<std::string>({"-x", "45", "-f", "27"}) == app.remaining());
+
+    std::vector<std::string> left_over = app.remaining_for_passthrough();
+
+    CLI::App capp{"cascade_program"};
+    int v1 = 0;
+    int v2 = 0;
+    capp.add_option("-x", v1);
+    capp.add_option("-f", v2);
+
+    capp.parse(left_over);
+    CHECK(45 == v1);
+    CHECK(27 == v2);
+}
+// makes sure the error throws on the rValue version of the parse
+TEST_CASE_METHOD(TApp, "ExtrasErrorRvalueParse", "[app]") {
+
+    args = {"-x", "45", "-f", "27"};
+    CHECK_THROWS_AS(app.parse(std::vector<std::string>({"-x", "45", "-f", "27"})), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(TApp, "AllowExtrasCascadeDirect", "[app]") {
+
+    app.allow_extras();
+
+    args = {"-x", "45", "-f", "27"};
+    REQUIRE_NOTHROW(run());
+    CHECK(std::vector<std::string>({"-x", "45", "-f", "27"}) == app.remaining());
+
+    CLI::App capp{"cascade_program"};
+    int v1{0};
+    int v2{0};
+    capp.add_option("-x", v1);
+    capp.add_option("-f", v2);
+
+    capp.parse(app.remaining_for_passthrough());
+    CHECK(45 == v1);
+    CHECK(27 == v2);
+}
+
+TEST_CASE_METHOD(TApp, "AllowExtrasArgModify", "[app]") {
+
+    int v1{0};
+    int v2{0};
+    app.allow_extras();
+    app.add_option("-f", v2);
+    args = {"27", "-f", "45", "-x"};
+    app.parse(args);
+    CHECK(std::vector<std::string>({"45", "-x"}) == args);
+
+    CLI::App capp{"cascade_program"};
+
+    capp.add_option("-x", v1);
+
+    capp.parse(args);
+    CHECK(45 == v1);
+    CHECK(27 == v2);
+}
+
+// Test horrible error
+TEST_CASE_METHOD(TApp, "CheckShortFail", "[app]") {
+    args = {"--two"};
+
+    CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::SHORT), CLI::HorribleError);
+}
+
+// Test horrible error
+TEST_CASE_METHOD(TApp, "CheckLongFail", "[app]") {
+    args = {"-t"};
+
+    CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::LONG), CLI::HorribleError);
+}
+
+// Test horrible error
+TEST_CASE_METHOD(TApp, "CheckWindowsFail", "[app]") {
+    args = {"-t"};
+
+    CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::WINDOWS_STYLE),
+                    CLI::HorribleError);
+}
+
+// Test horrible error
+TEST_CASE_METHOD(TApp, "CheckOtherFail", "[app]") {
+    args = {"-t"};
+
+    CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::NONE), CLI::HorribleError);
+}
+
+// Test horrible error
+TEST_CASE_METHOD(TApp, "CheckSubcomFail", "[app]") {
+    args = {"subcom"};
+
+    CHECK_THROWS_AS(CLI::detail::AppFriend::parse_subcommand(&app, args), CLI::HorribleError);
+}
+
+TEST_CASE_METHOD(TApp, "FallthroughParentFail", "[app]") {
+    CHECK_THROWS_AS(CLI::detail::AppFriend::get_fallthrough_parent(&app), CLI::HorribleError);
+}
+
+TEST_CASE_METHOD(TApp, "FallthroughParents", "[app]") {
+    auto sub = app.add_subcommand("test");
+    CHECK(&app == CLI::detail::AppFriend::get_fallthrough_parent(sub));
+
+    auto ssub = sub->add_subcommand("sub2");
+    CHECK(sub == CLI::detail::AppFriend::get_fallthrough_parent(ssub));
+
+    auto og1 = app.add_option_group("g1");
+    auto og2 = og1->add_option_group("g2");
+    auto og3 = og2->add_option_group("g3");
+    CHECK(&app == CLI::detail::AppFriend::get_fallthrough_parent(og3));
+
+    auto ogb1 = sub->add_option_group("g1");
+    auto ogb2 = ogb1->add_option_group("g2");
+    auto ogb3 = ogb2->add_option_group("g3");
+    CHECK(sub == CLI::detail::AppFriend::get_fallthrough_parent(ogb3));
+
+    ogb2->name("groupb");
+    CHECK(ogb2 == CLI::detail::AppFriend::get_fallthrough_parent(ogb3));
+}
+
+TEST_CASE_METHOD(TApp, "OptionWithDefaults", "[app]") {
+    int someint{2};
+    app.add_option("-a", someint)->capture_default_str();
+
+    args = {"-a1", "-a2"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+// Added to test ->transform
+TEST_CASE_METHOD(TApp, "OrderedModifyingTransforms", "[app]") {
+    std::vector<std::string> val;
+    auto m = app.add_option("-m", val);
+    m->transform([](std::string x) { return x + "1"; });
+    m->transform([](std::string x) { return x + "2"; });
+
+    args = {"-mone", "-mtwo"};
+
+    run();
+
+    CHECK(std::vector<std::string>({"one21", "two21"}) == val);
+}
+
+TEST_CASE_METHOD(TApp, "ThrowingTransform", "[app]") {
+    std::string val;
+    auto m = app.add_option("-m,--mess", val);
+    m->transform([](std::string) -> std::string { throw CLI::ValidationError("My Message"); });
+
+    REQUIRE_NOTHROW(run());
+
+    args = {"-mone"};
+
+    REQUIRE_THROWS_AS(run(), CLI::ValidationError);
+
+    try {
+        run();
+    } catch(const CLI::ValidationError &e) {
+        CHECK(std::string("--mess: My Message") == e.what());
+    }
+}
+
+// This was added to make running a simple function on each item easier
+TEST_CASE_METHOD(TApp, "EachItem", "[app]") {
+
+    std::vector<std::string> results;
+    std::vector<std::string> dummy;
+
+    auto opt = app.add_option("--vec", dummy);
+
+    opt->each([&results](std::string item) { results.push_back(item); });
+
+    args = {"--vec", "one", "two", "three"};
+
+    run();
+
+    CHECK(dummy == results);
+}
+
+// #128
+TEST_CASE_METHOD(TApp, "RepeatingMultiArgumentOptions", "[app]") {
+    std::vector<std::string> entries;
+    app.add_option("--entry", entries, "set a key and value")->type_name("KEY VALUE")->type_size(-2);
+
+    args = {"--entry", "key1", "value1", "--entry", "key2", "value2"};
+    REQUIRE_NOTHROW(run());
+    CHECK(std::vector<std::string>({"key1", "value1", "key2", "value2"}) == entries);
+
+    args.pop_back();
+    REQUIRE_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+// #122
+TEST_CASE_METHOD(TApp, "EmptyOptionEach", "[app]") {
+    std::string q;
+    app.add_option("--each")->each([&q](std::string s) { q = s; });
+
+    args = {"--each", "that"};
+    run();
+
+    CHECK("that" == q);
+}
+
+// #122
+TEST_CASE_METHOD(TApp, "EmptyOptionFail", "[app]") {
+    std::string q;
+    app.add_option("--each");
+
+    args = {"--each", "that"};
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "BeforeRequirements", "[app]") {
+    app.add_flag_function("-a", [](std::int64_t) { throw CLI::Success(); });
+    app.add_flag_function("-b", [](std::int64_t) { throw CLI::CallForHelp(); });
+
+    args = {"extra"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+
+    args = {"-a", "extra"};
+    CHECK_THROWS_AS(run(), CLI::Success);
+
+    args = {"-b", "extra"};
+    CHECK_THROWS_AS(run(), CLI::CallForHelp);
+
+    // These run in definition order.
+    args = {"-a", "-b", "extra"};
+    CHECK_THROWS_AS(run(), CLI::Success);
+
+    // Currently, the original order is not preserved when calling callbacks
+    // args = {"-b", "-a", "extra"};
+    // CHECK_THROWS_AS (run(), CLI::CallForHelp);
+}
+
+// #209
+TEST_CASE_METHOD(TApp, "CustomUserSepParse", "[app]") {
+
+    std::vector<int> vals{1, 2, 3};
+    args = {"--idx", "1,2,3"};
+    auto opt = app.add_option("--idx", vals)->delimiter(',');
+    run();
+    CHECK(std::vector<int>({1, 2, 3}) == vals);
+    std::vector<int> vals2;
+    // check that the results vector gets the results in the same way
+    opt->results(vals2);
+    CHECK(vals == vals2);
+
+    app.remove_option(opt);
+
+    app.add_option("--idx", vals)->delimiter(',')->capture_default_str();
+    run();
+    CHECK(std::vector<int>({1, 2, 3}) == vals);
+}
+
+// #209
+TEST_CASE_METHOD(TApp, "DefaultUserSepParse", "[app]") {
+
+    std::vector<std::string> vals;
+    args = {"--idx", "1 2 3", "4 5 6"};
+    auto opt = app.add_option("--idx", vals, "");
+    run();
+    CHECK(std::vector<std::string>({"1 2 3", "4 5 6"}) == vals);
+    opt->delimiter(',');
+    run();
+    CHECK(std::vector<std::string>({"1 2 3", "4 5 6"}) == vals);
+}
+
+// #209
+TEST_CASE_METHOD(TApp, "BadUserSepParse", "[app]") {
+
+    std::vector<int> vals;
+    app.add_option("--idx", vals);
+
+    args = {"--idx", "1,2,3"};
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+// #209
+TEST_CASE_METHOD(TApp, "CustomUserSepParse2", "[app]") {
+
+    std::vector<int> vals{1, 2, 3};
+    args = {"--idx", "1,2,"};
+    auto opt = app.add_option("--idx", vals)->delimiter(',');
+    run();
+    CHECK(std::vector<int>({1, 2}) == vals);
+
+    app.remove_option(opt);
+
+    app.add_option("--idx", vals, "")->delimiter(',')->capture_default_str();
+    run();
+    CHECK(std::vector<int>({1, 2}) == vals);
+}
+
+TEST_CASE_METHOD(TApp, "CustomUserSepParseFunction", "[app]") {
+
+    std::vector<int> vals{1, 2, 3};
+    args = {"--idx", "1,2,3"};
+    app.add_option_function<std::vector<int>>("--idx", [&vals](std::vector<int> v) { vals = std::move(v); })
+        ->delimiter(',');
+    run();
+    CHECK(std::vector<int>({1, 2, 3}) == vals);
+}
+
+// delimiter removal
+TEST_CASE_METHOD(TApp, "CustomUserSepParseToggle", "[app]") {
+
+    std::vector<std::string> vals;
+    args = {"--idx", "1,2,3"};
+    auto opt = app.add_option("--idx", vals)->delimiter(',');
+    run();
+    CHECK(std::vector<std::string>({"1", "2", "3"}) == vals);
+    opt->delimiter('\0');
+    run();
+    CHECK(std::vector<std::string>({"1,2,3"}) == vals);
+    opt->delimiter(',');
+    run();
+    CHECK(std::vector<std::string>({"1", "2", "3"}) == vals);
+}
+
+// #209
+TEST_CASE_METHOD(TApp, "CustomUserSepParse3", "[app]") {
+
+    std::vector<int> vals = {1, 2, 3};
+    args = {"--idx",
+            "1",
+            ","
+            "2"};
+    auto opt = app.add_option("--idx", vals)->delimiter(',');
+    run();
+    CHECK(std::vector<int>({1, 2}) == vals);
+    app.remove_option(opt);
+
+    app.add_option("--idx", vals)->delimiter(',');
+    run();
+    CHECK(std::vector<int>({1, 2}) == vals);
+}
+
+// #209
+TEST_CASE_METHOD(TApp, "CustomUserSepParse4", "[app]") {
+
+    std::vector<int> vals;
+    args = {"--idx", "1,    2"};
+    auto opt = app.add_option("--idx", vals)->delimiter(',')->capture_default_str();
+    run();
+    CHECK(std::vector<int>({1, 2}) == vals);
+
+    app.remove_option(opt);
+
+    app.add_option("--idx", vals)->delimiter(',');
+    run();
+    CHECK(std::vector<int>({1, 2}) == vals);
+}
+
+// #218
+TEST_CASE_METHOD(TApp, "CustomUserSepParse5", "[app]") {
+
+    std::vector<std::string> bar;
+    args = {"this", "is", "a", "test"};
+    auto opt = app.add_option("bar", bar, "bar");
+    run();
+    CHECK(std::vector<std::string>({"this", "is", "a", "test"}) == bar);
+
+    app.remove_option(opt);
+    args = {"this", "is", "a", "test"};
+    app.add_option("bar", bar, "bar")->capture_default_str();
+    run();
+    CHECK(std::vector<std::string>({"this", "is", "a", "test"}) == bar);
+}
+
+// #218
+TEST_CASE_METHOD(TApp, "logFormSingleDash", "[app]") {
+    bool verbose{false};
+    bool veryverbose{false};
+    bool veryveryverbose{false};
+    app.name("testargs");
+    app.allow_extras();
+    args = {"-v", "-vv", "-vvv"};
+    app.final_callback([&]() {
+        auto rem = app.remaining();
+        for(auto &arg : rem) {
+            if(arg == "-v") {
+                verbose = true;
+            }
+            if(arg == "-vv") {
+                veryverbose = true;
+            }
+            if(arg == "-vvv") {
+                veryveryverbose = true;
+            }
+        }
+    });
+    run();
+    CHECK(app.remaining().size() == 3U);
+    CHECK(verbose);
+    CHECK(veryverbose);
+    CHECK(veryveryverbose);
+}
diff --git a/external/CLI11/tests/BoostOptionTypeTest.cpp b/external/CLI11/tests/BoostOptionTypeTest.cpp
new file mode 100644
index 0000000..7ed096c
--- /dev/null
+++ b/external/CLI11/tests/BoostOptionTypeTest.cpp
@@ -0,0 +1,126 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <boost/container/slist.hpp>
+#include <boost/container/small_vector.hpp>
+#include <boost/container/stable_vector.hpp>
+#include <boost/container/static_vector.hpp>
+#include <boost/container/vector.hpp>
+#include <string>
+#include <vector>
+
+using namespace boost::container;
+
+TEMPLATE_TEST_CASE("Boost container single",
+                   "[boost][optional]",
+                   (small_vector<int, 2>),
+                   (small_vector<int, 3>),
+                   flat_set<int>,
+                   stable_vector<int>,
+                   slist<int>) {
+    TApp tapp;
+    TestType cv;
+    CLI::Option *opt = tapp.app.add_option("-v", cv);
+
+    tapp.args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
+    tapp.run();
+    CHECK(tapp.app.count("-v") == 4u);
+    CHECK(cv.size() == 4u);
+    opt->check(CLI::PositiveNumber.application_index(0));
+    opt->check((!CLI::PositiveNumber).application_index(1));
+    CHECK_NOTHROW(tapp.run());
+    CHECK(cv.size() == 4u);
+    // v[3] would be negative
+    opt->check(CLI::PositiveNumber.application_index(3));
+    CHECK_THROWS_AS(tapp.run(), CLI::ValidationError);
+}
+
+using isp = std::pair<int, std::string>;
+
+TEMPLATE_TEST_CASE("Boost container pair",
+                   "[boost][optional]",
+                   stable_vector<isp>,
+                   (small_vector<isp, 2>),
+                   flat_set<isp>,
+                   slist<isp>,
+                   vector<isp>,
+                   (flat_map<int, std::string>)) {
+
+    TApp tapp;
+    TestType cv;
+
+    tapp.app.add_option("--dict", cv);
+
+    tapp.args = {"--dict", "1", "str1", "--dict", "3", "str3"};
+
+    tapp.run();
+    CHECK(2u == cv.size());
+
+    tapp.args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
+    tapp.run();
+    CHECK(3u == cv.size());
+}
+
+using tup_obj = std::tuple<int, std::string, double>;
+
+TEMPLATE_TEST_CASE("Boost container tuple",
+                   "[boost][optional]",
+                   (small_vector<tup_obj, 3>),
+                   stable_vector<tup_obj>,
+                   flat_set<tup_obj>,
+                   slist<tup_obj>) {
+    TApp tapp;
+    TestType cv;
+
+    tapp.app.add_option("--dict", cv);
+
+    tapp.args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
+
+    tapp.run();
+    CHECK(2u == cv.size());
+
+    tapp.args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
+    tapp.run();
+    CHECK(3u == cv.size());
+}
+
+using icontainer1 = vector<int>;
+using icontainer2 = flat_set<int>;
+using icontainer3 = slist<int>;
+
+TEMPLATE_TEST_CASE("Boost container container",
+                   "[boost][optional]",
+                   std::vector<icontainer1>,
+                   slist<icontainer1>,
+                   flat_set<icontainer1>,
+                   (small_vector<icontainer1, 2>),
+                   std::vector<icontainer2>,
+                   slist<icontainer2>,
+                   flat_set<icontainer2>,
+                   stable_vector<icontainer2>,
+                   (static_vector<icontainer2, 10>),
+                   slist<icontainer3>,
+                   flat_set<icontainer3>,
+                   (static_vector<icontainer3, 10>)) {
+
+    TApp tapp;
+    TestType cv;
+
+    tapp.app.add_option("--dict", cv);
+
+    tapp.args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
+
+    tapp.run();
+    CHECK(2u == cv.size());
+
+    tapp.args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
+                 "3",      "3", "3", "3", "3",      "3", "3", "3",      "3", "-3"};
+    tapp.run();
+    CHECK(4u == cv.size());
+}
diff --git a/external/CLI11/tests/CMakeLists.txt b/external/CLI11/tests/CMakeLists.txt
new file mode 100644
index 0000000..80c4f6a
--- /dev/null
+++ b/external/CLI11/tests/CMakeLists.txt
@@ -0,0 +1,221 @@
+list(APPEND CMAKE_MODULE_PATH "${CLI11_SOURCE_DIR}/cmake")
+
+if(CLI11_SANITIZERS)
+  message(STATUS "Using arsenm/sanitizers-cmake")
+  FetchContent_Declare(
+    sanitizers
+    GIT_REPOSITORY https://github.com/arsenm/sanitizers-cmake.git
+    GIT_SHALLOW 1
+    GIT_TAG 99e159e)
+
+  FetchContent_GetProperties(sanitizers)
+
+  if(NOT sanitizers_POPULATED)
+    FetchContent_Populate(sanitizers)
+  endif()
+
+  list(APPEND CMAKE_MODULE_PATH "${sanitizers_SOURCE_DIR}/cmake")
+
+  find_package(Sanitizers)
+  if(SANITIZE_ADDRESS)
+    message(STATUS "You might want to use \"${ASan_WRAPPER}\" to run your program")
+  endif()
+else()
+  macro(add_sanitizers)
+
+  endmacro()
+endif()
+
+# Add boost to test boost::optional (currently explicitly requested)"
+option(CLI11_BOOST "Turn on boost test (currently may fail with Boost 1.70)" OFF)
+if(CLI11_BOOST)
+  find_package(Boost 1.61 REQUIRED)
+endif()
+set(boost-optional-def $<$<BOOL:${Boost_FOUND}>:CLI11_BOOST_OPTIONAL>)
+
+set(CLI11_TESTS
+    HelpersTest
+    ConfigFileTest
+    OptionTypeTest
+    SimpleTest
+    AppTest
+    SetTest
+    TransformTest
+    CreationTest
+    SubcommandTest
+    HelpTest
+    FormatterTest
+    NewParseTest
+    OptionalTest
+    DeprecatedTest
+    StringParseTest
+    ComplexTypeTest
+    TrueFalseTest
+    OptionGroupTest)
+
+if(WIN32)
+  list(APPEND CLI11_TESTS WindowsTest)
+endif()
+
+if(Boost_FOUND)
+  list(APPEND CLI11_TESTS BoostOptionTypeTest)
+endif()
+
+set(CLI11_MULTIONLY_TESTS TimerTest)
+
+add_library(catch_main main.cpp catch.hpp)
+target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
+
+find_package(Catch2 CONFIG)
+
+if(Catch2_FOUND)
+  if(NOT TARGET Catch2::Catch2)
+    message(FATAL_ERROR "Found Catch2 at ${Catch2_DIR} but targets are missing.")
+  endif()
+  message(STATUS "Found Catch2")
+  target_link_libraries(catch_main PUBLIC Catch2::Catch2)
+else()
+  message(STATUS "Downloading Catch2")
+
+  # FetchContent would be better, but requires newer CMake.
+  file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/catch2")
+  set(url https://github.com/philsquared/Catch/releases/download/v2.13.7/catch.hpp)
+  file(
+    DOWNLOAD ${url} "${CMAKE_CURRENT_BINARY_DIR}/catch2/catch.hpp"
+    STATUS status
+    EXPECTED_HASH SHA256=ea379c4a3cb5799027b1eb451163dff065a3d641aaba23bf4e24ee6b536bd9bc)
+  list(GET status 0 error)
+  if(error)
+    message(FATAL_ERROR "Could not download ${url}, and Catch2 not found on your system.")
+  endif()
+  target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
+endif()
+
+# Target must already exist
+macro(add_catch_test TESTNAME)
+  target_link_libraries(${TESTNAME} PUBLIC catch_main)
+
+  add_test(${TESTNAME} ${TESTNAME})
+  set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests")
+  if(CLI11_FORCE_LIBCXX)
+    set_property(
+      TARGET ${T}
+      APPEND_STRING
+      PROPERTY LINK_FLAGS -stdlib=libc++)
+  endif()
+endmacro()
+
+foreach(T IN LISTS CLI11_TESTS)
+  if(CLI11_CUDA_TESTS)
+    set_property(SOURCE ${T}.cpp PROPERTY LANGUAGE CUDA)
+  endif()
+  add_executable(${T} ${T}.cpp ${CLI11_headers})
+  add_sanitizers(${T})
+  if(NOT CLI11_CUDA_TESTS)
+    target_link_libraries(${T} PRIVATE CLI11_warnings)
+  endif()
+  target_link_libraries(${T} PRIVATE CLI11)
+  add_catch_test(${T})
+
+  if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
+    add_executable(${T}_Single ${T}.cpp)
+    target_link_libraries(${T}_Single PRIVATE CLI11_SINGLE)
+    add_catch_test(${T}_Single)
+    set_property(TARGET ${T}_Single PROPERTY FOLDER "Tests Single File")
+  endif()
+endforeach()
+
+foreach(T IN LISTS CLI11_MULTIONLY_TESTS)
+  add_executable(${T} ${T}.cpp ${CLI11_headers})
+  add_sanitizers(${T})
+  target_link_libraries(${T} PUBLIC CLI11)
+  add_catch_test(${T})
+endforeach()
+
+# Add -Wno-deprecated-declarations to DeprecatedTest
+set(no-deprecated-declarations $<$<CXX_COMPILER_ID:MSVC>:/wd4996>
+                               $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wno-deprecated-declarations>)
+target_compile_options(DeprecatedTest PRIVATE ${no-deprecated-declarations})
+if(TARGET DeprecatedTest_Single)
+  target_compile_options(DeprecatedTest_Single PRIVATE ${no-deprecated-declarations})
+endif()
+
+# Link test (build error if inlines missing)
+add_library(link_test_1 link_test_1.cpp)
+target_link_libraries(link_test_1 PUBLIC CLI11)
+set_target_properties(link_test_1 PROPERTIES FOLDER "Tests")
+add_executable(link_test_2 link_test_2.cpp)
+target_link_libraries(link_test_2 PUBLIC CLI11 link_test_1)
+add_catch_test(link_test_2)
+if(CLI11_FORCE_LIBCXX)
+  set_property(
+    TARGET link_test_1
+    APPEND_STRING
+    PROPERTY LINK_FLAGS -stdlib=libc++)
+  set_property(
+    TARGET link_test_2
+    APPEND_STRING
+    PROPERTY LINK_FLAGS -stdlib=libc++)
+endif()
+
+# Add informational printout
+add_executable(informational informational.cpp)
+target_link_libraries(informational PUBLIC CLI11)
+if(CLI11_FORCE_LIBCXX)
+  set_property(
+    TARGET informational
+    APPEND_STRING
+    PROPERTY LINK_FLAGS -stdlib=libc++)
+endif()
+
+# Force this to be in a standard location so CTest can find it
+set_target_properties(
+  informational
+  PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
+             RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}"
+             RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}"
+             RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}"
+             RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}")
+
+# Adding this printout to CTest
+file(WRITE "${PROJECT_BINARY_DIR}/CTestCustom.cmake"
+     "set(CTEST_CUSTOM_PRE_TEST \"${CMAKE_BINARY_DIR}/informational\")")
+
+target_compile_definitions(informational PRIVATE ${boost-optional-def})
+target_compile_definitions(OptionalTest PRIVATE ${boost-optional-def})
+
+if(TARGET Boost::boost)
+  message(STATUS "including boost target")
+  target_link_libraries(informational PRIVATE Boost::boost)
+  target_link_libraries(OptionalTest PRIVATE Boost::boost)
+  target_link_libraries(BoostOptionTypeTest PRIVATE Boost::boost)
+  if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
+    target_link_libraries(OptionalTest_Single PRIVATE Boost::boost)
+    target_link_libraries(BoostOptionTypeTest_Single PRIVATE Boost::boost)
+  endif()
+  message(STATUS "Boost libs=${Boost_INCLUDE_DIRS}")
+elseif(BOOST_FOUND)
+  message(STATUS "no boost target")
+  target_include_directories(informational PRIVATE ${Boost_INCLUDE_DIRS})
+  target_include_directories(OptionalTest PRIVATE ${Boost_INCLUDE_DIRS})
+  target_include_directories(BoostOptionTypeTest PRIVATE ${Boost_INCLUDE_DIRS})
+  if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS)
+    target_include_directories(OptionalTest_Single PRIVATE ${Boost_INCLUDE_DIRS})
+    target_include_directories(BoostOptionTypeTest_Single PRIVATE ${Boost_INCLUDE_DIRS})
+  endif()
+  message(STATUS "Boost libs=${Boost_INCLUDE_DIRS}")
+else()
+  message(STATUS "Boost not found, not adding boost tests")
+endif()
+
+if(CMAKE_BUILD_TYPE STREQUAL Coverage)
+  include(CodeCoverage)
+  setup_target_for_coverage(
+    NAME
+    CLI11_coverage
+    EXECUTABLE
+    ctest
+    DEPENDENCIES
+    ${CLI11_TESTS}
+    ${CLI11_MULTIONLY_TESTS})
+endif()
diff --git a/external/CLI11/tests/ComplexTypeTest.cpp b/external/CLI11/tests/ComplexTypeTest.cpp
new file mode 100644
index 0000000..4806c54
--- /dev/null
+++ b/external/CLI11/tests/ComplexTypeTest.cpp
@@ -0,0 +1,191 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+
+#include <complex>
+#include <cstdint>
+
+using Catch::Matchers::Contains;
+
+using cx = std::complex<double>;
+
+CLI::Option *
+add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) {
+    CLI::callback_t fun = [&variable](CLI::results_t res) {
+        double x, y;
+        bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y);
+        if(worked)
+            variable = cx(x, y);
+        return worked;
+    };
+
+    CLI::Option *opt = app.add_option(name, fun, description, defaulted);
+    opt->type_name("COMPLEX")->type_size(2);
+    if(defaulted) {
+        std::stringstream out;
+        out << variable;
+        opt->default_str(out.str());
+    }
+    return opt;
+}
+
+TEST_CASE_METHOD(TApp, "AddingComplexParser", "[complex]") {
+
+    cx comp{0, 0};
+    add_option(app, "-c,--complex", comp);
+    args = {"-c", "1.5", "2.5"};
+
+    run();
+
+    CHECK(comp.real() == Approx(1.5));
+    CHECK(comp.imag() == Approx(2.5));
+}
+
+TEST_CASE_METHOD(TApp, "DefaultedComplex", "[complex]") {
+
+    cx comp{1, 2};
+    add_option(app, "-c,--complex", comp, "", true);
+    args = {"-c", "4", "3"};
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("1"));
+    CHECK_THAT(help, Contains("2"));
+
+    CHECK(comp.real() == Approx(1));
+    CHECK(comp.imag() == Approx(2));
+
+    run();
+
+    CHECK(comp.real() == Approx(4));
+    CHECK(comp.imag() == Approx(3));
+}
+
+// an example of custom complex number converter that can be used to add new parsing options
+#if defined(__has_include)
+#if __has_include(<regex>)
+// an example of custom converter that can be used to add new parsing options
+#define HAS_REGEX_INCLUDE
+#endif
+#endif
+
+#ifdef HAS_REGEX_INCLUDE
+// Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would
+// fail.  And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to
+// simplify compilation and prevent a much more complicated #if expression
+#include <regex>
+namespace CLI {
+namespace detail {
+
+// On MSVC and possibly some other new compilers this can be a free standing function without the template
+// specialization but this is compiler dependent
+template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) {
+    // regular expression to handle complex numbers of various formats
+    static const std::regex creg(
+        R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
+
+    std::smatch m;
+    double x{0.0}, y{0.0};
+    bool worked;
+    std::regex_search(input, m, creg);
+    if(m.size() == 9) {
+        worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y);
+        if(worked) {
+            if(*m[5].first == '-') {
+                y = -y;
+            }
+        }
+    } else {
+        if((input.back() == 'j') || (input.back() == 'i')) {
+            auto strval = input.substr(0, input.size() - 1);
+            CLI::detail::trim(strval);
+            worked = CLI::detail::lexical_cast(strval, y);
+        } else {
+            std::string ival = input;
+            CLI::detail::trim(ival);
+            worked = CLI::detail::lexical_cast(ival, x);
+        }
+    }
+    if(worked) {
+        output = cx{x, y};
+    }
+    return worked;
+}
+}  // namespace detail
+}  // namespace CLI
+
+TEST_CASE_METHOD(TApp, "AddingComplexParserDetail", "[complex]") {
+
+    bool skip_tests = false;
+    try {  // check if the library actually supports regex,  it is possible to link against a non working regex in the
+           // standard library
+        std::smatch m;
+        std::string input = "1.5+2.5j";
+        static const std::regex creg(
+            R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)");
+
+        auto rsearch = std::regex_search(input, m, creg);
+        if(!rsearch) {
+            skip_tests = true;
+        } else {
+            CHECK(9u == m.size());
+        }
+
+    } catch(...) {
+        skip_tests = true;
+    }
+    static_assert(CLI::detail::is_complex<cx>::value, "complex should register as complex in this situation");
+    if(!skip_tests) {
+        cx comp{0, 0};
+
+        app.add_option("-c,--complex", comp, "add a complex number option");
+        args = {"-c", "1.5+2.5j"};
+
+        run();
+
+        CHECK(comp.real() == Approx(1.5));
+        CHECK(comp.imag() == Approx(2.5));
+        args = {"-c", "1.5-2.5j"};
+
+        run();
+
+        CHECK(comp.real() == Approx(1.5));
+        CHECK(comp.imag() == Approx(-2.5));
+    }
+}
+#endif
+// defining a new complex class
+class complex_new {
+  public:
+    complex_new() = default;
+    complex_new(double v1, double v2) : val1_{v1}, val2_{v2} {};
+    double real() { return val1_; }
+    double imag() { return val2_; }
+
+  private:
+    double val1_{0.0};
+    double val2_{0.0};
+};
+
+TEST_CASE_METHOD(TApp, "newComplex", "[complex]") {
+    complex_new cval;
+    static_assert(CLI::detail::is_complex<complex_new>::value, "complex new does not register as a complex type");
+    static_assert(CLI::detail::classify_object<complex_new>::value == CLI::detail::object_category::complex_number,
+                  "complex new does not result in complex number categorization");
+    app.add_option("-c,--complex", cval, "add a complex number option");
+    args = {"-c", "1.5+2.5j"};
+
+    run();
+
+    CHECK(cval.real() == Approx(1.5));
+    CHECK(cval.imag() == Approx(2.5));
+    args = {"-c", "1.5-2.5j"};
+
+    run();
+
+    CHECK(cval.real() == Approx(1.5));
+    CHECK(cval.imag() == Approx(-2.5));
+}
diff --git a/external/CLI11/tests/ConfigFileTest.cpp b/external/CLI11/tests/ConfigFileTest.cpp
new file mode 100644
index 0000000..61c066e
--- /dev/null
+++ b/external/CLI11/tests/ConfigFileTest.cpp
@@ -0,0 +1,2758 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+
+#include <cstdio>
+#include <sstream>
+
+using Catch::Matchers::Contains;
+
+TEST_CASE("StringBased: convert_arg_for_ini", "[config]") {
+
+    CHECK("\"\"" == CLI::detail::convert_arg_for_ini(std::string{}));
+
+    CHECK("true" == CLI::detail::convert_arg_for_ini("true"));
+
+    CHECK("nan" == CLI::detail::convert_arg_for_ini("nan"));
+
+    CHECK("\"happy hippo\"" == CLI::detail::convert_arg_for_ini("happy hippo"));
+
+    CHECK("47" == CLI::detail::convert_arg_for_ini("47"));
+
+    CHECK("47.365225" == CLI::detail::convert_arg_for_ini("47.365225"));
+
+    CHECK("+3.28e-25" == CLI::detail::convert_arg_for_ini("+3.28e-25"));
+    CHECK("-22E14" == CLI::detail::convert_arg_for_ini("-22E14"));
+
+    CHECK("'a'" == CLI::detail::convert_arg_for_ini("a"));
+    // hex
+    CHECK("0x5461FAED" == CLI::detail::convert_arg_for_ini("0x5461FAED"));
+    // hex fail
+    CHECK("\"0x5461FAEG\"" == CLI::detail::convert_arg_for_ini("0x5461FAEG"));
+
+    // octal
+    CHECK("0o546123567" == CLI::detail::convert_arg_for_ini("0o546123567"));
+    // octal fail
+    CHECK("\"0o546123587\"" == CLI::detail::convert_arg_for_ini("0o546123587"));
+
+    // binary
+    CHECK("0b01101110010" == CLI::detail::convert_arg_for_ini("0b01101110010"));
+    // binary fail
+    CHECK("\"0b01102110010\"" == CLI::detail::convert_arg_for_ini("0b01102110010"));
+}
+
+TEST_CASE("StringBased: IniJoin", "[config]") {
+    std::vector<std::string> items = {"one", "two", "three four"};
+    std::string result = "\"one\" \"two\" \"three four\"";
+
+    CHECK(result == CLI::detail::ini_join(items, ' ', '\0', '\0'));
+
+    result = "[\"one\", \"two\", \"three four\"]";
+
+    CHECK(result == CLI::detail::ini_join(items));
+
+    result = "{\"one\"; \"two\"; \"three four\"}";
+
+    CHECK(result == CLI::detail::ini_join(items, ';', '{', '}'));
+}
+
+TEST_CASE("StringBased: First", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "one=three\n";
+    ofile << "two=four\n";
+
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    CHECK(output.size() == 2u);
+    CHECK(output.at(0).name == "one");
+    CHECK(output.at(0).inputs.size() == 1u);
+    CHECK(output.at(0).inputs.at(0) == "three");
+    CHECK(output.at(1).name == "two");
+    CHECK(output.at(1).inputs.size() == 1u);
+    CHECK(output.at(1).inputs.at(0) == "four");
+}
+
+TEST_CASE("StringBased: FirstWithComments", "[config]") {
+    std::stringstream ofile;
+
+    ofile << ";this is a comment\n";
+    ofile << "one=three\n";
+    ofile << "two=four\n";
+    ofile << "; and another one\n";
+
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    CHECK(output.size() == 2u);
+    CHECK(output.at(0).name == "one");
+    CHECK(output.at(0).inputs.size() == 1u);
+    CHECK(output.at(0).inputs.at(0) == "three");
+    CHECK(output.at(1).name == "two");
+    CHECK(output.at(1).inputs.size() == 1u);
+    CHECK(output.at(1).inputs.at(0) == "four");
+}
+
+TEST_CASE("StringBased: Quotes", "[config]") {
+    std::stringstream ofile;
+
+    ofile << R"(one = "three")" << '\n';
+    ofile << R"(two = 'four')" << '\n';
+    ofile << R"(five = "six and seven")" << '\n';
+
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    CHECK(output.size() == 3u);
+    CHECK(output.at(0).name == "one");
+    CHECK(output.at(0).inputs.size() == 1u);
+    CHECK(output.at(0).inputs.at(0) == "three");
+    CHECK(output.at(1).name == "two");
+    CHECK(output.at(1).inputs.size() == 1u);
+    CHECK(output.at(1).inputs.at(0) == "four");
+    CHECK(output.at(2).name == "five");
+    CHECK(output.at(2).inputs.size() == 1u);
+    CHECK(output.at(2).inputs.at(0) == "six and seven");
+}
+
+TEST_CASE("StringBased: Vector", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "one = three\n";
+    ofile << "two = four\n";
+    ofile << "five = six and seven\n";
+
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    CHECK(output.size() == 3u);
+    CHECK(output.at(0).name == "one");
+    CHECK(output.at(0).inputs.size() == 1u);
+    CHECK(output.at(0).inputs.at(0) == "three");
+    CHECK(output.at(1).name == "two");
+    CHECK(output.at(1).inputs.size() == 1u);
+    CHECK(output.at(1).inputs.at(0) == "four");
+    CHECK(output.at(2).name == "five");
+    CHECK(output.at(2).inputs.size() == 3u);
+    CHECK(output.at(2).inputs.at(0) == "six");
+    CHECK(output.at(2).inputs.at(1) == "and");
+    CHECK(output.at(2).inputs.at(2) == "seven");
+}
+
+TEST_CASE("StringBased: TomlVector", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "one = [three]\n";
+    ofile << "two = [four]\n";
+    ofile << "five = [six, and, seven]\n";
+    ofile << "eight = [nine, \n"
+             "ten, eleven,     twelve    \n"
+             "]\n";
+    ofile << "one_more = [one, \n"
+             "two,     three  ]    \n";
+
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    CHECK(output.size() == 5u);
+    CHECK(output.at(0).name == "one");
+    CHECK(output.at(0).inputs.size() == 1u);
+    CHECK(output.at(0).inputs.at(0) == "three");
+    CHECK(output.at(1).name == "two");
+    CHECK(output.at(1).inputs.size() == 1u);
+    CHECK(output.at(1).inputs.at(0) == "four");
+    CHECK(output.at(2).name == "five");
+    CHECK(output.at(2).inputs.size() == 3u);
+    CHECK(output.at(2).inputs.at(0) == "six");
+    CHECK(output.at(2).inputs.at(1) == "and");
+    CHECK(output.at(2).inputs.at(2) == "seven");
+    CHECK(output.at(3).name == "eight");
+    CHECK(output.at(3).inputs.size() == 4u);
+    CHECK(output.at(3).inputs.at(0) == "nine");
+    CHECK(output.at(3).inputs.at(1) == "ten");
+    CHECK(output.at(3).inputs.at(2) == "eleven");
+    CHECK(output.at(3).inputs.at(3) == "twelve");
+    CHECK(output.at(4).name == "one_more");
+    CHECK(output.at(4).inputs.size() == 3u);
+    CHECK(output.at(4).inputs.at(0) == "one");
+    CHECK(output.at(4).inputs.at(1) == "two");
+    CHECK(output.at(4).inputs.at(2) == "three");
+}
+
+TEST_CASE("StringBased: Spaces", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "one = three\n";
+    ofile << "two = four";
+
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    CHECK(output.size() == 2u);
+    CHECK(output.at(0).name == "one");
+    CHECK(output.at(0).inputs.size() == 1u);
+    CHECK(output.at(0).inputs.at(0) == "three");
+    CHECK(output.at(1).name == "two");
+    CHECK(output.at(1).inputs.size() == 1u);
+    CHECK(output.at(1).inputs.at(0) == "four");
+}
+
+TEST_CASE("StringBased: Sections", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "one=three\n";
+    ofile << "[second]\n";
+    ofile << "  two=four\n";
+
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    CHECK(output.size() == 4u);
+    CHECK(output.at(0).name == "one");
+    CHECK(output.at(0).inputs.size() == 1u);
+    CHECK(output.at(0).inputs.at(0) == "three");
+    CHECK(output.at(2).name == "two");
+    CHECK(output.at(2).parents.at(0) == "second");
+    CHECK(output.at(2).inputs.size() == 1u);
+    CHECK(output.at(2).inputs.at(0) == "four");
+    CHECK(output.at(2).fullname() == "second.two");
+}
+
+TEST_CASE("StringBased: SpacesSections", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "one=three\n\n";
+    ofile << "[second]   \n";
+    ofile << "   \n";
+    ofile << "  two=four\n";
+
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    CHECK(output.size() == 4u);
+    CHECK(output.at(0).name == "one");
+    CHECK(output.at(0).inputs.size() == 1u);
+    CHECK(output.at(0).inputs.at(0) == "three");
+    CHECK(output.at(1).parents.at(0) == "second");
+    CHECK(output.at(1).name == "++");
+    CHECK(output.at(2).name == "two");
+    CHECK(output.at(2).parents.size() == 1u);
+    CHECK(output.at(2).parents.at(0) == "second");
+    CHECK(output.at(2).inputs.size() == 1u);
+    CHECK(output.at(2).inputs.at(0) == "four");
+    CHECK(output.at(3).parents.at(0) == "second");
+    CHECK(output.at(3).name == "--");
+}
+
+// check function to make sure that open sections match close sections
+bool checkSections(const std::vector<CLI::ConfigItem> &output) {
+    std::set<std::string> open;
+    for(auto &ci : output) {
+        if(ci.name == "++") {
+            auto nm = ci.fullname();
+            nm.pop_back();
+            nm.pop_back();
+            auto rv = open.insert(nm);
+            if(!rv.second) {
+                return false;
+            }
+        }
+        if(ci.name == "--") {
+            auto nm = ci.fullname();
+            nm.pop_back();
+            nm.pop_back();
+            auto rv = open.erase(nm);
+            if(rv != 1U) {
+                return false;
+            }
+        }
+    }
+    return open.empty();
+}
+TEST_CASE("StringBased: Layers", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "simple = true\n\n";
+    ofile << "[other]\n";
+    ofile << "[other.sub2]\n";
+    ofile << "[other.sub2.sub-level2]\n";
+    ofile << "[other.sub2.sub-level2.sub-level3]\n";
+    ofile << "absolute_newest = true\n";
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    // 2 flags and 4 openings and 4 closings
+    CHECK(output.size() == 10u);
+    CHECK(checkSections(output));
+}
+
+TEST_CASE("StringBased: LayersSkip", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "simple = true\n\n";
+    ofile << "[other.sub2]\n";
+    ofile << "[other.sub2.sub-level2.sub-level3]\n";
+    ofile << "absolute_newest = true\n";
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    // 2 flags and 4 openings and 4 closings
+    CHECK(output.size() == 10u);
+    CHECK(checkSections(output));
+}
+
+TEST_CASE("StringBased: LayersSkipOrdered", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "simple = true\n\n";
+    ofile << "[other.sub2.sub-level2.sub-level3]\n";
+    ofile << "[other.sub2]\n";
+    ofile << "absolute_newest = true\n";
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    // 2 flags and 4 openings and 4 closings
+    CHECK(output.size() == 12u);
+    CHECK(checkSections(output));
+}
+
+TEST_CASE("StringBased: LayersChange", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "simple = true\n\n";
+    ofile << "[other.sub2]\n";
+    ofile << "[other.sub3]\n";
+    ofile << "absolute_newest = true\n";
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    // 2 flags and 3 openings and 3 closings
+    CHECK(output.size() == 8u);
+    CHECK(checkSections(output));
+}
+
+TEST_CASE("StringBased: Layers2LevelChange", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "simple = true\n\n";
+    ofile << "[other.sub2.cmd]\n";
+    ofile << "[other.sub3.cmd]\n";
+    ofile << "absolute_newest = true\n";
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    // 2 flags and 5 openings and 5 closings
+    CHECK(output.size() == 12u);
+    CHECK(checkSections(output));
+}
+
+TEST_CASE("StringBased: Layers3LevelChange", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "[other.sub2.subsub.cmd]\n";
+    ofile << "[other.sub3.subsub.cmd]\n";
+    ofile << "absolute_newest = true\n";
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    // 1 flags and 7 openings and 7 closings
+    CHECK(output.size() == 15u);
+    CHECK(checkSections(output));
+}
+
+TEST_CASE("StringBased: newSegment", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "[other.sub2.subsub.cmd]\n";
+    ofile << "flag = true\n";
+    ofile << "[another]\n";
+    ofile << "absolute_newest = true\n";
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    // 2 flags and 5 openings and 5 closings
+    CHECK(output.size() == 12u);
+    CHECK(checkSections(output));
+}
+
+TEST_CASE("StringBased: LayersDirect", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "simple = true\n\n";
+    ofile << "[other.sub2.sub-level2.sub-level3]\n";
+    ofile << "absolute_newest = true\n";
+
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    // 2 flags and 4 openings and 4 closings
+    CHECK(output.size() == 10u);
+    CHECK(checkSections(output));
+}
+
+TEST_CASE("StringBased: LayersComplex", "[config]") {
+    std::stringstream ofile;
+
+    ofile << "simple = true\n\n";
+    ofile << "[other.sub2.sub-level2.sub-level3]\n";
+    ofile << "absolute_newest = true\n";
+    ofile << "[other.sub2.sub-level2]\n";
+    ofile << "still_newer = true\n";
+    ofile << "[other.sub2]\n";
+    ofile << "newest = true\n";
+
+    ofile.seekg(0, std::ios::beg);
+
+    std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile);
+
+    // 4 flags and 6 openings and 6 closings
+    CHECK(output.size() == 16u);
+    CHECK(checkSections(output));
+}
+
+TEST_CASE("StringBased: file_error", "[config]") {
+    CHECK_THROWS_AS(CLI::ConfigINI().from_file("nonexist_file"), CLI::FileError);
+}
+
+TEST_CASE_METHOD(TApp, "IniNotRequired", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=99" << std::endl;
+        out << "three=3" << std::endl;
+    }
+
+    int one = 0, two = 0, three = 0;
+    app.add_option("--one", one);
+    app.add_option("--two", two);
+    app.add_option("--three", three);
+
+    args = {"--one=1"};
+
+    run();
+
+    CHECK(one == 1);
+    CHECK(two == 99);
+    CHECK(three == 3);
+
+    one = two = three = 0;
+    args = {"--one=1", "--two=2"};
+
+    run();
+
+    CHECK(one == 1);
+    CHECK(two == 2);
+    CHECK(three == 3);
+    CHECK("TestIniTmp.ini" == app["--config"]->as<std::string>());
+}
+
+TEST_CASE_METHOD(TApp, "IniSuccessOnUnknownOption", "[config]") {
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+    app.allow_config_extras(true);
+
+    {
+        std::ofstream out{tmpini};
+        out << "three=3" << std::endl;
+        out << "two=99" << std::endl;
+    }
+
+    int two{0};
+    app.add_option("--two", two);
+    run();
+    CHECK(two == 99);
+}
+
+TEST_CASE_METHOD(TApp, "IniGetRemainingOption", "[config]") {
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+    app.allow_config_extras(true);
+
+    std::string ExtraOption = "three";
+    std::string ExtraOptionValue = "3";
+    {
+        std::ofstream out{tmpini};
+        out << ExtraOption << "=" << ExtraOptionValue << std::endl;
+        out << "two=99" << std::endl;
+    }
+
+    int two{0};
+    app.add_option("--two", two);
+    REQUIRE_NOTHROW(run());
+    std::vector<std::string> ExpectedRemaining = {ExtraOption};
+    CHECK(ExpectedRemaining == app.remaining());
+}
+
+TEST_CASE_METHOD(TApp, "IniGetNoRemaining", "[config]") {
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+    app.allow_config_extras(true);
+
+    {
+        std::ofstream out{tmpini};
+        out << "two=99" << std::endl;
+    }
+
+    int two{0};
+    app.add_option("--two", two);
+    REQUIRE_NOTHROW(run());
+    CHECK(0u == app.remaining().size());
+}
+
+TEST_CASE_METHOD(TApp, "IniRequiredNoDefault", "[config]") {
+
+    app.set_config("--config")->required();
+
+    int two{0};
+    app.add_option("--two", two);
+    REQUIRE_THROWS_AS(run(), CLI::FileError);
+    // test to make sure help still gets called correctly
+    // GitHub issue #533 https://github.com/CLIUtils/CLI11/issues/553
+    args = {"--help"};
+    REQUIRE_THROWS_AS(run(), CLI::CallForHelp);
+}
+
+TEST_CASE_METHOD(TApp, "IniNotRequiredNoDefault", "[config]") {
+
+    app.set_config("--config");
+
+    int two{0};
+    app.add_option("--two", two);
+    REQUIRE_NOTHROW(run());
+}
+
+/// Define a class for testing purposes that does bad things
+class EvilConfig : public CLI::Config {
+  public:
+    EvilConfig() = default;
+    virtual std::string to_config(const CLI::App *, bool, bool, std::string) const { throw CLI::FileError("evil"); }
+
+    virtual std::vector<CLI::ConfigItem> from_config(std::istream &) const { throw CLI::FileError("evil"); }
+};
+
+TEST_CASE_METHOD(TApp, "IniRequiredbadConfigurator", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=99" << std::endl;
+        out << "three=3" << std::endl;
+    }
+
+    app.set_config("--config", tmpini)->required();
+    app.config_formatter(std::make_shared<EvilConfig>());
+    int two{0};
+    app.add_option("--two", two);
+    REQUIRE_THROWS_AS(run(), CLI::FileError);
+}
+
+TEST_CASE_METHOD(TApp, "IniNotRequiredbadConfigurator", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=99" << std::endl;
+        out << "three=3" << std::endl;
+    }
+
+    app.set_config("--config", tmpini);
+    app.config_formatter(std::make_shared<EvilConfig>());
+    int two{0};
+    app.add_option("--two", two);
+    REQUIRE_NOTHROW(run());
+}
+
+TEST_CASE_METHOD(TApp, "IniNotRequiredNotDefault", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+    TempFile tmpini2{"TestIniTmp2.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=99" << std::endl;
+        out << "three=3" << std::endl;
+    }
+
+    {
+        std::ofstream out{tmpini2};
+        out << "[default]" << std::endl;
+        out << "two=98" << std::endl;
+        out << "three=4" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0};
+    app.add_option("--one", one);
+    app.add_option("--two", two);
+    app.add_option("--three", three);
+
+    run();
+    CHECK(tmpini.c_str() == app["--config"]->as<std::string>());
+    CHECK(two == 99);
+    CHECK(three == 3);
+
+    args = {"--config", tmpini2};
+    run();
+
+    CHECK(two == 98);
+    CHECK(three == 4);
+    CHECK(tmpini2.c_str() == app.get_config_ptr()->as<std::string>());
+}
+
+TEST_CASE_METHOD(TApp, "MultiConfig", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+    TempFile tmpini2{"TestIniTmp2.ini"};
+
+    app.set_config("--config")->expected(1, 3);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=99" << std::endl;
+        out << "three=3" << std::endl;
+    }
+
+    {
+        std::ofstream out{tmpini2};
+        out << "[default]" << std::endl;
+        out << "one=55" << std::endl;
+        out << "three=4" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0};
+    app.add_option("--one", one);
+    app.add_option("--two", two);
+    app.add_option("--three", three);
+
+    args = {"--config", tmpini2, "--config", tmpini};
+    run();
+
+    CHECK(two == 99);
+    CHECK(three == 3);
+    CHECK(one == 55);
+
+    args = {"--config", tmpini, "--config", tmpini2};
+    run();
+
+    CHECK(two == 99);
+    CHECK(three == 4);
+    CHECK(one == 55);
+}
+
+TEST_CASE_METHOD(TApp, "MultiConfig_single", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+    TempFile tmpini2{"TestIniTmp2.ini"};
+
+    app.set_config("--config")->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=99" << std::endl;
+        out << "three=3" << std::endl;
+    }
+
+    {
+        std::ofstream out{tmpini2};
+        out << "[default]" << std::endl;
+        out << "one=55" << std::endl;
+        out << "three=4" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0};
+    app.add_option("--one", one);
+    app.add_option("--two", two);
+    app.add_option("--three", three);
+
+    args = {"--config", tmpini2, "--config", tmpini};
+    run();
+
+    CHECK(two == 99);
+    CHECK(three == 3);
+    CHECK(one == 0);
+
+    two = 0;
+    args = {"--config", tmpini, "--config", tmpini2};
+    run();
+
+    CHECK(two == 0);
+    CHECK(three == 4);
+    CHECK(one == 55);
+}
+
+TEST_CASE_METHOD(TApp, "IniRequiredNotFound", "[config]") {
+
+    std::string noini = "TestIniNotExist.ini";
+    app.set_config("--config", noini, "", true);
+
+    CHECK_THROWS_AS(run(), CLI::FileError);
+}
+
+TEST_CASE_METHOD(TApp, "IniNotRequiredPassedNotFound", "[config]") {
+
+    std::string noini = "TestIniNotExist.ini";
+    app.set_config("--config", "", "", false);
+
+    args = {"--config", noini};
+    CHECK_THROWS_AS(run(), CLI::FileError);
+}
+
+TEST_CASE_METHOD(TApp, "IniOverwrite", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=99" << std::endl;
+    }
+
+    std::string orig = "filename_not_exist.ini";
+    std::string next = "TestIniTmp.ini";
+    app.set_config("--config", orig);
+    // Make sure this can be overwritten
+    app.set_config("--conf", next);
+    int two{7};
+    app.add_option("--two", two);
+
+    run();
+
+    CHECK(two == 99);
+}
+
+TEST_CASE_METHOD(TApp, "IniRequired", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini, "", true);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=99" << std::endl;
+        out << "three=3" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0};
+    app.add_option("--one", one)->required();
+    app.add_option("--two", two)->required();
+    app.add_option("--three", three)->required();
+
+    args = {"--one=1"};
+
+    run();
+    CHECK(1 == one);
+    CHECK(99 == two);
+    CHECK(3 == three);
+
+    one = two = three = 0;
+    args = {"--one=1", "--two=2"};
+
+    CHECK_NOTHROW(run());
+    CHECK(1 == one);
+    CHECK(2 == two);
+    CHECK(3 == three);
+
+    args = {};
+
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--two=2"};
+
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+}
+
+TEST_CASE_METHOD(TApp, "IniInlineComment", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini, "", true);
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=99 ; this is a two" << std::endl;
+        out << "three=3; this is a three" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0};
+    app.add_option("--one", one)->required();
+    app.add_option("--two", two)->required();
+    app.add_option("--three", three)->required();
+
+    args = {"--one=1"};
+
+    run();
+    CHECK(1 == one);
+    CHECK(99 == two);
+    CHECK(3 == three);
+
+    one = two = three = 0;
+    args = {"--one=1", "--two=2"};
+
+    CHECK_NOTHROW(run());
+    CHECK(1 == one);
+    CHECK(2 == two);
+    CHECK(3 == three);
+
+    args = {};
+
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--two=2"};
+
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+}
+
+TEST_CASE_METHOD(TApp, "TomlInlineComment", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini, "", true);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=99 # this is a two" << std::endl;
+        out << "three=3# this is a three" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0};
+    app.add_option("--one", one)->required();
+    app.add_option("--two", two)->required();
+    app.add_option("--three", three)->required();
+
+    args = {"--one=1"};
+
+    run();
+    CHECK(1 == one);
+    CHECK(99 == two);
+    CHECK(3 == three);
+
+    one = two = three = 0;
+    args = {"--one=1", "--two=2"};
+
+    CHECK_NOTHROW(run());
+    CHECK(1 == one);
+    CHECK(2 == two);
+    CHECK(3 == three);
+
+    args = {};
+
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--two=2"};
+
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+}
+
+TEST_CASE_METHOD(TApp, "ConfigModifiers", "[config]") {
+
+    app.set_config("--config", "test.ini", "", true);
+
+    auto cfgptr = app.get_config_formatter_base();
+
+    cfgptr->section("test");
+    CHECK(cfgptr->section() == "test");
+
+    CHECK(cfgptr->sectionRef() == "test");
+    auto &sref = cfgptr->sectionRef();
+    sref = "this";
+    CHECK(cfgptr->section() == "this");
+
+    cfgptr->index(5);
+    CHECK(cfgptr->index() == 5);
+
+    CHECK(cfgptr->indexRef() == 5);
+    auto &iref = cfgptr->indexRef();
+    iref = 7;
+    CHECK(cfgptr->index() == 7);
+}
+
+TEST_CASE_METHOD(TApp, "IniVector", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=2 3" << std::endl;
+        out << "three=1 2 3" << std::endl;
+    }
+
+    std::vector<int> two, three;
+    app.add_option("--two", two)->expected(2)->required();
+    app.add_option("--three", three)->required();
+
+    run();
+
+    CHECK(two == std::vector<int>({2, 3}));
+    CHECK(three == std::vector<int>({1, 2, 3}));
+}
+TEST_CASE_METHOD(TApp, "TOMLVector", "[config]") {
+
+    TempFile tmptoml{"TestTomlTmp.toml"};
+
+    app.set_config("--config", tmptoml);
+
+    {
+        std::ofstream out{tmptoml};
+        out << "#this is a comment line\n";
+        out << "[default]\n";
+        out << "two=[2,3]\n";
+        out << "three=[1,2,3]\n";
+    }
+
+    std::vector<int> two, three;
+    app.add_option("--two", two)->expected(2)->required();
+    app.add_option("--three", three)->required();
+
+    run();
+
+    CHECK(two == std::vector<int>({2, 3}));
+    CHECK(three == std::vector<int>({1, 2, 3}));
+}
+
+TEST_CASE_METHOD(TApp, "ColonValueSep", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "#this is a comment line\n";
+        out << "[default]\n";
+        out << "two:2\n";
+        out << "three:3\n";
+    }
+
+    int two{0}, three{0};
+    app.add_option("--two", two);
+    app.add_option("--three", three);
+
+    app.get_config_formatter_base()->valueSeparator(':');
+
+    run();
+
+    CHECK(two == 2);
+    CHECK(three == 3);
+}
+
+TEST_CASE_METHOD(TApp, "TOMLVectordirect", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    app.config_formatter(std::make_shared<CLI::ConfigTOML>());
+
+    {
+        std::ofstream out{tmpini};
+        out << "#this is a comment line\n";
+        out << "[default]\n";
+        out << "two=[2,3]\n";
+        out << "three=[1,2,3]\n";
+    }
+
+    std::vector<int> two, three;
+    app.add_option("--two", two)->expected(2)->required();
+    app.add_option("--three", three)->required();
+
+    run();
+
+    CHECK(two == std::vector<int>({2, 3}));
+    CHECK(three == std::vector<int>({1, 2, 3}));
+}
+
+TEST_CASE_METHOD(TApp, "TOMLStringVector", "[config]") {
+
+    TempFile tmptoml{"TestTomlTmp.toml"};
+
+    app.set_config("--config", tmptoml);
+
+    {
+        std::ofstream out{tmptoml};
+        out << "#this is a comment line\n";
+        out << "[default]\n";
+        out << "two=[\"2\",\"3\"]\n";
+        out << "three=[\"1\",\"2\",\"3\"]\n";
+    }
+
+    std::vector<std::string> two, three;
+    app.add_option("--two", two)->required();
+    app.add_option("--three", three)->required();
+
+    run();
+
+    CHECK(two == std::vector<std::string>({"2", "3"}));
+    CHECK(three == std::vector<std::string>({"1", "2", "3"}));
+}
+
+TEST_CASE_METHOD(TApp, "IniVectorCsep", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "#this is a comment line\n";
+        out << "[default]\n";
+        out << "two=[2,3]\n";
+        out << "three=1,2,3\n";
+    }
+
+    std::vector<int> two, three;
+    app.add_option("--two", two)->expected(2)->required();
+    app.add_option("--three", three)->required();
+
+    run();
+
+    CHECK(two == std::vector<int>({2, 3}));
+    CHECK(three == std::vector<int>({1, 2, 3}));
+}
+
+TEST_CASE_METHOD(TApp, "IniVectorMultiple", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "#this is a comment line\n";
+        out << "[default]\n";
+        out << "two=2\n";
+        out << "two=3\n";
+        out << "three=1\n";
+        out << "three=2\n";
+        out << "three=3\n";
+    }
+
+    std::vector<int> two, three;
+    app.add_option("--two", two)->expected(2)->required();
+    app.add_option("--three", three)->required();
+
+    run();
+
+    CHECK(two == std::vector<int>({2, 3}));
+    CHECK(three == std::vector<int>({1, 2, 3}));
+}
+
+TEST_CASE_METHOD(TApp, "IniLayered", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+        out << "[subcom]" << std::endl;
+        out << "val=2" << std::endl;
+        out << "subsubcom.val=3" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0};
+    app.add_option("--val", one);
+    auto subcom = app.add_subcommand("subcom");
+    subcom->add_option("--val", two);
+    auto subsubcom = subcom->add_subcommand("subsubcom");
+    subsubcom->add_option("--val", three);
+
+    run();
+
+    CHECK(one == 1);
+    CHECK(two == 2);
+    CHECK(three == 3);
+
+    CHECK(0U == subcom->count());
+    CHECK(!*subcom);
+}
+
+TEST_CASE_METHOD(TApp, "IniLayeredStream", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+        out << "[subcom]" << std::endl;
+        out << "val=2" << std::endl;
+        out << "subsubcom.val=3" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0};
+    app.add_option("--val", one);
+    auto subcom = app.add_subcommand("subcom");
+    subcom->add_option("--val", two);
+    auto subsubcom = subcom->add_subcommand("subsubcom");
+    subsubcom->add_option("--val", three);
+
+    std::ifstream in{tmpini};
+    app.parse_from_stream(in);
+
+    CHECK(one == 1);
+    CHECK(two == 2);
+    CHECK(three == 3);
+
+    CHECK(0U == subcom->count());
+    CHECK(!*subcom);
+}
+
+TEST_CASE_METHOD(TApp, "IniLayeredDotSection", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+        out << "[subcom]" << std::endl;
+        out << "val=2" << std::endl;
+        out << "[subcom.subsubcom]" << std::endl;
+        out << "val=3" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0};
+    app.add_option("--val", one);
+    auto subcom = app.add_subcommand("subcom");
+    subcom->add_option("--val", two);
+    auto subsubcom = subcom->add_subcommand("subsubcom");
+    subsubcom->add_option("--val", three);
+
+    run();
+
+    CHECK(one == 1);
+    CHECK(two == 2);
+    CHECK(three == 3);
+
+    CHECK(0U == subcom->count());
+    CHECK(!*subcom);
+
+    three = 0;
+    // check maxlayers
+    app.get_config_formatter_base()->maxLayers(1);
+    run();
+    CHECK(three == 0);
+}
+
+TEST_CASE_METHOD(TApp, "IniLayeredCustomSectionSeparator", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+        out << "[subcom]" << std::endl;
+        out << "val=2" << std::endl;
+        out << "[subcom|subsubcom]" << std::endl;
+        out << "val=3" << std::endl;
+    }
+    app.get_config_formatter_base()->parentSeparator('|');
+    int one{0}, two{0}, three{0};
+    app.add_option("--val", one);
+    auto subcom = app.add_subcommand("subcom");
+    subcom->add_option("--val", two);
+    auto subsubcom = subcom->add_subcommand("subsubcom");
+    subsubcom->add_option("--val", three);
+
+    run();
+
+    CHECK(one == 1);
+    CHECK(two == 2);
+    CHECK(three == 3);
+
+    CHECK(0U == subcom->count());
+    CHECK(!*subcom);
+}
+
+TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+        out << "[subcom]" << std::endl;
+        out << "val=2" << std::endl;
+        out << "subsubcom.val=3" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0};
+    app.add_option("--val", one);
+    auto subcom = app.add_subcommand("subcom");
+    subcom->configurable();
+    subcom->add_option("--val", two);
+    auto subsubcom = subcom->add_subcommand("subsubcom");
+    subsubcom->add_option("--val", three);
+
+    run();
+
+    CHECK(one == 1);
+    CHECK(two == 2);
+    CHECK(three == 3);
+
+    CHECK(1U == subcom->count());
+    CHECK(*subcom);
+    CHECK(app.got_subcommand(subcom));
+}
+
+TEST_CASE_METHOD(TApp, "IniSubcommandConfigurablePreParse", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+        out << "[subcom]" << std::endl;
+        out << "val=2" << std::endl;
+        out << "subsubcom.val=3" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0}, four{0};
+    app.add_option("--val", one);
+    auto subcom = app.add_subcommand("subcom");
+    auto subcom2 = app.add_subcommand("subcom2");
+    subcom->configurable();
+    std::vector<std::size_t> parse_c;
+    subcom->preparse_callback([&parse_c](std::size_t cnt) { parse_c.push_back(cnt); });
+    subcom->add_option("--val", two);
+    subcom2->add_option("--val", four);
+    subcom2->preparse_callback([&parse_c](std::size_t cnt) { parse_c.push_back(cnt + 2623); });
+    auto subsubcom = subcom->add_subcommand("subsubcom");
+    subsubcom->add_option("--val", three);
+
+    run();
+
+    CHECK(one == 1);
+    CHECK(two == 2);
+    CHECK(three == 3);
+    CHECK(four == 0);
+
+    CHECK(1U == parse_c.size());
+    CHECK(2U == parse_c[0]);
+
+    CHECK(0U == subcom2->count());
+}
+
+TEST_CASE_METHOD(TApp, "IniSection", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+    app.get_config_formatter_base()->section("config");
+
+    {
+        std::ofstream out{tmpini};
+        out << "[config]" << std::endl;
+        out << "val=2" << std::endl;
+        out << "subsubcom.val=3" << std::endl;
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+    }
+
+    int val{0};
+    app.add_option("--val", val);
+
+    run();
+
+    CHECK(2 == val);
+}
+
+TEST_CASE_METHOD(TApp, "IniSection2", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+    app.get_config_formatter_base()->section("config");
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+        out << "[config]" << std::endl;
+        out << "val=2" << std::endl;
+        out << "subsubcom.val=3" << std::endl;
+    }
+
+    int val{0};
+    app.add_option("--val", val);
+
+    run();
+
+    CHECK(2 == val);
+}
+
+TEST_CASE_METHOD(TApp, "jsonLikeParsing", "[config]") {
+
+    TempFile tmpjson{"TestJsonTmp.json"};
+
+    app.set_config("--config", tmpjson);
+    app.get_config_formatter_base()->valueSeparator(':');
+
+    {
+        std::ofstream out{tmpjson};
+        out << "{" << std::endl;
+        out << "\"val\":1," << std::endl;
+        out << "\"val2\":\"test\"," << std::endl;
+        out << "\"flag\":true" << std::endl;
+        out << "}" << std::endl;
+    }
+
+    int val{0};
+    app.add_option("--val", val);
+    std::string val2{0};
+    app.add_option("--val2", val2);
+
+    bool flag{false};
+    app.add_flag("--flag", flag);
+
+    run();
+
+    CHECK(1 == val);
+    CHECK(val2 == "test");
+    CHECK(flag);
+}
+
+TEST_CASE_METHOD(TApp, "TomlSectionNumber", "[config]") {
+
+    TempFile tmpini{"TestTomlTmp.toml"};
+
+    app.set_config("--config", tmpini);
+    app.get_config_formatter_base()->section("config")->index(0);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+        out << "[[config]]" << std::endl;
+        out << "val=2" << std::endl;
+        out << "subsubcom.val=3" << std::endl;
+        out << "[[config]]" << std::endl;
+        out << "val=4" << std::endl;
+        out << "subsubcom.val=3" << std::endl;
+        out << "[[config]]" << std::endl;
+        out << "val=6" << std::endl;
+        out << "subsubcom.val=3" << std::endl;
+    }
+
+    int val{0};
+    app.add_option("--val", val);
+
+    run();
+
+    CHECK(2 == val);
+
+    auto &index = app.get_config_formatter_base()->indexRef();
+    index = 1;
+    run();
+
+    CHECK(4 == val);
+
+    index = -1;
+    run();
+    // Take the first section in this case
+    CHECK(2 == val);
+    index = 2;
+    run();
+
+    CHECK(6 == val);
+}
+
+TEST_CASE_METHOD(TApp, "IniSubcommandConfigurableParseComplete", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+        out << "[subcom]" << std::endl;
+        out << "val=2" << std::endl;
+        out << "[subcom.subsubcom]" << std::endl;
+        out << "val=3" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0}, four{0};
+    app.add_option("--val", one);
+    auto subcom = app.add_subcommand("subcom");
+    auto subcom2 = app.add_subcommand("subcom2");
+    subcom->configurable();
+    std::vector<std::size_t> parse_c;
+    subcom->parse_complete_callback([&parse_c]() { parse_c.push_back(58); });
+    subcom->add_option("--val", two);
+    subcom2->add_option("--val", four);
+    subcom2->parse_complete_callback([&parse_c]() { parse_c.push_back(2623); });
+    auto subsubcom = subcom->add_subcommand("subsubcom");
+    // configurable should be inherited
+    subsubcom->parse_complete_callback([&parse_c]() { parse_c.push_back(68); });
+    subsubcom->add_option("--val", three);
+
+    run();
+
+    CHECK(one == 1);
+    CHECK(two == 2);
+    CHECK(three == 3);
+    CHECK(four == 0);
+
+    REQUIRE(2u == parse_c.size());
+    CHECK(68U == parse_c[0]);
+    CHECK(58U == parse_c[1]);
+    CHECK(1u == subsubcom->count());
+    CHECK(0u == subcom2->count());
+}
+
+TEST_CASE_METHOD(TApp, "IniSubcommandMultipleSections", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+        out << "[subcom]" << std::endl;
+        out << "val=2" << std::endl;
+        out << "[subcom.subsubcom]" << std::endl;
+        out << "val=3" << std::endl;
+        out << "[subcom2]" << std::endl;
+        out << "val=4" << std::endl;
+    }
+
+    int one{0}, two{0}, three{0}, four{0};
+    app.add_option("--val", one);
+    auto subcom = app.add_subcommand("subcom");
+    auto subcom2 = app.add_subcommand("subcom2");
+    subcom->configurable();
+    std::vector<std::size_t> parse_c;
+    subcom->parse_complete_callback([&parse_c]() { parse_c.push_back(58); });
+    subcom->add_option("--val", two);
+    subcom2->add_option("--val", four);
+    subcom2->parse_complete_callback([&parse_c]() { parse_c.push_back(2623); });
+    subcom2->configurable(false);
+    auto subsubcom = subcom->add_subcommand("subsubcom");
+    // configurable should be inherited
+    subsubcom->parse_complete_callback([&parse_c]() { parse_c.push_back(68); });
+    subsubcom->add_option("--val", three);
+
+    run();
+
+    CHECK(one == 1);
+    CHECK(two == 2);
+    CHECK(three == 3);
+    CHECK(four == 4);
+
+    REQUIRE(2u == parse_c.size());
+    CHECK(68U == parse_c[0]);
+    CHECK(58U == parse_c[1]);
+    CHECK(1u == subsubcom->count());
+    CHECK(0u == subcom2->count());
+}
+
+TEST_CASE_METHOD(TApp, "DuplicateSubcommandCallbacks", "[config]") {
+
+    TempFile tmptoml{"TesttomlTmp.toml"};
+
+    app.set_config("--config", tmptoml);
+
+    {
+        std::ofstream out{tmptoml};
+        out << "[[foo]]" << std::endl;
+        out << "[[foo]]" << std::endl;
+        out << "[[foo]]" << std::endl;
+    }
+
+    auto foo = app.add_subcommand("foo");
+    int count{0};
+    foo->callback([&count]() { ++count; });
+    foo->immediate_callback();
+    CHECK(foo->get_immediate_callback());
+    foo->configurable();
+
+    run();
+    CHECK(3 == count);
+}
+
+TEST_CASE_METHOD(TApp, "IniFailure", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+    app.allow_config_extras(false);
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+    }
+
+    CHECK_THROWS_AS(run(), CLI::ConfigError);
+}
+
+TEST_CASE_METHOD(TApp, "IniConfigurable", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+    bool value{false};
+    app.add_flag("--val", value)->configurable(true);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+    }
+
+    REQUIRE_NOTHROW(run());
+    CHECK(value);
+}
+
+TEST_CASE_METHOD(TApp, "IniNotConfigurable", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+    bool value{false};
+    app.add_flag("--val", value)->configurable(false);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "val=1" << std::endl;
+    }
+
+    CHECK_THROWS_AS(run(), CLI::ConfigError);
+}
+
+TEST_CASE_METHOD(TApp, "IniSubFailure", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.add_subcommand("other");
+    app.set_config("--config", tmpini);
+    app.allow_config_extras(false);
+    {
+        std::ofstream out{tmpini};
+        out << "[other]" << std::endl;
+        out << "val=1" << std::endl;
+    }
+
+    CHECK_THROWS_AS(run(), CLI::ConfigError);
+}
+
+TEST_CASE_METHOD(TApp, "IniNoSubFailure", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+    app.allow_config_extras(CLI::config_extras_mode::error);
+    {
+        std::ofstream out{tmpini};
+        out << "[other]" << std::endl;
+        out << "val=1" << std::endl;
+    }
+
+    CHECK_THROWS_AS(run(), CLI::ConfigError);
+}
+
+TEST_CASE_METHOD(TApp, "IniFlagConvertFailure", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.add_flag("--flag");
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "flag=moobook" << std::endl;
+    }
+    run();
+    bool result{false};
+    auto *opt = app.get_option("--flag");
+    CHECK_THROWS_AS(opt->results(result), CLI::ConversionError);
+    std::string res;
+    opt->results(res);
+    CHECK("moobook" == res);
+}
+
+TEST_CASE_METHOD(TApp, "IniFlagNumbers", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    bool boo{false};
+    app.add_flag("--flag", boo);
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "flag=3" << std::endl;
+    }
+
+    REQUIRE_NOTHROW(run());
+    CHECK(boo);
+}
+
+TEST_CASE_METHOD(TApp, "IniFlagDual", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    bool boo{false};
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    app.add_flag("--flag", boo);
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "flag=1 1" << std::endl;
+    }
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "IniShort", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    int key{0};
+    app.add_option("--flag,-f", key);
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "f=3" << std::endl;
+    }
+
+    REQUIRE_NOTHROW(run());
+    CHECK(3 == key);
+}
+
+TEST_CASE_METHOD(TApp, "IniPositional", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    int key{0};
+    app.add_option("key", key);
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "key=3" << std::endl;
+    }
+
+    REQUIRE_NOTHROW(run());
+    CHECK(3 == key);
+}
+
+TEST_CASE_METHOD(TApp, "IniEnvironmental", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    int key{0};
+    app.add_option("key", key)->envname("CLI11_TEST_ENV_KEY_TMP");
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "CLI11_TEST_ENV_KEY_TMP=3" << std::endl;
+    }
+
+    REQUIRE_NOTHROW(run());
+    CHECK(3 == key);
+}
+
+TEST_CASE_METHOD(TApp, "IniFlagText", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    bool flag1{false}, flag2{false}, flag3{false}, flag4{false};
+    app.add_flag("--flag1", flag1);
+    app.add_flag("--flag2", flag2);
+    app.add_flag("--flag3", flag3);
+    app.add_flag("--flag4", flag4);
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "flag1=true" << std::endl;
+        out << "flag2=on" << std::endl;
+        out << "flag3=off" << std::endl;
+        out << "flag4=1" << std::endl;
+    }
+
+    run();
+
+    CHECK(flag1);
+    CHECK(flag2);
+    CHECK(!flag3);
+    CHECK(flag4);
+}
+
+TEST_CASE_METHOD(TApp, "IniFlags", "[config]") {
+    TempFile tmpini{"TestIniTmp.ini"};
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=2" << std::endl;
+        out << "three=true" << std::endl;
+        out << "four=on" << std::endl;
+        out << "five" << std::endl;
+    }
+
+    int two{0};
+    bool three{false}, four{false}, five{false};
+    app.add_flag("--two", two);
+    app.add_flag("--three", three);
+    app.add_flag("--four", four);
+    app.add_flag("--five", five);
+
+    run();
+
+    CHECK(two == 2);
+    CHECK(three);
+    CHECK(four);
+    CHECK(five);
+}
+
+TEST_CASE_METHOD(TApp, "IniFalseFlags", "[config]") {
+    TempFile tmpini{"TestIniTmp.ini"};
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=-2" << std::endl;
+        out << "three=false" << std::endl;
+        out << "four=1" << std::endl;
+        out << "five" << std::endl;
+    }
+
+    int two{0};
+    bool three{false}, four{false}, five{false};
+    app.add_flag("--two", two);
+    app.add_flag("--three", three);
+    app.add_flag("--four", four);
+    app.add_flag("--five", five);
+
+    run();
+
+    CHECK(two == -2);
+    CHECK(!three);
+    CHECK(four);
+    CHECK(five);
+}
+
+TEST_CASE_METHOD(TApp, "IniFalseFlagsDef", "[config]") {
+    TempFile tmpini{"TestIniTmp.ini"};
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=2" << std::endl;
+        out << "three=true" << std::endl;
+        out << "four=on" << std::endl;
+        out << "five" << std::endl;
+    }
+
+    int two{0};
+    bool three{false}, four{false}, five{false};
+    app.add_flag("--two{false}", two);
+    app.add_flag("--three", three);
+    app.add_flag("!--four", four);
+    app.add_flag("--five", five);
+
+    run();
+
+    CHECK(two == -2);
+    CHECK(three);
+    CHECK(!four);
+    CHECK(five);
+}
+
+TEST_CASE_METHOD(TApp, "IniFalseFlagsDefDisableOverrideError", "[config]") {
+    TempFile tmpini{"TestIniTmp.ini"};
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=2" << std::endl;
+        out << "four=on" << std::endl;
+        out << "five" << std::endl;
+    }
+
+    int two{0};
+    bool four{false}, five{false};
+    app.add_flag("--two{false}", two)->disable_flag_override();
+    app.add_flag("!--four", four);
+    app.add_flag("--five", five);
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "IniFalseFlagsDefDisableOverrideSuccess", "[config]") {
+    TempFile tmpini{"TestIniTmp.ini"};
+    app.set_config("--config", tmpini);
+
+    {
+        std::ofstream out{tmpini};
+        out << "[default]" << std::endl;
+        out << "two=2" << std::endl;
+        out << "four={}" << std::endl;
+        out << "val=15" << std::endl;
+    }
+
+    int two{0}, four{0}, val{0};
+    app.add_flag("--two{2}", two)->disable_flag_override();
+    app.add_flag("--four{4}", four)->disable_flag_override();
+    app.add_flag("--val", val);
+
+    run();
+
+    CHECK(two == 2);
+    CHECK(four == 4);
+    CHECK(val == 15);
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputSimple", "[config]") {
+
+    int v{0};
+    app.add_option("--simple", v);
+
+    args = {"--simple=3"};
+
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK(str == "simple=3\n");
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputShort", "[config]") {
+
+    int v{0};
+    app.add_option("-s", v);
+
+    args = {"-s3"};
+
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK(str == "s=3\n");
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputPositional", "[config]") {
+
+    int v{0};
+    app.add_option("pos", v);
+
+    args = {"3"};
+
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK(str == "pos=3\n");
+}
+
+// try the output with environmental only arguments
+TEST_CASE_METHOD(TApp, "TomlOutputEnvironmental", "[config]") {
+
+    put_env("CLI11_TEST_ENV_TMP", "2");
+
+    int val{1};
+    app.add_option(std::string{}, val)->envname("CLI11_TEST_ENV_TMP");
+
+    run();
+
+    CHECK(val == 2);
+    std::string str = app.config_to_str();
+    CHECK(str == "CLI11_TEST_ENV_TMP=2\n");
+
+    unset_env("CLI11_TEST_ENV_TMP");
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputNoConfigurable", "[config]") {
+
+    int v1{0}, v2{0};
+    app.add_option("--simple", v1);
+    app.add_option("--noconf", v2)->configurable(false);
+
+    args = {"--simple=3", "--noconf=2"};
+
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK(str == "simple=3\n");
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputShortSingleDescription", "[config]") {
+    std::string flag = "some_flag";
+    const std::string description = "Some short description.";
+    app.add_flag("--" + flag, description);
+
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("# " + description + "\n" + flag + "=false\n"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputShortDoubleDescription", "[config]") {
+    std::string flag1 = "flagnr1";
+    std::string flag2 = "flagnr2";
+    const std::string description1 = "First description.";
+    const std::string description2 = "Second description.";
+    app.add_flag("--" + flag1, description1);
+    app.add_flag("--" + flag2, description2);
+
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    std::string ans = "# " + description1 + "\n" + flag1 + "=false\n\n# " + description2 + "\n" + flag2 + "=false\n";
+    CHECK_THAT(str, Contains(ans));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputGroups", "[config]") {
+    std::string flag1 = "flagnr1";
+    std::string flag2 = "flagnr2";
+    const std::string description1 = "First description.";
+    const std::string description2 = "Second description.";
+    app.add_flag("--" + flag1, description1)->group("group1");
+    app.add_flag("--" + flag2, description2)->group("group2");
+
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("group1"));
+    CHECK_THAT(str, Contains("group2"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputHiddenOptions", "[config]") {
+    std::string flag1 = "flagnr1";
+    std::string flag2 = "flagnr2";
+    double val{12.7};
+    const std::string description1 = "First description.";
+    const std::string description2 = "Second description.";
+    app.add_flag("--" + flag1, description1)->group("group1");
+    app.add_flag("--" + flag2, description2)->group("group2");
+    app.add_option("--dval", val)->capture_default_str()->group("");
+
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("group1"));
+    CHECK_THAT(str, Contains("group2"));
+    CHECK_THAT(str, Contains("dval=12.7"));
+    auto loc = str.find("dval=12.7");
+    auto locg1 = str.find("group1");
+    CHECK(loc < locg1);
+    // make sure it doesn't come twice
+    loc = str.find("dval=12.7", loc + 4);
+    CHECK(std::string::npos == loc);
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputAppMultiLineDescription", "[config]") {
+    app.description("Some short app description.\n"
+                    "That has multiple lines.");
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("# Some short app description.\n"));
+    CHECK_THAT(str, Contains("# That has multiple lines.\n"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputMultiLineDescription", "[config]") {
+    std::string flag = "some_flag";
+    const std::string description = "Some short description.\nThat has lines.";
+    app.add_flag("--" + flag, description);
+
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("# Some short description.\n"));
+    CHECK_THAT(str, Contains("# That has lines.\n"));
+    CHECK_THAT(str, Contains(flag + "=false\n"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputOptionGroupMultiLineDescription", "[config]") {
+    std::string flag = "flag";
+    const std::string description = "Short flag description.\n";
+    auto og = app.add_option_group("group");
+    og->description("Option group description.\n"
+                    "That has multiple lines.");
+    og->add_flag("--" + flag, description);
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("# Option group description.\n"));
+    CHECK_THAT(str, Contains("# That has multiple lines.\n"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputSubcommandMultiLineDescription", "[config]") {
+    std::string flag = "flag";
+    const std::string description = "Short flag description.\n";
+    auto subcom = app.add_subcommand("subcommand");
+    subcom->configurable();
+    subcom->description("Subcommand description.\n"
+                        "That has multiple lines.");
+    subcom->add_flag("--" + flag, description);
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("# Subcommand description.\n"));
+    CHECK_THAT(str, Contains("# That has multiple lines.\n"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputOptionGroup", "[config]") {
+    std::string flag1 = "flagnr1";
+    std::string flag2 = "flagnr2";
+    double val{12.7};
+    const std::string description1 = "First description.";
+    const std::string description2 = "Second description.";
+    app.add_flag("--" + flag1, description1)->group("group1");
+    app.add_flag("--" + flag2, description2)->group("group2");
+    auto og = app.add_option_group("group3", "g3 desc");
+    og->add_option("--dval", val)->capture_default_str()->group("");
+
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("group1"));
+    CHECK_THAT(str, Contains("group2"));
+    CHECK_THAT(str, Contains("dval=12.7"));
+    CHECK_THAT(str, Contains("group3"));
+    CHECK_THAT(str, Contains("g3 desc"));
+    auto loc = str.find("dval=12.7");
+    auto locg1 = str.find("group1");
+    auto locg3 = str.find("group3");
+    CHECK(loc > locg1);
+    // make sure it doesn't come twice
+    loc = str.find("dval=12.7", loc + 4);
+    CHECK(std::string::npos == loc);
+    CHECK(locg1 < locg3);
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputVector", "[config]") {
+
+    std::vector<int> v;
+    app.add_option("--vector", v);
+    app.config_formatter(std::make_shared<CLI::ConfigTOML>());
+    args = {"--vector", "1", "2", "3"};
+
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK(str == "vector=[1, 2, 3]\n");
+}
+
+TEST_CASE_METHOD(TApp, "ConfigOutputVectorCustom", "[config]") {
+
+    std::vector<int> v;
+    app.add_option("--vector", v);
+    auto V = std::make_shared<CLI::ConfigBase>();
+    V->arrayBounds('{', '}')->arrayDelimiter(';')->valueSeparator(':');
+    app.config_formatter(V);
+    args = {"--vector", "1", "2", "3"};
+
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK(str == "vector:{1; 2; 3}\n");
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputFlag", "[config]") {
+
+    int v{0}, q{0};
+    app.add_option("--simple", v);
+    app.add_flag("--nothing");
+    app.add_flag("--onething");
+    app.add_flag("--something", q);
+
+    args = {"--simple=3", "--onething", "--something", "--something"};
+
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=3"));
+    CHECK_THAT(str, !Contains("nothing"));
+    CHECK_THAT(str, Contains("onething=true"));
+    CHECK_THAT(str, Contains("something=[true, true]"));
+
+    str = app.config_to_str(true);
+    CHECK_THAT(str, Contains("nothing"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputSet", "[config]") {
+
+    int v{0};
+    app.add_option("--simple", v)->check(CLI::IsMember({1, 2, 3}));
+
+    args = {"--simple=2"};
+
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=2"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputDefault", "[config]") {
+
+    int v{7};
+    app.add_option("--simple", v)->capture_default_str();
+
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, !Contains("simple=7"));
+
+    str = app.config_to_str(true);
+    CHECK_THAT(str, Contains("simple=7"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputSubcom", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other");
+    subcom->add_flag("--newer");
+
+    args = {"--simple", "other", "--newer"};
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("other.newer=true"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputSubcomConfigurable", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other")->configurable();
+    subcom->add_flag("--newer");
+
+    args = {"--simple", "other", "--newer"};
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("[other]"));
+    CHECK_THAT(str, Contains("newer=true"));
+    CHECK(std::string::npos == str.find("other.newer=true"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputSubsubcom", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other");
+    subcom->add_flag("--newer");
+    auto subsubcom = subcom->add_subcommand("sub2");
+    subsubcom->add_flag("--newest");
+
+    args = {"--simple", "other", "--newer", "sub2", "--newest"};
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("other.newer=true"));
+    CHECK_THAT(str, Contains("other.sub2.newest=true"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputSubsubcomConfigurable", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other")->configurable();
+    subcom->add_flag("--newer");
+
+    auto subsubcom = subcom->add_subcommand("sub2");
+    subsubcom->add_flag("--newest");
+
+    args = {"--simple", "other", "--newer", "sub2", "--newest"};
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("[other]"));
+    CHECK_THAT(str, Contains("newer=true"));
+    CHECK_THAT(str, Contains("[other.sub2]"));
+    CHECK_THAT(str, Contains("newest=true"));
+    CHECK(std::string::npos == str.find("sub2.newest=true"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputSubcomNonConfigurable", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other", "other_descriptor")->configurable();
+    subcom->add_flag("--newer");
+
+    auto subcom2 = app.add_subcommand("sub2", "descriptor2");
+    subcom2->add_flag("--newest")->configurable(false);
+
+    args = {"--simple", "other", "--newer", "sub2", "--newest"};
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("other_descriptor"));
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("[other]"));
+    CHECK_THAT(str, Contains("newer=true"));
+    CHECK_THAT(str, !Contains("newest"));
+    CHECK_THAT(str, !Contains("descriptor2"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputSubsubcomConfigurableDeep", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other")->configurable();
+    subcom->add_flag("--newer");
+
+    auto subsubcom = subcom->add_subcommand("sub2");
+    subsubcom->add_flag("--newest");
+    auto sssscom = subsubcom->add_subcommand("sub-level2");
+    subsubcom->add_flag("--still_newer");
+    auto s5com = sssscom->add_subcommand("sub-level3");
+    s5com->add_flag("--absolute_newest");
+
+    args = {"--simple", "other", "sub2", "sub-level2", "sub-level3", "--absolute_newest"};
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("[other.sub2.sub-level2.sub-level3]"));
+    CHECK_THAT(str, Contains("absolute_newest=true"));
+    CHECK(std::string::npos == str.find(".absolute_newest=true"));
+}
+
+TEST_CASE_METHOD(TApp, "TomlOutputQuoted", "[config]") {
+
+    std::string val1;
+    app.add_option("--val1", val1);
+
+    std::string val2;
+    app.add_option("--val2", val2);
+
+    args = {"--val1", "I am a string", "--val2", R"(I am a "confusing" string)"};
+
+    run();
+
+    CHECK(val1 == "I am a string");
+    CHECK(val2 == "I am a \"confusing\" string");
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("val1=\"I am a string\""));
+    CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'"));
+}
+
+TEST_CASE_METHOD(TApp, "DefaultsTomlOutputQuoted", "[config]") {
+
+    std::string val1{"I am a string"};
+    app.add_option("--val1", val1)->capture_default_str();
+
+    std::string val2{R"(I am a "confusing" string)"};
+    app.add_option("--val2", val2)->capture_default_str();
+
+    run();
+
+    std::string str = app.config_to_str(true);
+    CHECK_THAT(str, Contains("val1=\"I am a string\""));
+    CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'"));
+}
+
+// #298
+TEST_CASE_METHOD(TApp, "StopReadingConfigOnClear", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.set_config("--config", tmpini);
+    auto ptr = app.set_config();  // Should *not* read config file
+    CHECK(nullptr == ptr);
+
+    {
+        std::ofstream out{tmpini};
+        out << "volume=1" << std::endl;
+    }
+
+    int volume{0};
+    app.add_option("--volume", volume, "volume1");
+
+    run();
+
+    CHECK(0 == volume);
+}
+
+TEST_CASE_METHOD(TApp, "ConfigWriteReadWrite", "[config]") {
+
+    TempFile tmpini{"TestIniTmp.ini"};
+
+    app.add_flag("--flag");
+    run();
+
+    // Save config, with default values too
+    std::string config1 = app.config_to_str(true, true);
+    {
+        std::ofstream out{tmpini};
+        out << config1 << std::endl;
+    }
+
+    app.set_config("--config", tmpini, "Read an ini file", true);
+    run();
+
+    std::string config2 = app.config_to_str(true, true);
+
+    CHECK(config2 == config1);
+}
+
+/////// INI output tests
+
+TEST_CASE_METHOD(TApp, "IniOutputSimple", "[config]") {
+
+    int v{0};
+    app.add_option("--simple", v);
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    args = {"--simple=3"};
+
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK(str == "simple=3\n");
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputNoConfigurable", "[config]") {
+
+    int v1{0}, v2{0};
+    app.add_option("--simple", v1);
+    app.add_option("--noconf", v2)->configurable(false);
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    args = {"--simple=3", "--noconf=2"};
+
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK(str == "simple=3\n");
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputShortSingleDescription", "[config]") {
+    std::string flag = "some_flag";
+    const std::string description = "Some short description.";
+    app.add_flag("--" + flag, description);
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("; " + description + "\n" + flag + "=false\n"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputShortDoubleDescription", "[config]") {
+    std::string flag1 = "flagnr1";
+    std::string flag2 = "flagnr2";
+    const std::string description1 = "First description.";
+    const std::string description2 = "Second description.";
+    app.add_flag("--" + flag1, description1);
+    app.add_flag("--" + flag2, description2);
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    std::string ans = "; " + description1 + "\n" + flag1 + "=false\n\n; " + description2 + "\n" + flag2 + "=false\n";
+    CHECK_THAT(str, Contains(ans));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputGroups", "[config]") {
+    std::string flag1 = "flagnr1";
+    std::string flag2 = "flagnr2";
+    const std::string description1 = "First description.";
+    const std::string description2 = "Second description.";
+    app.add_flag("--" + flag1, description1)->group("group1");
+    app.add_flag("--" + flag2, description2)->group("group2");
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("group1"));
+    CHECK_THAT(str, Contains("group2"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputHiddenOptions", "[config]") {
+    std::string flag1 = "flagnr1";
+    std::string flag2 = "flagnr2";
+    double val{12.7};
+    const std::string description1 = "First description.";
+    const std::string description2 = "Second description.";
+    app.add_flag("--" + flag1, description1)->group("group1");
+    app.add_flag("--" + flag2, description2)->group("group2");
+    app.add_option("--dval", val)->capture_default_str()->group("");
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("group1"));
+    CHECK_THAT(str, Contains("group2"));
+    CHECK_THAT(str, Contains("dval=12.7"));
+    auto loc = str.find("dval=12.7");
+    auto locg1 = str.find("group1");
+    CHECK(loc < locg1);
+    // make sure it doesn't come twice
+    loc = str.find("dval=12.7", loc + 4);
+    CHECK(std::string::npos == loc);
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputAppMultiLineDescription", "[config]") {
+    app.description("Some short app description.\n"
+                    "That has multiple lines.");
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("; Some short app description.\n"));
+    CHECK_THAT(str, Contains("; That has multiple lines.\n"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputMultiLineDescription", "[config]") {
+    std::string flag = "some_flag";
+    const std::string description = "Some short description.\nThat has lines.";
+    app.add_flag("--" + flag, description);
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("; Some short description.\n"));
+    CHECK_THAT(str, Contains("; That has lines.\n"));
+    CHECK_THAT(str, Contains(flag + "=false\n"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputOptionGroupMultiLineDescription", "[config]") {
+    std::string flag = "flag";
+    const std::string description = "Short flag description.\n";
+    auto og = app.add_option_group("group");
+    og->description("Option group description.\n"
+                    "That has multiple lines.");
+    og->add_flag("--" + flag, description);
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("; Option group description.\n"));
+    CHECK_THAT(str, Contains("; That has multiple lines.\n"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputSubcommandMultiLineDescription", "[config]") {
+    std::string flag = "flag";
+    const std::string description = "Short flag description.\n";
+    auto subcom = app.add_subcommand("subcommand");
+    subcom->configurable();
+    subcom->description("Subcommand description.\n"
+                        "That has multiple lines.");
+    subcom->add_flag("--" + flag, description);
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("; Subcommand description.\n"));
+    CHECK_THAT(str, Contains("; That has multiple lines.\n"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputOptionGroup", "[config]") {
+    std::string flag1 = "flagnr1";
+    std::string flag2 = "flagnr2";
+    double val{12.7};
+    const std::string description1 = "First description.";
+    const std::string description2 = "Second description.";
+    app.add_flag("--" + flag1, description1)->group("group1");
+    app.add_flag("--" + flag2, description2)->group("group2");
+    auto og = app.add_option_group("group3", "g3 desc");
+    og->add_option("--dval", val)->capture_default_str()->group("");
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str(true, true);
+    CHECK_THAT(str, Contains("group1"));
+    CHECK_THAT(str, Contains("group2"));
+    CHECK_THAT(str, Contains("dval=12.7"));
+    CHECK_THAT(str, Contains("group3"));
+    CHECK_THAT(str, Contains("g3 desc"));
+    auto loc = str.find("dval=12.7");
+    auto locg1 = str.find("group1");
+    auto locg3 = str.find("group3");
+    CHECK(loc > locg1);
+    // make sure it doesn't come twice
+    loc = str.find("dval=12.7", loc + 4);
+    CHECK(std::string::npos == loc);
+    CHECK(locg1 < locg3);
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputVector", "[config]") {
+
+    std::vector<int> v;
+    app.add_option("--vector", v);
+
+    args = {"--vector", "1", "2", "3"};
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK(str == "vector=1 2 3\n");
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputFlag", "[config]") {
+
+    int v{0}, q{0};
+    app.add_option("--simple", v);
+    app.add_flag("--nothing");
+    app.add_flag("--onething");
+    app.add_flag("--something", q);
+
+    args = {"--simple=3", "--onething", "--something", "--something"};
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=3"));
+    CHECK_THAT(str, !Contains("nothing"));
+    CHECK_THAT(str, Contains("onething=true"));
+    CHECK_THAT(str, Contains("something=true true"));
+
+    str = app.config_to_str(true);
+    CHECK_THAT(str, Contains("nothing"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputSet", "[config]") {
+
+    int v{0};
+    app.add_option("--simple", v)->check(CLI::IsMember({1, 2, 3}));
+
+    args = {"--simple=2"};
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=2"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputDefault", "[config]") {
+
+    int v{7};
+    app.add_option("--simple", v)->capture_default_str();
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, !Contains("simple=7"));
+
+    str = app.config_to_str(true);
+    CHECK_THAT(str, Contains("simple=7"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputSubcom", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other");
+    subcom->add_flag("--newer");
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    args = {"--simple", "other", "--newer"};
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("other.newer=true"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputSubcomCustomSep", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other");
+    subcom->add_flag("--newer");
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    app.get_config_formatter_base()->parentSeparator(':');
+    args = {"--simple", "other", "--newer"};
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("other:newer=true"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputSubcomConfigurable", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other")->configurable();
+    subcom->add_flag("--newer");
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    args = {"--simple", "other", "--newer"};
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("[other]"));
+    CHECK_THAT(str, Contains("newer=true"));
+    CHECK(std::string::npos == str.find("other.newer=true"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputSubsubcom", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other");
+    subcom->add_flag("--newer");
+    auto subsubcom = subcom->add_subcommand("sub2");
+    subsubcom->add_flag("--newest");
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    args = {"--simple", "other", "--newer", "sub2", "--newest"};
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("other.newer=true"));
+    CHECK_THAT(str, Contains("other.sub2.newest=true"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputSubsubcomCustomSep", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other");
+    subcom->add_flag("--newer");
+    auto subsubcom = subcom->add_subcommand("sub2");
+    subsubcom->add_flag("--newest");
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    app.get_config_formatter_base()->parentSeparator('|');
+    args = {"--simple", "other", "--newer", "sub2", "--newest"};
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("other|newer=true"));
+    CHECK_THAT(str, Contains("other|sub2|newest=true"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputSubsubcomConfigurable", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other")->configurable();
+    subcom->add_flag("--newer");
+
+    auto subsubcom = subcom->add_subcommand("sub2");
+    subsubcom->add_flag("--newest");
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    args = {"--simple", "other", "--newer", "sub2", "--newest"};
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("[other]"));
+    CHECK_THAT(str, Contains("newer=true"));
+    CHECK_THAT(str, Contains("[other.sub2]"));
+    CHECK_THAT(str, Contains("newest=true"));
+    CHECK(std::string::npos == str.find("sub2.newest=true"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputSubsubcomConfigurableDeep", "[config]") {
+
+    app.add_flag("--simple");
+    auto subcom = app.add_subcommand("other")->configurable();
+    subcom->add_flag("--newer");
+
+    auto subsubcom = subcom->add_subcommand("sub2");
+    subsubcom->add_flag("--newest");
+    auto sssscom = subsubcom->add_subcommand("sub-level2");
+    subsubcom->add_flag("--still_newer");
+    auto s5com = sssscom->add_subcommand("sub-level3");
+    s5com->add_flag("--absolute_newest");
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    args = {"--simple", "other", "sub2", "sub-level2", "sub-level3", "--absolute_newest"};
+    run();
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("simple=true"));
+    CHECK_THAT(str, Contains("[other.sub2.sub-level2.sub-level3]"));
+    CHECK_THAT(str, Contains("absolute_newest=true"));
+    CHECK(std::string::npos == str.find(".absolute_newest=true"));
+}
+
+TEST_CASE_METHOD(TApp, "IniOutputQuoted", "[config]") {
+
+    std::string val1;
+    app.add_option("--val1", val1);
+
+    std::string val2;
+    app.add_option("--val2", val2);
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    args = {"--val1", "I am a string", "--val2", R"(I am a "confusing" string)"};
+
+    run();
+
+    CHECK(val1 == "I am a string");
+    CHECK(val2 == "I am a \"confusing\" string");
+
+    std::string str = app.config_to_str();
+    CHECK_THAT(str, Contains("val1=\"I am a string\""));
+    CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'"));
+}
+
+TEST_CASE_METHOD(TApp, "DefaultsIniOutputQuoted", "[config]") {
+
+    std::string val1{"I am a string"};
+    app.add_option("--val1", val1)->capture_default_str();
+
+    std::string val2{R"(I am a "confusing" string)"};
+    app.add_option("--val2", val2)->capture_default_str();
+    app.config_formatter(std::make_shared<CLI::ConfigINI>());
+    run();
+
+    std::string str = app.config_to_str(true);
+    CHECK_THAT(str, Contains("val1=\"I am a string\""));
+    CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'"));
+}
diff --git a/external/CLI11/tests/CreationTest.cpp b/external/CLI11/tests/CreationTest.cpp
new file mode 100644
index 0000000..4790057
--- /dev/null
+++ b/external/CLI11/tests/CreationTest.cpp
@@ -0,0 +1,758 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+#include <cstdlib>
+
+TEST_CASE_METHOD(TApp, "AddingExistingShort", "[creation]") {
+    CLI::Option *opt = app.add_flag("-c,--count");
+    CHECK(std::vector<std::string>({"count"}) == opt->get_lnames());
+    CHECK(std::vector<std::string>({"c"}) == opt->get_snames());
+
+    CHECK_THROWS_AS(app.add_flag("--cat,-c"), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "AddingExistingLong", "[creation]") {
+    app.add_flag("-q,--count");
+    CHECK_THROWS_AS(app.add_flag("--count,-c"), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "AddingExistingShortNoCase", "[creation]") {
+    app.add_flag("-C,--count")->ignore_case();
+    CHECK_THROWS_AS(app.add_flag("--cat,-c"), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "AddingExistingLongNoCase", "[creation]") {
+    app.add_flag("-q,--count")->ignore_case();
+    CHECK_THROWS_AS(app.add_flag("--Count,-c"), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "AddingExistingNoCaseReversed", "[creation]") {
+    app.add_flag("-c,--count")->ignore_case();
+    CHECK_THROWS_AS(app.add_flag("--cat,-C"), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "AddingExistingWithCase", "[creation]") {
+    app.add_flag("-c,--count");
+    CHECK_NOTHROW(app.add_flag("--Cat,-C"));
+}
+
+TEST_CASE_METHOD(TApp, "AddingExistingWithCaseAfter", "[creation]") {
+    auto count = app.add_flag("-c,--count");
+    app.add_flag("--Cat,-C");
+
+    CHECK_THROWS_AS(count->ignore_case(), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "AddingExistingWithCaseAfter2", "[creation]") {
+    app.add_flag("-c,--count");
+    auto cat = app.add_flag("--Cat,-C");
+
+    CHECK_THROWS_AS(cat->ignore_case(), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "AddingExistingWithUnderscoreAfter", "[creation]") {
+    auto count = app.add_flag("--underscore");
+    app.add_flag("--under_score");
+
+    CHECK_THROWS_AS(count->ignore_underscore(), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "AddingExistingWithUnderscoreAfter2", "[creation]") {
+    auto count = app.add_flag("--under_score");
+    app.add_flag("--underscore");
+
+    CHECK_THROWS_AS(count->ignore_underscore(), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "AddingMultipleInfPositionals", "[creation]") {
+    std::vector<std::string> one, two;
+    app.add_option("one", one);
+    app.add_option("two", two);
+
+    CHECK_THROWS_AS(run(), CLI::InvalidError);
+}
+
+TEST_CASE_METHOD(TApp, "AddingMultipleInfPositionalsSubcom", "[creation]") {
+    std::vector<std::string> one, two;
+    CLI::App *below = app.add_subcommand("below");
+    below->add_option("one", one);
+    below->add_option("two", two);
+
+    CHECK_THROWS_AS(run(), CLI::InvalidError);
+}
+
+TEST_CASE_METHOD(TApp, "MultipleSubcomMatching", "[creation]") {
+    app.add_subcommand("first");
+    app.add_subcommand("second");
+    app.add_subcommand("Second");
+    CHECK_THROWS_AS(app.add_subcommand("first"), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "RecoverSubcommands", "[creation]") {
+    CLI::App *app1 = app.add_subcommand("app1");
+    CLI::App *app2 = app.add_subcommand("app2");
+    CLI::App *app3 = app.add_subcommand("app3");
+    CLI::App *app4 = app.add_subcommand("app4");
+
+    CHECK(std::vector<CLI::App *>({app1, app2, app3, app4}) == app.get_subcommands({}));
+}
+
+TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithCase", "[creation]") {
+    app.add_subcommand("first")->ignore_case();
+    CHECK_THROWS_AS(app.add_subcommand("fIrst"), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithCaseFirst", "[creation]") {
+    app.ignore_case();
+    app.add_subcommand("first");
+    CHECK_THROWS_AS(app.add_subcommand("fIrst"), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithUnderscore", "[creation]") {
+    app.add_subcommand("first_option")->ignore_underscore();
+    CHECK_THROWS_AS(app.add_subcommand("firstoption"), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithUnderscoreFirst", "[creation]") {
+    app.ignore_underscore();
+    app.add_subcommand("first_option");
+    CHECK_THROWS_AS(app.add_subcommand("firstoption"), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithCaseInplace", "[creation]") {
+    app.add_subcommand("first");
+    auto first = app.add_subcommand("fIrst");
+
+    CHECK_THROWS_AS(first->ignore_case(), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithCaseInplace2", "[creation]") {
+    auto first = app.add_subcommand("first");
+    app.add_subcommand("fIrst");
+
+    CHECK_THROWS_AS(first->ignore_case(), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithUnderscoreInplace", "[creation]") {
+    app.add_subcommand("first_option");
+    auto first = app.add_subcommand("firstoption");
+
+    CHECK_THROWS_AS(first->ignore_underscore(), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "MultipleSubcomMatchingWithUnderscoreInplace2", "[creation]") {
+    auto first = app.add_subcommand("firstoption");
+    app.add_subcommand("first_option");
+
+    CHECK_THROWS_AS(first->ignore_underscore(), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "MultipleSubcomNoMatchingInplace2", "[creation]") {
+    auto first = app.add_subcommand("first");
+    auto second = app.add_subcommand("second");
+
+    CHECK_NOTHROW(first->ignore_case());
+    CHECK_NOTHROW(second->ignore_case());
+}
+
+TEST_CASE_METHOD(TApp, "MultipleSubcomNoMatchingInplaceUnderscore2", "[creation]") {
+    auto first = app.add_subcommand("first_option");
+    auto second = app.add_subcommand("second_option");
+
+    CHECK_NOTHROW(first->ignore_underscore());
+    CHECK_NOTHROW(second->ignore_underscore());
+}
+
+TEST_CASE_METHOD(TApp, "IncorrectConstructionFlagPositional1", "[creation]") {
+    // This wants to be one line with clang-format
+    CHECK_THROWS_AS(app.add_flag("cat"), CLI::IncorrectConstruction);
+}
+
+TEST_CASE_METHOD(TApp, "IncorrectConstructionFlagPositional2", "[creation]") {
+    int x{0};
+    CHECK_THROWS_AS(app.add_flag("cat", x), CLI::IncorrectConstruction);
+}
+
+TEST_CASE_METHOD(TApp, "IncorrectConstructionFlagPositional3", "[creation]") {
+    bool x{false};
+    CHECK_THROWS_AS(app.add_flag("cat", x), CLI::IncorrectConstruction);
+}
+
+TEST_CASE_METHOD(TApp, "IncorrectConstructionNeedsCannotFind", "[creation]") {
+    auto cat = app.add_flag("--cat");
+    CHECK_THROWS_AS(cat->needs("--nothing"), CLI::IncorrectConstruction);
+}
+
+TEST_CASE_METHOD(TApp, "IncorrectConstructionExcludesCannotFind", "[creation]") {
+    auto cat = app.add_flag("--cat");
+    CHECK_THROWS_AS(cat->excludes("--nothing"), CLI::IncorrectConstruction);
+}
+
+TEST_CASE_METHOD(TApp, "IncorrectConstructionDuplicateNeeds", "[creation]") {
+    auto cat = app.add_flag("--cat");
+    auto other = app.add_flag("--other");
+    REQUIRE_NOTHROW(cat->needs(other));
+    // duplicated needs is redundant but not an error
+    CHECK_NOTHROW(cat->needs(other));
+}
+
+TEST_CASE_METHOD(TApp, "IncorrectConstructionDuplicateNeedsTxt", "[creation]") {
+    auto cat = app.add_flag("--cat");
+    app.add_flag("--other");
+    REQUIRE_NOTHROW(cat->needs("--other"));
+    // duplicate needs is redundant but not an error
+    CHECK_NOTHROW(cat->needs("--other"));
+}
+
+// Now allowed
+TEST_CASE_METHOD(TApp, "CorrectConstructionDuplicateExcludes", "[creation]") {
+    auto cat = app.add_flag("--cat");
+    auto other = app.add_flag("--other");
+    REQUIRE_NOTHROW(cat->excludes(other));
+    REQUIRE_NOTHROW(other->excludes(cat));
+}
+
+// Now allowed
+TEST_CASE_METHOD(TApp, "CorrectConstructionDuplicateExcludesTxt", "[creation]") {
+    auto cat = app.add_flag("--cat");
+    auto other = app.add_flag("--other");
+    REQUIRE_NOTHROW(cat->excludes("--other"));
+    REQUIRE_NOTHROW(other->excludes("--cat"));
+}
+
+TEST_CASE_METHOD(TApp, "CheckName", "[creation]") {
+    auto long1 = app.add_flag("--long1");
+    auto long2 = app.add_flag("--Long2");
+    auto short1 = app.add_flag("-a");
+    auto short2 = app.add_flag("-B");
+    int x{0}, y{0};
+    auto pos1 = app.add_option("pos1", x);
+    auto pos2 = app.add_option("pOs2", y);
+
+    CHECK(long1->check_name("--long1"));
+    CHECK(!long1->check_name("--lonG1"));
+
+    CHECK(long2->check_name("--Long2"));
+    CHECK(!long2->check_name("--long2"));
+
+    CHECK(short1->check_name("-a"));
+    CHECK(!short1->check_name("-A"));
+
+    CHECK(short2->check_name("-B"));
+    CHECK(!short2->check_name("-b"));
+
+    CHECK(pos1->check_name("pos1"));
+    CHECK(!pos1->check_name("poS1"));
+
+    CHECK(pos2->check_name("pOs2"));
+    CHECK(!pos2->check_name("pos2"));
+}
+
+TEST_CASE_METHOD(TApp, "CheckNameNoCase", "[creation]") {
+    auto long1 = app.add_flag("--long1")->ignore_case();
+    auto long2 = app.add_flag("--Long2")->ignore_case();
+    auto short1 = app.add_flag("-a")->ignore_case();
+    auto short2 = app.add_flag("-B")->ignore_case();
+    int x{0}, y{0};
+    auto pos1 = app.add_option("pos1", x)->ignore_case();
+    auto pos2 = app.add_option("pOs2", y)->ignore_case();
+
+    CHECK(long1->check_name("--long1"));
+    CHECK(long1->check_name("--lonG1"));
+
+    CHECK(long2->check_name("--Long2"));
+    CHECK(long2->check_name("--long2"));
+
+    CHECK(short1->check_name("-a"));
+    CHECK(short1->check_name("-A"));
+
+    CHECK(short2->check_name("-B"));
+    CHECK(short2->check_name("-b"));
+
+    CHECK(pos1->check_name("pos1"));
+    CHECK(pos1->check_name("poS1"));
+
+    CHECK(pos2->check_name("pOs2"));
+    CHECK(pos2->check_name("pos2"));
+}
+
+TEST_CASE_METHOD(TApp, "CheckNameNoUnderscore", "[creation]") {
+    auto long1 = app.add_flag("--longoption1")->ignore_underscore();
+    auto long2 = app.add_flag("--long_option2")->ignore_underscore();
+
+    int x{0}, y{0};
+    auto pos1 = app.add_option("pos_option_1", x)->ignore_underscore();
+    auto pos2 = app.add_option("posoption2", y)->ignore_underscore();
+
+    CHECK(long1->check_name("--long_option1"));
+    CHECK(long1->check_name("--longoption_1"));
+    CHECK(long1->check_name("--longoption1"));
+    CHECK(long1->check_name("--long__opt_ion__1"));
+    CHECK(long1->check_name("--__l_o_n_g_o_p_t_i_o_n_1"));
+
+    CHECK(long2->check_name("--long_option2"));
+    CHECK(long2->check_name("--longoption2"));
+    CHECK(long2->check_name("--longoption_2"));
+    CHECK(long2->check_name("--long__opt_ion__2"));
+    CHECK(long2->check_name("--__l_o_n_go_p_t_i_o_n_2__"));
+
+    CHECK(pos1->check_name("pos_option1"));
+    CHECK(pos1->check_name("pos_option_1"));
+    CHECK(pos1->check_name("pos_o_p_t_i_on_1"));
+    CHECK(pos1->check_name("posoption1"));
+
+    CHECK(pos2->check_name("pos_option2"));
+    CHECK(pos2->check_name("pos_option_2"));
+    CHECK(pos2->check_name("pos_o_p_t_i_on_2"));
+    CHECK(pos2->check_name("posoption2"));
+}
+
+TEST_CASE_METHOD(TApp, "CheckNameNoCaseNoUnderscore", "[creation]") {
+    auto long1 = app.add_flag("--LongoptioN1")->ignore_underscore()->ignore_case();
+    auto long2 = app.add_flag("--long_Option2")->ignore_case()->ignore_underscore();
+
+    int x{0}, y{0};
+    auto pos1 = app.add_option("pos_Option_1", x)->ignore_underscore()->ignore_case();
+    auto pos2 = app.add_option("posOption2", y)->ignore_case()->ignore_underscore();
+
+    CHECK(long1->check_name("--Long_Option1"));
+    CHECK(long1->check_name("--lONgoption_1"));
+    CHECK(long1->check_name("--LongOption1"));
+    CHECK(long1->check_name("--long__Opt_ion__1"));
+    CHECK(long1->check_name("--__l_o_N_g_o_P_t_i_O_n_1"));
+
+    CHECK(long2->check_name("--long_Option2"));
+    CHECK(long2->check_name("--LongOption2"));
+    CHECK(long2->check_name("--longOPTION_2"));
+    CHECK(long2->check_name("--long__OPT_ion__2"));
+    CHECK(long2->check_name("--__l_o_n_GO_p_t_i_o_n_2__"));
+
+    CHECK(pos1->check_name("POS_Option1"));
+    CHECK(pos1->check_name("pos_option_1"));
+    CHECK(pos1->check_name("pos_o_p_t_i_on_1"));
+    CHECK(pos1->check_name("posoption1"));
+
+    CHECK(pos2->check_name("pos_option2"));
+    CHECK(pos2->check_name("pos_OPTION_2"));
+    CHECK(pos2->check_name("poS_o_p_T_I_on_2"));
+    CHECK(pos2->check_name("PosOption2"));
+}
+
+TEST_CASE_METHOD(TApp, "PreSpaces", "[creation]") {
+    int x{0};
+    auto myapp = app.add_option(" -a, --long, other", x);
+
+    CHECK(myapp->check_lname("long"));
+    CHECK(myapp->check_sname("a"));
+    CHECK(myapp->check_name("other"));
+}
+
+TEST_CASE_METHOD(TApp, "AllSpaces", "[creation]") {
+    int x{0};
+    auto myapp = app.add_option(" -a , --long , other ", x);
+
+    CHECK(myapp->check_lname("long"));
+    CHECK(myapp->check_sname("a"));
+    CHECK(myapp->check_name("other"));
+}
+
+TEST_CASE_METHOD(TApp, "OptionFromDefaults", "[creation]") {
+    app.option_defaults()->required();
+
+    // Options should remember defaults
+    int x{0};
+    auto opt = app.add_option("--simple", x);
+    CHECK(opt->get_required());
+
+    // Flags cannot be required
+    auto flag = app.add_flag("--other");
+    CHECK(!flag->get_required());
+
+    app.option_defaults()->required(false);
+    auto opt2 = app.add_option("--simple2", x);
+    CHECK(!opt2->get_required());
+
+    app.option_defaults()->required()->ignore_case();
+
+    auto opt3 = app.add_option("--simple3", x);
+    CHECK(opt3->get_required());
+    CHECK(opt3->get_ignore_case());
+
+    app.option_defaults()->required()->ignore_underscore();
+
+    auto opt4 = app.add_option("--simple4", x);
+    CHECK(opt4->get_required());
+    CHECK(opt4->get_ignore_underscore());
+}
+
+TEST_CASE_METHOD(TApp, "OptionFromDefaultsSubcommands", "[creation]") {
+    // Initial defaults
+    CHECK(!app.option_defaults()->get_required());
+    CHECK(CLI::MultiOptionPolicy::Throw == app.option_defaults()->get_multi_option_policy());
+    CHECK(!app.option_defaults()->get_ignore_case());
+    CHECK(!app.option_defaults()->get_ignore_underscore());
+    CHECK(!app.option_defaults()->get_disable_flag_override());
+    CHECK(app.option_defaults()->get_configurable());
+    CHECK("Options" == app.option_defaults()->get_group());
+
+    app.option_defaults()
+        ->required()
+        ->multi_option_policy(CLI::MultiOptionPolicy::TakeLast)
+        ->ignore_case()
+        ->ignore_underscore()
+        ->configurable(false)
+        ->disable_flag_override()
+        ->group("Something");
+
+    auto app2 = app.add_subcommand("app2");
+
+    CHECK(app2->option_defaults()->get_required());
+    CHECK(CLI::MultiOptionPolicy::TakeLast == app2->option_defaults()->get_multi_option_policy());
+    CHECK(app2->option_defaults()->get_ignore_case());
+    CHECK(app2->option_defaults()->get_ignore_underscore());
+    CHECK(!app2->option_defaults()->get_configurable());
+    CHECK(app.option_defaults()->get_disable_flag_override());
+    CHECK("Something" == app2->option_defaults()->get_group());
+}
+
+TEST_CASE_METHOD(TApp, "GetNameCheck", "[creation]") {
+    int x{0};
+    auto a = app.add_flag("--that");
+    auto b = app.add_flag("-x");
+    auto c = app.add_option("pos", x);
+    auto d = app.add_option("one,-o,--other", x);
+
+    CHECK("--that" == a->get_name(false, true));
+    CHECK("-x" == b->get_name(false, true));
+    CHECK("pos" == c->get_name(false, true));
+
+    CHECK("--other" == d->get_name());
+    CHECK("--other" == d->get_name(false, false));
+    CHECK("-o,--other" == d->get_name(false, true));
+    CHECK("one,-o,--other" == d->get_name(true, true));
+    CHECK("one" == d->get_name(true, false));
+}
+
+TEST_CASE_METHOD(TApp, "SubcommandDefaults", "[creation]") {
+    // allow_extras, prefix_command, ignore_case, fallthrough, group, min/max subcommand, validate_positionals
+
+    // Initial defaults
+    CHECK(!app.get_allow_extras());
+    CHECK(!app.get_prefix_command());
+    CHECK(!app.get_immediate_callback());
+    CHECK(!app.get_ignore_case());
+    CHECK(!app.get_ignore_underscore());
+#ifdef _WIN32
+    CHECK(app.get_allow_windows_style_options());
+#else
+    CHECK(!app.get_allow_windows_style_options());
+#endif
+    CHECK(!app.get_fallthrough());
+    CHECK(!app.get_configurable());
+    CHECK(!app.get_validate_positionals());
+
+    CHECK("" == app.get_footer());
+    CHECK("Subcommands" == app.get_group());
+    CHECK(0u == app.get_require_subcommand_min());
+    CHECK(0u == app.get_require_subcommand_max());
+
+    app.allow_extras();
+    app.prefix_command();
+    app.immediate_callback();
+    app.ignore_case();
+    app.ignore_underscore();
+    app.configurable();
+#ifdef _WIN32
+    app.allow_windows_style_options(false);
+#else
+    app.allow_windows_style_options();
+#endif
+
+    app.fallthrough();
+    app.validate_positionals();
+    app.footer("footy");
+    app.group("Stuff");
+    app.require_subcommand(2, 3);
+
+    auto app2 = app.add_subcommand("app2");
+
+    // Initial defaults
+    CHECK(app2->get_allow_extras());
+    CHECK(app2->get_prefix_command());
+    CHECK(app2->get_immediate_callback());
+    CHECK(app2->get_ignore_case());
+    CHECK(app2->get_ignore_underscore());
+#ifdef _WIN32
+    CHECK(!app2->get_allow_windows_style_options());
+#else
+    CHECK(app2->get_allow_windows_style_options());
+#endif
+    CHECK(app2->get_fallthrough());
+    CHECK(app2->get_validate_positionals());
+    CHECK(app2->get_configurable());
+    CHECK("footy" == app2->get_footer());
+    CHECK("Stuff" == app2->get_group());
+    CHECK(0u == app2->get_require_subcommand_min());
+    CHECK(3u == app2->get_require_subcommand_max());
+}
+
+TEST_CASE_METHOD(TApp, "SubcommandMinMax", "[creation]") {
+
+    CHECK(0u == app.get_require_subcommand_min());
+    CHECK(0u == app.get_require_subcommand_max());
+
+    app.require_subcommand();
+
+    CHECK(1u == app.get_require_subcommand_min());
+    CHECK(0u == app.get_require_subcommand_max());
+
+    app.require_subcommand(2);
+
+    CHECK(2u == app.get_require_subcommand_min());
+    CHECK(2u == app.get_require_subcommand_max());
+
+    app.require_subcommand(0);
+
+    CHECK(0u == app.get_require_subcommand_min());
+    CHECK(0u == app.get_require_subcommand_max());
+
+    app.require_subcommand(-2);
+
+    CHECK(0u == app.get_require_subcommand_min());
+    CHECK(2u == app.get_require_subcommand_max());
+
+    app.require_subcommand(3, 7);
+
+    CHECK(3u == app.get_require_subcommand_min());
+    CHECK(7u == app.get_require_subcommand_max());
+}
+
+TEST_CASE_METHOD(TApp, "GetOptionList", "[creation]") {
+    int two{0};
+    auto flag = app.add_flag("--one");
+    auto opt = app.add_option("--two", two);
+
+    const CLI::App &const_app = app;  // const alias to force use of const-methods
+    std::vector<const CLI::Option *> opt_list = const_app.get_options();
+
+    REQUIRE(static_cast<std::size_t>(3) == opt_list.size());
+    CHECK(flag == opt_list.at(1));
+    CHECK(opt == opt_list.at(2));
+
+    std::vector<CLI::Option *> nonconst_opt_list = app.get_options();
+    for(std::size_t i = 0; i < opt_list.size(); ++i) {
+        CHECK(opt_list.at(i) == nonconst_opt_list.at(i));
+    }
+}
+
+TEST_CASE("ValidatorTests: TestValidatorCreation", "[creation]") {
+    std::function<std::string(std::string &)> op1 = [](std::string &val) {
+        return (val.size() >= 5) ? std::string{} : val;
+    };
+    CLI::Validator V(op1, "", "size");
+
+    CHECK("size" == V.get_name());
+    V.name("harry");
+    CHECK("harry" == V.get_name());
+    CHECK(V.get_active());
+
+    CHECK("test" == V("test"));
+    CHECK(std::string{} == V("test5"));
+
+    CHECK(std::string{} == V.get_description());
+    V.description("this is a description");
+    CHECK("this is a description" == V.get_description());
+}
+
+TEST_CASE("ValidatorTests: TestValidatorOps", "[creation]") {
+    std::function<std::string(std::string &)> op1 = [](std::string &val) {
+        return (val.size() >= 5) ? std::string{} : val;
+    };
+    std::function<std::string(std::string &)> op2 = [](std::string &val) {
+        return (val.size() >= 9) ? std::string{} : val;
+    };
+    std::function<std::string(std::string &)> op3 = [](std::string &val) {
+        return (val.size() < 3) ? std::string{} : val;
+    };
+    std::function<std::string(std::string &)> op4 = [](std::string &val) {
+        return (val.size() <= 9) ? std::string{} : val;
+    };
+    CLI::Validator V1(op1, "SIZE >= 5");
+
+    CLI::Validator V2(op2, "SIZE >= 9");
+    CLI::Validator V3(op3, "SIZE < 3");
+    CLI::Validator V4(op4, "SIZE <= 9");
+
+    std::string two(2, 'a');
+    std::string four(4, 'a');
+    std::string five(5, 'a');
+    std::string eight(8, 'a');
+    std::string nine(9, 'a');
+    std::string ten(10, 'a');
+    CHECK(V1(five).empty());
+    CHECK(!V1(four).empty());
+
+    CHECK(V2(nine).empty());
+    CHECK(!V2(eight).empty());
+
+    CHECK(V3(two).empty());
+    CHECK(!V3(four).empty());
+
+    CHECK(V4(eight).empty());
+    CHECK(!V4(ten).empty());
+
+    auto V1a2 = V1 & V2;
+    CHECK("(SIZE >= 5) AND (SIZE >= 9)" == V1a2.get_description());
+    CHECK(!V1a2(five).empty());
+    CHECK(V1a2(nine).empty());
+
+    auto V1a4 = V1 & V4;
+    CHECK("(SIZE >= 5) AND (SIZE <= 9)" == V1a4.get_description());
+    CHECK(V1a4(five).empty());
+    CHECK(V1a4(eight).empty());
+    CHECK(!V1a4(ten).empty());
+    CHECK(!V1a4(four).empty());
+
+    auto V1o3 = V1 | V3;
+    CHECK("(SIZE >= 5) OR (SIZE < 3)" == V1o3.get_description());
+    CHECK(V1o3(two).empty());
+    CHECK(V1o3(eight).empty());
+    CHECK(V1o3(ten).empty());
+    CHECK(V1o3(two).empty());
+    CHECK(!V1o3(four).empty());
+
+    auto m1 = V1o3 & V4;
+    CHECK("((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)" == m1.get_description());
+    CHECK(m1(two).empty());
+    CHECK(m1(eight).empty());
+    CHECK(!m1(ten).empty());
+    CHECK(m1(two).empty());
+    CHECK(m1(five).empty());
+    CHECK(!m1(four).empty());
+
+    auto m2 = m1 & V2;
+    CHECK("(((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)) AND (SIZE >= 9)" == m2.get_description());
+    CHECK(!m2(two).empty());
+    CHECK(!m2(eight).empty());
+    CHECK(!m2(ten).empty());
+    CHECK(!m2(two).empty());
+    CHECK(m2(nine).empty());
+    CHECK(!m2(four).empty());
+
+    auto m3 = m2 | V3;
+    CHECK("((((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)) AND (SIZE >= 9)) OR (SIZE < 3)" == m3.get_description());
+    CHECK(m3(two).empty());
+    CHECK(!m3(eight).empty());
+    CHECK(m3(nine).empty());
+    CHECK(!m3(four).empty());
+
+    auto m4 = V3 | m2;
+    CHECK("(SIZE < 3) OR ((((SIZE >= 5) OR (SIZE < 3)) AND (SIZE <= 9)) AND (SIZE >= 9))" == m4.get_description());
+    CHECK(m4(two).empty());
+    CHECK(!m4(eight).empty());
+    CHECK(m4(nine).empty());
+    CHECK(!m4(four).empty());
+}
+
+TEST_CASE("ValidatorTests: TestValidatorNegation", "[creation]") {
+
+    std::function<std::string(std::string &)> op1 = [](std::string &val) {
+        return (val.size() >= 5) ? std::string{} : val;
+    };
+
+    CLI::Validator V1(op1, "SIZE >= 5", "size");
+
+    std::string four(4, 'a');
+    std::string five(5, 'a');
+
+    CHECK(V1(five).empty());
+    CHECK(!V1(four).empty());
+
+    auto V2 = !V1;
+    CHECK(!V2(five).empty());
+    CHECK(V2(four).empty());
+    CHECK("NOT SIZE >= 5" == V2.get_description());
+
+    V2.active(false);
+    CHECK(V2(five).empty());
+    CHECK(V2(four).empty());
+    CHECK(V2.get_description().empty());
+}
+
+TEST_CASE("ValidatorTests: ValidatorDefaults", "[creation]") {
+
+    CLI::Validator V1{};
+
+    std::string four(4, 'a');
+    std::string five(5, 'a');
+
+    // make sure this doesn't generate a seg fault or something
+    CHECK(V1(five).empty());
+    CHECK(V1(four).empty());
+
+    CHECK(V1.get_name().empty());
+    CHECK(V1.get_description().empty());
+    CHECK(V1.get_active());
+    CHECK(V1.get_modifying());
+
+    CLI::Validator V2{"check"};
+    // make sure this doesn't generate a seg fault or something
+    CHECK(V2(five).empty());
+    CHECK(V2(four).empty());
+
+    CHECK(V2.get_name().empty());
+    CHECK("check" == V2.get_description());
+    CHECK(V2.get_active());
+    CHECK(V2.get_modifying());
+    // This class only support streaming in, not out
+}
+
+class Unstreamable {
+  private:
+    int x_{-1};
+
+  public:
+    Unstreamable() = default;
+    int get_x() const { return x_; }
+    void set_x(int x) { x_ = x; }
+};
+
+// this needs to be a different check then the one after the function definition otherwise they conflict
+static_assert(!CLI::detail::is_istreamable<Unstreamable, std::istream>::value, "Unstreamable type is streamable");
+
+std::istream &operator>>(std::istream &in, Unstreamable &value) {
+    int x;
+    in >> x;
+    value.set_x(x);
+    return in;
+}
+// these need to be different classes otherwise the definitions conflict
+static_assert(CLI::detail::is_istreamable<Unstreamable>::value,
+              "Unstreamable type is still unstreamable and it should be");
+
+TEST_CASE_METHOD(TApp, "MakeUnstreamableOptions", "[creation]") {
+    Unstreamable value;
+    app.add_option("--value", value);
+
+    // This used to fail to build, since it tries to stream from Unstreamable
+    app.add_option("--value2", value);
+
+    std::vector<Unstreamable> values;
+    app.add_option("--values", values);
+
+    // This used to fail to build, since it tries to stream from Unstreamable
+    app.add_option("--values2", values);
+
+    args = {"--value", "45"};
+    run();
+    CHECK(45 == value.get_x());
+
+    args = {"--values", "45", "27", "34"};
+    run();
+    CHECK(3u == values.size());
+    CHECK(34 == values[2].get_x());
+}
diff --git a/external/CLI11/tests/DeprecatedTest.cpp b/external/CLI11/tests/DeprecatedTest.cpp
new file mode 100644
index 0000000..f763a89
--- /dev/null
+++ b/external/CLI11/tests/DeprecatedTest.cpp
@@ -0,0 +1,14 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+
+using Catch::Matchers::Contains;
+
+TEST_CASE("Deprecated: Empty", "[deprecated]") {
+    // No deprecated features at this time.
+    CHECK(true);
+}
diff --git a/external/CLI11/tests/FormatterTest.cpp b/external/CLI11/tests/FormatterTest.cpp
new file mode 100644
index 0000000..1516945
--- /dev/null
+++ b/external/CLI11/tests/FormatterTest.cpp
@@ -0,0 +1,211 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifdef CLI11_SINGLE_FILE
+#include "CLI11.hpp"
+#else
+#include "CLI/CLI.hpp"
+#endif
+
+#include "catch.hpp"
+#include <fstream>
+
+using Catch::Matchers::Contains;
+
+class SimpleFormatter : public CLI::FormatterBase {
+  public:
+    SimpleFormatter() : FormatterBase() {}
+
+    std::string make_help(const CLI::App *, std::string, CLI::AppFormatMode) const override {
+        return "This is really simple";
+    }
+};
+
+TEST_CASE("Formatter: Nothing", "[formatter]") {
+    CLI::App app{"My prog"};
+
+    app.formatter(std::make_shared<SimpleFormatter>());
+
+    std::string help = app.help();
+
+    CHECK("This is really simple" == help);
+}
+
+TEST_CASE("Formatter: NothingLambda", "[formatter]") {
+    CLI::App app{"My prog"};
+
+    app.formatter_fn(
+        [](const CLI::App *, std::string, CLI::AppFormatMode) { return std::string("This is really simple"); });
+
+    std::string help = app.help();
+
+    CHECK("This is really simple" == help);
+}
+
+TEST_CASE("Formatter: OptCustomize", "[formatter]") {
+    CLI::App app{"My prog"};
+
+    auto optfmt = std::make_shared<CLI::Formatter>();
+    optfmt->column_width(25);
+    optfmt->label("REQUIRED", "(MUST HAVE)");
+    app.formatter(optfmt);
+
+    int v{0};
+    app.add_option("--opt", v, "Something")->required();
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("(MUST HAVE)"));
+    CHECK(help == "My prog\n"
+                  "Usage: [OPTIONS]\n\n"
+                  "Options:\n"
+                  "  -h,--help              Print this help message and exit\n"
+                  "  --opt INT (MUST HAVE)  Something\n\n");
+}
+
+TEST_CASE("Formatter: OptCustomizeSimple", "[formatter]") {
+    CLI::App app{"My prog"};
+
+    app.get_formatter()->column_width(25);
+    app.get_formatter()->label("REQUIRED", "(MUST HAVE)");
+
+    int v{0};
+    app.add_option("--opt", v, "Something")->required();
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("(MUST HAVE)"));
+    CHECK(help == "My prog\n"
+                  "Usage: [OPTIONS]\n\n"
+                  "Options:\n"
+                  "  -h,--help              Print this help message and exit\n"
+                  "  --opt INT (MUST HAVE)  Something\n\n");
+}
+
+TEST_CASE("Formatter: OptCustomizeOptionText", "[formatter]") {
+    CLI::App app{"My prog"};
+
+    app.get_formatter()->column_width(25);
+
+    int v{0};
+    app.add_option("--opt", v, "Something")->option_text("(ARG)");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("(ARG)"));
+    CHECK(help == "My prog\n"
+                  "Usage: [OPTIONS]\n\n"
+                  "Options:\n"
+                  "  -h,--help              Print this help message and exit\n"
+                  "  --opt (ARG)            Something\n\n");
+}
+
+TEST_CASE("Formatter: FalseFlagExample", "[formatter]") {
+    CLI::App app{"My prog"};
+
+    app.get_formatter()->column_width(25);
+    app.get_formatter()->label("REQUIRED", "(MUST HAVE)");
+
+    int v{0};
+    app.add_flag("--opt,!--no_opt", v, "Something");
+
+    bool flag{false};
+    app.add_flag("!-O,--opt2,--no_opt2{false}", flag, "Something else");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("--no_opt{false}"));
+    CHECK_THAT(help, Contains("--no_opt2{false}"));
+    CHECK_THAT(help, Contains("-O{false}"));
+}
+
+TEST_CASE("Formatter: AppCustomize", "[formatter]") {
+    CLI::App app{"My prog"};
+    app.add_subcommand("subcom1", "This");
+
+    auto appfmt = std::make_shared<CLI::Formatter>();
+    appfmt->column_width(20);
+    appfmt->label("Usage", "Run");
+    app.formatter(appfmt);
+
+    app.add_subcommand("subcom2", "This");
+
+    std::string help = app.help();
+    CHECK(help == "My prog\n"
+                  "Run: [OPTIONS] [SUBCOMMAND]\n\n"
+                  "Options:\n"
+                  "  -h,--help         Print this help message and exit\n\n"
+                  "Subcommands:\n"
+                  "  subcom1           This\n"
+                  "  subcom2           This\n\n");
+}
+
+TEST_CASE("Formatter: AppCustomizeSimple", "[formatter]") {
+    CLI::App app{"My prog"};
+    app.add_subcommand("subcom1", "This");
+
+    app.get_formatter()->column_width(20);
+    app.get_formatter()->label("Usage", "Run");
+
+    app.add_subcommand("subcom2", "This");
+
+    std::string help = app.help();
+    CHECK(help == "My prog\n"
+                  "Run: [OPTIONS] [SUBCOMMAND]\n\n"
+                  "Options:\n"
+                  "  -h,--help         Print this help message and exit\n\n"
+                  "Subcommands:\n"
+                  "  subcom1           This\n"
+                  "  subcom2           This\n\n");
+}
+
+TEST_CASE("Formatter: AllSub", "[formatter]") {
+    CLI::App app{"My prog"};
+    CLI::App *sub = app.add_subcommand("subcom", "This");
+    sub->add_flag("--insub", "MyFlag");
+
+    std::string help = app.help("", CLI::AppFormatMode::All);
+    CHECK_THAT(help, Contains("--insub"));
+    CHECK_THAT(help, Contains("subcom"));
+}
+
+TEST_CASE("Formatter: AllSubRequired", "[formatter]") {
+    CLI::App app{"My prog"};
+    CLI::App *sub = app.add_subcommand("subcom", "This");
+    sub->add_flag("--insub", "MyFlag");
+    sub->required();
+    std::string help = app.help("", CLI::AppFormatMode::All);
+    CHECK_THAT(help, Contains("--insub"));
+    CHECK_THAT(help, Contains("subcom"));
+    CHECK_THAT(help, Contains("REQUIRED"));
+}
+
+TEST_CASE("Formatter: NamelessSub", "[formatter]") {
+    CLI::App app{"My prog"};
+    CLI::App *sub = app.add_subcommand("", "This subcommand");
+    sub->add_flag("--insub", "MyFlag");
+
+    std::string help = app.help("", CLI::AppFormatMode::Normal);
+    CHECK_THAT(help, Contains("--insub"));
+    CHECK_THAT(help, Contains("This subcommand"));
+}
+
+TEST_CASE("Formatter: NamelessSubInGroup", "[formatter]") {
+    CLI::App app{"My prog"};
+    CLI::App *sub = app.add_subcommand("", "This subcommand");
+    CLI::App *sub2 = app.add_subcommand("sub2", "subcommand2");
+    sub->add_flag("--insub", "MyFlag");
+    int val{0};
+    sub2->add_option("pos", val, "positional");
+    sub->group("group1");
+    sub2->group("group1");
+    std::string help = app.help("", CLI::AppFormatMode::Normal);
+    CHECK_THAT(help, Contains("--insub"));
+    CHECK_THAT(help, Contains("This subcommand"));
+    CHECK_THAT(help, Contains("group1"));
+    CHECK_THAT(help, Contains("sub2"));
+    CHECK(help.find("pos") == std::string::npos);
+}
diff --git a/external/CLI11/tests/HelpTest.cpp b/external/CLI11/tests/HelpTest.cpp
new file mode 100644
index 0000000..cb8c81a
--- /dev/null
+++ b/external/CLI11/tests/HelpTest.cpp
@@ -0,0 +1,1263 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifdef CLI11_SINGLE_FILE
+#include "CLI11.hpp"
+#else
+#include "CLI/CLI.hpp"
+#endif
+
+#include "catch.hpp"
+#include <fstream>
+
+using Catch::Matchers::Contains;
+
+TEST_CASE("THelp: Basic", "[help]") {
+    CLI::App app{"My prog"};
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, Contains("-h,--help"));
+    CHECK_THAT(help, Contains("Options:"));
+    CHECK_THAT(help, Contains("Usage:"));
+}
+
+TEST_CASE("THelp: Footer", "[help]") {
+    CLI::App app{"My prog"};
+    app.footer("Report bugs to bugs@example.com");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, Contains("-h,--help"));
+    CHECK_THAT(help, Contains("Options:"));
+    CHECK_THAT(help, Contains("Usage:"));
+    CHECK_THAT(help, Contains("Report bugs to bugs@example.com"));
+}
+
+TEST_CASE("THelp: FooterCallback", "[help]") {
+    CLI::App app{"My prog"};
+    app.footer([]() { return "Report bugs to bugs@example.com"; });
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, Contains("-h,--help"));
+    CHECK_THAT(help, Contains("Options:"));
+    CHECK_THAT(help, Contains("Usage:"));
+    CHECK_THAT(help, Contains("Report bugs to bugs@example.com"));
+}
+
+TEST_CASE("THelp: FooterCallbackBoth", "[help]") {
+    CLI::App app{"My prog"};
+    app.footer([]() { return "Report bugs to bugs@example.com"; });
+    app.footer(" foot!!!!");
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, Contains("-h,--help"));
+    CHECK_THAT(help, Contains("Options:"));
+    CHECK_THAT(help, Contains("Usage:"));
+    CHECK_THAT(help, Contains("Report bugs to bugs@example.com"));
+    CHECK_THAT(help, Contains("foot!!!!"));
+}
+
+TEST_CASE("THelp: OptionalPositional", "[help]") {
+    CLI::App app{"My prog", "program"};
+
+    std::string x;
+    app.add_option("something", x, "My option here");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, Contains("-h,--help"));
+    CHECK_THAT(help, Contains("Options:"));
+    CHECK_THAT(help, Contains("Positionals:"));
+    CHECK_THAT(help, Contains("something TEXT"));
+    CHECK_THAT(help, Contains("My option here"));
+    CHECK_THAT(help, Contains("Usage: program [OPTIONS] [something]"));
+}
+
+TEST_CASE("THelp: Hidden", "[help]") {
+    CLI::App app{"My prog"};
+
+    std::string x;
+    app.add_option("something", x, "My option here")->group("");
+    std::string y;
+    app.add_option("--another", y)->group("");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, Contains("-h,--help"));
+    CHECK_THAT(help, Contains("Options:"));
+    CHECK_THAT(help, !Contains("[something]"));
+    CHECK_THAT(help, !Contains("something "));
+    CHECK_THAT(help, !Contains("another"));
+}
+
+TEST_CASE("THelp: deprecatedOptions", "[help]") {
+    CLI::App app{"My prog"};
+
+    std::string x;
+    auto soption = app.add_option("--something", x, "My option here");
+    app.add_option("--something_else", x, "My option here");
+    std::string y;
+    app.add_option("--another", y);
+
+    CLI::deprecate_option(soption, "something_else");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("DEPRECATED"));
+    CHECK_THAT(help, Contains("something"));
+    CHECK_NOTHROW(app.parse("--something deprecated"));
+}
+
+TEST_CASE("THelp: deprecatedOptions2", "[help]") {
+    CLI::App app{"My prog"};
+
+    std::string x;
+    app.add_option("--something", x, "My option here");
+    app.add_option("--something_else", x, "My option here");
+    std::string y;
+    app.add_option("--another", y);
+
+    CLI::deprecate_option(&app, "--something");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("DEPRECATED"));
+    CHECK_THAT(help, Contains("something"));
+    CHECK_NOTHROW(app.parse("--something deprecated"));
+}
+
+TEST_CASE("THelp: deprecatedOptions3", "[help]") {
+    CLI::App app{"My prog"};
+
+    std::string x;
+    app.add_option("--something", x, "Some Description");
+    app.add_option("--something_else", x, "Some other description");
+    std::string y;
+    app.add_option("--another", y);
+
+    CLI::deprecate_option(app, "--something", "--something_else");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("DEPRECATED"));
+    CHECK_THAT(help, Contains("'--something_else' instead"));
+    CHECK_NOTHROW(app.parse("--something deprecated"));
+}
+
+TEST_CASE("THelp: retiredOptions", "[help]") {
+    CLI::App app{"My prog"};
+
+    std::string x;
+    auto opt1 = app.add_option("--something", x, "My option here");
+    app.add_option("--something_else", x, "My option here");
+    std::string y;
+    app.add_option("--another", y);
+
+    CLI::retire_option(app, opt1);
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("RETIRED"));
+    CHECK_THAT(help, Contains("something"));
+
+    CHECK_NOTHROW(app.parse("--something old"));
+}
+
+TEST_CASE("THelp: retiredOptions2", "[help]") {
+    CLI::App app{"My prog"};
+
+    std::string x;
+    app.add_option("--something_else", x, "My option here");
+    std::string y;
+    app.add_option("--another", y);
+
+    CLI::retire_option(&app, "--something");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("RETIRED"));
+    CHECK_THAT(help, Contains("something"));
+    CHECK_NOTHROW(app.parse("--something old"));
+}
+
+TEST_CASE("THelp: retiredOptions3", "[help]") {
+    CLI::App app{"My prog"};
+
+    std::string x;
+    app.add_option("--something", x, "My option here");
+    app.add_option("--something_else", x, "My option here");
+    std::string y;
+    app.add_option("--another", y);
+
+    CLI::retire_option(app, "--something");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("RETIRED"));
+    CHECK_THAT(help, Contains("something"));
+
+    CHECK_NOTHROW(app.parse("--something old"));
+}
+
+TEST_CASE("THelp: HiddenGroup", "[help]") {
+    CLI::App app{"My prog"};
+    // empty option group name should be hidden
+    auto hgroup = app.add_option_group("");
+    std::string x;
+    hgroup->add_option("something", x, "My option here");
+    std::string y;
+    hgroup->add_option("--another", y);
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, Contains("-h,--help"));
+    CHECK_THAT(help, Contains("Options:"));
+    CHECK_THAT(help, !Contains("[something]"));
+    CHECK_THAT(help, !Contains("something "));
+    CHECK_THAT(help, !Contains("another"));
+
+    hgroup->group("ghidden");
+
+    help = app.help();
+
+    CHECK_THAT(help, Contains("something "));
+    CHECK_THAT(help, Contains("another"));
+}
+
+TEST_CASE("THelp: OptionalPositionalAndOptions", "[help]") {
+    CLI::App app{"My prog", "AnotherProgram"};
+    app.add_flag("-q,--quick");
+
+    std::string x;
+    app.add_option("something", x, "My option here");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, Contains("-h,--help"));
+    CHECK_THAT(help, Contains("Options:"));
+    CHECK_THAT(help, Contains("Usage: AnotherProgram [OPTIONS] [something]"));
+}
+
+TEST_CASE("THelp: RequiredPositionalAndOptions", "[help]") {
+    CLI::App app{"My prog"};
+    app.add_flag("-q,--quick");
+
+    std::string x;
+    app.add_option("something", x, "My option here")->required();
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, Contains("-h,--help"));
+    CHECK_THAT(help, Contains("Options:"));
+    CHECK_THAT(help, Contains("Positionals:"));
+    CHECK_THAT(help, Contains("Usage: [OPTIONS] something"));
+}
+
+TEST_CASE("THelp: MultiOpts", "[help]") {
+    CLI::App app{"My prog"};
+    std::vector<int> x, y;
+    app.add_option("-q,--quick", x, "Disc")->expected(2);
+    app.add_option("-v,--vals", y, "Other");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, !Contains("Positionals:"));
+    CHECK_THAT(help, Contains("Usage: [OPTIONS]"));
+    CHECK_THAT(help, Contains("INT x 2"));
+    CHECK_THAT(help, Contains("INT ..."));
+}
+
+TEST_CASE("THelp: VectorOpts", "[help]") {
+    CLI::App app{"My prog"};
+    std::vector<int> x = {1, 2};
+    app.add_option("-q,--quick", x)->capture_default_str();
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("INT=[1,2] ..."));
+}
+
+TEST_CASE("THelp: MultiPosOpts", "[help]") {
+    CLI::App app{"My prog"};
+    app.name("program");
+    std::vector<int> x, y;
+    app.add_option("quick", x, "Disc")->expected(2);
+    app.add_option("vals", y, "Other");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, Contains("Positionals:"));
+    CHECK_THAT(help, Contains("Usage: program [OPTIONS]"));
+    CHECK_THAT(help, Contains("INT x 2"));
+    CHECK_THAT(help, Contains("INT ..."));
+    CHECK_THAT(help, Contains("[quick(2x)]"));
+    CHECK_THAT(help, Contains("[vals...]"));
+}
+
+TEST_CASE("THelp: EnvName", "[help]") {
+    CLI::App app{"My prog"};
+    std::string input;
+    app.add_option("--something", input)->envname("SOME_ENV");
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("SOME_ENV"));
+}
+
+TEST_CASE("THelp: Needs", "[help]") {
+    CLI::App app{"My prog"};
+
+    CLI::Option *op1 = app.add_flag("--op1");
+    app.add_flag("--op2")->needs(op1);
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("Needs: --op1"));
+}
+
+TEST_CASE("THelp: NeedsPositional", "[help]") {
+    CLI::App app{"My prog"};
+
+    int x{0}, y{0};
+
+    CLI::Option *op1 = app.add_option("op1", x, "one");
+    app.add_option("op2", y, "two")->needs(op1);
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("Positionals:"));
+    CHECK_THAT(help, Contains("Needs: op1"));
+}
+
+TEST_CASE("THelp: Excludes", "[help]") {
+    CLI::App app{"My prog"};
+
+    CLI::Option *op1 = app.add_flag("--op1");
+    app.add_flag("--op2")->excludes(op1);
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("Excludes: --op1"));
+}
+
+TEST_CASE("THelp: ExcludesPositional", "[help]") {
+    CLI::App app{"My prog"};
+
+    int x{0}, y{0};
+
+    CLI::Option *op1 = app.add_option("op1", x);
+    app.add_option("op2", y)->excludes(op1);
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("Positionals:"));
+    CHECK_THAT(help, Contains("Excludes: op1"));
+}
+
+TEST_CASE("THelp: ExcludesSymmetric", "[help]") {
+    CLI::App app{"My prog"};
+
+    CLI::Option *op1 = app.add_flag("--op1");
+    app.add_flag("--op2")->excludes(op1);
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("Excludes: --op2"));
+}
+
+TEST_CASE("THelp: ManualSetters", "[help]") {
+
+    CLI::App app{"My prog"};
+
+    int x{1};
+
+    CLI::Option *op1 = app.add_option("--op", x);
+    op1->default_str("12");
+    op1->type_name("BIGGLES");
+    CHECK(1 == x);
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("=12"));
+    CHECK_THAT(help, Contains("BIGGLES"));
+
+    op1->default_val("14");
+    CHECK(14 == x);
+    help = app.help();
+    CHECK_THAT(help, Contains("=14"));
+
+    op1->default_val(12);
+    CHECK(12 == x);
+    help = app.help();
+    CHECK_THAT(help, Contains("=12"));
+
+    CHECK(op1->get_run_callback_for_default());
+    op1->run_callback_for_default(false);
+    CHECK(!op1->get_run_callback_for_default());
+
+    op1->default_val(18);
+    // x should not be modified in this case
+    CHECK(12 == x);
+    help = app.help();
+    CHECK_THAT(help, Contains("=18"));
+}
+
+TEST_CASE("THelp: ManualSetterOverFunction", "[help]") {
+
+    CLI::App app{"My prog"};
+
+    int x{1};
+
+    CLI::Option *op1 = app.add_option("--op1", x)->check(CLI::IsMember({1, 2}));
+    CLI::Option *op2 = app.add_option("--op2", x)->transform(CLI::IsMember({1, 2}));
+    op1->default_str("12");
+    op1->type_name("BIGGLES");
+    op2->type_name("QUIGGLES");
+    CHECK(1 == x);
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("=12"));
+    CHECK_THAT(help, Contains("BIGGLES"));
+    CHECK_THAT(help, Contains("QUIGGLES"));
+    CHECK_THAT(help, Contains("{1,2}"));
+}
+
+TEST_CASE("THelp: Subcom", "[help]") {
+    CLI::App app{"My prog"};
+
+    auto sub1 = app.add_subcommand("sub1");
+    app.add_subcommand("sub2");
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("Usage: [OPTIONS] [SUBCOMMAND]"));
+
+    app.require_subcommand();
+
+    help = app.help();
+    CHECK_THAT(help, Contains("Usage: [OPTIONS] SUBCOMMAND"));
+
+    help = sub1->help();
+    CHECK_THAT(help, Contains("Usage: sub1"));
+
+    char x[] = "./myprogram";
+    char y[] = "sub2";
+
+    std::vector<char *> args = {x, y};
+    app.parse(static_cast<int>(args.size()), args.data());
+
+    help = app.help();
+    CHECK_THAT(help, Contains("Usage: ./myprogram sub2"));
+}
+
+TEST_CASE("THelp: Subcom_alias", "[help]") {
+    CLI::App app{"My prog"};
+
+    auto sub1 = app.add_subcommand("sub1", "Subcommand1 description test");
+    sub1->alias("sub_alias1");
+    sub1->alias("sub_alias2");
+
+    app.add_subcommand("sub2", "Subcommand2 description test");
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("Usage: [OPTIONS] [SUBCOMMAND]"));
+    CHECK_THAT(help, Contains("sub_alias1"));
+    CHECK_THAT(help, Contains("sub_alias2"));
+}
+
+TEST_CASE("THelp: Subcom_alias_group", "[help]") {
+    CLI::App app{"My prog"};
+
+    auto sub1 = app.add_subcommand("", "Subcommand1 description test");
+    sub1->alias("sub_alias1");
+    sub1->alias("sub_alias2");
+
+    app.add_subcommand("sub2", "Subcommand2 description test");
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("Usage: [OPTIONS] [SUBCOMMAND]"));
+    CHECK_THAT(help, Contains("sub_alias1"));
+    CHECK_THAT(help, Contains("sub_alias2"));
+}
+
+TEST_CASE("THelp: MasterName", "[help]") {
+    CLI::App app{"My prog", "MyRealName"};
+
+    char x[] = "./myprogram";
+
+    std::vector<char *> args = {x};
+    app.parse(static_cast<int>(args.size()), args.data());
+
+    CHECK_THAT(app.help(), Contains("Usage: MyRealName"));
+}
+
+TEST_CASE("THelp: IntDefaults", "[help]") {
+    CLI::App app{"My prog"};
+
+    int one{1}, two{2};
+    app.add_option("--one", one, "Help for one")->capture_default_str();
+    app.add_option("--set", two, "Help for set")->capture_default_str()->check(CLI::IsMember({2, 3, 4}));
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("--one"));
+    CHECK_THAT(help, Contains("--set"));
+    CHECK_THAT(help, Contains("1"));
+    CHECK_THAT(help, Contains("=2"));
+    CHECK_THAT(help, Contains("2,3,4"));
+}
+
+TEST_CASE("THelp: SetLower", "[help]") {
+    CLI::App app{"My prog"};
+    app.option_defaults()->always_capture_default();
+
+    std::string def{"One"};
+    app.add_option("--set", def, "Help for set")->check(CLI::IsMember({"oNe", "twO", "THREE"}));
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("--set"));
+    CHECK_THAT(help, Contains("=One"));
+    CHECK_THAT(help, Contains("oNe"));
+    CHECK_THAT(help, Contains("twO"));
+    CHECK_THAT(help, Contains("THREE"));
+}
+
+TEST_CASE("THelp: OnlyOneHelp", "[help]") {
+    CLI::App app{"My prog"};
+
+    // It is not supported to have more than one help flag, last one wins
+    app.set_help_flag("--help", "No short name allowed");
+    app.set_help_flag("--yelp", "Alias for help");
+
+    std::vector<std::string> input{"--help"};
+    CHECK_THROWS_AS(app.parse(input), CLI::ExtrasError);
+}
+
+TEST_CASE("THelp: MultiHelp", "[help]") {
+    CLI::App app{"My prog"};
+
+    // It is not supported to have more than one help flag, last one wins
+    app.set_help_flag("--help,-h,-?", "No short name allowed");
+    app.allow_windows_style_options();
+
+    std::vector<std::string> input{"/?"};
+    CHECK_THROWS_AS(app.parse(input), CLI::CallForHelp);
+}
+
+TEST_CASE("THelp: OnlyOneAllHelp", "[help]") {
+    CLI::App app{"My prog"};
+
+    // It is not supported to have more than one help flag, last one wins
+    app.set_help_all_flag("--help-all", "No short name allowed");
+    app.set_help_all_flag("--yelp", "Alias for help");
+
+    std::vector<std::string> input{"--help-all"};
+    CHECK_THROWS_AS(app.parse(input), CLI::ExtrasError);
+
+    std::vector<std::string> input2{"--yelp"};
+    CHECK_THROWS_AS(app.parse(input2), CLI::CallForAllHelp);
+
+    // Remove the flag
+    app.set_help_all_flag();
+    std::vector<std::string> input3{"--yelp"};
+    CHECK_THROWS_AS(app.parse(input3), CLI::ExtrasError);
+}
+
+TEST_CASE("THelp: RemoveHelp", "[help]") {
+    CLI::App app{"My prog"};
+    app.set_help_flag();
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, !Contains("-h,--help"));
+    CHECK_THAT(help, !Contains("Options:"));
+    CHECK_THAT(help, Contains("Usage:"));
+
+    std::vector<std::string> input{"--help"};
+    try {
+        app.parse(input);
+    } catch(const CLI::ParseError &e) {
+        CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::ExtrasError));
+    }
+}
+
+TEST_CASE("THelp: RemoveOtherMethodHelp", "[help]") {
+    CLI::App app{"My prog"};
+
+    // Don't do this. Just in case, let's make sure it works.
+    app.remove_option(const_cast<CLI::Option *>(app.get_help_ptr()));
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, !Contains("-h,--help"));
+    CHECK_THAT(help, !Contains("Options:"));
+    CHECK_THAT(help, Contains("Usage:"));
+
+    std::vector<std::string> input{"--help"};
+    try {
+        app.parse(input);
+    } catch(const CLI::ParseError &e) {
+        CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::ExtrasError));
+    }
+}
+
+TEST_CASE("THelp: RemoveOtherMethodHelpAll", "[help]") {
+    CLI::App app{"My prog"};
+
+    app.set_help_all_flag("--help-all");
+    // Don't do this. Just in case, let's make sure it works.
+    app.remove_option(const_cast<CLI::Option *>(app.get_help_all_ptr()));
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, !Contains("--help-all"));
+    CHECK_THAT(help, Contains("Options:"));
+    CHECK_THAT(help, Contains("Usage:"));
+
+    std::vector<std::string> input{"--help-all"};
+    try {
+        app.parse(input);
+    } catch(const CLI::ParseError &e) {
+        CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::ExtrasError));
+    }
+}
+
+TEST_CASE("THelp: NoHelp", "[help]") {
+    CLI::App app{"My prog"};
+    app.set_help_flag();
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, !Contains("-h,--help"));
+    CHECK_THAT(help, !Contains("Options:"));
+    CHECK_THAT(help, Contains("Usage:"));
+
+    std::vector<std::string> input{"--help"};
+    try {
+        app.parse(input);
+    } catch(const CLI::ParseError &e) {
+        CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::ExtrasError));
+    }
+}
+
+TEST_CASE("THelp: CustomHelp", "[help]") {
+    CLI::App app{"My prog"};
+
+    CLI::Option *help_option = app.set_help_flag("--yelp", "display help and exit");
+    CHECK(help_option == app.get_help_ptr());
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("My prog"));
+    CHECK_THAT(help, !Contains("-h,--help"));
+    CHECK_THAT(help, Contains("--yelp"));
+    CHECK_THAT(help, Contains("Options:"));
+    CHECK_THAT(help, Contains("Usage:"));
+
+    std::vector<std::string> input{"--yelp"};
+    try {
+        app.parse(input);
+    } catch(const CLI::CallForHelp &e) {
+        CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::Success));
+    }
+}
+
+TEST_CASE("THelp: NextLineShouldBeAlignmentInMultilineDescription", "[help]") {
+    CLI::App app;
+    int i{0};
+    const std::string first{"first line"};
+    const std::string second{"second line"};
+    app.add_option("-i,--int", i, first + "\n" + second);
+
+    const std::string help = app.help();
+    const auto width = app.get_formatter()->get_column_width();
+    CHECK_THAT(help, Contains(first + "\n" + std::string(width, ' ') + second));
+}
+
+TEST_CASE("THelp: NiceName", "[help]") {
+    CLI::App app;
+
+    int x{0};
+    auto long_name = app.add_option("-s,--long,-q,--other,that", x);
+    auto short_name = app.add_option("more,-x,-y", x);
+    auto positional = app.add_option("posit", x);
+
+    CHECK("--long" == long_name->get_name());
+    CHECK("-x" == short_name->get_name());
+    CHECK("posit" == positional->get_name());
+}
+
+TEST_CASE("Exit: ErrorWithHelp", "[help]") {
+    CLI::App app{"My prog"};
+
+    std::vector<std::string> input{"-h"};
+    try {
+        app.parse(input);
+    } catch(const CLI::CallForHelp &e) {
+        CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::Success));
+    }
+}
+
+TEST_CASE("Exit: ErrorWithAllHelp", "[help]") {
+    CLI::App app{"My prog"};
+    app.set_help_all_flag("--help-all", "All help");
+
+    std::vector<std::string> input{"--help-all"};
+    try {
+        app.parse(input);
+    } catch(const CLI::CallForAllHelp &e) {
+        CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::Success));
+    }
+}
+
+TEST_CASE("Exit: ErrorWithoutHelp", "[help]") {
+    CLI::App app{"My prog"};
+
+    std::vector<std::string> input{"--none"};
+    try {
+        app.parse(input);
+    } catch(const CLI::ParseError &e) {
+        CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::ExtrasError));
+    }
+}
+
+TEST_CASE("Exit: ExitCodes", "[help]") {
+    CLI::App app;
+
+    auto i = static_cast<int>(CLI::ExitCodes::ExtrasError);
+    CHECK(app.exit(CLI::Success()) == 0);
+    CHECK(app.exit(CLI::CallForHelp()) == 0);
+    CHECK(app.exit(CLI::ExtrasError({"Thing"})) == i);
+    CHECK(app.exit(CLI::RuntimeError(42)) == 42);
+    CHECK(app.exit(CLI::RuntimeError()) == 1);
+}
+
+struct CapturedHelp {
+    CLI::App app{"My Test Program"};
+    std::stringstream out{};
+    std::stringstream err{};
+
+    int run(const CLI::Error &e) { return app.exit(e, out, err); }
+
+    void reset() {
+        out.clear();
+        err.clear();
+    }
+};
+
+TEST_CASE_METHOD(CapturedHelp, "Successful", "[help]") {
+    CHECK(0 == run(CLI::Success()));
+    CHECK("" == out.str());
+    CHECK("" == err.str());
+}
+
+TEST_CASE_METHOD(CapturedHelp, "JustAnError", "[help]") {
+    CHECK(42 == run(CLI::RuntimeError(42)));
+    CHECK("" == out.str());
+    CHECK("" == err.str());
+}
+
+TEST_CASE_METHOD(CapturedHelp, "CallForHelp", "[help]") {
+    CHECK(0 == run(CLI::CallForHelp()));
+    CHECK(app.help() == out.str());
+    CHECK("" == err.str());
+}
+TEST_CASE_METHOD(CapturedHelp, "CallForAllHelp", "[help]") {
+    CHECK(0 == run(CLI::CallForAllHelp()));
+    CHECK(app.help("", CLI::AppFormatMode::All) == out.str());
+    CHECK("" == err.str());
+}
+TEST_CASE_METHOD(CapturedHelp, "CallForAllHelpOutput", "[help]") {
+    app.set_help_all_flag("--help-all", "Help all");
+    app.add_subcommand("one", "One description");
+    CLI::App *sub = app.add_subcommand("two");
+    sub->add_flag("--three");
+
+    CHECK(0 == run(CLI::CallForAllHelp()));
+    CHECK(app.help("", CLI::AppFormatMode::All) == out.str());
+    CHECK("" == err.str());
+    CHECK_THAT(out.str(), Contains("one"));
+    CHECK_THAT(out.str(), Contains("two"));
+    CHECK_THAT(out.str(), Contains("--three"));
+
+    CHECK(out.str() == "My Test Program\n"
+                       "Usage: [OPTIONS] [SUBCOMMAND]\n"
+                       "\n"
+                       "Options:\n"
+                       "  -h,--help                   Print this help message and exit\n"
+                       "  --help-all                  Help all\n"
+                       "\n"
+                       "Subcommands:\n"
+                       "one\n"
+                       "  One description\n\n"
+                       "two\n"
+                       "  Options:\n"
+                       "    --three                     \n\n\n");
+}
+TEST_CASE_METHOD(CapturedHelp, "NewFormattedHelp", "[help]") {
+    app.formatter_fn([](const CLI::App *, std::string, CLI::AppFormatMode) { return "New Help"; });
+    CHECK(0 == run(CLI::CallForHelp()));
+    CHECK("New Help" == out.str());
+    CHECK("" == err.str());
+}
+
+TEST_CASE_METHOD(CapturedHelp, "NormalError", "[help]") {
+    CHECK(static_cast<int>(CLI::ExitCodes::ExtrasError) == run(CLI::ExtrasError({"Thing"})));
+    CHECK("" == out.str());
+    CHECK_THAT(err.str(), Contains("for more information"));
+    CHECK_THAT(err.str(), !Contains("ExtrasError"));
+    CHECK_THAT(err.str(), Contains("Thing"));
+    CHECK_THAT(err.str(), !Contains(" or "));
+    CHECK_THAT(err.str(), !Contains("Usage"));
+}
+
+TEST_CASE_METHOD(CapturedHelp, "DoubleError", "[help]") {
+    app.set_help_all_flag("--help-all");
+    CHECK(static_cast<int>(CLI::ExitCodes::ExtrasError) == run(CLI::ExtrasError({"Thing"})));
+    CHECK("" == out.str());
+    CHECK_THAT(err.str(), Contains("for more information"));
+    CHECK_THAT(err.str(), Contains(" --help "));
+    CHECK_THAT(err.str(), Contains(" --help-all "));
+    CHECK_THAT(err.str(), Contains(" or "));
+    CHECK_THAT(err.str(), !Contains("ExtrasError"));
+    CHECK_THAT(err.str(), Contains("Thing"));
+    CHECK_THAT(err.str(), !Contains("Usage"));
+}
+
+TEST_CASE_METHOD(CapturedHelp, "AllOnlyError", "[help]") {
+    app.set_help_all_flag("--help-all");
+    app.set_help_flag();
+    CHECK(static_cast<int>(CLI::ExitCodes::ExtrasError) == run(CLI::ExtrasError({"Thing"})));
+    CHECK("" == out.str());
+    CHECK_THAT(err.str(), Contains("for more information"));
+    CHECK_THAT(err.str(), !Contains(" --help "));
+    CHECK_THAT(err.str(), Contains(" --help-all "));
+    CHECK_THAT(err.str(), !Contains(" or "));
+    CHECK_THAT(err.str(), !Contains("ExtrasError"));
+    CHECK_THAT(err.str(), Contains("Thing"));
+    CHECK_THAT(err.str(), !Contains("Usage"));
+}
+
+TEST_CASE_METHOD(CapturedHelp, "ReplacedError", "[help]") {
+    app.failure_message(CLI::FailureMessage::help);
+
+    CHECK(static_cast<int>(CLI::ExitCodes::ExtrasError) == run(CLI::ExtrasError({"Thing"})));
+    CHECK("" == out.str());
+    CHECK_THAT(err.str(), !Contains("for more information"));
+    CHECK_THAT(err.str(), Contains("ERROR: ExtrasError"));
+    CHECK_THAT(err.str(), Contains("Thing"));
+    CHECK_THAT(err.str(), Contains("Usage"));
+}
+
+// #87
+TEST_CASE("THelp: CustomDoubleOption", "[help]") {
+
+    std::pair<int, double> custom_opt;
+
+    CLI::App app;
+
+    auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) {
+        custom_opt = {stol(vals.at(0)), stod(vals.at(1))};
+        return true;
+    });
+    opt->type_name("INT FLOAT")->type_size(2);
+
+    CHECK_THAT(app.help(), !Contains("x 2"));
+}
+
+TEST_CASE("THelp: CheckEmptyTypeName", "[help]") {
+    CLI::App app;
+
+    auto opt = app.add_flag("-f,--flag");
+    std::string name = opt->get_type_name();
+    CHECK(name.empty());
+}
+
+TEST_CASE("THelp: AccessDescription", "[help]") {
+    CLI::App app{"My description goes here"};
+
+    CHECK("My description goes here" == app.get_description());
+}
+
+TEST_CASE("THelp: SetDescriptionAfterCreation", "[help]") {
+    CLI::App app{""};
+
+    app.description("My description goes here");
+
+    CHECK("My description goes here" == app.get_description());
+    CHECK_THAT(app.help(), Contains("My description goes here"));
+}
+
+TEST_CASE("THelp: AccessOptionDescription", "[help]") {
+    CLI::App app{};
+
+    int x{0};
+    auto opt = app.add_option("-a,--alpha", x, "My description goes here");
+
+    CHECK("My description goes here" == opt->get_description());
+}
+
+TEST_CASE("THelp: SetOptionDescriptionAfterCreation", "[help]") {
+    CLI::App app{};
+
+    int x{0};
+    auto opt = app.add_option("-a,--alpha", x);
+    opt->description("My description goes here");
+
+    CHECK("My description goes here" == opt->get_description());
+    CHECK_THAT(app.help(), Contains("My description goes here"));
+}
+
+TEST_CASE("THelp: CleanNeeds", "[help]") {
+    CLI::App app;
+
+    int x{0};
+    auto a_name = app.add_option("-a,--alpha", x);
+    app.add_option("-b,--boo", x)->needs(a_name);
+
+    CHECK_THAT(app.help(), !Contains("Requires"));
+    CHECK_THAT(app.help(), !Contains("Needs: -a,--alpha"));
+    CHECK_THAT(app.help(), Contains("Needs: --alpha"));
+}
+
+TEST_CASE("THelp: RequiredPrintout", "[help]") {
+    CLI::App app;
+
+    int x{0};
+    app.add_option("-a,--alpha", x)->required();
+
+    CHECK_THAT(app.help(), Contains(" REQUIRED"));
+}
+
+TEST_CASE("THelp: GroupOrder", "[help]") {
+    CLI::App app;
+
+    app.add_flag("--one")->group("zee");
+    app.add_flag("--two")->group("aee");
+
+    std::string help = app.help();
+
+    auto zee_loc = help.find("zee");
+    auto aee_loc = help.find("aee");
+
+    CHECK(std::string::npos != zee_loc);
+    CHECK(std::string::npos != aee_loc);
+    CHECK(aee_loc > zee_loc);
+}
+
+TEST_CASE("THelp: ValidatorsText", "[help]") {
+    CLI::App app;
+
+    std::string filename;
+    int x{0};
+    unsigned int y{0};
+    app.add_option("--f1", filename)->check(CLI::ExistingFile);
+    app.add_option("--f3", x)->check(CLI::Range(1, 4));
+    app.add_option("--f4", y)->check(CLI::Range(12));
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("TEXT:FILE"));
+    CHECK_THAT(help, Contains("INT in [1 - 4]"));
+    CHECK_THAT(help, Contains("UINT:INT in [0 - 12]"));
+}
+
+TEST_CASE("THelp: ValidatorsTextCustom", "[help]") {
+    CLI::App app;
+
+    std::string filename;
+    app.add_option("--f1", filename)->check(CLI::ExistingFile.description("Existing file"));
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("Existing file"));
+}
+
+TEST_CASE("THelp: ValidatorsNonPathText", "[help]") {
+    CLI::App app;
+
+    std::string filename;
+    app.add_option("--f2", filename)->check(CLI::NonexistentPath);
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("TEXT:PATH"));
+}
+
+TEST_CASE("THelp: ValidatorsDirText", "[help]") {
+    CLI::App app;
+
+    std::string filename;
+    app.add_option("--f2", filename)->check(CLI::ExistingDirectory);
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("TEXT:DIR"));
+}
+
+TEST_CASE("THelp: ValidatorsPathText", "[help]") {
+    CLI::App app;
+
+    std::string filename;
+    app.add_option("--f2", filename)->check(CLI::ExistingPath);
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("TEXT:PATH"));
+}
+
+TEST_CASE("THelp: CombinedValidatorsText", "[help]") {
+    CLI::App app;
+
+    std::string filename;
+    app.add_option("--f1", filename)->check(CLI::ExistingFile | CLI::ExistingDirectory);
+
+    // This would be nice if it put something other than string, but would it be path or file?
+    // Can't programmatically tell!
+    // (Users can use ExistingPath, by the way)
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("TEXT:(FILE) OR (DIR)"));
+    CHECK_THAT(help, !Contains("PATH"));
+}
+
+// Don't do this in real life, please
+TEST_CASE("THelp: CombinedValidatorsPathyText", "[help]") {
+    CLI::App app;
+
+    std::string filename;
+    app.add_option("--f1", filename)->check(CLI::ExistingPath | CLI::NonexistentPath);
+
+    // Combining validators with the same type string is OK
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("TEXT:"));
+    CHECK_THAT(help, Contains("PATH"));
+}
+
+// Don't do this in real life, please (and transform does nothing here)
+TEST_CASE("THelp: CombinedValidatorsPathyTextAsTransform", "[help]") {
+    CLI::App app;
+
+    std::string filename;
+    app.add_option("--f1", filename)->transform(CLI::ExistingPath | CLI::NonexistentPath);
+
+    // Combining validators with the same type string is OK
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("TEXT:(PATH(existing)) OR (PATH"));
+}
+
+// #113 Part 2
+TEST_CASE("THelp: ChangingSet", "[help]") {
+    CLI::App app;
+
+    std::set<int> vals{1, 2, 3};
+    int val{0};
+    app.add_option("--val", val)->check(CLI::IsMember(&vals));
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("1"));
+    CHECK_THAT(help, !Contains("4"));
+
+    vals.insert(4);
+    vals.erase(1);
+
+    help = app.help();
+
+    CHECK_THAT(help, !Contains("1"));
+    CHECK_THAT(help, Contains("4"));
+}
+
+TEST_CASE("THelp: ChangingSetDefaulted", "[help]") {
+    CLI::App app;
+
+    std::set<int> vals{1, 2, 3};
+    int val{2};
+    app.add_option("--val", val, "")->check(CLI::IsMember(&vals))->capture_default_str();
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("1"));
+    CHECK_THAT(help, !Contains("4"));
+
+    vals.insert(4);
+    vals.erase(1);
+
+    help = app.help();
+
+    CHECK_THAT(help, !Contains("1"));
+    CHECK_THAT(help, Contains("4"));
+}
+
+TEST_CASE("THelp: ChangingCaselessSet", "[help]") {
+    CLI::App app;
+
+    std::set<std::string> vals{"1", "2", "3"};
+    std::string val;
+    app.add_option("--val", val)->check(CLI::IsMember(&vals, CLI::ignore_case));
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("1"));
+    CHECK_THAT(help, !Contains("4"));
+
+    vals.insert("4");
+    vals.erase("1");
+
+    help = app.help();
+
+    CHECK_THAT(help, !Contains("1"));
+    CHECK_THAT(help, Contains("4"));
+}
+
+TEST_CASE("THelp: ChangingCaselessSetDefaulted", "[help]") {
+    CLI::App app;
+    app.option_defaults()->always_capture_default();
+
+    std::set<std::string> vals{"1", "2", "3"};
+    std::string val = "2";
+    app.add_option("--val", val)->check(CLI::IsMember(&vals, CLI::ignore_case));
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("1"));
+    CHECK_THAT(help, !Contains("4"));
+
+    vals.insert("4");
+    vals.erase("1");
+
+    help = app.help();
+
+    CHECK_THAT(help, !Contains("1"));
+    CHECK_THAT(help, Contains("4"));
+}
+
+// New defaults tests (1.8)
+
+TEST_CASE("THelp: ChangingDefaults", "[help]") {
+
+    CLI::App app;
+
+    std::vector<int> x = {1, 2};
+    CLI::Option *opt = app.add_option("-q,--quick", x);
+    x = {3, 4};
+    CHECK(x[0] == 3);
+
+    opt->capture_default_str();
+
+    x = {5, 6};
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("INT=[3,4] ..."));
+    CHECK(x[0] == 5);
+}
+
+TEST_CASE("THelp: ChangingDefaultsWithAutoCapture", "[help]") {
+
+    CLI::App app;
+    app.option_defaults()->always_capture_default();
+
+    std::vector<int> x = {1, 2};
+    CHECK(x[0] == 1);
+    app.add_option("-q,--quick", x);
+    x = {3, 4};
+    CHECK(x[0] == 3);
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("INT=[1,2] ..."));
+}
+
+TEST_CASE("THelp: FunctionDefaultString", "[help]") {
+
+    CLI::App app;
+
+    std::vector<int> x = {1, 2};
+    CLI::Option *opt = app.add_option("-q,--quick", x);
+
+    opt->default_function([]() { return std::string("Powerful"); });
+    opt->capture_default_str();
+
+    std::string help = app.help();
+
+    CHECK_THAT(help, Contains("INT=Powerful"));
+}
+
+TEST_CASE("TVersion: simple_flag", "[help]") {
+
+    CLI::App app;
+
+    app.set_version_flag("-v,--version", "VERSION " CLI11_VERSION);
+
+    auto vers = app.version();
+    CHECK_THAT(vers, Contains("VERSION"));
+
+    app.set_version_flag();
+    CHECK(app.version().empty());
+}
+
+TEST_CASE("TVersion: callback_flag", "[help]") {
+
+    CLI::App app;
+
+    app.set_version_flag("-v,--version", []() { return std::string("VERSION " CLI11_VERSION); });
+
+    auto vers = app.version();
+    CHECK_THAT(vers, Contains("VERSION"));
+
+    app.set_version_flag("-v", []() { return std::string("VERSION2 " CLI11_VERSION); });
+    vers = app.version();
+    CHECK_THAT(vers, Contains("VERSION"));
+}
+
+TEST_CASE("TVersion: help", "[help]") {
+
+    CLI::App app;
+
+    app.set_version_flag("-v,--version", "version_string", "help_for_version");
+
+    auto hvers = app.help();
+    CHECK_THAT(hvers, Contains("help_for_version"));
+
+    app.set_version_flag(
+        "-v", []() { return std::string("VERSION2 " CLI11_VERSION); }, "help_for_version2");
+    hvers = app.help();
+    CHECK_THAT(hvers, Contains("help_for_version2"));
+}
+
+TEST_CASE("TVersion: parse_throw", "[help]") {
+
+    CLI::App app;
+
+    app.set_version_flag("--version", CLI11_VERSION);
+
+    CHECK_THROWS_AS(app.parse("--version"), CLI::CallForVersion);
+    CHECK_THROWS_AS(app.parse("--version --arg2 5"), CLI::CallForVersion);
+
+    auto ptr = app.get_version_ptr();
+
+    ptr->ignore_case();
+    try {
+        app.parse("--Version");
+    } catch(const CLI::CallForVersion &v) {
+        CHECK_THAT(CLI11_VERSION, Catch::Equals(v.what()));
+        CHECK(0 == v.get_exit_code());
+        const auto &appc = app;
+        auto cptr = appc.get_version_ptr();
+        CHECK(1U == cptr->count());
+    }
+}
diff --git a/external/CLI11/tests/HelpersTest.cpp b/external/CLI11/tests/HelpersTest.cpp
new file mode 100644
index 0000000..7a497aa
--- /dev/null
+++ b/external/CLI11/tests/HelpersTest.cpp
@@ -0,0 +1,1310 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+
+#include <array>
+#include <atomic>
+#include <complex>
+#include <cstdint>
+#include <cstdio>
+#include <fstream>
+#include <limits>
+#include <map>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+#include <utility>
+
+class NotStreamable {};
+
+class Streamable {};
+
+std::ostream &operator<<(std::ostream &out, const Streamable &) { return out << "Streamable"; }
+
+TEST_CASE("TypeTools: Streaming", "[helpers]") {
+
+    CHECK("" == CLI::detail::to_string(NotStreamable{}));
+
+    CHECK("Streamable" == CLI::detail::to_string(Streamable{}));
+
+    CHECK("5" == CLI::detail::to_string(5));
+
+    CHECK(std::string("string") == CLI::detail::to_string("string"));
+    CHECK(std::string("string") == CLI::detail::to_string(std::string("string")));
+}
+
+TEST_CASE("TypeTools: tuple", "[helpers]") {
+    CHECK_FALSE(CLI::detail::is_tuple_like<int>::value);
+    CHECK_FALSE(CLI::detail::is_tuple_like<std::vector<double>>::value);
+    auto v = CLI::detail::is_tuple_like<std::tuple<double, int>>::value;
+    CHECK(v);
+    v = CLI::detail::is_tuple_like<std::tuple<double, double, double>>::value;
+    CHECK(v);
+}
+
+TEST_CASE("TypeTools: type_size", "[helpers]") {
+    auto V = CLI::detail::type_count<int>::value;
+    CHECK(1 == V);
+    V = CLI::detail::type_count<void>::value;
+    CHECK(0 == V);
+    V = CLI::detail::type_count<std::vector<double>>::value;
+    CHECK(1 == V);
+    V = CLI::detail::type_count<std::tuple<double, int>>::value;
+    CHECK(2 == V);
+    V = CLI::detail::type_count<std::tuple<std::string, double, int>>::value;
+    CHECK(3 == V);
+    V = CLI::detail::type_count<std::array<std::string, 5>>::value;
+    CHECK(5 == V);
+    V = CLI::detail::type_count<std::vector<std::pair<std::string, double>>>::value;
+    CHECK(2 == V);
+    V = CLI::detail::type_count<std::tuple<std::pair<std::string, double>>>::value;
+    CHECK(2 == V);
+    V = CLI::detail::type_count<std::tuple<int, std::pair<std::string, double>>>::value;
+    CHECK(3 == V);
+    V = CLI::detail::type_count<std::tuple<std::pair<int, double>, std::pair<std::string, double>>>::value;
+    CHECK(4 == V);
+    // maps
+    V = CLI::detail::type_count<std::map<int, std::pair<int, double>>>::value;
+    CHECK(3 == V);
+    // three level tuples
+    V = CLI::detail::type_count<std::tuple<int, std::pair<int, std::tuple<int, double, std::string>>>>::value;
+    CHECK(5 == V);
+    V = CLI::detail::type_count<std::pair<int, std::vector<int>>>::value;
+    CHECK(CLI::detail::expected_max_vector_size <= V);
+    V = CLI::detail::type_count<std::vector<std::vector<int>>>::value;
+    CHECK(CLI::detail::expected_max_vector_size == V);
+}
+
+TEST_CASE("TypeTools: type_size_min", "[helpers]") {
+    auto V = CLI::detail::type_count_min<int>::value;
+    CHECK(1 == V);
+    V = CLI::detail::type_count_min<void>::value;
+    CHECK(0 == V);
+    V = CLI::detail::type_count_min<std::vector<double>>::value;
+    CHECK(1 == V);
+    V = CLI::detail::type_count_min<std::tuple<double, int>>::value;
+    CHECK(2 == V);
+    V = CLI::detail::type_count_min<std::tuple<std::string, double, int>>::value;
+    CHECK(3 == V);
+    V = CLI::detail::type_count_min<std::array<std::string, 5>>::value;
+    CHECK(5 == V);
+    V = CLI::detail::type_count_min<std::vector<std::pair<std::string, double>>>::value;
+    CHECK(2 == V);
+    V = CLI::detail::type_count_min<std::tuple<std::pair<std::string, double>>>::value;
+    CHECK(2 == V);
+    V = CLI::detail::type_count_min<std::tuple<int, std::pair<std::string, double>>>::value;
+    CHECK(3 == V);
+    V = CLI::detail::type_count_min<std::tuple<std::pair<int, double>, std::pair<std::string, double>>>::value;
+    CHECK(4 == V);
+    // maps
+    V = CLI::detail::type_count_min<std::map<int, std::pair<int, double>>>::value;
+    CHECK(3 == V);
+    // three level tuples
+    V = CLI::detail::type_count_min<std::tuple<int, std::pair<int, std::tuple<int, double, std::string>>>>::value;
+    CHECK(5 == V);
+    V = CLI::detail::type_count_min<std::pair<int, std::vector<int>>>::value;
+    CHECK(2 == V);
+    V = CLI::detail::type_count_min<std::vector<std::vector<int>>>::value;
+    CHECK(1 == V);
+    V = CLI::detail::type_count_min<std::vector<std::vector<std::pair<int, int>>>>::value;
+    CHECK(2 == V);
+}
+
+TEST_CASE("TypeTools: expected_count", "[helpers]") {
+    auto V = CLI::detail::expected_count<int>::value;
+    CHECK(1 == V);
+    V = CLI::detail::expected_count<void>::value;
+    CHECK(0 == V);
+    V = CLI::detail::expected_count<std::vector<double>>::value;
+    CHECK(CLI::detail::expected_max_vector_size == V);
+    V = CLI::detail::expected_count<std::tuple<double, int>>::value;
+    CHECK(1 == V);
+    V = CLI::detail::expected_count<std::tuple<std::string, double, int>>::value;
+    CHECK(1 == V);
+    V = CLI::detail::expected_count<std::array<std::string, 5>>::value;
+    CHECK(1 == V);
+    V = CLI::detail::expected_count<std::vector<std::pair<std::string, double>>>::value;
+    CHECK(CLI::detail::expected_max_vector_size == V);
+}
+
+TEST_CASE("Split: SimpleByToken", "[helpers]") {
+    auto out = CLI::detail::split("one.two.three", '.');
+    REQUIRE(out.size() == 3u);
+    CHECK(out.at(0) == "one");
+    CHECK(out.at(1) == "two");
+    CHECK(out.at(2) == "three");
+}
+
+TEST_CASE("Split: Single", "[helpers]") {
+    auto out = CLI::detail::split("one", '.');
+    REQUIRE(out.size() == 1u);
+    CHECK(out.at(0) == "one");
+}
+
+TEST_CASE("Split: Empty", "[helpers]") {
+    auto out = CLI::detail::split("", '.');
+    REQUIRE(out.size() == 1u);
+    CHECK(out.at(0) == "");
+}
+
+TEST_CASE("String: InvalidName", "[helpers]") {
+    CHECK(CLI::detail::valid_name_string("valid"));
+    CHECK_FALSE(CLI::detail::valid_name_string("-invalid"));
+    CHECK(CLI::detail::valid_name_string("va-li-d"));
+    CHECK_FALSE(CLI::detail::valid_name_string("valid{}"));
+    CHECK(CLI::detail::valid_name_string("_valid"));
+    CHECK(CLI::detail::valid_name_string("/valid"));
+    CHECK(CLI::detail::valid_name_string("vali?d"));
+    CHECK(CLI::detail::valid_name_string("@@@@"));
+    CHECK(CLI::detail::valid_name_string("b@d2?"));
+    CHECK(CLI::detail::valid_name_string("2vali?d"));
+    CHECK_FALSE(CLI::detail::valid_name_string("!valid"));
+}
+
+TEST_CASE("StringTools: Modify", "[helpers]") {
+    int cnt{0};
+    std::string newString = CLI::detail::find_and_modify("======", "=", [&cnt](std::string &str, std::size_t index) {
+        if((++cnt) % 2 == 0) {
+            str[index] = ':';
+        }
+        return index + 1;
+    });
+    CHECK("=:=:=:" == newString);
+}
+
+TEST_CASE("StringTools: Modify2", "[helpers]") {
+    std::string newString =
+        CLI::detail::find_and_modify("this is a string test", "is", [](std::string &str, std::size_t index) {
+            if((index > 1) && (str[index - 1] != ' ')) {
+                str[index] = 'a';
+                str[index + 1] = 't';
+            }
+            return index + 1;
+        });
+    CHECK("that is a string test" == newString);
+}
+
+TEST_CASE("StringTools: Modify3", "[helpers]") {
+    // this picks up 3 sets of 3 after the 'b' then collapses the new first set
+    std::string newString = CLI::detail::find_and_modify("baaaaaaaaaa", "aaa", [](std::string &str, std::size_t index) {
+        str.erase(index, 3);
+        str.insert(str.begin(), 'a');
+        return 0u;
+    });
+    CHECK("aba" == newString);
+}
+
+TEST_CASE("StringTools: flagValues", "[helpers]") {
+    CHECK(-1 == CLI::detail::to_flag_value("0"));
+    CHECK(1 == CLI::detail::to_flag_value("t"));
+    CHECK(1 == CLI::detail::to_flag_value("1"));
+    CHECK(6 == CLI::detail::to_flag_value("6"));
+    CHECK(-6 == CLI::detail::to_flag_value("-6"));
+    CHECK(-1 == CLI::detail::to_flag_value("false"));
+    CHECK(1 == CLI::detail::to_flag_value("YES"));
+    CHECK_THROWS_AS(CLI::detail::to_flag_value("frog"), std::invalid_argument);
+    CHECK_THROWS_AS(CLI::detail::to_flag_value("q"), std::invalid_argument);
+    CHECK(-1 == CLI::detail::to_flag_value("NO"));
+    CHECK(475555233 == CLI::detail::to_flag_value("475555233"));
+}
+
+TEST_CASE("StringTools: Validation", "[helpers]") {
+    CHECK(CLI::detail::isalpha(""));
+    CHECK(CLI::detail::isalpha("a"));
+    CHECK(CLI::detail::isalpha("abcd"));
+    CHECK_FALSE(CLI::detail::isalpha("_"));
+    CHECK_FALSE(CLI::detail::isalpha("2"));
+    CHECK_FALSE(CLI::detail::isalpha("test test"));
+    CHECK_FALSE(CLI::detail::isalpha("test "));
+    CHECK_FALSE(CLI::detail::isalpha(" test"));
+    CHECK_FALSE(CLI::detail::isalpha("test2"));
+}
+
+TEST_CASE("Trim: Various", "[helpers]") {
+    std::string s1{"  sdlfkj sdflk sd s  "};
+    std::string a1{"sdlfkj sdflk sd s"};
+    CLI::detail::trim(s1);
+    CHECK(s1 == a1);
+
+    std::string s2{" a \t"};
+    CLI::detail::trim(s2);
+    CHECK(s2 == "a");
+
+    std::string s3{" a \n"};
+    CLI::detail::trim(s3);
+    CHECK(s3 == "a");
+
+    std::string s4{" a b "};
+    CHECK(CLI::detail::trim(s4) == "a b");
+}
+
+TEST_CASE("Trim: VariousFilters", "[helpers]") {
+    std::string s1{"  sdlfkj sdflk sd s  "};
+    std::string a1{"sdlfkj sdflk sd s"};
+    CLI::detail::trim(s1, " ");
+    CHECK(s1 == a1);
+
+    std::string s2{" a \t"};
+    CLI::detail::trim(s2, " ");
+    CHECK(s2 == "a \t");
+
+    std::string s3{"abdavda"};
+    CLI::detail::trim(s3, "a");
+    CHECK(s3 == "bdavd");
+
+    std::string s4{"abcabcabc"};
+    CHECK(CLI::detail::trim(s4, "ab") == "cabcabc");
+}
+
+TEST_CASE("Trim: TrimCopy", "[helpers]") {
+    std::string orig{" cabc  "};
+    std::string trimmed = CLI::detail::trim_copy(orig);
+    CHECK(trimmed == "cabc");
+    CHECK(trimmed != orig);
+    CLI::detail::trim(orig);
+    CHECK(orig == trimmed);
+
+    orig = "abcabcabc";
+    trimmed = CLI::detail::trim_copy(orig, "ab");
+    CHECK(trimmed == "cabcabc");
+    CHECK(trimmed != orig);
+    CLI::detail::trim(orig, "ab");
+    CHECK(orig == trimmed);
+}
+
+TEST_CASE("Validators: FileExists", "[helpers]") {
+    std::string myfile{"TestFileNotUsed.txt"};
+    CHECK_FALSE(CLI::ExistingFile(myfile).empty());
+    bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a'));  // create file
+    CHECK(ok);
+    CHECK(CLI::ExistingFile(myfile).empty());
+
+    std::remove(myfile.c_str());
+    CHECK_FALSE(CLI::ExistingFile(myfile).empty());
+}
+
+TEST_CASE("Validators: FileNotExists", "[helpers]") {
+    std::string myfile{"TestFileNotUsed.txt"};
+    CHECK(CLI::NonexistentPath(myfile).empty());
+    bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a'));  // create file
+    CHECK(ok);
+    CHECK_FALSE(CLI::NonexistentPath(myfile).empty());
+
+    std::remove(myfile.c_str());
+    CHECK(CLI::NonexistentPath(myfile).empty());
+}
+
+TEST_CASE("Validators: FileIsDir", "[helpers]") {
+    std::string mydir{"../tests"};
+    CHECK("" != CLI::ExistingFile(mydir));
+}
+
+TEST_CASE("Validators: DirectoryExists", "[helpers]") {
+    std::string mydir{"../tests"};
+    CHECK("" == CLI::ExistingDirectory(mydir));
+}
+
+TEST_CASE("Validators: DirectoryNotExists", "[helpers]") {
+    std::string mydir{"nondirectory"};
+    CHECK("" != CLI::ExistingDirectory(mydir));
+}
+
+TEST_CASE("Validators: DirectoryIsFile", "[helpers]") {
+    std::string myfile{"TestFileNotUsed.txt"};
+    CHECK(CLI::NonexistentPath(myfile).empty());
+    bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a'));  // create file
+    CHECK(ok);
+    CHECK_FALSE(CLI::ExistingDirectory(myfile).empty());
+
+    std::remove(myfile.c_str());
+    CHECK(CLI::NonexistentPath(myfile).empty());
+}
+
+TEST_CASE("Validators: PathExistsDir", "[helpers]") {
+    std::string mydir{"../tests"};
+    CHECK("" == CLI::ExistingPath(mydir));
+}
+
+TEST_CASE("Validators: PathExistsFile", "[helpers]") {
+    std::string myfile{"TestFileNotUsed.txt"};
+    CHECK_FALSE(CLI::ExistingPath(myfile).empty());
+    bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a'));  // create file
+    CHECK(ok);
+    CHECK(CLI::ExistingPath(myfile).empty());
+
+    std::remove(myfile.c_str());
+    CHECK_FALSE(CLI::ExistingPath(myfile).empty());
+}
+
+TEST_CASE("Validators: PathNotExistsDir", "[helpers]") {
+    std::string mydir{"nonpath"};
+    CHECK("" != CLI::ExistingPath(mydir));
+}
+
+TEST_CASE("Validators: IPValidate1", "[helpers]") {
+    std::string ip = "1.1.1.1";
+    CHECK(CLI::ValidIPV4(ip).empty());
+    ip = "224.255.0.1";
+    CHECK(CLI::ValidIPV4(ip).empty());
+    ip = "-1.255.0.1";
+    CHECK_FALSE(CLI::ValidIPV4(ip).empty());
+    ip = "1.256.0.1";
+    CHECK_FALSE(CLI::ValidIPV4(ip).empty());
+    ip = "1.256.0.1";
+    CHECK_FALSE(CLI::ValidIPV4(ip).empty());
+    ip = "aaa";
+    CHECK_FALSE(CLI::ValidIPV4(ip).empty());
+    ip = "1.2.3.abc";
+    CHECK_FALSE(CLI::ValidIPV4(ip).empty());
+    ip = "11.22";
+    CHECK_FALSE(CLI::ValidIPV4(ip).empty());
+}
+
+TEST_CASE("Validators: PositiveValidator", "[helpers]") {
+    std::string num = "1.1.1.1";
+    CHECK_FALSE(CLI::PositiveNumber(num).empty());
+    num = "1";
+    CHECK(CLI::PositiveNumber(num).empty());
+    num = "10000";
+    CHECK(CLI::PositiveNumber(num).empty());
+    num = "0";
+    CHECK_FALSE(CLI::PositiveNumber(num).empty());
+    num = "+0.5";
+    CHECK(CLI::PositiveNumber(num).empty());
+    num = "-1";
+    CHECK_FALSE(CLI::PositiveNumber(num).empty());
+    num = "-1.5";
+    CHECK_FALSE(CLI::PositiveNumber(num).empty());
+    num = "a";
+    CHECK_FALSE(CLI::PositiveNumber(num).empty());
+}
+
+TEST_CASE("Validators: NonNegativeValidator", "[helpers]") {
+    std::string num = "1.1.1.1";
+    CHECK_FALSE(CLI::NonNegativeNumber(num).empty());
+    num = "1";
+    CHECK(CLI::NonNegativeNumber(num).empty());
+    num = "10000";
+    CHECK(CLI::NonNegativeNumber(num).empty());
+    num = "0";
+    CHECK(CLI::NonNegativeNumber(num).empty());
+    num = "+0.5";
+    CHECK(CLI::NonNegativeNumber(num).empty());
+    num = "-1";
+    CHECK_FALSE(CLI::NonNegativeNumber(num).empty());
+    num = "-1.5";
+    CHECK_FALSE(CLI::NonNegativeNumber(num).empty());
+    num = "a";
+    CHECK_FALSE(CLI::NonNegativeNumber(num).empty());
+}
+
+TEST_CASE("Validators: NumberValidator", "[helpers]") {
+    std::string num = "1.1.1.1";
+    CHECK_FALSE(CLI::Number(num).empty());
+    num = "1.7";
+    CHECK(CLI::Number(num).empty());
+    num = "10000";
+    CHECK(CLI::Number(num).empty());
+    num = "-0.000";
+    CHECK(CLI::Number(num).empty());
+    num = "+1.55";
+    CHECK(CLI::Number(num).empty());
+    num = "a";
+    CHECK_FALSE(CLI::Number(num).empty());
+}
+
+TEST_CASE("Validators: CombinedAndRange", "[helpers]") {
+    auto crange = CLI::Range(0, 12) & CLI::Range(4, 16);
+    CHECK(crange("4").empty());
+    CHECK(crange("12").empty());
+    CHECK(crange("7").empty());
+
+    CHECK_FALSE(crange("-2").empty());
+    CHECK_FALSE(crange("2").empty());
+    CHECK_FALSE(crange("15").empty());
+    CHECK_FALSE(crange("16").empty());
+    CHECK_FALSE(crange("18").empty());
+}
+
+TEST_CASE("Validators: CombinedOrRange", "[helpers]") {
+    auto crange = CLI::Range(0, 4) | CLI::Range(8, 12);
+
+    CHECK_FALSE(crange("-2").empty());
+    CHECK(crange("2").empty());
+    CHECK_FALSE(crange("5").empty());
+    CHECK(crange("8").empty());
+    CHECK(crange("12").empty());
+    CHECK_FALSE(crange("16").empty());
+}
+
+TEST_CASE("Validators: CombinedPaths", "[helpers]") {
+    std::string myfile{"TestFileNotUsed.txt"};
+    CHECK_FALSE(CLI::ExistingFile(myfile).empty());
+    bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a'));  // create file
+    CHECK(ok);
+
+    std::string dir{"../tests"};
+    std::string notpath{"nondirectory"};
+
+    auto path_or_dir = CLI::ExistingPath | CLI::ExistingDirectory;
+    CHECK(path_or_dir(dir).empty());
+    CHECK(path_or_dir(myfile).empty());
+    CHECK_FALSE(path_or_dir(notpath).empty());
+
+    auto file_or_dir = CLI::ExistingFile | CLI::ExistingDirectory;
+    CHECK(file_or_dir(dir).empty());
+    CHECK(file_or_dir(myfile).empty());
+    CHECK_FALSE(file_or_dir(notpath).empty());
+
+    auto path_and_dir = CLI::ExistingPath & CLI::ExistingDirectory;
+    CHECK(path_and_dir(dir).empty());
+    CHECK_FALSE(path_and_dir(myfile).empty());
+    CHECK_FALSE(path_and_dir(notpath).empty());
+
+    auto path_and_file = CLI::ExistingFile & CLI::ExistingDirectory;
+    CHECK_FALSE(path_and_file(dir).empty());
+    CHECK_FALSE(path_and_file(myfile).empty());
+    CHECK_FALSE(path_and_file(notpath).empty());
+
+    std::remove(myfile.c_str());
+    CHECK_FALSE(CLI::ExistingFile(myfile).empty());
+}
+
+TEST_CASE("Validators: ProgramNameSplit", "[helpers]") {
+    TempFile myfile{"program_name1.exe"};
+    {
+        std::ofstream out{myfile};
+        out << "useless string doesn't matter" << std::endl;
+    }
+    auto res =
+        CLI::detail::split_program_name(std::string("./") + std::string(myfile) + " this is a bunch of extra stuff  ");
+    CHECK(std::string("./") + std::string(myfile) == res.first);
+    CHECK("this is a bunch of extra stuff" == res.second);
+
+    TempFile myfile2{"program name1.exe"};
+    {
+        std::ofstream out{myfile2};
+        out << "useless string doesn't matter" << std::endl;
+    }
+    res = CLI::detail::split_program_name(std::string("   ") + std::string("./") + std::string(myfile2) +
+                                          "      this is a bunch of extra stuff  ");
+    CHECK(std::string("./") + std::string(myfile2) == res.first);
+    CHECK("this is a bunch of extra stuff" == res.second);
+
+    res = CLI::detail::split_program_name("./program_name    this is a bunch of extra stuff  ");
+    CHECK("./program_name" == res.first);
+    CHECK("this is a bunch of extra stuff" == res.second);
+
+    res = CLI::detail::split_program_name(std::string("  ./") + std::string(myfile) + "    ");
+    CHECK(std::string("./") + std::string(myfile) == res.first);
+    CHECK(res.second.empty());
+}
+
+TEST_CASE("CheckedMultiply: Int", "[helpers]") {
+    int a{10};
+    int b{-20};
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(-200 == a);
+
+    a = 0;
+    b = -20;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(0 == a);
+
+    a = 20;
+    b = 0;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(0 == a);
+
+    a = std::numeric_limits<int>::max();
+    b = 1;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<int>::max() == a);
+
+    a = std::numeric_limits<int>::max();
+    b = 2;
+    REQUIRE(!CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<int>::max() == a);
+
+    a = std::numeric_limits<int>::max();
+    b = -1;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(-std::numeric_limits<int>::max() == a);
+
+    a = std::numeric_limits<int>::max();
+    b = std::numeric_limits<int>::max();
+    REQUIRE(!CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<int>::max() == a);
+
+    a = std::numeric_limits<int>::min();
+    b = std::numeric_limits<int>::max();
+    REQUIRE(!CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<int>::min() == a);
+
+    a = std::numeric_limits<int>::min();
+    b = 1;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<int>::min() == a);
+
+    a = std::numeric_limits<int>::min();
+    b = -1;
+    REQUIRE(!CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<int>::min() == a);
+
+    b = std::numeric_limits<int>::min();
+    a = -1;
+    REQUIRE(!CLI::detail::checked_multiply(a, b));
+    REQUIRE(-1 == a);
+
+    a = std::numeric_limits<int>::min() / 100;
+    b = 99;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<int>::min() / 100 * 99 == a);
+
+    a = std::numeric_limits<int>::min() / 100;
+    b = -101;
+    REQUIRE(!CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<int>::min() / 100 == a);
+    a = 2;
+    b = std::numeric_limits<int>::min() / 2;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    a = std::numeric_limits<int>::min() / 2;
+    b = 2;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+
+    a = 4;
+    b = std::numeric_limits<int>::min() / 4;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+
+    a = 48;
+    b = std::numeric_limits<int>::min() / 48;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+}
+
+TEST_CASE("CheckedMultiply: SizeT", "[helpers]") {
+    std::size_t a = 10;
+    std::size_t b = 20;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(200u == a);
+
+    a = 0u;
+    b = 20u;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(0u == a);
+
+    a = 20u;
+    b = 0u;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(0u == a);
+
+    a = std::numeric_limits<std::size_t>::max();
+    b = 1u;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<std::size_t>::max() == a);
+
+    a = std::numeric_limits<std::size_t>::max();
+    b = 2u;
+    REQUIRE(!CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<std::size_t>::max() == a);
+
+    a = std::numeric_limits<std::size_t>::max();
+    b = std::numeric_limits<std::size_t>::max();
+    REQUIRE(!CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<std::size_t>::max() == a);
+
+    a = std::numeric_limits<std::size_t>::max() / 100;
+    b = 99u;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<std::size_t>::max() / 100u * 99u == a);
+}
+
+TEST_CASE("CheckedMultiply: Float", "[helpers]") {
+    float a{10.0F};
+    float b{20.0F};
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(200 == Approx(a));
+
+    a = 0.0F;
+    b = 20.0F;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(0 == Approx(a));
+
+    a = INFINITY;
+    b = 20.0F;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(INFINITY == Approx(a));
+
+    a = 2.0F;
+    b = -INFINITY;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(-INFINITY == Approx(a));
+
+    a = std::numeric_limits<float>::max() / 100.0F;
+    b = 1.0F;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<float>::max() / 100.0F == Approx(a));
+
+    a = std::numeric_limits<float>::max() / 100.0F;
+    b = 99.0F;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<float>::max() / 100.0F * 99.0F == Approx(a));
+
+    a = std::numeric_limits<float>::max() / 100.0F;
+    b = 101;
+    REQUIRE(!CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<float>::max() / 100.0F == Approx(a));
+
+    a = std::numeric_limits<float>::max() / 100.0F;
+    b = -99;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<float>::max() / 100.0F * -99.0F == Approx(a));
+
+    a = std::numeric_limits<float>::max() / 100.0F;
+    b = -101;
+    REQUIRE(!CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<float>::max() / 100.0F == Approx(a));
+}
+
+TEST_CASE("CheckedMultiply: Double", "[helpers]") {
+    double a{10.0F};
+    double b{20.0F};
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(200 == Approx(a));
+
+    a = 0;
+    b = 20;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(0 == Approx(a));
+
+    a = INFINITY;
+    b = 20;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(INFINITY == Approx(a));
+
+    a = 2;
+    b = -INFINITY;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(-INFINITY == Approx(a));
+
+    a = std::numeric_limits<double>::max() / 100;
+    b = 1;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<double>::max() / 100 == Approx(a));
+
+    a = std::numeric_limits<double>::max() / 100;
+    b = 99;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<double>::max() / 100 * 99 == Approx(a));
+
+    a = std::numeric_limits<double>::max() / 100;
+    b = 101;
+    REQUIRE(!CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<double>::max() / 100 == Approx(a));
+
+    a = std::numeric_limits<double>::max() / 100;
+    b = -99;
+    REQUIRE(CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<double>::max() / 100 * -99 == Approx(a));
+
+    a = std::numeric_limits<double>::max() / 100;
+    b = -101;
+    REQUIRE(!CLI::detail::checked_multiply(a, b));
+    REQUIRE(std::numeric_limits<double>::max() / 100 == Approx(a));
+}
+
+// Yes, this is testing an app_helper :)
+TEST_CASE("AppHelper: TempfileCreated", "[helpers]") {
+    std::string name = "TestFileNotUsed.txt";
+    {
+        TempFile myfile{name};
+
+        CHECK_FALSE(CLI::ExistingFile(myfile).empty());
+
+        bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a'));  // create file
+        CHECK(ok);
+        CHECK(CLI::ExistingFile(name).empty());
+        CHECK_THROWS_AS([&]() { TempFile otherfile(name); }(), std::runtime_error);
+    }
+    CHECK_FALSE(CLI::ExistingFile(name).empty());
+}
+
+TEST_CASE("AppHelper: TempfileNotCreated", "[helpers]") {
+    std::string name = "TestFileNotUsed.txt";
+    {
+        TempFile myfile{name};
+
+        CHECK_FALSE(CLI::ExistingFile(myfile).empty());
+    }
+    CHECK_FALSE(CLI::ExistingFile(name).empty());
+}
+
+TEST_CASE("AppHelper: Ofstream", "[helpers]") {
+
+    std::string name = "TestFileNotUsed.txt";
+    {
+        TempFile myfile(name);
+
+        {
+            std::ofstream out{myfile};
+            out << "this is output" << std::endl;
+        }
+
+        CHECK(CLI::ExistingFile(myfile).empty());
+    }
+    CHECK_FALSE(CLI::ExistingFile(name).empty());
+}
+
+TEST_CASE("Split: StringList", "[helpers]") {
+
+    std::vector<std::string> results{"a", "long", "--lone", "-q"};
+    CHECK(CLI::detail::split_names("a,long,--lone,-q") == results);
+    CHECK(CLI::detail::split_names(" a, long, --lone, -q") == results);
+    CHECK(CLI::detail::split_names(" a , long , --lone , -q ") == results);
+    CHECK(CLI::detail::split_names("   a  ,  long  ,  --lone  ,    -q  ") == results);
+
+    CHECK(CLI::detail::split_names("one") == std::vector<std::string>({"one"}));
+}
+
+TEST_CASE("RegEx: Shorts", "[helpers]") {
+    std::string name, value;
+
+    CHECK(CLI::detail::split_short("-a", name, value));
+    CHECK(name == "a");
+    CHECK(value == "");
+
+    CHECK(CLI::detail::split_short("-B", name, value));
+    CHECK(name == "B");
+    CHECK(value == "");
+
+    CHECK(CLI::detail::split_short("-cc", name, value));
+    CHECK(name == "c");
+    CHECK(value == "c");
+
+    CHECK(CLI::detail::split_short("-simple", name, value));
+    CHECK(name == "s");
+    CHECK(value == "imple");
+
+    CHECK_FALSE(CLI::detail::split_short("--a", name, value));
+    CHECK_FALSE(CLI::detail::split_short("--thing", name, value));
+    CHECK_FALSE(CLI::detail::split_short("--", name, value));
+    CHECK_FALSE(CLI::detail::split_short("something", name, value));
+    CHECK_FALSE(CLI::detail::split_short("s", name, value));
+}
+
+TEST_CASE("RegEx: Longs", "[helpers]") {
+    std::string name, value;
+
+    CHECK(CLI::detail::split_long("--a", name, value));
+    CHECK(name == "a");
+    CHECK(value == "");
+
+    CHECK(CLI::detail::split_long("--thing", name, value));
+    CHECK(name == "thing");
+    CHECK(value == "");
+
+    CHECK(CLI::detail::split_long("--some=thing", name, value));
+    CHECK(name == "some");
+    CHECK(value == "thing");
+
+    CHECK_FALSE(CLI::detail::split_long("-a", name, value));
+    CHECK_FALSE(CLI::detail::split_long("-things", name, value));
+    CHECK_FALSE(CLI::detail::split_long("Q", name, value));
+    CHECK_FALSE(CLI::detail::split_long("--", name, value));
+}
+
+TEST_CASE("RegEx: SplittingNew", "[helpers]") {
+
+    std::vector<std::string> shorts;
+    std::vector<std::string> longs;
+    std::string pname;
+
+    CHECK_NOTHROW(std::tie(shorts, longs, pname) = CLI::detail::get_names({"--long", "-s", "-q", "--also-long"}));
+    CHECK(longs == std::vector<std::string>({"long", "also-long"}));
+    CHECK(shorts == std::vector<std::string>({"s", "q"}));
+    CHECK(pname == "");
+
+    std::tie(shorts, longs, pname) = CLI::detail::get_names({"--long", "", "-s", "-q", "", "--also-long"});
+    CHECK(longs == std::vector<std::string>({"long", "also-long"}));
+    CHECK(shorts == std::vector<std::string>({"s", "q"}));
+
+    CHECK_THROWS_AS([&]() { std::tie(shorts, longs, pname) = CLI::detail::get_names({"-"}); }(), CLI::BadNameString);
+    CHECK_THROWS_AS([&]() { std::tie(shorts, longs, pname) = CLI::detail::get_names({"--"}); }(), CLI::BadNameString);
+    CHECK_THROWS_AS([&]() { std::tie(shorts, longs, pname) = CLI::detail::get_names({"-hi"}); }(), CLI::BadNameString);
+    CHECK_THROWS_AS([&]() { std::tie(shorts, longs, pname) = CLI::detail::get_names({"---hi"}); }(),
+                    CLI::BadNameString);
+    CHECK_THROWS_AS(
+        [&]() {
+            std::tie(shorts, longs, pname) = CLI::detail::get_names({"one", "two"});
+        }(),
+        CLI::BadNameString);
+}
+
+TEST_CASE("String: ToLower", "[helpers]") { CHECK("one and two" == CLI::detail::to_lower("one And TWO")); }
+
+TEST_CASE("Join: Forward", "[helpers]") {
+    std::vector<std::string> val{{"one", "two", "three"}};
+    CHECK(CLI::detail::join(val) == "one,two,three");
+    CHECK(CLI::detail::join(val, ";") == "one;two;three");
+}
+
+TEST_CASE("Join: Backward", "[helpers]") {
+    std::vector<std::string> val{{"three", "two", "one"}};
+    CHECK(CLI::detail::rjoin(val) == "one,two,three");
+    CHECK(CLI::detail::rjoin(val, ";") == "one;two;three");
+}
+
+TEST_CASE("SplitUp: Simple", "[helpers]") {
+    std::vector<std::string> oput = {"one", "two three"};
+    std::string orig{R"(one "two three")"};
+    std::vector<std::string> result = CLI::detail::split_up(orig);
+    CHECK(result == oput);
+}
+
+TEST_CASE("SplitUp: SimpleDifferentQuotes", "[helpers]") {
+    std::vector<std::string> oput = {"one", "two three"};
+    std::string orig{R"(one `two three`)"};
+    std::vector<std::string> result = CLI::detail::split_up(orig);
+    CHECK(result == oput);
+}
+
+TEST_CASE("SplitUp: SimpleDifferentQuotes2", "[helpers]") {
+    std::vector<std::string> oput = {"one", "two three"};
+    std::string orig{R"(one 'two three')"};
+    std::vector<std::string> result = CLI::detail::split_up(orig);
+    CHECK(result == oput);
+}
+
+TEST_CASE("SplitUp: Layered", "[helpers]") {
+    std::vector<std::string> output = {R"(one 'two three')"};
+    std::string orig{R"("one 'two three'")"};
+    std::vector<std::string> result = CLI::detail::split_up(orig);
+    CHECK(result == output);
+}
+
+TEST_CASE("SplitUp: Spaces", "[helpers]") {
+    std::vector<std::string> oput = {"one", "  two three"};
+    std::string orig{R"(  one  "  two three" )"};
+    std::vector<std::string> result = CLI::detail::split_up(orig);
+    CHECK(result == oput);
+}
+
+TEST_CASE("SplitUp: BadStrings", "[helpers]") {
+    std::vector<std::string> oput = {"one", "  two three"};
+    std::string orig{R"(  one  "  two three )"};
+    std::vector<std::string> result = CLI::detail::split_up(orig);
+    CHECK(result == oput);
+
+    oput = {"one", "  two three"};
+    orig = R"(  one  '  two three )";
+    result = CLI::detail::split_up(orig);
+    CHECK(result == oput);
+}
+
+TEST_CASE("Types: TypeName", "[helpers]") {
+    std::string int_name = CLI::detail::type_name<int>();
+    CHECK(int_name == "INT");
+
+    std::string int2_name = CLI::detail::type_name<std::int16_t>();
+    CHECK(int2_name == "INT");
+
+    std::string uint_name = CLI::detail::type_name<unsigned char>();
+    CHECK(uint_name == "UINT");
+
+    std::string float_name = CLI::detail::type_name<double>();
+    CHECK(float_name == "FLOAT");
+
+    std::string char_name = CLI::detail::type_name<char>();
+    CHECK(char_name == "CHAR");
+
+    std::string vector_name = CLI::detail::type_name<std::vector<int>>();
+    CHECK(vector_name == "INT");
+
+    vector_name = CLI::detail::type_name<std::vector<double>>();
+    CHECK(vector_name == "FLOAT");
+
+    static_assert(CLI::detail::classify_object<std::pair<int, std::string>>::value ==
+                      CLI::detail::object_category::tuple_value,
+                  "pair<int,string> does not read like a tuple");
+
+    static_assert(CLI::detail::classify_object<std::tuple<std::string, double>>::value ==
+                      CLI::detail::object_category::tuple_value,
+                  "tuple<string,double> does not read like a tuple");
+
+    std::string pair_name = CLI::detail::type_name<std::vector<std::pair<int, std::string>>>();
+    CHECK(pair_name == "[INT,TEXT]");
+
+    vector_name = CLI::detail::type_name<std::vector<std::vector<unsigned char>>>();
+    CHECK(vector_name == "UINT");
+
+    auto vclass = CLI::detail::classify_object<std::vector<std::vector<unsigned char>>>::value;
+    CHECK(CLI::detail::object_category::container_value == vclass);
+
+    auto tclass = CLI::detail::classify_object<std::tuple<double>>::value;
+    CHECK(CLI::detail::object_category::number_constructible == tclass);
+
+    std::string tuple_name = CLI::detail::type_name<std::tuple<double>>();
+    CHECK(tuple_name == "FLOAT");
+
+    static_assert(CLI::detail::classify_object<std::tuple<int, std::string>>::value ==
+                      CLI::detail::object_category::tuple_value,
+                  "tuple<int,string> does not read like a tuple");
+    tuple_name = CLI::detail::type_name<std::tuple<int, std::string>>();
+    CHECK(tuple_name == "[INT,TEXT]");
+
+    tuple_name = CLI::detail::type_name<std::tuple<const int, std::string>>();
+    CHECK(tuple_name == "[INT,TEXT]");
+
+    tuple_name = CLI::detail::type_name<const std::tuple<int, std::string>>();
+    CHECK(tuple_name == "[INT,TEXT]");
+
+    tuple_name = CLI::detail::type_name<std::tuple<std::string, double>>();
+    CHECK(tuple_name == "[TEXT,FLOAT]");
+
+    tuple_name = CLI::detail::type_name<const std::tuple<std::string, double>>();
+    CHECK(tuple_name == "[TEXT,FLOAT]");
+
+    tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double>>();
+    CHECK(tuple_name == "[INT,TEXT,FLOAT]");
+
+    tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double, unsigned int>>();
+    CHECK(tuple_name == "[INT,TEXT,FLOAT,UINT]");
+
+    tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double, unsigned int, std::string>>();
+    CHECK(tuple_name == "[INT,TEXT,FLOAT,UINT,TEXT]");
+
+    tuple_name = CLI::detail::type_name<std::array<int, 10>>();
+    CHECK(tuple_name == "[INT,INT,INT,INT,INT,INT,INT,INT,INT,INT]");
+
+    std::string text_name = CLI::detail::type_name<std::string>();
+    CHECK(text_name == "TEXT");
+
+    std::string text2_name = CLI::detail::type_name<char *>();
+    CHECK(text2_name == "TEXT");
+
+    enum class test { test1, test2, test3 };
+    std::string enum_name = CLI::detail::type_name<test>();
+    CHECK(enum_name == "ENUM");
+
+    vclass = CLI::detail::classify_object<std::tuple<test>>::value;
+    CHECK(CLI::detail::object_category::tuple_value == vclass);
+    static_assert(CLI::detail::classify_object<std::tuple<test>>::value == CLI::detail::object_category::tuple_value,
+                  "tuple<test> does not classify as a tuple");
+    std::string enum_name2 = CLI::detail::type_name<std::tuple<test>>();
+    CHECK(enum_name2 == "ENUM");
+    std::string umapName = CLI::detail::type_name<std::unordered_map<int, std::tuple<std::string, double>>>();
+    CHECK(umapName == "[INT,[TEXT,FLOAT]]");
+
+    // On older compilers, this may show up as other/TEXT
+    vclass = CLI::detail::classify_object<std::atomic<int>>::value;
+    CHECK((CLI::detail::object_category::wrapper_value == vclass || CLI::detail::object_category::other == vclass));
+
+    std::string atomic_name = CLI::detail::type_name<std::atomic<int>>();
+    CHECK((atomic_name == "INT" || atomic_name == "TEXT"));
+}
+
+TEST_CASE("Types: OverflowSmall", "[helpers]") {
+    signed char x;
+    auto strmax = std::to_string(std::numeric_limits<signed char>::max() + 1);
+    CHECK_FALSE(CLI::detail::lexical_cast(strmax, x));
+
+    unsigned char y;
+    strmax = std::to_string(std::numeric_limits<unsigned char>::max() + 1);
+    CHECK_FALSE(CLI::detail::lexical_cast(strmax, y));
+}
+
+TEST_CASE("Types: LexicalCastInt", "[helpers]") {
+    std::string signed_input = "-912";
+    int x_signed;
+    CHECK(CLI::detail::lexical_cast(signed_input, x_signed));
+    CHECK(x_signed == -912);
+
+    std::string unsigned_input = "912";
+    unsigned int x_unsigned;
+    CHECK(CLI::detail::lexical_cast(unsigned_input, x_unsigned));
+    CHECK(x_unsigned == (unsigned int)912);
+
+    CHECK_FALSE(CLI::detail::lexical_cast(signed_input, x_unsigned));
+
+    unsigned char y;
+    std::string overflow_input = std::to_string(std::numeric_limits<uint64_t>::max()) + "0";
+    CHECK_FALSE(CLI::detail::lexical_cast(overflow_input, y));
+
+    char y_signed;
+    CHECK_FALSE(CLI::detail::lexical_cast(overflow_input, y_signed));
+
+    std::string bad_input = "hello";
+    CHECK_FALSE(CLI::detail::lexical_cast(bad_input, y));
+
+    std::string extra_input = "912i";
+    CHECK_FALSE(CLI::detail::lexical_cast(extra_input, y));
+
+    std::string empty_input{};
+    CHECK_FALSE(CLI::detail::lexical_cast(empty_input, x_signed));
+    CHECK_FALSE(CLI::detail::lexical_cast(empty_input, x_unsigned));
+    CHECK_FALSE(CLI::detail::lexical_cast(empty_input, y_signed));
+}
+
+TEST_CASE("Types: LexicalCastDouble", "[helpers]") {
+    std::string input = "9.12";
+    long double x;
+    CHECK(CLI::detail::lexical_cast(input, x));
+    CHECK((float)x == Approx((float)9.12));
+
+    std::string bad_input = "hello";
+    CHECK_FALSE(CLI::detail::lexical_cast(bad_input, x));
+
+    std::string overflow_input = "1" + std::to_string(std::numeric_limits<long double>::max());
+    CHECK(CLI::detail::lexical_cast(overflow_input, x));
+    CHECK_FALSE(std::isfinite(x));
+
+    std::string extra_input = "9.12i";
+    CHECK_FALSE(CLI::detail::lexical_cast(extra_input, x));
+
+    std::string empty_input{};
+    CHECK_FALSE(CLI::detail::lexical_cast(empty_input, x));
+}
+
+TEST_CASE("Types: LexicalCastBool", "[helpers]") {
+    std::string input = "false";
+    bool x;
+    CHECK(CLI::detail::lexical_cast(input, x));
+    CHECK_FALSE(x);
+
+    std::string bad_input = "happy";
+    CHECK_FALSE(CLI::detail::lexical_cast(bad_input, x));
+
+    std::string input_true = "EnaBLE";
+    CHECK(CLI::detail::lexical_cast(input_true, x));
+    CHECK(x);
+}
+
+TEST_CASE("Types: LexicalCastString", "[helpers]") {
+    std::string input = "one";
+    std::string output;
+    CLI::detail::lexical_cast(input, output);
+    CHECK(output == input);
+}
+
+TEST_CASE("Types: LexicalCastParsable", "[helpers]") {
+    std::string input = "(4.2,7.3)";
+    std::string fail_input = "4.2,7.3";
+    std::string extra_input = "(4.2,7.3)e";
+
+    std::complex<double> output;
+    CHECK(CLI::detail::lexical_cast(input, output));
+    CHECK(4.2 == Approx(output.real()));
+    CHECK(7.3 == Approx(output.imag()));
+
+    CHECK(CLI::detail::lexical_cast("2.456", output));
+    CHECK(2.456 == Approx(output.real()));
+    CHECK(0.0 == Approx(output.imag()));
+
+    CHECK_FALSE(CLI::detail::lexical_cast(fail_input, output));
+    CHECK_FALSE(CLI::detail::lexical_cast(extra_input, output));
+}
+
+TEST_CASE("Types: LexicalCastEnum", "[helpers]") {
+    enum t1 : signed char { v1 = 5, v3 = 7, v5 = -9 };
+
+    t1 output;
+    CHECK(CLI::detail::lexical_cast("-9", output));
+    CHECK(v5 == output);
+
+    CHECK_FALSE(CLI::detail::lexical_cast("invalid", output));
+    enum class t2 : std::uint64_t { enum1 = 65, enum2 = 45667, enum3 = 9999999999999 };
+    t2 output2{t2::enum2};
+    CHECK(CLI::detail::lexical_cast("65", output2));
+    CHECK(t2::enum1 == output2);
+
+    CHECK_FALSE(CLI::detail::lexical_cast("invalid", output2));
+
+    CHECK(CLI::detail::lexical_cast("9999999999999", output2));
+    CHECK(t2::enum3 == output2);
+}
+
+TEST_CASE("Types: LexicalConversionDouble", "[helpers]") {
+    CLI::results_t input = {"9.12"};
+    long double x{0.0};
+    bool res = CLI::detail::lexical_conversion<long double, double>(input, x);
+    CHECK(res);
+    CHECK((float)x == Approx((float)9.12));
+
+    CLI::results_t bad_input = {"hello"};
+    res = CLI::detail::lexical_conversion<long double, double>(bad_input, x);
+    CHECK_FALSE(res);
+}
+
+TEST_CASE("Types: LexicalConversionDoubleTuple", "[helpers]") {
+    CLI::results_t input = {"9.12"};
+    std::tuple<double> x{0.0};
+    bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
+    CHECK(res);
+    CHECK(std::get<0>(x) == Approx(9.12));
+
+    CLI::results_t bad_input = {"hello"};
+    res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(bad_input, x);
+    CHECK_FALSE(res);
+}
+
+TEST_CASE("Types: LexicalConversionVectorDouble", "[helpers]") {
+    CLI::results_t input = {"9.12", "10.79", "-3.54"};
+    std::vector<double> x;
+    bool res = CLI::detail::lexical_conversion<std::vector<double>, double>(input, x);
+    CHECK(res);
+    CHECK(3u == x.size());
+    CHECK(-3.54 == Approx(x[2]));
+
+    res = CLI::detail::lexical_conversion<std::vector<double>, std::vector<double>>(input, x);
+    CHECK(res);
+    CHECK(3u == x.size());
+    CHECK(-3.54 == Approx(x[2]));
+}
+
+static_assert(!CLI::detail::is_tuple_like<std::vector<double>>::value, "vector should not be like a tuple");
+static_assert(CLI::detail::is_tuple_like<std::pair<double, double>>::value, "pair of double should be like a tuple");
+static_assert(CLI::detail::is_tuple_like<std::array<double, 4>>::value, "std::array<double,4> should be like a tuple");
+static_assert(CLI::detail::is_tuple_like<std::array<int, 10>>::value, "std::array<int,10> should be like a tuple");
+static_assert(!CLI::detail::is_tuple_like<std::string>::value, "std::string should not be like a tuple");
+static_assert(!CLI::detail::is_tuple_like<double>::value, "double should not be like a tuple");
+static_assert(CLI::detail::is_tuple_like<std::tuple<double, int, double>>::value, "tuple should look like a tuple");
+
+TEST_CASE("Types: LexicalConversionTuple2", "[helpers]") {
+    CLI::results_t input = {"9.12", "19"};
+
+    std::tuple<double, int> x{0.0, 0};
+    static_assert(CLI::detail::is_tuple_like<decltype(x)>::value,
+                  "tuple type must have is_tuple_like trait to be true");
+    bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
+    CHECK(res);
+    CHECK(19 == std::get<1>(x));
+    CHECK(9.12 == Approx(std::get<0>(x)));
+
+    input = {"19", "9.12"};
+    res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
+    CHECK_FALSE(res);
+}
+
+TEST_CASE("Types: LexicalConversionTuple3", "[helpers]") {
+    CLI::results_t input = {"9.12", "19", "hippo"};
+    std::tuple<double, int, std::string> x;
+    bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
+    CHECK(res);
+    CHECK(19 == std::get<1>(x));
+    CHECK(9.12 == Approx(std::get<0>(x)));
+    CHECK("hippo" == std::get<2>(x));
+
+    input = {"19", "9.12"};
+    res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
+    CHECK_FALSE(res);
+}
+
+TEST_CASE("Types: LexicalConversionTuple4", "[helpers]") {
+    CLI::results_t input = {"9.12", "19", "18.6", "5.87"};
+    std::array<double, 4> x;
+    bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
+    CHECK(res);
+    CHECK(19 == Approx(std::get<1>(x)));
+    CHECK(9.12 == Approx(x[0]));
+    CHECK(18.6 == Approx(x[2]));
+    CHECK(5.87 == Approx(x[3]));
+
+    input = {"19", "9.12", "hippo"};
+    res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
+    CHECK_FALSE(res);
+}
+
+TEST_CASE("Types: LexicalConversionTuple5", "[helpers]") {
+    CLI::results_t input = {"9", "19", "18", "5", "235235"};
+    std::array<unsigned int, 5> x;
+    bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
+    CHECK(res);
+    CHECK(19u == std::get<1>(x));
+    CHECK(9u == x[0]);
+    CHECK(18u == x[2]);
+    CHECK(5u == x[3]);
+    CHECK(235235u == x[4]);
+
+    input = {"19", "9.12", "hippo"};
+    res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
+    CHECK_FALSE(res);
+}
+
+TEST_CASE("Types: LexicalConversionTuple10", "[helpers]") {
+    CLI::results_t input = {"9", "19", "18", "5", "235235", "9", "19", "18", "5", "235235"};
+    std::array<unsigned int, 10> x;
+    bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
+    CHECK(res);
+    CHECK(19u == std::get<1>(x));
+    CHECK(9u == x[0]);
+    CHECK(18u == x[2]);
+    CHECK(5u == x[3]);
+    CHECK(235235u == x[4]);
+    CHECK(235235u == x[9]);
+    input[3] = "hippo";
+    res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x);
+    CHECK_FALSE(res);
+}
+
+TEST_CASE("Types: LexicalConversionTuple10XC", "[helpers]") {
+    CLI::results_t input = {"9", "19", "18", "5", "235235", "9", "19", "18", "5", "235235"};
+    std::array<double, 10> x;
+    bool res = CLI::detail::lexical_conversion<decltype(x), std::array<unsigned int, 10>>(input, x);
+
+    CHECK(res);
+    CHECK(19.0 == std::get<1>(x));
+    CHECK(9.0 == x[0]);
+    CHECK(18.0 == x[2]);
+    CHECK(5.0 == x[3]);
+    CHECK(235235.0 == x[4]);
+    CHECK(235235.0 == x[9]);
+    input[3] = "19.7";
+    res = CLI::detail::lexical_conversion<decltype(x), std::array<unsigned int, 10>>(input, x);
+    CHECK_FALSE(res);
+}
+
+TEST_CASE("Types: LexicalConversionComplex", "[helpers]") {
+    CLI::results_t input = {"5.1", "3.5"};
+    std::complex<double> x;
+    bool res = CLI::detail::lexical_conversion<std::complex<double>, std::array<double, 2>>(input, x);
+    CHECK(res);
+    CHECK(5.1 == x.real());
+    CHECK(3.5 == x.imag());
+}
+
+static_assert(CLI::detail::is_wrapper<std::vector<double>>::value, "vector double should be a wrapper");
+static_assert(CLI::detail::is_wrapper<std::vector<std::string>>::value, "vector string should be a wrapper");
+static_assert(CLI::detail::is_wrapper<std::string>::value, "string should be a wrapper");
+static_assert(!CLI::detail::is_wrapper<double>::value, "double should not be a wrapper");
+
+static_assert(CLI::detail::is_mutable_container<std::vector<double>>::value, "vector class should be a container");
+static_assert(CLI::detail::is_mutable_container<std::vector<std::string>>::value, "vector class should be a container");
+static_assert(!CLI::detail::is_mutable_container<std::string>::value, "string should be a container");
+static_assert(!CLI::detail::is_mutable_container<double>::value, "double should not be a container");
+static_assert(!CLI::detail::is_mutable_container<std::array<double, 5>>::value, "array should not be a container");
+
+static_assert(CLI::detail::is_mutable_container<std::vector<int>>::value, "vector int should be a container");
+
+static_assert(CLI::detail::is_readable_container<std::vector<int> &>::value,
+              "vector int & should be a readable container");
+static_assert(CLI::detail::is_readable_container<const std::vector<int>>::value,
+              "const vector int should be a readable container");
+static_assert(CLI::detail::is_readable_container<const std::vector<int> &>::value,
+              "const vector int & should be a readable container");
+
+TEST_CASE("FixNewLines: BasicCheck", "[helpers]") {
+    std::string input = "one\ntwo";
+    std::string output = "one\n; two";
+    std::string result = CLI::detail::fix_newlines("; ", input);
+    CHECK(output == result);
+}
+
+TEST_CASE("FixNewLines: EdgesCheck", "[helpers]") {
+    std::string input = "\none\ntwo\n";
+    std::string output = "\n; one\n; two\n; ";
+    std::string result = CLI::detail::fix_newlines("; ", input);
+    CHECK(output == result);
+}
diff --git a/external/CLI11/tests/NewParseTest.cpp b/external/CLI11/tests/NewParseTest.cpp
new file mode 100644
index 0000000..30a0c9f
--- /dev/null
+++ b/external/CLI11/tests/NewParseTest.cpp
@@ -0,0 +1,492 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+
+#include <complex>
+#include <cstdint>
+
+using Catch::Matchers::Contains;
+
+using cx = std::complex<double>;
+
+TEST_CASE_METHOD(TApp, "ComplexOption", "[newparse]") {
+    cx comp{1, 2};
+    app.add_option("-c,--complex", comp)->capture_default_str();
+
+    args = {"-c", "4", "3"};
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("1"));
+    CHECK_THAT(help, Contains("2"));
+    CHECK_THAT(help, Contains("COMPLEX"));
+
+    CHECK(comp.real() == Approx(1));
+    CHECK(comp.imag() == Approx(2));
+
+    run();
+
+    CHECK(comp.real() == Approx(4));
+    CHECK(comp.imag() == Approx(3));
+}
+
+TEST_CASE_METHOD(TApp, "ComplexFloatOption", "[newparse]") {
+    std::complex<float> comp{1, 2};
+    app.add_option("-c,--complex", comp)->capture_default_str();
+
+    args = {"-c", "4", "3"};
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("1"));
+    CHECK_THAT(help, Contains("2"));
+    CHECK_THAT(help, Contains("COMPLEX"));
+
+    CHECK(comp.real() == Approx(1));
+    CHECK(comp.imag() == Approx(2));
+
+    run();
+
+    CHECK(comp.real() == Approx(4));
+    CHECK(comp.imag() == Approx(3));
+}
+
+TEST_CASE_METHOD(TApp, "ComplexWithDelimiterOption", "[newparse]") {
+    cx comp{1, 2};
+    app.add_option("-c,--complex", comp)->capture_default_str()->delimiter('+');
+
+    args = {"-c", "4+3i"};
+
+    std::string help = app.help();
+    CHECK_THAT(help, Contains("1"));
+    CHECK_THAT(help, Contains("2"));
+    CHECK_THAT(help, Contains("COMPLEX"));
+
+    CHECK(comp.real() == Approx(1));
+    CHECK(comp.imag() == Approx(2));
+
+    run();
+
+    CHECK(comp.real() == Approx(4));
+    CHECK(comp.imag() == Approx(3));
+
+    args = {"-c", "5+-3i"};
+    run();
+
+    CHECK(comp.real() == Approx(5));
+    CHECK(comp.imag() == Approx(-3));
+
+    args = {"-c", "6", "-4i"};
+    run();
+
+    CHECK(comp.real() == Approx(6));
+    CHECK(comp.imag() == Approx(-4));
+}
+
+TEST_CASE_METHOD(TApp, "ComplexIgnoreIOption", "[newparse]") {
+    cx comp{1, 2};
+    app.add_option("-c,--complex", comp);
+
+    args = {"-c", "4", "3i"};
+
+    run();
+
+    CHECK(comp.real() == Approx(4));
+    CHECK(comp.imag() == Approx(3));
+}
+
+TEST_CASE_METHOD(TApp, "ComplexSingleArgOption", "[newparse]") {
+    cx comp{1, 2};
+    app.add_option("-c,--complex", comp);
+
+    args = {"-c", "4"};
+    run();
+    CHECK(comp.real() == Approx(4));
+    CHECK(comp.imag() == Approx(0));
+
+    args = {"-c", "4-2i"};
+    run();
+    CHECK(comp.real() == Approx(4));
+    CHECK(comp.imag() == Approx(-2));
+    args = {"-c", "4+2i"};
+    run();
+    CHECK(comp.real() == Approx(4));
+    CHECK(comp.imag() == Approx(2));
+
+    args = {"-c", "-4+2j"};
+    run();
+    CHECK(comp.real() == Approx(-4));
+    CHECK(comp.imag() == Approx(2));
+
+    args = {"-c", "-4.2-2j"};
+    run();
+    CHECK(comp.real() == Approx(-4.2));
+    CHECK(comp.imag() == Approx(-2));
+
+    args = {"-c", "-4.2-2.7i"};
+    run();
+    CHECK(comp.real() == Approx(-4.2));
+    CHECK(comp.imag() == Approx(-2.7));
+}
+
+TEST_CASE_METHOD(TApp, "ComplexSingleImagOption", "[newparse]") {
+    cx comp{1, 2};
+    app.add_option("-c,--complex", comp);
+
+    args = {"-c", "4j"};
+    run();
+    CHECK(comp.real() == Approx(0));
+    CHECK(comp.imag() == Approx(4));
+
+    args = {"-c", "-4j"};
+    run();
+    CHECK(comp.real() == Approx(0));
+    CHECK(comp.imag() == Approx(-4));
+    args = {"-c", "-4"};
+    run();
+    CHECK(comp.real() == Approx(-4));
+    CHECK(comp.imag() == Approx(0));
+    args = {"-c", "+4"};
+    run();
+    CHECK(comp.real() == Approx(4));
+    CHECK(comp.imag() == Approx(0));
+}
+
+/// Simple class containing two strings useful for testing lexical cast and conversions
+class spair {
+  public:
+    spair() = default;
+    spair(const std::string &s1, const std::string &s2) : first(s1), second(s2) {}
+    std::string first{};
+    std::string second{};
+};
+// an example of custom converter that can be used to add new parsing options
+// On MSVC and possibly some other new compilers this can be a free standing function without the template
+// specialization but this is compiler dependent
+namespace CLI {
+namespace detail {
+
+template <> bool lexical_cast<spair>(const std::string &input, spair &output) {
+
+    auto sep = input.find_first_of(':');
+    if((sep == std::string::npos) && (sep > 0)) {
+        return false;
+    }
+    output = {input.substr(0, sep), input.substr(sep + 1)};
+    return true;
+}
+}  // namespace detail
+}  // namespace CLI
+
+TEST_CASE_METHOD(TApp, "custom_string_converter", "[newparse]") {
+    spair val;
+    app.add_option("-d,--dual_string", val);
+
+    args = {"-d", "string1:string2"};
+
+    run();
+    CHECK("string1" == val.first);
+    CHECK("string2" == val.second);
+}
+
+TEST_CASE_METHOD(TApp, "custom_string_converterFail", "[newparse]") {
+    spair val;
+    app.add_option("-d,--dual_string", val);
+
+    args = {"-d", "string2"};
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+/// simple class to wrap another  with a very specific type constructor and assignment operators to test out some of the
+/// option assignments
+template <class X> class objWrapper {
+  public:
+    objWrapper() = default;
+    explicit objWrapper(X obj) : val_{obj} {};
+    objWrapper(const objWrapper &ow) = default;
+    template <class TT> objWrapper(const TT &obj) = delete;
+    objWrapper &operator=(const objWrapper &) = default;
+    objWrapper &operator=(objWrapper &&) = default;
+    // delete all other assignment operators
+    template <typename TT> void operator=(TT &&obj) = delete;
+
+    const X &value() const { return val_; }
+
+  private:
+    X val_{};
+};
+
+// I think there is a bug with the is_assignable in visual studio 2015 it is fixed in later versions
+// so this test will not compile in that compiler
+#if !defined(_MSC_VER) || _MSC_VER >= 1910
+
+static_assert(CLI::detail::is_direct_constructible<objWrapper<std::string>, std::string>::value,
+              "string wrapper isn't properly constructible");
+
+static_assert(!std::is_assignable<objWrapper<std::string>, std::string>::value,
+              "string wrapper is improperly assignable");
+TEST_CASE_METHOD(TApp, "stringWrapper", "[newparse]") {
+    objWrapper<std::string> sWrapper;
+    app.add_option("-v", sWrapper);
+    args = {"-v", "string test"};
+
+    run();
+
+    CHECK("string test" == sWrapper.value());
+}
+
+static_assert(CLI::detail::is_direct_constructible<objWrapper<double>, double>::value,
+              "double wrapper isn't properly assignable");
+
+static_assert(!CLI::detail::is_direct_constructible<objWrapper<double>, int>::value,
+              "double wrapper can be assigned from int");
+
+static_assert(!CLI::detail::is_istreamable<objWrapper<double>>::value,
+              "double wrapper is input streamable and it shouldn't be");
+
+TEST_CASE_METHOD(TApp, "doubleWrapper", "[newparse]") {
+    objWrapper<double> dWrapper;
+    app.add_option("-v", dWrapper);
+    args = {"-v", "2.36"};
+
+    run();
+
+    CHECK(2.36 == dWrapper.value());
+
+    args = {"-v", "thing"};
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+static_assert(CLI::detail::is_direct_constructible<objWrapper<int>, int>::value,
+              "int wrapper is not constructible from int64");
+
+static_assert(!CLI::detail::is_direct_constructible<objWrapper<int>, double>::value,
+              "int wrapper is constructible from double");
+
+static_assert(!CLI::detail::is_istreamable<objWrapper<int>>::value,
+              "int wrapper is input streamable and it shouldn't be");
+
+TEST_CASE_METHOD(TApp, "intWrapper", "[newparse]") {
+    objWrapper<int> iWrapper;
+    app.add_option("-v", iWrapper);
+    args = {"-v", "45"};
+
+    run();
+
+    CHECK(45 == iWrapper.value());
+    args = {"-v", "thing"};
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+static_assert(!CLI::detail::is_direct_constructible<objWrapper<float>, int>::value,
+              "float wrapper is constructible from int");
+static_assert(!CLI::detail::is_direct_constructible<objWrapper<float>, double>::value,
+              "float wrapper is constructible from double");
+
+static_assert(!CLI::detail::is_istreamable<objWrapper<float>>::value,
+              "float wrapper is input streamable and it shouldn't be");
+
+TEST_CASE_METHOD(TApp, "floatWrapper", "[newparse]") {
+    objWrapper<float> iWrapper;
+    app.add_option<objWrapper<float>, float>("-v", iWrapper);
+    args = {"-v", "45.3"};
+
+    run();
+
+    CHECK(45.3f == iWrapper.value());
+    args = {"-v", "thing"};
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+#endif
+/// simple class to wrap another  with a very specific type constructor to test out some of the option assignments
+class dobjWrapper {
+  public:
+    dobjWrapper() = default;
+    explicit dobjWrapper(double obj) : dval_{obj} {};
+    explicit dobjWrapper(int obj) : ival_{obj} {};
+
+    double dvalue() const { return dval_; }
+    int ivalue() const { return ival_; }
+
+  private:
+    double dval_{0.0};
+    int ival_{0};
+};
+
+TEST_CASE_METHOD(TApp, "dobjWrapper", "[newparse]") {
+    dobjWrapper iWrapper;
+    app.add_option("-v", iWrapper);
+    args = {"-v", "45"};
+
+    run();
+
+    CHECK(45 == iWrapper.ivalue());
+    CHECK(0.0 == iWrapper.dvalue());
+
+    args = {"-v", "thing"};
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+    iWrapper = dobjWrapper{};
+
+    args = {"-v", "45.1"};
+
+    run();
+    CHECK(0 == iWrapper.ivalue());
+    CHECK(45.1 == iWrapper.dvalue());
+}
+
+/// simple class to wrap another  with a very specific type constructor and assignment operators to test out some of the
+/// option assignments
+template <class X> class AobjWrapper {
+  public:
+    AobjWrapper() = default;
+    // delete all other constructors
+    template <class TT> AobjWrapper(TT &&obj) = delete;
+    // single assignment operator
+    AobjWrapper &operator=(X val) {
+        val_ = val;
+        return *this;
+    }
+    // delete all other assignment operators
+    template <typename TT> void operator=(TT &&obj) = delete;
+
+    const X &value() const { return val_; }
+
+  private:
+    X val_{};
+};
+
+static_assert(std::is_assignable<AobjWrapper<std::uint16_t> &, std::uint16_t>::value,
+              "AobjWrapper not assignable like it should be ");
+
+TEST_CASE_METHOD(TApp, "uint16Wrapper", "[newparse]") {
+    AobjWrapper<std::uint16_t> sWrapper;
+    app.add_option<AobjWrapper<std::uint16_t>, std::uint16_t>("-v", sWrapper);
+    args = {"-v", "9"};
+
+    run();
+
+    CHECK(9u == sWrapper.value());
+    args = {"-v", "thing"};
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-v", "72456245754"};
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"-v", "-3"};
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+template <class T> class SimpleWrapper {
+  public:
+    SimpleWrapper() : val_{} {};
+    explicit SimpleWrapper(const T &initial) : val_{initial} {};
+    T &getRef() { return val_; }
+    using value_type = T;
+
+  private:
+    T val_;
+};
+
+TEST_CASE_METHOD(TApp, "wrapperInt", "[newparse]") {
+    SimpleWrapper<int> wrap;
+    app.add_option("--val", wrap);
+    args = {"--val", "2"};
+
+    run();
+    CHECK(2 == wrap.getRef());
+}
+
+TEST_CASE_METHOD(TApp, "wrapperString", "[newparse]") {
+    SimpleWrapper<std::string> wrap;
+    app.add_option("--val", wrap);
+    args = {"--val", "str"};
+
+    run();
+    CHECK("str" == wrap.getRef());
+}
+
+TEST_CASE_METHOD(TApp, "wrapperVector", "[newparse]") {
+    SimpleWrapper<std::vector<int>> wrap;
+    app.add_option("--val", wrap);
+    args = {"--val", "1", "2", "3", "4"};
+
+    run();
+    auto v1 = wrap.getRef();
+    auto v2 = std::vector<int>{1, 2, 3, 4};
+    CHECK(v2 == v1);
+}
+
+TEST_CASE_METHOD(TApp, "wrapperwrapperString", "[newparse]") {
+    SimpleWrapper<SimpleWrapper<std::string>> wrap;
+    app.add_option("--val", wrap);
+    args = {"--val", "arg"};
+
+    run();
+    auto v1 = wrap.getRef().getRef();
+    auto v2 = "arg";
+    CHECK(v2 == v1);
+}
+
+TEST_CASE_METHOD(TApp, "wrapperwrapperVector", "[newparse]") {
+    SimpleWrapper<SimpleWrapper<std::vector<int>>> wrap;
+    auto opt = app.add_option("--val", wrap);
+    args = {"--val", "1", "2", "3", "4"};
+
+    run();
+    auto v1 = wrap.getRef().getRef();
+    auto v2 = std::vector<int>{1, 2, 3, 4};
+    CHECK(v2 == v1);
+    opt->type_size(0, 5);
+
+    args = {"--val"};
+
+    run();
+    CHECK(wrap.getRef().getRef().empty());
+
+    args = {"--val", "happy", "sad"};
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "wrapperComplex", "[newparse]") {
+    SimpleWrapper<std::complex<double>> wrap;
+    app.add_option("--val", wrap);
+    args = {"--val", "1", "2"};
+
+    run();
+    auto &v1 = wrap.getRef();
+    auto v2 = std::complex<double>{1, 2};
+    CHECK(v2.real() == v1.real());
+    CHECK(v2.imag() == v1.imag());
+    args = {"--val", "1.4-4j"};
+
+    run();
+    v2 = std::complex<double>{1.4, -4};
+    CHECK(v2.real() == v1.real());
+    CHECK(v2.imag() == v1.imag());
+}
+
+TEST_CASE_METHOD(TApp, "vectorComplex", "[newparse]") {
+    std::vector<std::complex<double>> vcomplex;
+    app.add_option("--val", vcomplex);
+    args = {"--val", "1", "2", "--val", "1.4-4j"};
+
+    run();
+
+    REQUIRE(2U == vcomplex.size());
+    CHECK(1.0 == vcomplex[0].real());
+    CHECK(2.0 == vcomplex[0].imag());
+    CHECK(1.4 == vcomplex[1].real());
+    CHECK(-4.0 == vcomplex[1].imag());
+}
diff --git a/external/CLI11/tests/OptionGroupTest.cpp b/external/CLI11/tests/OptionGroupTest.cpp
new file mode 100644
index 0000000..ac6684f
--- /dev/null
+++ b/external/CLI11/tests/OptionGroupTest.cpp
@@ -0,0 +1,797 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+
+using Catch::Matchers::Contains;
+
+using vs_t = std::vector<std::string>;
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroup", "[optiongroup]") {
+    auto ogroup = app.add_option_group("clusters");
+    int res;
+    ogroup->add_option("--test1", res);
+    ogroup->add_option("--test2", res);
+    ogroup->add_option("--test3", res);
+
+    args = {"--test1", "5"};
+    run();
+    CHECK(5 == res);
+    CHECK(1u == app.count_all());
+}
+
+TEST_CASE_METHOD(TApp, "OptionGroupInvalidNames", "[optiongroup]") {
+    CHECK_THROWS_AS(app.add_option_group("clusters\ncluster2", "description"), CLI::IncorrectConstruction);
+
+    std::string groupName("group1");
+    groupName += '\0';
+    groupName.append("group2");
+
+    CHECK_THROWS_AS(app.add_option_group(groupName), CLI::IncorrectConstruction);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupExact", "[optiongroup]") {
+    auto ogroup = app.add_option_group("clusters");
+    int res{0};
+    ogroup->add_option("--test1", res);
+    ogroup->add_option("--test2", res);
+    ogroup->add_option("--test3", res);
+    int val2{0};
+    app.add_option("--option", val2);
+    ogroup->require_option(1);
+    args = {"--test1", "5"};
+    run();
+    CHECK(5 == res);
+
+    args = {"--test1", "5", "--test2", "4"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--option", "9"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    std::string help = ogroup->help();
+    auto exactloc = help.find("[Exactly 1");
+    CHECK(std::string::npos != exactloc);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupExactTooMany", "[optiongroup]") {
+    auto ogroup = app.add_option_group("clusters");
+    int res{0};
+    ogroup->add_option("--test1", res);
+    ogroup->add_option("--test2", res);
+    ogroup->add_option("--test3", res);
+    int val2{0};
+    app.add_option("--option", val2);
+    ogroup->require_option(10);
+    args = {"--test1", "5"};
+    CHECK_THROWS_AS(run(), CLI::InvalidError);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMax", "[optiongroup]") {
+    auto ogroup = app.add_option_group("clusters");
+    int res{0};
+    ogroup->add_option("--test1", res);
+    ogroup->add_option("--test2", res);
+    ogroup->add_option("--test3", res);
+    int val2{0};
+    app.add_option("--option", val2);
+    ogroup->require_option(1, 1);
+    args = {"--test1", "5"};
+    run();
+    CHECK(5 == res);
+
+    args = {"--test1", "5", "--test2", "4"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--option", "9"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    std::string help = ogroup->help();
+    auto exactloc = help.find("[Exactly 1");
+    CHECK(std::string::npos != exactloc);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMaxDifferent", "[optiongroup]") {
+    auto ogroup = app.add_option_group("clusters");
+    int res{0};
+    ogroup->add_option("--test1", res);
+    ogroup->add_option("--test2", res);
+    ogroup->add_option("--test3", res);
+    int val2{0};
+    app.add_option("--option", val2);
+    ogroup->require_option(1, 2);
+    args = {"--test1", "5"};
+    run();
+    CHECK(5 == res);
+
+    args = {"--test1", "5", "--test2", "4"};
+    CHECK_NOTHROW(run());
+    CHECK(2u == app.count_all());
+
+    args = {"--option", "9"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--test1", "5", "--test2", "4", "--test3=5"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    std::string help = ogroup->help();
+    auto exactloc = help.find("[Between 1 and 2");
+    CHECK(std::string::npos != exactloc);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMaxDifferentReversed", "[optiongroup]") {
+    auto ogroup = app.add_option_group("clusters");
+    int res{0};
+    ogroup->add_option("--test1", res);
+    ogroup->add_option("--test2", res);
+    ogroup->add_option("--test3", res);
+    int val2{0};
+    app.add_option("--option", val2);
+    ogroup->require_option(2, 1);
+    CHECK(2u == ogroup->get_require_option_min());
+    CHECK(1u == ogroup->get_require_option_max());
+    args = {"--test1", "5"};
+    CHECK_THROWS_AS(run(), CLI::InvalidError);
+    ogroup->require_option(1, 2);
+    CHECK_NOTHROW(run());
+    CHECK(5 == res);
+    CHECK(1u == ogroup->get_require_option_min());
+    CHECK(2u == ogroup->get_require_option_max());
+    args = {"--test1", "5", "--test2", "4"};
+    CHECK_NOTHROW(run());
+
+    args = {"--option", "9"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--test1", "5", "--test2", "4", "--test3=5"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    std::string help = ogroup->help();
+    auto exactloc = help.find("[Between 1 and 2");
+    CHECK(std::string::npos != exactloc);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupMax", "[optiongroup]") {
+    auto ogroup = app.add_option_group("clusters");
+    int res{0};
+    ogroup->add_option("--test1", res);
+    ogroup->add_option("--test2", res);
+    ogroup->add_option("--test3", res);
+    int val2;
+    app.add_option("--option", val2);
+    ogroup->require_option(-2);
+    args = {"--test1", "5"};
+    run();
+    CHECK(5 == res);
+
+    args = {"--option", "9"};
+    CHECK_NOTHROW(run());
+
+    args = {"--test1", "5", "--test2", "4", "--test3=5"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    std::string help = ogroup->help();
+    auto exactloc = help.find("[At most 2");
+    CHECK(std::string::npos != exactloc);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupMax1", "[optiongroup]") {
+    auto ogroup = app.add_option_group("clusters");
+    int res{0};
+    ogroup->add_option("--test1", res);
+    ogroup->add_option("--test2", res);
+    ogroup->add_option("--test3", res);
+    int val2{0};
+    app.add_option("--option", val2);
+    ogroup->require_option(-1);
+    args = {"--test1", "5"};
+    run();
+    CHECK(5 == res);
+
+    args = {"--option", "9"};
+    CHECK_NOTHROW(run());
+
+    args = {"--test1", "5", "--test2", "4"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    std::string help = ogroup->help();
+    auto exactloc = help.find("[At most 1");
+    CHECK(std::string::npos != exactloc);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupMin", "[optiongroup]") {
+    auto ogroup = app.add_option_group("clusters");
+    int res{0};
+    ogroup->add_option("--test1", res);
+    ogroup->add_option("--test2", res);
+    ogroup->add_option("--test3", res);
+    int val2{0};
+    app.add_option("--option", val2);
+    ogroup->require_option();
+
+    args = {"--option", "9"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--test1", "5", "--test2", "4", "--test3=5"};
+    CHECK_NOTHROW(run());
+
+    std::string help = ogroup->help();
+    auto exactloc = help.find("[At least 1");
+    CHECK(std::string::npos != exactloc);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupExact2", "[optiongroup]") {
+    auto ogroup = app.add_option_group("clusters");
+    int res{0};
+    ogroup->add_option("--test1", res);
+    ogroup->add_option("--test2", res);
+    ogroup->add_option("--test3", res);
+    int val2{0};
+    app.add_option("--option", val2);
+    ogroup->require_option(2);
+
+    args = {"--option", "9"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--test1", "5", "--test2", "4", "--test3=5"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--test1", "5", "--test3=5"};
+    CHECK_NOTHROW(run());
+
+    std::string help = ogroup->help();
+    auto exactloc = help.find("[Exactly 2");
+    CHECK(std::string::npos != exactloc);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupMin2", "[optiongroup]") {
+    auto ogroup = app.add_option_group("clusters");
+    int res{0};
+    ogroup->add_option("--test1", res);
+    ogroup->add_option("--test2", res);
+    ogroup->add_option("--test3", res);
+    int val2{0};
+    app.add_option("--option", val2);
+    ogroup->require_option(2, 0);
+
+    args = {"--option", "9"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--test1", "5", "--test2", "4", "--test3=5"};
+    CHECK_NOTHROW(run());
+
+    std::string help = ogroup->help();
+    auto exactloc = help.find("[At least 2");
+    CHECK(std::string::npos != exactloc);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMoved", "[optiongroup]") {
+
+    int res{0};
+    auto opt1 = app.add_option("--test1", res);
+    auto opt2 = app.add_option("--test2", res);
+    auto opt3 = app.add_option("--test3", res);
+    int val2{0};
+    app.add_option("--option", val2);
+
+    auto ogroup = app.add_option_group("clusters");
+    ogroup->require_option();
+    ogroup->add_option(opt1);
+    ogroup->add_option(opt2);
+    ogroup->add_option(opt3);
+
+    args = {"--option", "9"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--test1", "5", "--test2", "4", "--test3=5"};
+    CHECK_NOTHROW(run());
+
+    std::string help = app.help();
+    auto exactloc = help.find("[At least 1");
+    auto oloc = help.find("--test1");
+    CHECK(std::string::npos != exactloc);
+    CHECK(std::string::npos != oloc);
+    CHECK(oloc > exactloc);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupMinMovedAsGroup", "[optiongroup]") {
+
+    int res{0};
+    auto opt1 = app.add_option("--test1", res);
+    auto opt2 = app.add_option("--test2", res);
+    auto opt3 = app.add_option("--test3", res);
+    int val2{0};
+    app.add_option("--option", val2);
+
+    auto ogroup = app.add_option_group("clusters");
+    ogroup->require_option();
+    ogroup->add_options(opt1, opt2, opt3);
+
+    CHECK_THROWS_AS(ogroup->add_options(opt1), CLI::OptionNotFound);
+    args = {"--option", "9"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--test1", "5", "--test2", "4", "--test3=5"};
+    CHECK_NOTHROW(run());
+
+    std::string help = app.help();
+    auto exactloc = help.find("[At least 1");
+    auto oloc = help.find("--test1");
+    CHECK(std::string::npos != exactloc);
+    CHECK(std::string::npos != oloc);
+    CHECK(oloc > exactloc);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupAddFailures", "[optiongroup]") {
+
+    int res{0};
+    auto opt1 = app.add_option("--test1", res);
+    app.set_config("--config");
+    int val2{0};
+    app.add_option("--option", val2);
+
+    auto ogroup = app.add_option_group("clusters");
+    CHECK_THROWS_AS(ogroup->add_options(app.get_config_ptr()), CLI::OptionAlreadyAdded);
+    CHECK_THROWS_AS(ogroup->add_options(app.get_help_ptr()), CLI::OptionAlreadyAdded);
+
+    auto sub = app.add_subcommand("sub", "subcommand");
+    auto opt2 = sub->add_option("--option2", val2);
+
+    CHECK_THROWS_AS(ogroup->add_option(opt2), CLI::OptionNotFound);
+
+    CHECK_THROWS_AS(ogroup->add_options(nullptr), CLI::OptionNotFound);
+
+    ogroup->add_option(opt1);
+
+    auto opt3 = app.add_option("--test1", res);
+
+    CHECK_THROWS_AS(ogroup->add_option(opt3), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE_METHOD(TApp, "BasicOptionGroupScrewedUpMove", "[optiongroup]") {
+
+    int res{0};
+    auto opt1 = app.add_option("--test1", res);
+    auto opt2 = app.add_option("--test2", res);
+    int val2{0};
+    app.add_option("--option", val2);
+
+    auto ogroup = app.add_option_group("clusters");
+    ogroup->require_option();
+    auto ogroup2 = ogroup->add_option_group("clusters2");
+    CHECK_THROWS_AS(ogroup2->add_options(opt1, opt2), CLI::OptionNotFound);
+
+    CLI::Option_group EmptyGroup("description", "new group", nullptr);
+
+    CHECK_THROWS_AS(EmptyGroup.add_option(opt2), CLI::OptionNotFound);
+    CHECK_THROWS_AS(app._move_option(opt2, ogroup2), CLI::OptionNotFound);
+}
+
+TEST_CASE_METHOD(TApp, "InvalidOptions", "[optiongroup]") {
+    auto ogroup = app.add_option_group("clusters");
+    CLI::Option *opt = nullptr;
+    CHECK_THROWS_AS(ogroup->excludes(opt), CLI::OptionNotFound);
+    CLI::App *app_p = nullptr;
+    CHECK_THROWS_AS(ogroup->excludes(app_p), CLI::OptionNotFound);
+    CHECK_THROWS_AS(ogroup->excludes(ogroup), CLI::OptionNotFound);
+    CHECK_THROWS_AS(ogroup->add_option(opt), CLI::OptionNotFound);
+}
+
+TEST_CASE_METHOD(TApp, "OptionGroupInheritedOptionDefaults", "[optiongroup]") {
+    app.option_defaults()->ignore_case();
+    auto ogroup = app.add_option_group("clusters");
+    int res{0};
+    ogroup->add_option("--test1", res);
+
+    args = {"--Test1", "5"};
+    run();
+    CHECK(5 == res);
+    CHECK(1u == app.count_all());
+}
+
+struct ManyGroups : public TApp {
+
+    CLI::Option_group *main{nullptr};
+    CLI::Option_group *g1{nullptr};
+    CLI::Option_group *g2{nullptr};
+    CLI::Option_group *g3{nullptr};
+    std::string name1{};
+    std::string name2{};
+    std::string name3{};
+    std::string val1{};
+    std::string val2{};
+    std::string val3{};
+
+    ManyGroups(const ManyGroups &) = delete;
+    ManyGroups &operator=(const ManyGroups &) = delete;
+
+    ManyGroups() {
+        main = app.add_option_group("main", "the main outer group");
+        g1 = main->add_option_group("g1", "group1 description");
+        g2 = main->add_option_group("g2", "group2 description");
+        g3 = main->add_option_group("g3", "group3 description");
+        g1->add_option("--name1", name1)->required();
+        g1->add_option("--val1", val1);
+        g2->add_option("--name2", name2)->required();
+        g2->add_option("--val2", val2);
+        g3->add_option("--name3", name3)->required();
+        g3->add_option("--val3", val3);
+    }
+
+    void remove_required() {
+        g1->get_option("--name1")->required(false);
+        g2->get_option("--name2")->required(false);
+        g3->get_option("--name3")->required(false);
+        g1->required(false);
+        g2->required(false);
+        g3->required(false);
+    }
+};
+
+TEST_CASE_METHOD(ManyGroups, "SingleGroup", "[optiongroup]") {
+    // only 1 group can be used
+    main->require_option(1);
+    args = {"--name1", "test"};
+    run();
+    CHECK("test" == name1);
+
+    args = {"--name2", "test", "--val2", "tval"};
+
+    run();
+    CHECK("tval" == val2);
+
+    args = {"--name1", "test", "--val2", "tval"};
+
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+}
+
+TEST_CASE_METHOD(ManyGroups, "ExcludesGroup", "[optiongroup]") {
+    // only 1 group can be used
+    g1->excludes(g2);
+    g1->excludes(g3);
+    args = {"--name1", "test"};
+    run();
+    CHECK("test" == name1);
+
+    args = {"--name1", "test", "--name2", "test2"};
+
+    CHECK_THROWS_AS(run(), CLI::ExcludesError);
+
+    CHECK(g1->remove_excludes(g2));
+    CHECK_NOTHROW(run());
+    CHECK(!g1->remove_excludes(g1));
+    CHECK(!g1->remove_excludes(g2));
+}
+
+TEST_CASE_METHOD(ManyGroups, "NeedsGroup", "[optiongroup]") {
+    remove_required();
+    // all groups needed if g1 is used
+    g1->needs(g2);
+    g1->needs(g3);
+    args = {"--name1", "test"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+    // other groups should run fine
+    args = {"--name2", "test2"};
+
+    run();
+    // all three groups should be fine
+    args = {"--name1", "test", "--name2", "test2", "--name3", "test3"};
+
+    CHECK_NOTHROW(run());
+}
+
+// test adding an option group with existing subcommands to an app
+TEST_CASE_METHOD(TApp, "ExistingSubcommandMatch", "[optiongroup]") {
+    auto sshared = std::make_shared<CLI::Option_group>("documenting the subcommand", "sub1g", nullptr);
+    auto s1 = sshared->add_subcommand("sub1");
+    auto o1 = sshared->add_option_group("opt1");
+    o1->add_subcommand("sub3")->alias("sub4");
+
+    app.add_subcommand("sub1");
+
+    try {
+        app.add_subcommand(sshared);
+        // this should throw the next line should never be reached
+        CHECK(!true);
+    } catch(const CLI::OptionAlreadyAdded &oaa) {
+        CHECK_THAT(oaa.what(), Contains("sub1"));
+    }
+    sshared->remove_subcommand(s1);
+
+    app.add_subcommand("sub3");
+    // now check that the subsubcommand overlaps
+    try {
+        app.add_subcommand(sshared);
+        // this should throw the next line should never be reached
+        CHECK(!true);
+    } catch(const CLI::OptionAlreadyAdded &oaa) {
+        CHECK_THAT(oaa.what(), Contains("sub3"));
+    }
+}
+
+TEST_CASE_METHOD(ManyGroups, "SingleGroupError", "[optiongroup]") {
+    // only 1 group can be used
+    main->require_option(1);
+    args = {"--name1", "test", "--name2", "test3"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+}
+
+TEST_CASE_METHOD(ManyGroups, "AtMostOneGroup", "[optiongroup]") {
+    // only 1 group can be used
+    main->require_option(0, 1);
+    args = {"--name1", "test", "--name2", "test3"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {};
+    CHECK_NOTHROW(run());
+}
+
+TEST_CASE_METHOD(ManyGroups, "AtLeastTwoGroups", "[optiongroup]") {
+    // only 1 group can be used
+    main->require_option(2, 0);
+    args = {"--name1", "test", "--name2", "test3"};
+    run();
+
+    args = {"--name1", "test"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+}
+
+TEST_CASE_METHOD(ManyGroups, "BetweenOneAndTwoGroups", "[optiongroup]") {
+    // only 1 group can be used
+    main->require_option(1, 2);
+    args = {"--name1", "test", "--name2", "test3"};
+    run();
+
+    args = {"--name1", "test"};
+    run();
+
+    args = {};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"--name1", "test", "--name2", "test3", "--name3=test3"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+}
+
+TEST_CASE_METHOD(ManyGroups, "RequiredFirst", "[optiongroup]") {
+    // only 1 group can be used
+    remove_required();
+    g1->required();
+
+    CHECK(g1->get_required());
+    CHECK(!g2->get_required());
+    args = {"--name1", "test", "--name2", "test3"};
+    run();
+
+    args = {"--name2", "test"};
+    try {
+        run();
+    } catch(const CLI::RequiredError &re) {
+        CHECK_THAT(re.what(), Contains("g1"));
+    }
+
+    args = {"--name1", "test", "--name2", "test3", "--name3=test3"};
+    CHECK_NOTHROW(run());
+}
+
+TEST_CASE_METHOD(ManyGroups, "DisableFirst", "[optiongroup]") {
+    // only 1 group can be used if remove_required not used
+    remove_required();
+    g1->disabled();
+
+    CHECK(g1->get_disabled());
+    CHECK(!g2->get_disabled());
+    args = {"--name2", "test"};
+
+    run();
+
+    args = {"--name1", "test", "--name2", "test3"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+    g1->disabled(false);
+    args = {"--name1", "test", "--name2", "test3", "--name3=test3"};
+    CHECK_NOTHROW(run());
+}
+
+TEST_CASE_METHOD(ManyGroups, "SameSubcommand", "[optiongroup]") {
+    // only 1 group can be used if remove_required not used
+    remove_required();
+    auto sub1 = g1->add_subcommand("sub1")->disabled();
+    auto sub2 = g2->add_subcommand("sub1")->disabled();
+    auto sub3 = g3->add_subcommand("sub1");
+    // so when the subcommands are disabled they can have the same name
+    sub1->disabled(false);
+    sub2->disabled(false);
+    // if they are re-enabled they are not checked for overlap on enabling so they can have the same name
+    args = {"sub1", "sub1", "sub1"};
+
+    run();
+
+    CHECK(*sub1);
+    CHECK(*sub2);
+    CHECK(*sub3);
+    auto subs = app.get_subcommands();
+    CHECK(3u == subs.size());
+    CHECK(sub1 == subs[0]);
+    CHECK(sub2 == subs[1]);
+    CHECK(sub3 == subs[2]);
+
+    args = {"sub1", "sub1", "sub1", "sub1"};
+    // for the 4th and future ones they will route to the first one
+    run();
+    CHECK(2u == sub1->count());
+    CHECK(1u == sub2->count());
+    CHECK(1u == sub3->count());
+
+    // subs should remain the same since the duplicate would not be registered there
+    subs = app.get_subcommands();
+    CHECK(3u == subs.size());
+    CHECK(sub1 == subs[0]);
+    CHECK(sub2 == subs[1]);
+    CHECK(sub3 == subs[2]);
+}
+TEST_CASE_METHOD(ManyGroups, "CallbackOrder", "[optiongroup]") {
+    // only 1 group can be used if remove_required not used
+    remove_required();
+    std::vector<int> callback_order;
+    g1->callback([&callback_order]() { callback_order.push_back(1); });
+    g2->callback([&callback_order]() { callback_order.push_back(2); });
+    main->callback([&callback_order]() { callback_order.push_back(3); });
+
+    args = {"--name2", "test"};
+    run();
+    CHECK(std::vector<int>({2, 3}) == callback_order);
+
+    callback_order.clear();
+    args = {"--name1", "t2", "--name2", "test"};
+    g2->immediate_callback();
+    run();
+    CHECK(std::vector<int>({2, 1, 3}) == callback_order);
+    callback_order.clear();
+
+    args = {"--name2", "test", "--name1", "t2"};
+    g2->immediate_callback(false);
+    run();
+    CHECK(std::vector<int>({1, 2, 3}) == callback_order);
+}
+
+// Test the fallthrough for extra arguments
+TEST_CASE_METHOD(ManyGroups, "ExtrasFallDown", "[optiongroup]") {
+    // only 1 group can be used if remove_required not used
+    remove_required();
+
+    args = {"--test1", "--flag", "extra"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+    main->allow_extras();
+    CHECK_NOTHROW(run());
+
+    CHECK(3u == app.remaining_size(true));
+    CHECK(3u == main->remaining_size());
+
+    std::vector<std::string> extras{"--test1", "--flag", "extra"};
+    CHECK(extras == app.remaining(true));
+    CHECK(extras == main->remaining());
+}
+
+// Test the option Inheritance
+TEST_CASE_METHOD(ManyGroups, "Inheritance", "[optiongroup]") {
+    remove_required();
+    g1->ignore_case();
+    g1->ignore_underscore();
+    auto t2 = g1->add_subcommand("t2");
+    args = {"T2", "t_2"};
+    CHECK(t2->get_ignore_underscore());
+    CHECK(t2->get_ignore_case());
+    run();
+    CHECK(2u == t2->count());
+}
+
+TEST_CASE_METHOD(ManyGroups, "Moving", "[optiongroup]") {
+    remove_required();
+    auto mg = app.add_option_group("maing");
+    mg->add_subcommand(g1);
+    mg->add_subcommand(g2);
+
+    CHECK(mg == g1->get_parent());
+    CHECK(mg == g2->get_parent());
+    CHECK(main == g3->get_parent());
+}
+
+struct ManyGroupsPreTrigger : public ManyGroups {
+    std::size_t triggerMain{0u}, trigger1{87u}, trigger2{34u}, trigger3{27u};
+    ManyGroupsPreTrigger() {
+        remove_required();
+        app.preparse_callback([this](std::size_t count) { triggerMain = count; });
+
+        g1->preparse_callback([this](std::size_t count) { trigger1 = count; });
+        g2->preparse_callback([this](std::size_t count) { trigger2 = count; });
+        g3->preparse_callback([this](std::size_t count) { trigger3 = count; });
+    }
+};
+
+TEST_CASE_METHOD(ManyGroupsPreTrigger, "PreTriggerTestsOptions", "[optiongroup]") {
+
+    args = {"--name1", "test", "--name2", "test3"};
+    run();
+    CHECK(4u == triggerMain);
+    CHECK(2u == trigger1);
+    CHECK(0u == trigger2);
+    CHECK(27u == trigger3);
+
+    args = {"--name1", "test"};
+    trigger2 = 34u;
+    run();
+    CHECK(2u == triggerMain);
+    CHECK(0u == trigger1);
+    CHECK(34u == trigger2);
+
+    args = {};
+    run();
+    CHECK(0u == triggerMain);
+
+    args = {"--name1", "test", "--val1", "45", "--name2", "test3", "--name3=test3", "--val2=37"};
+    run();
+    CHECK(8u == triggerMain);
+    CHECK(6u == trigger1);
+    CHECK(2u == trigger2);
+    CHECK(1u == trigger3);
+}
+
+TEST_CASE_METHOD(ManyGroupsPreTrigger, "PreTriggerTestsPositionals", "[optiongroup]") {
+    // only 1 group can be used
+    g1->add_option("pos1");
+    g2->add_option("pos2");
+    g3->add_option("pos3");
+
+    args = {"pos1"};
+    run();
+    CHECK(1u == triggerMain);
+    CHECK(0u == trigger1);
+    CHECK(34u == trigger2);
+    CHECK(27u == trigger3);
+
+    args = {"pos1", "pos2"};
+    run();
+    CHECK(2u == triggerMain);
+    CHECK(1u == trigger1);
+    CHECK(0u == trigger2);
+
+    args = {"pos1", "pos2", "pos3"};
+    run();
+    CHECK(3u == triggerMain);
+    CHECK(2u == trigger1);
+    CHECK(1u == trigger2);
+    CHECK(0u == trigger3);
+}
+
+TEST_CASE_METHOD(ManyGroupsPreTrigger, "PreTriggerTestsSubcommand", "[optiongroup]") {
+
+    auto sub1 = g1->add_subcommand("sub1")->fallthrough();
+    g2->add_subcommand("sub2")->fallthrough();
+    g3->add_subcommand("sub3")->fallthrough();
+
+    std::size_t subtrigger;
+    sub1->preparse_callback([&subtrigger](std::size_t count) { subtrigger = count; });
+    args = {"sub1"};
+    run();
+    CHECK(1u == triggerMain);
+    CHECK(0u == trigger1);
+    CHECK(34u == trigger2);
+    CHECK(27u == trigger3);
+
+    args = {"sub1", "sub2"};
+    run();
+    CHECK(2u == triggerMain);
+    CHECK(1u == subtrigger);
+    CHECK(1u == trigger1);
+    CHECK(0u == trigger2);
+
+    args = {"sub2", "sub3", "--name1=test", "sub1"};
+    run();
+    CHECK(4u == triggerMain);
+    CHECK(1u == trigger1);
+    CHECK(3u == trigger2);
+    CHECK(1u == trigger3);
+    // go until the sub1 command is given
+}
diff --git a/external/CLI11/tests/OptionTypeTest.cpp b/external/CLI11/tests/OptionTypeTest.cpp
new file mode 100644
index 0000000..19b6974
--- /dev/null
+++ b/external/CLI11/tests/OptionTypeTest.cpp
@@ -0,0 +1,999 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+#include <atomic>
+#include <complex>
+#include <cstdint>
+#include <cstdlib>
+#include <deque>
+#include <forward_list>
+#include <list>
+#include <map>
+#include <queue>
+#include <set>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+TEST_CASE_METHOD(TApp, "OneStringAgain", "[optiontype]") {
+    std::string str;
+    app.add_option("-s,--string", str);
+    args = {"--string", "mystring"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--string") == 1u);
+    CHECK("mystring" == str);
+}
+
+TEST_CASE_METHOD(TApp, "OneStringFunction", "[optiontype]") {
+    std::string str;
+    app.add_option_function<std::string>("-s,--string", [&str](const std::string &val) { str = val; });
+    args = {"--string", "mystring"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--string") == 1u);
+    CHECK("mystring" == str);
+}
+
+TEST_CASE_METHOD(TApp, "doubleFunction", "[optiontype]") {
+    double res{0.0};
+    app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
+    args = {"--val", "-354.356"};
+    run();
+    CHECK(300.356 == res);
+    // get the original value as entered as an integer
+    CHECK(-354.356f == app["--val"]->as<float>());
+}
+
+TEST_CASE_METHOD(TApp, "doubleFunctionFail", "[optiontype]") {
+    double res;
+    app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); });
+    args = {"--val", "not_double"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "doubleVectorFunction", "[optiontype]") {
+    std::vector<double> res;
+    app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
+        res = val;
+        std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
+    });
+    args = {"--val", "5", "--val", "6", "--val", "7"};
+    run();
+    CHECK(3u == res.size());
+    CHECK(10.0 == res[0]);
+    CHECK(12.0 == res[2]);
+}
+
+TEST_CASE_METHOD(TApp, "doubleVectorFunctionFail", "[optiontype]") {
+    std::vector<double> res;
+    std::string vstring = "--val";
+    app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) {
+        res = val;
+        std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
+    });
+    args = {"--val", "five", "--val", "nine", "--val", "7"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+    // check that getting the results through the results function generates the same error
+    CHECK_THROWS_AS(app[vstring]->results(res), CLI::ConversionError);
+    auto strvec = app[vstring]->as<std::vector<std::string>>();
+    CHECK(3u == strvec.size());
+}
+
+TEST_CASE_METHOD(TApp, "doubleVectorFunctionRunCallbackOnDefault", "[optiontype]") {
+    std::vector<double> res;
+    auto opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) {
+        res = val;
+        std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; });
+    });
+    args = {"--val", "5", "--val", "6", "--val", "7"};
+    run();
+    CHECK(3u == res.size());
+    CHECK(10.0 == res[0]);
+    CHECK(12.0 == res[2]);
+    CHECK(!opt->get_run_callback_for_default());
+    opt->run_callback_for_default();
+    opt->default_val(std::vector<int>{2, 1, -2});
+    CHECK(7.0 == res[0]);
+    CHECK(3.0 == res[2]);
+
+    CHECK_THROWS_AS(opt->default_val("this is a string"), CLI::ConversionError);
+    auto vec = opt->as<std::vector<double>>();
+    REQUIRE(3U == vec.size());
+    CHECK(5.0 == vec[0]);
+    CHECK(7.0 == vec[2]);
+    opt->check(CLI::Number);
+    opt->run_callback_for_default(false);
+    CHECK_THROWS_AS(opt->default_val("this is a string"), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "BoolAndIntFlags", "[optiontype]") {
+
+    bool bflag{false};
+    int iflag{0};
+    unsigned int uflag{0};
+
+    app.add_flag("-b", bflag);
+    app.add_flag("-i", iflag);
+    app.add_flag("-u", uflag);
+
+    args = {"-b", "-i", "-u"};
+    run();
+    CHECK(bflag);
+    CHECK(iflag == 1);
+    CHECK(uflag == (unsigned int)1);
+
+    args = {"-b", "-b"};
+    REQUIRE_NOTHROW(run());
+    CHECK(bflag);
+
+    bflag = false;
+
+    args = {"-iiiuu"};
+    run();
+    CHECK(!bflag);
+    CHECK(iflag == 3);
+    CHECK(uflag == (unsigned int)2);
+}
+
+TEST_CASE_METHOD(TApp, "atomic_bool_flags", "[optiontype]") {
+
+    std::atomic<bool> bflag{false};
+    std::atomic<int> iflag{0};
+
+    app.add_flag("-b", bflag);
+    app.add_flag("-i,--int", iflag);
+
+    args = {"-b", "-i"};
+    run();
+    CHECK(bflag.load());
+    CHECK(iflag.load() == 1);
+
+    args = {"-b", "-b"};
+    REQUIRE_NOTHROW(run());
+    CHECK(bflag.load());
+
+    bflag = false;
+
+    args = {"-iii"};
+    run();
+    CHECK(!bflag.load());
+    CHECK(iflag.load() == 3);
+    args = {"--int=notanumber"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "BoolOption", "[optiontype]") {
+    bool bflag{false};
+    app.add_option("-b", bflag);
+
+    args = {"-b", "false"};
+    run();
+    CHECK(!bflag);
+
+    args = {"-b", "1"};
+    run();
+    CHECK(bflag);
+
+    args = {"-b", "-7"};
+    run();
+    CHECK(!bflag);
+
+    // cause an out of bounds error internally
+    args = {"-b", "751615654161688126132138844896646748852"};
+    run();
+    CHECK(bflag);
+
+    args = {"-b", "-751615654161688126132138844896646748852"};
+    run();
+    CHECK(!bflag);
+}
+
+TEST_CASE_METHOD(TApp, "atomic_int_option", "[optiontype]") {
+    std::atomic<int> i{0};
+    auto aopt = app.add_option("-i,--int", i);
+    args = {"-i4"};
+    run();
+    CHECK(app.count("--int") == 1u);
+    CHECK(app.count("-i") == 1u);
+    CHECK(4 == i);
+    CHECK("4" == app["-i"]->as<std::string>());
+    CHECK(4.0 == app["--int"]->as<double>());
+
+    args = {"--int", "notAnInt"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    aopt->expected(0, 1);
+    args = {"--int"};
+    run();
+    CHECK(0 == i);
+}
+
+TEST_CASE_METHOD(TApp, "CharOption", "[optiontype]") {
+    char c1{'t'};
+    app.add_option("-c", c1);
+
+    args = {"-c", "g"};
+    run();
+    CHECK('g' == c1);
+
+    args = {"-c", "1"};
+    run();
+    CHECK('1' == c1);
+
+    args = {"-c", "77"};
+    run();
+    CHECK(77 == c1);
+
+    // convert hex for digit
+    args = {"-c", "0x44"};
+    run();
+    CHECK(0x44 == c1);
+
+    args = {"-c", "751615654161688126132138844896646748852"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "vectorDefaults", "[optiontype]") {
+    std::vector<int> vals{4, 5};
+    auto opt = app.add_option("--long", vals)->capture_default_str();
+
+    args = {"--long", "[1,2,3]"};
+
+    run();
+
+    CHECK(std::vector<int>({1, 2, 3}) == vals);
+
+    args.clear();
+    run();
+    auto res = app["--long"]->as<std::vector<int>>();
+    CHECK(std::vector<int>({4, 5}) == res);
+
+    app.clear();
+    opt->expected(1)->take_last();
+    res = app["--long"]->as<std::vector<int>>();
+    CHECK(std::vector<int>({5}) == res);
+    opt->take_first();
+    res = app["--long"]->as<std::vector<int>>();
+    CHECK(std::vector<int>({4}) == res);
+
+    opt->expected(0, 1)->take_last();
+    run();
+
+    CHECK(std::vector<int>({4}) == res);
+    res = app["--long"]->as<std::vector<int>>();
+    CHECK(std::vector<int>({5}) == res);
+}
+
+TEST_CASE_METHOD(TApp, "mapInput", "[optiontype]") {
+    std::map<int, std::string> vals{};
+    app.add_option("--long", vals);
+
+    args = {"--long", "5", "test"};
+
+    run();
+
+    CHECK(vals.at(5) == "test");
+}
+
+TEST_CASE_METHOD(TApp, "CallbackBoolFlags", "[optiontype]") {
+
+    bool value{false};
+
+    auto func = [&value]() { value = true; };
+
+    auto cback = app.add_flag_callback("--val", func);
+    args = {"--val"};
+    run();
+    CHECK(value);
+    value = false;
+    args = {"--val=false"};
+    run();
+    CHECK(!value);
+
+    CHECK_THROWS_AS(app.add_flag_callback("hi", func), CLI::IncorrectConstruction);
+    cback->multi_option_policy(CLI::MultiOptionPolicy::Throw);
+    args = {"--val", "--val=false"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "pair_check", "[optiontype]") {
+    std::string myfile{"pair_check_file.txt"};
+    bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a'));  // create file
+    CHECK(ok);
+
+    CHECK(CLI::ExistingFile(myfile).empty());
+    std::pair<std::string, int> findex;
+
+    auto v0 = CLI::ExistingFile;
+    v0.application_index(0);
+    auto v1 = CLI::PositiveNumber;
+    v1.application_index(1);
+    app.add_option("--file", findex)->check(v0)->check(v1);
+
+    args = {"--file", myfile, "2"};
+
+    CHECK_NOTHROW(run());
+
+    CHECK(myfile == findex.first);
+    CHECK(2 == findex.second);
+
+    args = {"--file", myfile, "-3"};
+
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--file", myfile, "2"};
+    std::remove(myfile.c_str());
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "pair_check_take_first", "[optiontype]") {
+    std::string myfile{"pair_check_file2.txt"};
+    bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a'));  // create file
+    CHECK(ok);
+
+    CHECK(CLI::ExistingFile(myfile).empty());
+    std::pair<std::string, int> findex;
+
+    auto opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber);
+    CHECK_THROWS_AS(opt->get_validator(3), CLI::OptionNotFound);
+    opt->get_validator(0)->application_index(0);
+    opt->get_validator(1)->application_index(1);
+    opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
+    args = {"--file", "not_a_file.txt", "-16", "--file", myfile, "2"};
+    // should only check the last one
+    CHECK_NOTHROW(run());
+
+    CHECK(myfile == findex.first);
+    CHECK(2 == findex.second);
+
+    opt->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst);
+
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "VectorFixedString", "[optiontype]") {
+    std::vector<std::string> strvec;
+    std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
+
+    CLI::Option *opt = app.add_option("-s,--string", strvec)->expected(3);
+    CHECK(opt->get_expected() == 3);
+
+    args = {"--string", "mystring", "mystring2", "mystring3"};
+    run();
+    CHECK(app.count("--string") == 3u);
+    CHECK(strvec == answer);
+}
+
+TEST_CASE_METHOD(TApp, "VectorDefaultedFixedString", "[optiontype]") {
+    std::vector<std::string> strvec{"one"};
+    std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
+
+    CLI::Option *opt = app.add_option("-s,--string", strvec, "")->expected(3)->capture_default_str();
+    CHECK(opt->get_expected() == 3);
+
+    args = {"--string", "mystring", "mystring2", "mystring3"};
+    run();
+    CHECK(app.count("--string") == 3u);
+    CHECK(strvec == answer);
+}
+
+TEST_CASE_METHOD(TApp, "VectorIndexedValidator", "[optiontype]") {
+    std::vector<int> vvec;
+
+    CLI::Option *opt = app.add_option("-v", vvec);
+
+    args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
+    run();
+    CHECK(app.count("-v") == 4u);
+    CHECK(vvec.size() == 4u);
+    opt->check(CLI::PositiveNumber.application_index(0));
+    opt->check((!CLI::PositiveNumber).application_index(1));
+    CHECK_NOTHROW(run());
+    CHECK(vvec.size() == 4u);
+    // v[3] would be negative
+    opt->check(CLI::PositiveNumber.application_index(3));
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "VectorUnlimString", "[optiontype]") {
+    std::vector<std::string> strvec;
+    std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
+
+    CLI::Option *opt = app.add_option("-s,--string", strvec);
+    CHECK(opt->get_expected() == 1);
+    CHECK(opt->get_expected_max() == CLI::detail::expected_max_vector_size);
+
+    args = {"--string", "mystring", "mystring2", "mystring3"};
+    run();
+    CHECK(app.count("--string") == 3u);
+    CHECK(strvec == answer);
+
+    args = {"-s", "mystring", "mystring2", "mystring3"};
+    run();
+    CHECK(app.count("--string") == 3u);
+    CHECK(strvec == answer);
+}
+
+// From https://github.com/CLIUtils/CLI11/issues/420
+TEST_CASE_METHOD(TApp, "stringLikeTests", "[optiontype]") {
+    struct nType {
+        explicit nType(const std::string &a_value) : m_value{a_value} {}
+
+        explicit operator std::string() const { return std::string{"op str"}; }
+
+        std::string m_value;
+    };
+
+    nType m_type{"abc"};
+    app.add_option("--type", m_type, "type")->capture_default_str();
+    run();
+
+    CHECK("op str" == app["--type"]->as<std::string>());
+    args = {"--type", "bca"};
+    run();
+    CHECK("op str" == std::string(m_type));
+    CHECK("bca" == m_type.m_value);
+}
+
+TEST_CASE_METHOD(TApp, "VectorExpectedRange", "[optiontype]") {
+    std::vector<std::string> strvec;
+
+    CLI::Option *opt = app.add_option("--string", strvec);
+    opt->expected(2, 4)->multi_option_policy(CLI::MultiOptionPolicy::Throw);
+
+    args = {"--string", "mystring", "mystring2", "mystring3"};
+    run();
+    CHECK(app.count("--string") == 3u);
+
+    args = {"--string", "mystring"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+
+    args = {"--string", "mystring", "mystring2", "string2", "--string", "string4", "string5"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+
+    CHECK(4 == opt->get_expected_max());
+    CHECK(2 == opt->get_expected_min());
+    opt->expected(4, 2);  // just test the handling of reversed arguments
+    CHECK(4 == opt->get_expected_max());
+    CHECK(2 == opt->get_expected_min());
+    opt->expected(-5);
+    CHECK(5 == opt->get_expected_max());
+    CHECK(5 == opt->get_expected_min());
+    opt->expected(-5, 7);
+    CHECK(7 == opt->get_expected_max());
+    CHECK(5 == opt->get_expected_min());
+}
+
+TEST_CASE_METHOD(TApp, "VectorFancyOpts", "[optiontype]") {
+    std::vector<std::string> strvec;
+    std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
+
+    CLI::Option *opt = app.add_option("-s,--string", strvec)->required()->expected(3);
+    CHECK(opt->get_expected() == 3);
+
+    args = {"--string", "mystring", "mystring2", "mystring3"};
+    run();
+    CHECK(app.count("--string") == 3u);
+    CHECK(strvec == answer);
+
+    args = {"one", "two"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    CHECK_THROWS_AS(run(), CLI::ParseError);
+}
+
+// #87
+TEST_CASE_METHOD(TApp, "CustomDoubleOption", "[optiontype]") {
+
+    std::pair<int, double> custom_opt;
+
+    auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) {
+        custom_opt = {stol(vals.at(0)), stod(vals.at(1))};
+        return true;
+    });
+    opt->type_name("INT FLOAT")->type_size(2);
+
+    args = {"12", "1.5"};
+
+    run();
+    CHECK(12 == custom_opt.first);
+    CHECK(1.5 == Approx(custom_opt.second));
+}
+
+// now with tuple support this is possible
+TEST_CASE_METHOD(TApp, "CustomDoubleOptionAlt", "[optiontype]") {
+
+    std::pair<int, double> custom_opt;
+
+    app.add_option("posit", custom_opt);
+
+    args = {"12", "1.5"};
+
+    run();
+    CHECK(12 == custom_opt.first);
+    CHECK(1.5 == Approx(custom_opt.second));
+}
+
+// now with independent type sizes and expected this is possible
+TEST_CASE_METHOD(TApp, "vectorPair", "[optiontype]") {
+
+    std::vector<std::pair<int, std::string>> custom_opt;
+
+    auto opt = app.add_option("--dict", custom_opt);
+
+    args = {"--dict", "1", "str1", "--dict", "3", "str3"};
+
+    run();
+    REQUIRE(2u == custom_opt.size());
+    CHECK(1 == custom_opt[0].first);
+    CHECK("str3" == custom_opt[1].second);
+
+    args = {"--dict", "1", "str1", "--dict", "3", "str3", "--dict", "-1", "str4"};
+    run();
+    REQUIRE(3u == custom_opt.size());
+    CHECK(-1 == custom_opt[2].first);
+    CHECK("str4" == custom_opt[2].second);
+    opt->check(CLI::PositiveNumber.application_index(0));
+
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "vectorPairFail", "[optiontype]") {
+
+    std::vector<std::pair<int, std::string>> custom_opt;
+
+    app.add_option("--dict", custom_opt);
+
+    args = {"--dict", "1", "str1", "--dict", "str3", "1"};
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "vectorPairTypeRange", "[optiontype]") {
+
+    std::vector<std::pair<int, std::string>> custom_opt;
+
+    auto opt = app.add_option("--dict", custom_opt);
+
+    opt->type_size(2, 1);  // just test switched arguments
+    CHECK(1 == opt->get_type_size_min());
+    CHECK(2 == opt->get_type_size_max());
+
+    args = {"--dict", "1", "str1", "--dict", "3", "str3"};
+
+    run();
+    REQUIRE(2u == custom_opt.size());
+    CHECK(1 == custom_opt[0].first);
+    CHECK("str3" == custom_opt[1].second);
+
+    args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
+    run();
+    REQUIRE(3u == custom_opt.size());
+    CHECK(custom_opt[1].second.empty());
+    CHECK(-1 == custom_opt[2].first);
+    CHECK("str4" == custom_opt[2].second);
+
+    opt->type_size(-2, -1);  // test negative arguments
+    CHECK(1 == opt->get_type_size_min());
+    CHECK(2 == opt->get_type_size_max());
+    // this type size spec should run exactly as before
+    run();
+    REQUIRE(3u == custom_opt.size());
+    CHECK(custom_opt[1].second.empty());
+    CHECK(-1 == custom_opt[2].first);
+    CHECK("str4" == custom_opt[2].second);
+}
+
+// now with independent type sizes and expected this is possible
+TEST_CASE_METHOD(TApp, "vectorTuple", "[optiontype]") {
+
+    std::vector<std::tuple<int, std::string, double>> custom_opt;
+
+    auto opt = app.add_option("--dict", custom_opt);
+
+    args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
+
+    run();
+    REQUIRE(2u == custom_opt.size());
+    CHECK(1 == std::get<0>(custom_opt[0]));
+    CHECK("str3" == std::get<1>(custom_opt[1]));
+    CHECK(2.7 == std::get<2>(custom_opt[1]));
+
+    args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
+    run();
+    REQUIRE(3u == custom_opt.size());
+    CHECK(-1 == std::get<0>(custom_opt[2]));
+    CHECK("str4" == std::get<1>(custom_opt[2]));
+    CHECK(-1.87 == std::get<2>(custom_opt[2]));
+    opt->check(CLI::PositiveNumber.application_index(0));
+
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args.back() = "haha";
+    args[9] = "45";
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+// now with independent type sizes and expected this is possible
+TEST_CASE_METHOD(TApp, "vectorVector", "[optiontype]") {
+
+    std::vector<std::vector<int>> custom_opt;
+
+    auto opt = app.add_option("--dict", custom_opt);
+
+    args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
+
+    run();
+    REQUIRE(2u == custom_opt.size());
+    CHECK(3u == custom_opt[0].size());
+    CHECK(2u == custom_opt[1].size());
+
+    args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
+            "3",      "3", "3", "3", "3",      "3", "3", "3",      "3", "-3"};
+    run();
+    REQUIRE(4u == custom_opt.size());
+    CHECK(3u == custom_opt[0].size());
+    CHECK(2u == custom_opt[1].size());
+    CHECK(1u == custom_opt[2].size());
+    CHECK(10u == custom_opt[3].size());
+    opt->check(CLI::PositiveNumber.application_index(9));
+
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+    args.pop_back();
+    CHECK_NOTHROW(run());
+
+    args.back() = "haha";
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    args = {"--dict", "1", "2", "4", "%%", "3", "1", "%%", "3", "%%", "3", "3", "3", "3", "3", "3", "3", "3", "3", "3"};
+    run();
+    REQUIRE(4u == custom_opt.size());
+}
+
+// now with independent type sizes and expected this is possible
+TEST_CASE_METHOD(TApp, "vectorVectorFixedSize", "[optiontype]") {
+
+    std::vector<std::vector<int>> custom_opt;
+
+    auto opt = app.add_option("--dict", custom_opt)->type_size(4);
+
+    args = {"--dict", "1", "2", "4", "3", "--dict", "3", "1", "2", "8"};
+
+    run();
+    REQUIRE(2u == custom_opt.size());
+    CHECK(4u == custom_opt[0].size());
+    CHECK(4u == custom_opt[1].size());
+
+    args = {"--dict", "1", "2", "4", "--dict", "3", "1", "7", "6"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+    // this should reset it
+    opt->type_size(CLI::detail::expected_max_vector_size);
+    opt->type_size(1, CLI::detail::expected_max_vector_size);
+    CHECK_NOTHROW(run());
+    REQUIRE(2U == custom_opt.size());
+}
+
+// now with independent type sizes and expected this is possible
+TEST_CASE_METHOD(TApp, "tuplePair", "[optiontype]") {
+    std::tuple<std::pair<int, double>> custom_opt;
+
+    app.add_option("--pr", custom_opt);
+
+    args = {"--pr", "1", "2"};
+
+    run();
+    CHECK(1 == std::get<0>(custom_opt).first);
+    CHECK(2.0 == std::get<0>(custom_opt).second);
+}
+// now with independent type sizes and expected this is possible
+TEST_CASE_METHOD(TApp, "tupleintPair", "[optiontype]") {
+    std::tuple<int, std::pair<int, double>> custom_opt;
+
+    app.add_option("--pr", custom_opt);
+
+    args = {"--pr", "3", "1", "2"};
+
+    run();
+    CHECK(3 == std::get<0>(custom_opt));
+    CHECK(1 == std::get<1>(custom_opt).first);
+    CHECK(2.0 == std::get<1>(custom_opt).second);
+}
+
+static_assert(CLI::detail::is_mutable_container<std::set<std::string>>::value, "set should be a container");
+static_assert(CLI::detail::is_mutable_container<std::map<std::string, std::string>>::value,
+              "map should be a container");
+static_assert(CLI::detail::is_mutable_container<std::unordered_map<std::string, double>>::value,
+              "unordered_map should be a container");
+
+static_assert(CLI::detail::is_mutable_container<std::list<std::pair<int, std::string>>>::value,
+              "list should be a container");
+
+static_assert(CLI::detail::type_count<std::set<std::string>>::value == 1, "set should have a type size of 1");
+static_assert(CLI::detail::type_count<std::set<std::tuple<std::string, int, int>>>::value == 3,
+              "tuple set should have size of 3");
+static_assert(CLI::detail::type_count<std::map<std::string, std::string>>::value == 2,
+              "map should have a type size of 2");
+static_assert(CLI::detail::type_count<std::unordered_map<std::string, double>>::value == 2,
+              "unordered_map should have a type size of 2");
+
+static_assert(CLI::detail::type_count<std::list<std::pair<int, std::string>>>::value == 2,
+              "list<int,string> should have a type size of 2");
+static_assert(CLI::detail::type_count<std::map<std::string, std::pair<int, std::string>>>::value == 3,
+              "map<string,pair<int,string>> should have a type size of 3");
+
+TEMPLATE_TEST_CASE("Container int single",
+                   "[optiontype]",
+                   std::vector<int>,
+                   std::deque<int>,
+                   std::set<int>,
+                   std::list<int>,
+                   std::unordered_set<int>) {
+    TApp tapp;
+    TestType cv;
+
+    CLI::Option *opt = tapp.app.add_option("-v", cv);
+
+    tapp.args = {"-v", "1", "-1", "-v", "3", "-v", "-976"};
+    tapp.run();
+    CHECK(tapp.app.count("-v") == 4u);
+    CHECK(cv.size() == 4u);
+    opt->check(CLI::PositiveNumber.application_index(0));
+    opt->check((!CLI::PositiveNumber).application_index(1));
+    CHECK_NOTHROW(tapp.run());
+    CHECK(cv.size() == 4u);
+    // v[3] would be negative
+    opt->check(CLI::PositiveNumber.application_index(3));
+    CHECK_THROWS_AS(tapp.run(), CLI::ValidationError);
+}
+
+using isp = std::pair<int, std::string>;
+
+TEMPLATE_TEST_CASE("Container pair",
+                   "[optiontype]",
+                   std::vector<isp>,
+                   std::deque<isp>,
+                   std::set<isp>,
+                   std::list<isp>,
+                   (std::map<int, std::string>),
+                   (std::unordered_map<int, std::string>)) {
+    TApp tapp;
+    TestType cv;
+
+    (tapp.app).add_option("--dict", cv);
+
+    tapp.args = {"--dict", "1", "str1", "--dict", "3", "str3"};
+
+    tapp.run();
+    CHECK(2u == cv.size());
+
+    tapp.args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"};
+    tapp.run();
+    CHECK(3u == cv.size());
+}
+
+template <class T> class TApp_container_tuple : public TApp {
+  public:
+    using container_type = T;
+    container_type cval{};
+    TApp_container_tuple() : TApp() {}
+};
+
+using tup_obj = std::tuple<int, std::string, double>;
+
+TEMPLATE_TEST_CASE("Container tuple",
+                   "[optiontype]",
+                   std::vector<tup_obj>,
+                   std::deque<tup_obj>,
+                   std::set<tup_obj>,
+                   std::list<tup_obj>,
+                   (std::map<int, std::pair<std::string, double>>),
+                   (std::unordered_map<int, std::tuple<std::string, double>>)) {
+    TApp tapp;
+    TestType cv;
+
+    (tapp.app).add_option("--dict", cv);
+
+    tapp.args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"};
+
+    tapp.run();
+    CHECK(2u == cv.size());
+
+    tapp.args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"};
+    tapp.run();
+    CHECK(3u == cv.size());
+}
+
+using icontainer1 = std::vector<int>;
+using icontainer2 = std::list<int>;
+using icontainer3 = std::set<int>;
+using icontainer4 = std::pair<int, std::vector<int>>;
+
+TEMPLATE_TEST_CASE("Container container",
+                   "[optiontype]",
+                   std::vector<icontainer1>,
+                   std::list<icontainer1>,
+                   std::set<icontainer1>,
+                   std::deque<icontainer1>,
+                   std::vector<icontainer2>,
+                   std::list<icontainer2>,
+                   std::set<icontainer2>,
+                   std::deque<icontainer2>,
+                   std::vector<icontainer3>,
+                   std::list<icontainer3>,
+                   std::set<icontainer3>,
+                   std::deque<icontainer3>) {
+    TApp tapp;
+    TestType cv;
+
+    (tapp.app).add_option("--dict", cv);
+
+    tapp.args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
+
+    tapp.run();
+    CHECK(2u == cv.size());
+
+    tapp.args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict",
+                 "3",      "3", "3", "3", "3",      "3", "3", "3",      "3", "-3"};
+    tapp.run();
+    CHECK(4u == cv.size());
+}
+
+TEST_CASE_METHOD(TApp, "containerContainer", "[optiontype]") {
+
+    std::vector<icontainer4> cv;
+    app.add_option("--dict", cv);
+
+    args = {"--dict", "1", "2", "4", "--dict", "3", "1"};
+
+    run();
+    CHECK(2u == cv.size());
+
+    args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "",  "--dict",
+            "3",      "3", "3", "3", "3",      "3", "3", "3",      "3", "-3"};
+    run();
+    CHECK(4u == cv.size());
+}
+
+TEST_CASE_METHOD(TApp, "unknownContainerWrapper", "[optiontype]") {
+
+    class vopt {
+      public:
+        vopt() = default;
+        explicit vopt(const std::vector<double> &vdub) : val_{vdub} {};
+        std::vector<double> val_{};
+    };
+
+    vopt cv;
+    app.add_option<vopt, std::vector<double>>("--vv", cv);
+
+    args = {"--vv", "1", "2", "4"};
+
+    run();
+    CHECK(3u == cv.val_.size());
+    args = {"--vv", ""};
+
+    run();
+    CHECK(cv.val_.empty());
+}
+
+TEST_CASE_METHOD(TApp, "tupleTwoVectors", "[optiontype]") {
+
+    std::tuple<std::vector<int>, std::vector<int>> cv;
+    app.add_option("--vv", cv);
+
+    args = {"--vv", "1", "2", "4"};
+
+    run();
+    CHECK(3U == std::get<0>(cv).size());
+    CHECK(std::get<1>(cv).empty());
+
+    args = {"--vv", "1", "2", "%%", "4", "4", "5"};
+
+    run();
+    CHECK(2U == std::get<0>(cv).size());
+    CHECK(3U == std::get<1>(cv).size());
+}
+
+TEST_CASE_METHOD(TApp, "vectorSingleArg", "[optiontype]") {
+
+    std::vector<int> cv;
+    app.add_option("-c", cv)->allow_extra_args(false);
+    std::string extra;
+    app.add_option("args", extra);
+    args = {"-c", "1", "-c", "2", "4"};
+
+    run();
+    CHECK(2U == cv.size());
+    CHECK("4" == extra);
+}
+
+TEST_CASE_METHOD(TApp, "vectorDoubleArg", "[optiontype]") {
+
+    std::vector<std::pair<int, std::string>> cv;
+    app.add_option("-c", cv)->allow_extra_args(false);
+    std::vector<std::string> extras;
+    app.add_option("args", extras);
+    args = {"-c", "1", "bob", "-c", "2", "apple", "4", "key"};
+
+    run();
+    CHECK(2U == cv.size());
+    CHECK(2U == extras.size());
+}
+
+TEST_CASE_METHOD(TApp, "OnParseCall", "[optiontype]") {
+
+    int cnt{0};
+
+    auto *opt = app.add_option("-c",
+                               [&cnt](const CLI::results_t &) {
+                                   ++cnt;
+                                   return true;
+                               })
+                    ->expected(1, 20)
+                    ->trigger_on_parse();
+    std::vector<std::string> extras;
+    app.add_option("args", extras);
+    args = {"-c", "1", "-c", "2", "-c", "3"};
+    CHECK(opt->get_trigger_on_parse());
+    run();
+    CHECK(3 == cnt);
+}
+
+TEST_CASE_METHOD(TApp, "OnParseCallVector", "[optiontype]") {
+
+    std::vector<std::string> vec;
+
+    app.add_option("-c", vec)->trigger_on_parse();
+    args = {"-c", "1", "2", "3", "-c", "2", "-c", "3", "4", "5"};
+    run();
+    CHECK(vec.size() == 3U);
+}
+
+TEST_CASE_METHOD(TApp, "force_callback", "[optiontype]") {
+
+    int cnt{0};
+
+    auto *opt = app.add_option("-c",
+                               [&cnt](const CLI::results_t &) {
+                                   ++cnt;
+                                   return true;
+                               })
+                    ->expected(1, 20)
+                    ->force_callback()
+                    ->default_str("5");
+    std::vector<std::string> extras;
+    app.add_option("args", extras);
+    args = {};
+    CHECK(opt->get_force_callback());
+    run();
+    CHECK(1 == cnt);
+    cnt = 0;
+    args = {"-c", "10"};
+    run();
+    CHECK(1 == cnt);
+}
+
+TEST_CASE_METHOD(TApp, "force_callback2", "[optiontype]") {
+
+    int cnt{0};
+
+    app.add_option("-c", cnt)->force_callback()->default_val(5);
+    args = {};
+    run();
+    CHECK(5 == cnt);
+}
+
+TEST_CASE_METHOD(TApp, "force_callback3", "[optiontype]") {
+
+    int cnt{10};
+
+    app.add_option("-c", cnt)->force_callback();
+    args = {};
+    run();
+    CHECK(0 == cnt);
+}
diff --git a/external/CLI11/tests/OptionalTest.cpp b/external/CLI11/tests/OptionalTest.cpp
new file mode 100644
index 0000000..5ea20be
--- /dev/null
+++ b/external/CLI11/tests/OptionalTest.cpp
@@ -0,0 +1,333 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <complex>
+#include <cstdint>
+#include <cstdlib>
+#include <iostream>
+
+#include "app_helper.hpp"
+
+// You can explicitly enable or disable support
+// by defining to 1 or 0. Extra check here to ensure it's in the stdlib too.
+// We nest the check for __has_include and it's usage
+#ifndef CLI11_STD_OPTIONAL
+#ifdef __has_include
+#if defined(CLI11_CPP17) && __has_include(<optional>)
+#define CLI11_STD_OPTIONAL 1
+#else
+#define CLI11_STD_OPTIONAL 0
+#endif
+#else
+#define CLI11_STD_OPTIONAL 0
+#endif
+#endif
+
+#ifndef CLI11_EXPERIMENTAL_OPTIONAL
+#define CLI11_EXPERIMENTAL_OPTIONAL 0
+#endif
+
+#ifndef CLI11_BOOST_OPTIONAL
+#define CLI11_BOOST_OPTIONAL 0
+#endif
+
+#if CLI11_BOOST_OPTIONAL
+#include <boost/version.hpp>
+#if BOOST_VERSION < 106100
+#error "This boost::optional version is not supported, use 1.61 or better"
+#endif
+#endif
+
+#if CLI11_STD_OPTIONAL
+#include <optional>
+#endif
+#if CLI11_EXPERIMENTAL_OPTIONAL
+#include <experimental/optional>
+#endif
+#if CLI11_BOOST_OPTIONAL
+#include <boost/optional.hpp>
+#include <boost/optional/optional_io.hpp>
+#endif
+// [CLI11:verbatim]
+
+#if CLI11_STD_OPTIONAL
+
+#ifdef _MSC_VER
+// this warning suppresses double to int conversions that are inherent in the test
+// on windows.  This may be able to removed in the future as the add_option capability
+// improves
+#pragma warning(disable : 4244)
+#endif
+
+TEST_CASE_METHOD(TApp, "StdOptionalTest", "[optional]") {
+    std::optional<int> opt;
+    app.add_option("-c,--count", opt);
+    run();
+    CHECK(!opt);
+
+    args = {"-c", "1"};
+    run();
+    CHECK(opt);
+    CHECK(1 == *opt);
+
+    args = {"--count", "3"};
+    run();
+    CHECK(opt);
+    CHECK(3 == *opt);
+}
+
+TEST_CASE_METHOD(TApp, "StdOptionalVectorEmptyDirect", "[optional]") {
+    std::optional<std::vector<int>> opt;
+    app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
+    // app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
+    run();
+    CHECK(!opt);
+    args = {"-v"};
+    opt = std::vector<int>{4, 3};
+    run();
+    CHECK(!opt);
+    args = {"-v", "1", "4", "5"};
+    run();
+    CHECK(opt);
+    std::vector<int> expV{1, 4, 5};
+    CHECK(expV == *opt);
+}
+
+TEST_CASE_METHOD(TApp, "StdOptionalComplexDirect", "[optional]") {
+    std::optional<std::complex<double>> opt;
+    app.add_option("-c,--complex", opt)->type_size(0, 2);
+    run();
+    CHECK(!opt);
+    args = {"-c"};
+    opt = std::complex<double>{4.0, 3.0};
+    run();
+    CHECK(!opt);
+    args = {"-c", "1+2j"};
+    run();
+    CHECK(opt);
+    std::complex<double> val{1, 2};
+    CHECK(val == *opt);
+    args = {"-c", "3", "-4"};
+    run();
+    CHECK(opt);
+    std::complex<double> val2{3, -4};
+    CHECK(val2 == *opt);
+}
+
+TEST_CASE_METHOD(TApp, "StdOptionalUint", "[optional]") {
+    std::optional<std::uint64_t> opt;
+    app.add_option("-i,--int", opt);
+    run();
+    CHECK(!opt);
+
+    args = {"-i", "15"};
+    run();
+    CHECK(15U == *opt);
+    static_assert(CLI::detail::classify_object<std::optional<std::uint64_t>>::value ==
+                  CLI::detail::object_category::wrapper_value);
+}
+
+#ifdef _MSC_VER
+#pragma warning(default : 4244)
+#endif
+
+#endif
+#if CLI11_EXPERIMENTAL_OPTIONAL
+
+TEST_CASE_METHOD(TApp, "ExperimentalOptionalTest", "[optional]") {
+    std::experimental::optional<int> opt;
+    app.add_option("-c,--count", opt);
+    run();
+    CHECK(!opt);
+
+    args = {"-c", "1"};
+    run();
+    CHECK(opt);
+    CHECK(1 == *opt);
+
+    args = {"--count", "3"};
+    run();
+    CHECK(opt);
+    CHECK(3 == *opt);
+}
+
+#endif
+#if CLI11_BOOST_OPTIONAL
+
+TEST_CASE_METHOD(TApp, "BoostOptionalTest", "[optional]") {
+    boost::optional<int> opt;
+    app.add_option("-c,--count", opt);
+    run();
+    CHECK(!opt);
+
+    args = {"-c", "1"};
+    run();
+    CHECK(opt);
+    CHECK(1 == *opt);
+    opt = {};
+    args = {"--count", "3"};
+    run();
+    CHECK(opt);
+    CHECK(3 == *opt);
+}
+
+TEST_CASE_METHOD(TApp, "BoostOptionalTestZarg", "[optional]") {
+    boost::optional<int> opt;
+    app.add_option("-c,--count", opt)->expected(0, 1);
+    run();
+    CHECK(!opt);
+
+    args = {"-c", "1"};
+    run();
+    CHECK(opt);
+    CHECK(1 == *opt);
+    opt = {};
+    args = {"--count"};
+    run();
+    CHECK(!opt);
+}
+
+TEST_CASE_METHOD(TApp, "BoostOptionalint64Test", "[optional]") {
+    boost::optional<std::int64_t> opt;
+    app.add_option("-c,--count", opt);
+    run();
+    CHECK(!opt);
+
+    args = {"-c", "1"};
+    run();
+    CHECK(opt);
+    CHECK(1 == *opt);
+    opt = {};
+    args = {"--count", "3"};
+    run();
+    CHECK(opt);
+    CHECK(3 == *opt);
+}
+
+TEST_CASE_METHOD(TApp, "BoostOptionalStringTest", "[optional]") {
+    boost::optional<std::string> opt;
+    app.add_option("-s,--string", opt);
+    run();
+    CHECK(!opt);
+
+    args = {"-s", "strval"};
+    run();
+    CHECK(opt);
+    CHECK("strval" == *opt);
+    opt = {};
+    args = {"--string", "strv"};
+    run();
+    CHECK(opt);
+    CHECK("strv" == *opt);
+}
+namespace boost {
+using CLI::enums::operator<<;
+}
+
+TEST_CASE_METHOD(TApp, "BoostOptionalEnumTest", "[optional]") {
+
+    enum class eval : char { val0 = 0, val1 = 1, val2 = 2, val3 = 3, val4 = 4 };
+    boost::optional<eval> opt, opt2;
+
+    auto optptr = app.add_option<decltype(opt), eval>("-v,--val", opt);
+    app.add_option_no_stream("-e,--eval", opt2);
+    optptr->capture_default_str();
+
+    auto dstring = optptr->get_default_str();
+    CHECK(dstring.empty());
+    run();
+    CHECK(!opt);
+
+    args = {"-v", "3"};
+    run();
+    CHECK(opt);
+    CHECK(*opt == eval::val3);
+    opt = {};
+    args = {"--val", "1"};
+    run();
+    CHECK(opt);
+    CHECK(*opt == eval::val1);
+}
+
+TEST_CASE_METHOD(TApp, "BoostOptionalVector", "[optional]") {
+    boost::optional<std::vector<int>> opt;
+    app.add_option_function<std::vector<int>>(
+           "-v,--vec", [&opt](const std::vector<int> &v) { opt = v; }, "some vector")
+        ->expected(3);
+    run();
+    bool checkOpt = static_cast<bool>(opt);
+    CHECK(!checkOpt);
+
+    args = {"-v", "1", "4", "5"};
+    run();
+    checkOpt = static_cast<bool>(opt);
+    CHECK(checkOpt);
+    std::vector<int> expV{1, 4, 5};
+    CHECK(expV == *opt);
+}
+
+TEST_CASE_METHOD(TApp, "BoostOptionalVectorEmpty", "[optional]") {
+    boost::optional<std::vector<int>> opt;
+    app.add_option<decltype(opt), std::vector<int>>("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
+    // app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
+    run();
+    bool checkOpt = static_cast<bool>(opt);
+    CHECK(!checkOpt);
+    args = {"-v"};
+    opt = std::vector<int>{4, 3};
+    run();
+    checkOpt = static_cast<bool>(opt);
+    CHECK(!checkOpt);
+    args = {"-v", "1", "4", "5"};
+    run();
+    checkOpt = static_cast<bool>(opt);
+    CHECK(checkOpt);
+    std::vector<int> expV{1, 4, 5};
+    CHECK(expV == *opt);
+}
+
+TEST_CASE_METHOD(TApp, "BoostOptionalVectorEmptyDirect", "[optional]") {
+    boost::optional<std::vector<int>> opt;
+    app.add_option_no_stream("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
+    // app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args();
+    run();
+    bool checkOpt = static_cast<bool>(opt);
+    CHECK(!checkOpt);
+    args = {"-v"};
+    opt = std::vector<int>{4, 3};
+    run();
+    checkOpt = static_cast<bool>(opt);
+    CHECK(!checkOpt);
+    args = {"-v", "1", "4", "5"};
+    run();
+    checkOpt = static_cast<bool>(opt);
+    CHECK(checkOpt);
+    std::vector<int> expV{1, 4, 5};
+    CHECK(expV == *opt);
+}
+
+TEST_CASE_METHOD(TApp, "BoostOptionalComplexDirect", "[optional]") {
+    boost::optional<std::complex<double>> opt;
+    app.add_option("-c,--complex", opt)->type_size(0, 2);
+    run();
+    CHECK(!opt);
+    args = {"-c"};
+    opt = std::complex<double>{4.0, 3.0};
+    run();
+    CHECK(!opt);
+    args = {"-c", "1+2j"};
+    run();
+    CHECK(opt);
+    std::complex<double> val{1, 2};
+    CHECK(val == *opt);
+    args = {"-c", "3", "-4"};
+    run();
+    CHECK(opt);
+    std::complex<double> val2{3, -4};
+    CHECK(val2 == *opt);
+}
+
+#endif
diff --git a/external/CLI11/tests/SetTest.cpp b/external/CLI11/tests/SetTest.cpp
new file mode 100644
index 0000000..5d225ff
--- /dev/null
+++ b/external/CLI11/tests/SetTest.cpp
@@ -0,0 +1,711 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+#include <map>
+
+static_assert(CLI::is_shared_ptr<std::shared_ptr<int>>::value == true, "is_shared_ptr should work on shared pointers");
+static_assert(CLI::is_shared_ptr<int *>::value == false, "is_shared_ptr should work on pointers");
+static_assert(CLI::is_shared_ptr<int>::value == false, "is_shared_ptr should work on non-pointers");
+static_assert(CLI::is_shared_ptr<const std::shared_ptr<int>>::value == true,
+              "is_shared_ptr should work on const shared pointers");
+static_assert(CLI::is_shared_ptr<const int *>::value == false, "is_shared_ptr should work on const pointers");
+static_assert(CLI::is_shared_ptr<const int &>::value == false, "is_shared_ptr should work on const references");
+static_assert(CLI::is_shared_ptr<int &>::value == false, "is_shared_ptr should work on non-const references");
+
+static_assert(CLI::is_copyable_ptr<std::shared_ptr<int>>::value == true,
+              "is_copyable_ptr should work on shared pointers");
+static_assert(CLI::is_copyable_ptr<int *>::value == true, "is_copyable_ptr should work on pointers");
+static_assert(CLI::is_copyable_ptr<int>::value == false, "is_copyable_ptr should work on non-pointers");
+static_assert(CLI::is_copyable_ptr<const std::shared_ptr<int>>::value == true,
+              "is_copyable_ptr should work on const shared pointers");
+static_assert(CLI::is_copyable_ptr<const int *>::value == true, "is_copyable_ptr should work on const pointers");
+static_assert(CLI::is_copyable_ptr<const int &>::value == false, "is_copyable_ptr should work on const references");
+static_assert(CLI::is_copyable_ptr<int &>::value == false, "is_copyable_ptr should work on non-const references");
+
+static_assert(CLI::detail::pair_adaptor<std::set<int>>::value == false, "Should not have pairs");
+static_assert(CLI::detail::pair_adaptor<std::vector<std::string>>::value == false, "Should not have pairs");
+static_assert(CLI::detail::pair_adaptor<std::map<int, int>>::value == true, "Should have pairs");
+static_assert(CLI::detail::pair_adaptor<std::vector<std::pair<int, int>>>::value == true, "Should have pairs");
+
+TEST_CASE_METHOD(TApp, "SimpleMaps", "[set]") {
+    int value{0};
+    std::map<std::string, int> map = {{"one", 1}, {"two", 2}};
+    auto opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
+    args = {"-s", "one"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(1 == value);
+}
+
+TEST_CASE_METHOD(TApp, "StringStringMap", "[set]") {
+    std::string value;
+    std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}};
+    app.add_option("-s,--set", value)->transform(CLI::CheckedTransformer(map));
+    args = {"-s", "a"};
+    run();
+    CHECK("b" == value);
+
+    args = {"-s", "b"};
+    run();
+    CHECK("c" == value);
+
+    args = {"-s", "c"};
+    CHECK("c" == value);
+}
+
+TEST_CASE_METHOD(TApp, "StringStringMapNoModify", "[set]") {
+    std::string value;
+    std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}};
+    app.add_option("-s,--set", value)->check(CLI::IsMember(map));
+    args = {"-s", "a"};
+    run();
+    CHECK("a" == value);
+
+    args = {"-s", "b"};
+    run();
+    CHECK("b" == value);
+
+    args = {"-s", "c"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+enum SimpleEnum { SE_one = 1, SE_two = 2 };
+
+TEST_CASE_METHOD(TApp, "EnumMap", "[set]") {
+    SimpleEnum value;
+    std::map<std::string, SimpleEnum> map = {{"one", SE_one}, {"two", SE_two}};
+    auto opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
+    args = {"-s", "one"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(SE_one == value);
+}
+
+enum class SimpleEnumC { one = 1, two = 2 };
+
+TEST_CASE_METHOD(TApp, "EnumCMap", "[set]") {
+    SimpleEnumC value;
+    std::map<std::string, SimpleEnumC> map = {{"one", SimpleEnumC::one}, {"two", SimpleEnumC::two}};
+    auto opt = app.add_option("-s,--set", value)->transform(CLI::Transformer(map));
+    args = {"-s", "one"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(SimpleEnumC::one == value);
+}
+
+TEST_CASE_METHOD(TApp, "structMap", "[set]") {
+    struct tstruct {
+        int val2;
+        double val3;
+        std::string v4;
+    };
+    std::string struct_name;
+    std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
+    auto opt = app.add_option("-s,--set", struct_name)->check(CLI::IsMember(map));
+    args = {"-s", "sone"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK("sone" == struct_name);
+
+    args = {"-s", "sthree"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "structMapChange", "[set]") {
+    struct tstruct {
+        int val2;
+        double val3;
+        std::string v4;
+    };
+    std::string struct_name;
+    std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
+    auto opt = app.add_option("-s,--set", struct_name)
+                   ->transform(CLI::IsMember(map, CLI::ignore_case, CLI::ignore_underscore, CLI::ignore_space));
+    args = {"-s", "s one"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK("sone" == struct_name);
+
+    args = {"-s", "sthree"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"-s", "S_t_w_o"};
+    run();
+    CHECK("stwo" == struct_name);
+    args = {"-s", "S two"};
+    run();
+    CHECK("stwo" == struct_name);
+}
+
+TEST_CASE_METHOD(TApp, "structMapNoChange", "[set]") {
+    struct tstruct {
+        int val2;
+        double val3;
+        std::string v4;
+    };
+    std::string struct_name;
+    std::map<std::string, struct tstruct> map = {{"sone", {4, 32.4, "foo"}}, {"stwo", {5, 99.7, "bar"}}};
+    auto opt = app.add_option("-s,--set", struct_name)
+                   ->check(CLI::IsMember(map, CLI::ignore_case, CLI::ignore_underscore, CLI::ignore_space));
+    args = {"-s", "SONE"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK("SONE" == struct_name);
+
+    args = {"-s", "sthree"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"-s", "S_t_w_o"};
+    run();
+    CHECK("S_t_w_o" == struct_name);
+
+    args = {"-s", "S two"};
+    run();
+    CHECK("S two" == struct_name);
+}
+
+TEST_CASE_METHOD(TApp, "NonCopyableMap", "[set]") {
+
+    std::string map_name;
+    std::map<std::string, std::unique_ptr<double>> map;
+    map["e1"] = std::unique_ptr<double>(new double(5.7));
+    map["e3"] = std::unique_ptr<double>(new double(23.8));
+    auto opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(&map));
+    args = {"-s", "e1"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK("e1" == map_name);
+
+    args = {"-s", "e45"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "NonCopyableMapWithFunction", "[set]") {
+
+    std::string map_name;
+    std::map<std::string, std::unique_ptr<double>> map;
+    map["e1"] = std::unique_ptr<double>(new double(5.7));
+    map["e3"] = std::unique_ptr<double>(new double(23.8));
+    auto opt = app.add_option("-s,--set", map_name)->transform(CLI::IsMember(&map, CLI::ignore_underscore));
+    args = {"-s", "e_1"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK("e1" == map_name);
+
+    args = {"-s", "e45"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "NonCopyableMapNonStringMap", "[set]") {
+
+    std::string map_name;
+    std::map<int, std::unique_ptr<double>> map;
+    map[4] = std::unique_ptr<double>(new double(5.7));
+    map[17] = std::unique_ptr<double>(new double(23.8));
+    auto opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(&map));
+    args = {"-s", "4"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK("4" == map_name);
+
+    args = {"-s", "e45"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "CopyableMapMove", "[set]") {
+
+    std::string map_name;
+    std::map<int, double> map;
+    map[4] = 5.7;
+    map[17] = 23.8;
+    auto opt = app.add_option("-s,--set", map_name)->check(CLI::IsMember(std::move(map)));
+    args = {"-s", "4"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK("4" == map_name);
+
+    args = {"-s", "e45"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "SimpleSets", "[set]") {
+    std::string value;
+    auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<std::string>({"one", "two", "three"})});
+    args = {"-s", "one"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK("one" == value);
+}
+
+TEST_CASE_METHOD(TApp, "SimpleSetsPtrs", "[set]") {
+    auto set = std::shared_ptr<std::set<std::string>>(new std::set<std::string>{"one", "two", "three"});
+    std::string value;
+    auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{set});
+    args = {"-s", "one"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK("one" == value);
+
+    set->insert("four");
+
+    args = {"-s", "four"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK("four" == value);
+}
+
+TEST_CASE_METHOD(TApp, "SimiShortcutSets", "[set]") {
+    std::string value;
+    auto opt = app.add_option("--set", value)->check(CLI::IsMember({"one", "two", "three"}));
+    args = {"--set", "one"};
+    run();
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK("one" == value);
+
+    std::string value2;
+    auto opt2 = app.add_option("--set2", value2)->transform(CLI::IsMember({"One", "two", "three"}, CLI::ignore_case));
+    args = {"--set2", "onE"};
+    run();
+    CHECK(app.count("--set2") == 1u);
+    CHECK(opt2->count() == 1u);
+    CHECK("One" == value2);
+
+    std::string value3;
+    auto opt3 = app.add_option("--set3", value3)
+                    ->transform(CLI::IsMember({"O_ne", "two", "three"}, CLI::ignore_case, CLI::ignore_underscore));
+    args = {"--set3", "onE"};
+    run();
+    CHECK(app.count("--set3") == 1u);
+    CHECK(opt3->count() == 1u);
+    CHECK("O_ne" == value3);
+}
+
+TEST_CASE_METHOD(TApp, "SetFromCharStarArrayVector", "[set]") {
+    constexpr const char *names[3]{"one", "two", "three"};
+    std::string value;
+    auto opt = app.add_option("-s,--set", value)
+                   ->check(CLI::IsMember{std::vector<std::string>(std::begin(names), std::end(names))});
+    args = {"-s", "one"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK("one" == value);
+}
+
+TEST_CASE_METHOD(TApp, "OtherTypeSets", "[set]") {
+    int value{0};
+    std::vector<int> set = {2, 3, 4};
+    auto opt = app.add_option("--set", value)->check(CLI::IsMember(set));
+    args = {"--set", "3"};
+    run();
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(3 == value);
+
+    args = {"--set", "5"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    std::vector<int> set2 = {-2, 3, 4};
+    auto opt2 = app.add_option("--set2", value)->transform(CLI::IsMember(set2, [](int x) { return std::abs(x); }));
+    args = {"--set2", "-3"};
+    run();
+    CHECK(app.count("--set2") == 1u);
+    CHECK(opt2->count() == 1u);
+    CHECK(3 == value);
+
+    args = {"--set2", "-3"};
+    run();
+    CHECK(app.count("--set2") == 1u);
+    CHECK(opt2->count() == 1u);
+    CHECK(3 == value);
+
+    args = {"--set2", "2"};
+    run();
+    CHECK(app.count("--set2") == 1u);
+    CHECK(opt2->count() == 1u);
+    CHECK(-2 == value);
+}
+
+TEST_CASE_METHOD(TApp, "NumericalSets", "[set]") {
+    int value{0};
+    auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<int>({1, 2, 3})});
+    args = {"-s", "1"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(app.count("--set") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(1 == value);
+}
+
+// Converted original set tests
+
+TEST_CASE_METHOD(TApp, "SetWithDefaults", "[set]") {
+    int someint{2};
+    app.add_option("-a", someint)->capture_default_str()->check(CLI::IsMember({1, 2, 3, 4}));
+
+    args = {"-a1", "-a2"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "SetWithDefaultsConversion", "[set]") {
+    int someint{2};
+    app.add_option("-a", someint)->capture_default_str()->check(CLI::IsMember({1, 2, 3, 4}));
+
+    args = {"-a", "hi"};
+
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "SetWithDefaultsIC", "[set]") {
+    std::string someint = "ho";
+    app.add_option("-a", someint)->capture_default_str()->check(CLI::IsMember({"Hi", "Ho"}));
+
+    args = {"-aHi", "-aHo"};
+
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "InSet", "[set]") {
+
+    std::string choice;
+    app.add_option("-q,--quick", choice)->check(CLI::IsMember({"one", "two", "three"}));
+
+    args = {"--quick", "two"};
+
+    run();
+    CHECK(choice == "two");
+
+    args = {"--quick", "four"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "InSetWithDefault", "[set]") {
+
+    std::string choice = "one";
+    app.add_option("-q,--quick", choice)->capture_default_str()->check(CLI::IsMember({"one", "two", "three"}));
+
+    run();
+    CHECK(choice == "one");
+
+    args = {"--quick", "two"};
+
+    run();
+    CHECK(choice == "two");
+
+    args = {"--quick", "four"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "InCaselessSetWithDefault", "[set]") {
+
+    std::string choice = "one";
+    app.add_option("-q,--quick", choice)
+        ->capture_default_str()
+        ->transform(CLI::IsMember({"one", "two", "three"}, CLI::ignore_case));
+
+    run();
+    CHECK(choice == "one");
+
+    args = {"--quick", "tWo"};
+
+    run();
+    CHECK(choice == "two");
+
+    args = {"--quick", "four"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "InIntSet", "[set]") {
+
+    int choice{0};
+    app.add_option("-q,--quick", choice)->check(CLI::IsMember({1, 2, 3}));
+
+    args = {"--quick", "2"};
+
+    run();
+    CHECK(choice == 2);
+
+    args = {"--quick", "4"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "InIntSetWindows", "[set]") {
+
+    int choice{0};
+    app.add_option("-q,--quick", choice)->check(CLI::IsMember({1, 2, 3}));
+    app.allow_windows_style_options();
+    args = {"/q", "2"};
+
+    run();
+    CHECK(choice == 2);
+
+    args = {"/q", "4"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"/q4"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(TApp, "FailSet", "[set]") {
+
+    int choice{0};
+    app.add_option("-q,--quick", choice)->check(CLI::IsMember({1, 2, 3}));
+
+    args = {"--quick", "3", "--quick=2"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+
+    args = {"--quick=hello"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "FailMutableSet", "[set]") {
+
+    int choice{0};
+    auto vals = std::shared_ptr<std::set<int>>(new std::set<int>({1, 2, 3}));
+    app.add_option("-q,--quick", choice)->check(CLI::IsMember(vals));
+    app.add_option("-s,--slow", choice)->capture_default_str()->check(CLI::IsMember(vals));
+
+    args = {"--quick=hello"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--slow=hello"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "InSetIgnoreCase", "[set]") {
+
+    std::string choice;
+    app.add_option("-q,--quick", choice)->transform(CLI::IsMember({"one", "Two", "THREE"}, CLI::ignore_case));
+
+    args = {"--quick", "One"};
+    run();
+    CHECK(choice == "one");
+
+    args = {"--quick", "two"};
+    run();
+    CHECK(choice == "Two");
+
+    args = {"--quick", "ThrEE"};
+    run();
+    CHECK(choice == "THREE");
+
+    args = {"--quick", "four"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--quick=one", "--quick=two"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "InSetIgnoreCaseMutableValue", "[set]") {
+
+    std::set<std::string> options{"one", "Two", "THREE"};
+    std::string choice;
+    app.add_option("-q,--quick", choice)->transform(CLI::IsMember(&options, CLI::ignore_case));
+
+    args = {"--quick", "One"};
+    run();
+    CHECK(choice == "one");
+
+    args = {"--quick", "two"};
+    run();
+    CHECK(choice == "Two");
+
+    args = {"--quick", "ThrEE"};
+    run();
+    CHECK(choice == "THREE");
+
+    options.clear();
+    args = {"--quick", "ThrEE"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "InSetIgnoreCasePointer", "[set]") {
+
+    std::set<std::string> *options = new std::set<std::string>{"one", "Two", "THREE"};
+    std::string choice;
+    app.add_option("-q,--quick", choice)->transform(CLI::IsMember(*options, CLI::ignore_case));
+
+    args = {"--quick", "One"};
+    run();
+    CHECK(choice == "one");
+
+    args = {"--quick", "two"};
+    run();
+    CHECK(choice == "Two");
+
+    args = {"--quick", "ThrEE"};
+    run();
+    CHECK(choice == "THREE");
+
+    delete options;
+    args = {"--quick", "ThrEE"};
+    run();
+    CHECK(choice == "THREE");
+
+    args = {"--quick", "four"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--quick=one", "--quick=two"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "NotInSetIgnoreCasePointer", "[set]") {
+
+    std::set<std::string> *options = new std::set<std::string>{"one", "Two", "THREE"};
+    std::string choice;
+    app.add_option("-q,--quick", choice)->check(!CLI::IsMember(*options, CLI::ignore_case));
+
+    args = {"--quick", "One"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--quick", "four"};
+    run();
+    CHECK("four" == choice);
+}
+
+TEST_CASE_METHOD(TApp, "InSetIgnoreUnderscore", "[set]") {
+
+    std::string choice;
+    app.add_option("-q,--quick", choice)
+        ->transform(CLI::IsMember({"option_one", "option_two", "optionthree"}, CLI::ignore_underscore));
+
+    args = {"--quick", "option_one"};
+    run();
+    CHECK(choice == "option_one");
+
+    args = {"--quick", "optiontwo"};
+    run();
+    CHECK(choice == "option_two");
+
+    args = {"--quick", "_option_thr_ee"};
+    run();
+    CHECK(choice == "optionthree");
+
+    args = {"--quick", "Option4"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--quick=option_one", "--quick=option_two"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+TEST_CASE_METHOD(TApp, "InSetIgnoreCaseUnderscore", "[set]") {
+
+    std::string choice;
+    app.add_option("-q,--quick", choice)
+        ->transform(
+            CLI::IsMember({"Option_One", "option_two", "OptionThree"}, CLI::ignore_case, CLI::ignore_underscore));
+
+    args = {"--quick", "option_one"};
+    run();
+    CHECK(choice == "Option_One");
+
+    args = {"--quick", "OptionTwo"};
+    run();
+    CHECK(choice == "option_two");
+
+    args = {"--quick", "_OPTION_thr_ee"};
+    run();
+    CHECK(choice == "OptionThree");
+
+    args = {"--quick", "Option4"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--quick=option_one", "--quick=option_two"};
+    CHECK_THROWS_AS(run(), CLI::ArgumentMismatch);
+}
+
+// #113
+TEST_CASE_METHOD(TApp, "AddRemoveSetItems", "[set]") {
+    std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
+
+    std::string type1, type2;
+    app.add_option("--type1", type1)->check(CLI::IsMember(&items));
+    app.add_option("--type2", type2)->capture_default_str()->check(CLI::IsMember(&items));
+
+    args = {"--type1", "TYPE1", "--type2", "TYPE2"};
+
+    run();
+    CHECK("TYPE1" == type1);
+    CHECK("TYPE2" == type2);
+
+    items.insert("TYPE6");
+    items.insert("TYPE7");
+
+    items.erase("TYPE1");
+    items.erase("TYPE2");
+
+    args = {"--type1", "TYPE6", "--type2", "TYPE7"};
+    run();
+    CHECK("TYPE6" == type1);
+    CHECK("TYPE7" == type2);
+
+    args = {"--type1", "TYPE1"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--type2", "TYPE2"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "AddRemoveSetItemsNoCase", "[set]") {
+    std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
+
+    std::string type1, type2;
+    app.add_option("--type1", type1)->transform(CLI::IsMember(&items, CLI::ignore_case));
+    app.add_option("--type2", type2)->capture_default_str()->transform(CLI::IsMember(&items, CLI::ignore_case));
+
+    args = {"--type1", "TYPe1", "--type2", "TyPE2"};
+
+    run();
+    CHECK("TYPE1" == type1);
+    CHECK("TYPE2" == type2);
+
+    items.insert("TYPE6");
+    items.insert("TYPE7");
+
+    items.erase("TYPE1");
+    items.erase("TYPE2");
+
+    args = {"--type1", "TyPE6", "--type2", "tYPE7"};
+    run();
+    CHECK("TYPE6" == type1);
+    CHECK("TYPE7" == type2);
+
+    args = {"--type1", "TYPe1"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"--type2", "TYpE2"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
diff --git a/external/CLI11/tests/SimpleTest.cpp b/external/CLI11/tests/SimpleTest.cpp
new file mode 100644
index 0000000..3051a46
--- /dev/null
+++ b/external/CLI11/tests/SimpleTest.cpp
@@ -0,0 +1,34 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifdef CLI11_SINGLE_FILE
+#include "CLI11.hpp"
+#else
+#include "CLI/CLI.hpp"
+#endif
+
+#include "catch.hpp"
+
+using input_t = std::vector<std::string>;
+
+TEST_CASE("Basic: Empty", "[simple]") {
+
+    {
+        CLI::App app;
+        input_t simpleput;
+        app.parse(simpleput);
+    }
+    {
+        CLI::App app;
+        input_t spare = {"spare"};
+        CHECK_THROWS_AS(app.parse(spare), CLI::ExtrasError);
+    }
+    {
+        CLI::App app;
+        input_t simpleput;
+        app.parse(simpleput);
+    }
+}
diff --git a/external/CLI11/tests/StringParseTest.cpp b/external/CLI11/tests/StringParseTest.cpp
new file mode 100644
index 0000000..a984248
--- /dev/null
+++ b/external/CLI11/tests/StringParseTest.cpp
@@ -0,0 +1,108 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+
+#include <cstdio>
+#include <sstream>
+
+TEST_CASE_METHOD(TApp, "ExistingExeCheck", "[stringparse]") {
+
+    TempFile tmpexe{"existingExe.out"};
+
+    std::string str, str2, str3;
+    app.add_option("-s,--string", str);
+    app.add_option("-t,--tstr", str2);
+    app.add_option("-m,--mstr", str3);
+
+    {
+        std::ofstream out{tmpexe};
+        out << "useless string doesn't matter" << std::endl;
+    }
+
+    app.parse(std::string("./") + std::string(tmpexe) +
+                  " --string=\"this is my quoted string\" -t 'qstring 2' -m=`\"quoted string\"`",
+              true);
+    CHECK("this is my quoted string" == str);
+    CHECK("qstring 2" == str2);
+    CHECK("\"quoted string\"" == str3);
+}
+
+TEST_CASE_METHOD(TApp, "ExistingExeCheckWithSpace", "[stringparse]") {
+
+    TempFile tmpexe{"Space File.out"};
+
+    std::string str, str2, str3;
+    app.add_option("-s,--string", str);
+    app.add_option("-t,--tstr", str2);
+    app.add_option("-m,--mstr", str3);
+
+    {
+        std::ofstream out{tmpexe};
+        out << "useless string doesn't matter" << std::endl;
+    }
+
+    app.parse(std::string("./") + std::string(tmpexe) +
+                  " --string=\"this is my quoted string\" -t 'qstring 2' -m=`\"quoted string\"`",
+              true);
+    CHECK("this is my quoted string" == str);
+    CHECK("qstring 2" == str2);
+    CHECK("\"quoted string\"" == str3);
+
+    CHECK(std::string("./") + std::string(tmpexe) == app.get_name());
+}
+
+TEST_CASE_METHOD(TApp, "ExistingExeCheckWithLotsOfSpace", "[stringparse]") {
+
+    TempFile tmpexe{"this is a weird file.exe"};
+
+    std::string str, str2, str3;
+    app.add_option("-s,--string", str);
+    app.add_option("-t,--tstr", str2);
+    app.add_option("-m,--mstr", str3);
+
+    {
+        std::ofstream out{tmpexe};
+        out << "useless string doesn't matter" << std::endl;
+    }
+
+    app.parse(std::string("./") + std::string(tmpexe) +
+                  " --string=\"this is my quoted string\" -t 'qstring 2' -m=`\"quoted string\"`",
+              true);
+    CHECK("this is my quoted string" == str);
+    CHECK("qstring 2" == str2);
+    CHECK("\"quoted string\"" == str3);
+
+    CHECK(std::string("./") + std::string(tmpexe) == app.get_name());
+}
+
+// From GitHub issue #591 https://github.com/CLIUtils/CLI11/issues/591
+TEST_CASE_METHOD(TApp, "ProgNameWithSpace", "[stringparse]") {
+
+    app.add_flag("--foo");
+    CHECK_NOTHROW(app.parse("\"Foo Bar\" --foo", true));
+
+    CHECK(app["--foo"]->as<bool>());
+    CHECK(app.get_name() == "Foo Bar");
+}
+
+TEST_CASE_METHOD(TApp, "ProgNameWithSpaceEmbeddedQuote", "[stringparse]") {
+
+    app.add_flag("--foo");
+    CHECK_NOTHROW(app.parse("\"Foo\\\" Bar\" --foo", true));
+
+    CHECK(app["--foo"]->as<bool>());
+    CHECK(app.get_name() == "Foo\" Bar");
+}
+
+TEST_CASE_METHOD(TApp, "ProgNameWithSpaceSingleQuote", "[stringparse]") {
+
+    app.add_flag("--foo");
+    CHECK_NOTHROW(app.parse(R"('Foo\' Bar' --foo)", true));
+
+    CHECK(app["--foo"]->as<bool>());
+    CHECK(app.get_name() == "Foo' Bar");
+}
diff --git a/external/CLI11/tests/SubcommandTest.cpp b/external/CLI11/tests/SubcommandTest.cpp
new file mode 100644
index 0000000..e1ffded
--- /dev/null
+++ b/external/CLI11/tests/SubcommandTest.cpp
@@ -0,0 +1,1957 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+
+using Catch::Matchers::Contains;
+
+using vs_t = std::vector<std::string>;
+
+TEST_CASE_METHOD(TApp, "BasicSubcommands", "[subcom]") {
+    auto sub1 = app.add_subcommand("sub1");
+    auto sub2 = app.add_subcommand("sub2");
+
+    CHECK(&app == sub1->get_parent());
+
+    CHECK(app.get_subcommand(sub1) == sub1);
+    CHECK(app.get_subcommand("sub1") == sub1);
+    CHECK_THROWS_AS(app.get_subcommand("sub3"), CLI::OptionNotFound);
+
+    run();
+    CHECK(app.get_subcommands().size() == 0u);
+
+    args = {"sub1"};
+    run();
+    CHECK(app.get_subcommands().at(0) == sub1);
+    CHECK(app.get_subcommands().size() == 1u);
+
+    app.clear();
+    CHECK(app.get_subcommands().size() == 0u);
+
+    args = {"sub2"};
+    run();
+    CHECK(app.get_subcommands().size() == 1u);
+    CHECK(app.get_subcommands().at(0) == sub2);
+
+    args = {"SUb2"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+
+    args = {"SUb2"};
+    try {
+        run();
+    } catch(const CLI::ExtrasError &e) {
+        CHECK_THAT(e.what(), Contains("SUb2"));
+    }
+
+    args = {"sub1", "extra"};
+    try {
+        run();
+    } catch(const CLI::ExtrasError &e) {
+        CHECK_THAT(e.what(), Contains("extra"));
+    }
+}
+
+TEST_CASE_METHOD(TApp, "MultiSubFallthrough", "[subcom]") {
+
+    // No explicit fallthrough
+    auto sub1 = app.add_subcommand("sub1");
+    auto sub2 = app.add_subcommand("sub2");
+
+    args = {"sub1", "sub2"};
+    run();
+    CHECK(app.got_subcommand("sub1"));
+    CHECK(app.got_subcommand(sub1));
+    CHECK(*sub1);
+    CHECK(sub1->parsed());
+    CHECK(1u == sub1->count());
+
+    CHECK(app.got_subcommand("sub2"));
+    CHECK(app.got_subcommand(sub2));
+    CHECK(*sub2);
+
+    app.require_subcommand();
+    run();
+
+    app.require_subcommand(2);
+    run();
+
+    app.require_subcommand(1);
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+
+    args = {"sub1"};
+    run();
+
+    CHECK(app.got_subcommand("sub1"));
+    CHECK(!app.got_subcommand("sub2"));
+
+    CHECK(*sub1);
+    CHECK(!*sub2);
+    CHECK(!sub2->parsed());
+    CHECK(0u == sub2->count());
+
+    CHECK_THROWS_AS(app.got_subcommand("sub3"), CLI::OptionNotFound);
+}
+
+TEST_CASE_METHOD(TApp, "CrazyNameSubcommand", "[subcom]") {
+    auto sub1 = app.add_subcommand("sub1");
+    // name can be set to whatever
+    CHECK_NOTHROW(sub1->name("crazy name with spaces"));
+    args = {"crazy name with spaces"};
+    run();
+
+    CHECK(app.got_subcommand("crazy name with spaces"));
+    CHECK(1u == sub1->count());
+}
+
+TEST_CASE_METHOD(TApp, "RequiredAndSubcommands", "[subcom]") {
+
+    std::string baz;
+    app.add_option("baz", baz, "Baz Description")->required()->capture_default_str();
+    auto foo = app.add_subcommand("foo");
+    auto bar = app.add_subcommand("bar");
+
+    args = {"bar", "foo"};
+    REQUIRE_NOTHROW(run());
+    CHECK(*foo);
+    CHECK(!*bar);
+    CHECK("bar" == baz);
+
+    args = {"foo"};
+    REQUIRE_NOTHROW(run());
+    CHECK(!*foo);
+    CHECK("foo" == baz);
+
+    args = {"foo", "foo"};
+    REQUIRE_NOTHROW(run());
+    CHECK(*foo);
+    CHECK("foo" == baz);
+
+    args = {"foo", "other"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(TApp, "RequiredAndSubcomFallthrough", "[subcom]") {
+
+    std::string baz;
+    app.add_option("baz", baz)->required();
+    app.add_subcommand("foo");
+    auto bar = app.add_subcommand("bar");
+    app.fallthrough();
+
+    args = {"other", "bar"};
+    run();
+    CHECK(bar);
+    CHECK("other" == baz);
+
+    args = {"bar", "other2"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(TApp, "FooFooProblem", "[subcom]") {
+
+    std::string baz_str, other_str;
+    auto baz = app.add_option("baz", baz_str);
+    auto foo = app.add_subcommand("foo");
+    auto other = foo->add_option("other", other_str);
+
+    args = {"foo", "foo"};
+    run();
+    CHECK(*foo);
+    CHECK(!*baz);
+    CHECK(*other);
+    CHECK("" == baz_str);
+    CHECK("foo" == other_str);
+
+    baz_str = "";
+    other_str = "";
+    baz->required();
+    run();
+    CHECK(*foo);
+    CHECK(*baz);
+    CHECK(!*other);
+    CHECK("foo" == baz_str);
+    CHECK("" == other_str);
+}
+
+TEST_CASE_METHOD(TApp, "DuplicateSubcommands", "[subcom]") {
+
+    auto foo = app.add_subcommand("foo");
+
+    args = {"foo", "foo"};
+    run();
+    CHECK(*foo);
+    CHECK(2u == foo->count());
+
+    args = {"foo", "foo", "foo"};
+    run();
+    CHECK(*foo);
+    CHECK(3u == foo->count());
+}
+
+TEST_CASE_METHOD(TApp, "DuplicateSubcommandCallbacks", "[subcom]") {
+
+    auto foo = app.add_subcommand("foo");
+    int count{0};
+    foo->callback([&count]() { ++count; });
+    foo->immediate_callback();
+    CHECK(foo->get_immediate_callback());
+    args = {"foo", "foo"};
+    run();
+    CHECK(2 == count);
+    count = 0;
+    args = {"foo", "foo", "foo"};
+    run();
+    CHECK(3 == count);
+}
+
+TEST_CASE_METHOD(TApp, "DuplicateSubcommandCallbacksValues", "[subcom]") {
+
+    auto foo = app.add_subcommand("foo");
+    int val{0};
+    foo->add_option("--val", val);
+    std::vector<int> vals;
+    foo->callback([&vals, &val]() { vals.push_back(val); });
+    foo->immediate_callback();
+    args = {"foo", "--val=45", "foo", "--val=27"};
+    run();
+    CHECK(2u == vals.size());
+    CHECK(45 == vals[0]);
+    CHECK(27 == vals[1]);
+    vals.clear();
+    args = {"foo", "--val=45", "foo", "--val=27", "foo", "--val=36"};
+    run();
+    CHECK(3u == vals.size());
+    CHECK(45 == vals[0]);
+    CHECK(27 == vals[1]);
+    CHECK(36 == vals[2]);
+}
+
+TEST_CASE_METHOD(TApp, "Callbacks", "[subcom]") {
+    auto sub1 = app.add_subcommand("sub1");
+    sub1->callback([]() { throw CLI::Success(); });
+    auto sub2 = app.add_subcommand("sub2");
+    bool val{false};
+    sub2->callback([&val]() { val = true; });
+
+    args = {"sub2"};
+    CHECK(!val);
+    run();
+    CHECK(val);
+}
+
+TEST_CASE_METHOD(TApp, "CallbackOrder", "[subcom]") {
+
+    std::vector<std::string> cb;
+    app.parse_complete_callback([&cb]() { cb.push_back("ac1"); });
+    app.final_callback([&cb]() { cb.push_back("ac2"); });
+    auto sub1 =
+        app.add_subcommand("sub1")
+            ->parse_complete_callback([&cb]() { cb.push_back("c1"); })
+            ->preparse_callback([&cb](std::size_t v1) { cb.push_back(std::string("pc1-") + std::to_string(v1)); });
+    auto sub2 =
+        app.add_subcommand("sub2")
+            ->final_callback([&cb]() { cb.push_back("c2"); })
+            ->preparse_callback([&cb](std::size_t v1) { cb.push_back(std::string("pc2-") + std::to_string(v1)); });
+    app.preparse_callback([&cb](std::size_t v1) { cb.push_back(std::string("pa-") + std::to_string(v1)); });
+
+    app.add_option("--opt1");
+    sub1->add_flag("--sub1opt");
+    sub1->add_option("--sub1optb");
+    sub1->add_flag("--sub1opt2");
+    sub2->add_flag("--sub2opt");
+    sub2->add_option("--sub2opt2");
+    args = {"--opt1",
+            "opt1_val",
+            "sub1",
+            "--sub1opt",
+            "--sub1optb",
+            "val",
+            "sub2",
+            "--sub2opt",
+            "sub1",
+            "--sub1opt2",
+            "sub2",
+            "--sub2opt2",
+            "val"};
+    run();
+    CHECK(8u == cb.size());
+    CHECK("pa-13" == cb[0]);
+    CHECK("pc1-10" == cb[1]);
+    CHECK("c1" == cb[2]);
+    CHECK("pc2-6" == cb[3]);
+    CHECK("c1" == cb[4]);
+    CHECK("ac1" == cb[5]);
+    CHECK("c2" == cb[6]);
+    CHECK("ac2" == cb[7]);
+}
+
+TEST_CASE_METHOD(TApp, "CallbackOrder2", "[subcom]") {
+
+    std::vector<std::string> cb;
+    app.add_subcommand("sub1")->parse_complete_callback([&cb]() { cb.push_back("sub1"); });
+    app.add_subcommand("sub2")->parse_complete_callback([&cb]() { cb.push_back("sub2"); });
+    app.add_subcommand("sub3")->parse_complete_callback([&cb]() { cb.push_back("sub3"); });
+
+    args = {"sub1", "sub2", "sub3", "sub1", "sub1", "sub2", "sub1"};
+    run();
+    CHECK(7u == cb.size());
+    CHECK("sub1" == cb[0]);
+    CHECK("sub2" == cb[1]);
+    CHECK("sub3" == cb[2]);
+    CHECK("sub1" == cb[3]);
+    CHECK("sub1" == cb[4]);
+    CHECK("sub2" == cb[5]);
+    CHECK("sub1" == cb[6]);
+}
+
+TEST_CASE_METHOD(TApp, "CallbackOrder2_withFallthrough", "[subcom]") {
+
+    std::vector<std::string> cb;
+
+    app.add_subcommand("sub1")->parse_complete_callback([&cb]() { cb.push_back("sub1"); })->fallthrough();
+    app.add_subcommand("sub2")->parse_complete_callback([&cb]() { cb.push_back("sub2"); });
+    app.add_subcommand("sub3")->parse_complete_callback([&cb]() { cb.push_back("sub3"); });
+
+    args = {"sub1", "sub2", "sub3", "sub1", "sub1", "sub2", "sub1"};
+    run();
+    CHECK(7u == cb.size());
+    CHECK("sub1" == cb[0]);
+    CHECK("sub2" == cb[1]);
+    CHECK("sub3" == cb[2]);
+    CHECK("sub1" == cb[3]);
+    CHECK("sub1" == cb[4]);
+    CHECK("sub2" == cb[5]);
+    CHECK("sub1" == cb[6]);
+}
+
+TEST_CASE_METHOD(TApp, "RuntimeErrorInCallback", "[subcom]") {
+    auto sub1 = app.add_subcommand("sub1");
+    sub1->callback([]() { throw CLI::RuntimeError(); });
+    auto sub2 = app.add_subcommand("sub2");
+    sub2->callback([]() { throw CLI::RuntimeError(2); });
+
+    args = {"sub1"};
+    CHECK_THROWS_AS(run(), CLI::RuntimeError);
+
+    args = {"sub1"};
+    try {
+        run();
+    } catch(const CLI::RuntimeError &e) {
+        CHECK(e.get_exit_code() == 1);
+    }
+
+    args = {"sub2"};
+    CHECK_THROWS_AS(run(), CLI::RuntimeError);
+
+    args = {"sub2"};
+    try {
+        run();
+    } catch(const CLI::RuntimeError &e) {
+        CHECK(e.get_exit_code() == 2);
+    }
+}
+
+TEST_CASE_METHOD(TApp, "NoFallThroughOpts", "[subcom]") {
+    int val{1};
+    app.add_option("--val", val);
+
+    app.add_subcommand("sub");
+
+    args = {"sub", "--val", "2"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(TApp, "NoFallThroughPositionals", "[subcom]") {
+    int val{1};
+    app.add_option("val", val);
+
+    app.add_subcommand("sub");
+
+    args = {"sub", "2"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(TApp, "NoFallThroughOptsWithTerminator", "[subcom]") {
+    int val{1};
+    app.add_option("--val", val);
+
+    app.add_subcommand("sub");
+
+    args = {"sub", "++", "--val", "2"};
+    run();
+    CHECK(2 == val);
+}
+
+TEST_CASE_METHOD(TApp, "NoFallThroughPositionalsWithTerminator", "[subcom]") {
+    int val{1};
+    app.add_option("val", val);
+
+    app.add_subcommand("sub");
+
+    args = {"sub", "++", "2"};
+    run();
+    CHECK(2 == val);
+
+    // try with positional only mark
+    args = {"sub", "--", "3"};
+    run();
+    CHECK(3 == val);
+}
+
+TEST_CASE_METHOD(TApp, "NamelessSubComPositionals", "[subcom]") {
+
+    auto sub = app.add_subcommand();
+    int val{1};
+    sub->add_option("val", val);
+
+    args = {"2"};
+    run();
+    CHECK(2 == val);
+}
+
+TEST_CASE_METHOD(TApp, "NamelessSubWithSub", "[subcom]") {
+
+    auto sub = app.add_subcommand();
+    auto subsub = sub->add_subcommand("val");
+
+    args = {"val"};
+    run();
+    CHECK(subsub->parsed());
+    CHECK(app.got_subcommand("val"));
+}
+
+TEST_CASE_METHOD(TApp, "NamelessSubWithMultipleSub", "[subcom]") {
+
+    auto sub1 = app.add_subcommand();
+    auto sub2 = app.add_subcommand();
+    auto sub1sub1 = sub1->add_subcommand("val1");
+    auto sub1sub2 = sub1->add_subcommand("val2");
+    auto sub2sub1 = sub2->add_subcommand("val3");
+    auto sub2sub2 = sub2->add_subcommand("val4");
+    args = {"val1"};
+    run();
+    CHECK(sub1sub1->parsed());
+    CHECK(app.got_subcommand("val1"));
+
+    args = {"val2"};
+    run();
+    CHECK(sub1sub2->parsed());
+    CHECK(app.got_subcommand("val2"));
+
+    args = {"val3"};
+    run();
+    CHECK(sub2sub1->parsed());
+    CHECK(app.got_subcommand("val3"));
+
+    args = {"val4"};
+    run();
+    CHECK(sub2sub2->parsed());
+    CHECK(app.got_subcommand("val4"));
+
+    args = {"val4", "val1"};
+    run();
+    CHECK(sub2sub2->parsed());
+    CHECK(app.got_subcommand("val4"));
+    CHECK(sub1sub1->parsed());
+    CHECK(app.got_subcommand("val1"));
+}
+
+TEST_CASE_METHOD(TApp, "Nameless4LayerDeep", "[subcom]") {
+
+    auto sub = app.add_subcommand();
+    auto ssub = sub->add_subcommand();
+    auto sssub = ssub->add_subcommand();
+
+    auto ssssub = sssub->add_subcommand();
+    auto sssssub = ssssub->add_subcommand("val");
+
+    args = {"val"};
+    run();
+    CHECK(sssssub->parsed());
+    CHECK(app.got_subcommand("val"));
+}
+
+/// Put subcommands in some crazy pattern and make everything still works
+TEST_CASE_METHOD(TApp, "Nameless4LayerDeepMulti", "[subcom]") {
+
+    auto sub1 = app.add_subcommand();
+    auto sub2 = app.add_subcommand();
+    auto ssub1 = sub1->add_subcommand();
+    auto ssub2 = sub2->add_subcommand();
+
+    auto sssub1 = ssub1->add_subcommand();
+    auto sssub2 = ssub2->add_subcommand();
+    sssub1->add_subcommand("val1");
+    ssub2->add_subcommand("val2");
+    sub2->add_subcommand("val3");
+    ssub1->add_subcommand("val4");
+    sssub2->add_subcommand("val5");
+    args = {"val1"};
+    run();
+    CHECK(app.got_subcommand("val1"));
+
+    args = {"val2"};
+    run();
+    CHECK(app.got_subcommand("val2"));
+
+    args = {"val3"};
+    run();
+    CHECK(app.got_subcommand("val3"));
+
+    args = {"val4"};
+    run();
+    CHECK(app.got_subcommand("val4"));
+    args = {"val5"};
+    run();
+    CHECK(app.got_subcommand("val5"));
+
+    args = {"val4", "val1", "val5"};
+    run();
+    CHECK(app.got_subcommand("val4"));
+    CHECK(app.got_subcommand("val1"));
+    CHECK(app.got_subcommand("val5"));
+}
+
+TEST_CASE_METHOD(TApp, "FallThroughRegular", "[subcom]") {
+    app.fallthrough();
+    int val{1};
+    app.add_option("--val", val);
+
+    app.add_subcommand("sub");
+
+    args = {"sub", "--val", "2"};
+    // Should not throw
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "FallThroughShort", "[subcom]") {
+    app.fallthrough();
+    int val{1};
+    app.add_option("-v", val);
+
+    app.add_subcommand("sub");
+
+    args = {"sub", "-v", "2"};
+    // Should not throw
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "FallThroughPositional", "[subcom]") {
+    app.fallthrough();
+    int val{1};
+    app.add_option("val", val);
+
+    app.add_subcommand("sub");
+
+    args = {"sub", "2"};
+    // Should not throw
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "FallThroughEquals", "[subcom]") {
+    app.fallthrough();
+    int val{1};
+    app.add_option("--val", val);
+
+    app.add_subcommand("sub");
+
+    args = {"sub", "--val=2"};
+    // Should not throw
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "EvilParseFallthrough", "[subcom]") {
+    app.fallthrough();
+    int val1{0}, val2{0};
+    app.add_option("--val1", val1);
+
+    auto sub = app.add_subcommand("sub");
+    sub->add_option("val2", val2);
+
+    args = {"sub", "--val1", "1", "2"};
+    // Should not throw
+    run();
+
+    CHECK(val1 == 1);
+    CHECK(val2 == 2);
+}
+
+TEST_CASE_METHOD(TApp, "CallbackOrdering", "[subcom]") {
+    app.fallthrough();
+    int val{1}, sub_val{0};
+    app.add_option("--val", val);
+
+    auto sub = app.add_subcommand("sub");
+    sub->callback([&val, &sub_val]() { sub_val = val; });
+
+    args = {"sub", "--val=2"};
+    run();
+    CHECK(val == 2);
+    CHECK(sub_val == 2);
+
+    args = {"--val=2", "sub"};
+    run();
+    CHECK(val == 2);
+    CHECK(sub_val == 2);
+}
+
+TEST_CASE_METHOD(TApp, "CallbackOrderingImmediate", "[subcom]") {
+    app.fallthrough();
+    int val{1}, sub_val{0};
+    app.add_option("--val", val);
+
+    auto sub = app.add_subcommand("sub")->immediate_callback();
+    sub->callback([&val, &sub_val]() { sub_val = val; });
+
+    args = {"sub", "--val=2"};
+    run();
+    CHECK(val == 2);
+    CHECK(sub_val == 1);
+
+    args = {"--val=2", "sub"};
+    run();
+    CHECK(val == 2);
+    CHECK(sub_val == 2);
+}
+
+TEST_CASE_METHOD(TApp, "CallbackOrderingImmediateMain", "[subcom]") {
+    app.fallthrough();
+    int val{0}, sub_val{0};
+
+    auto sub = app.add_subcommand("sub");
+    sub->callback([&val, &sub_val]() {
+        sub_val = val;
+        val = 2;
+    });
+    app.callback([&val]() { val = 1; });
+    args = {"sub"};
+    run();
+    CHECK(val == 1);
+    CHECK(sub_val == 0);
+    // the main app callback should run before the subcommand callbacks
+    app.immediate_callback();
+    val = 0;  // reset value
+    run();
+    CHECK(val == 2);
+    CHECK(sub_val == 1);
+    // the subcommand callback now runs immediately after processing and before the main app callback again
+    sub->immediate_callback();
+    val = 0;  // reset value
+    run();
+    CHECK(val == 1);
+    CHECK(sub_val == 0);
+}
+
+// Test based on issue #308
+TEST_CASE_METHOD(TApp, "CallbackOrderingImmediateModeOrder", "[subcom]") {
+
+    app.require_subcommand(1, 1);
+    std::vector<int> v;
+    app.callback([&v]() { v.push_back(1); })->immediate_callback(true);
+
+    auto sub = app.add_subcommand("hello")->callback([&v]() { v.push_back(2); })->immediate_callback(false);
+    args = {"hello"};
+    run();
+    // immediate_callback inherited
+    REQUIRE(2u == v.size());
+    CHECK(1 == v[0]);
+    CHECK(2 == v[1]);
+    v.clear();
+    sub->immediate_callback(true);
+    run();
+    // immediate_callback is now triggered for the main first
+    REQUIRE(2u == v.size());
+    CHECK(2 == v[0]);
+    CHECK(1 == v[1]);
+}
+
+TEST_CASE_METHOD(TApp, "RequiredSubCom", "[subcom]") {
+    app.add_subcommand("sub1");
+    app.add_subcommand("sub2");
+
+    app.require_subcommand();
+
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"sub1"};
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "SubComExtras", "[subcom]") {
+    app.allow_extras();
+    auto sub = app.add_subcommand("sub");
+
+    args = {"extra", "sub"};
+    run();
+    CHECK(std::vector<std::string>({"extra"}) == app.remaining());
+    CHECK(std::vector<std::string>() == sub->remaining());
+
+    args = {"extra1", "extra2", "sub"};
+    run();
+    CHECK(std::vector<std::string>({"extra1", "extra2"}) == app.remaining());
+    CHECK(std::vector<std::string>() == sub->remaining());
+
+    args = {"sub", "extra1", "extra2"};
+    run();
+    CHECK(std::vector<std::string>() == app.remaining());
+    CHECK(std::vector<std::string>({"extra1", "extra2"}) == sub->remaining());
+
+    args = {"extra1", "extra2", "sub", "extra3", "extra4"};
+    run();
+    CHECK(std::vector<std::string>({"extra1", "extra2"}) == app.remaining());
+    CHECK(std::vector<std::string>({"extra1", "extra2", "extra3", "extra4"}) == app.remaining(true));
+    CHECK(std::vector<std::string>({"extra3", "extra4"}) == sub->remaining());
+}
+
+TEST_CASE_METHOD(TApp, "Required1SubCom", "[subcom]") {
+    app.require_subcommand(1);
+    app.add_subcommand("sub1");
+    app.add_subcommand("sub2");
+    app.add_subcommand("sub3");
+
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+
+    args = {"sub1"};
+    run();
+
+    args = {"sub1", "sub2"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(TApp, "BadSubcommandSearch", "[subcom]") {
+
+    auto one = app.add_subcommand("one");
+    auto two = one->add_subcommand("two");
+
+    CHECK_THROWS_AS(app.get_subcommand(two), CLI::OptionNotFound);
+    CHECK_THROWS_AS(app.get_subcommand_ptr(two), CLI::OptionNotFound);
+}
+
+TEST_CASE_METHOD(TApp, "PrefixProgram", "[subcom]") {
+
+    app.prefix_command();
+
+    app.add_flag("--simple");
+
+    args = {"--simple", "other", "--simple", "--mine"};
+    run();
+
+    CHECK(std::vector<std::string>({"other", "--simple", "--mine"}) == app.remaining());
+}
+
+TEST_CASE_METHOD(TApp, "PrefixNoSeparation", "[subcom]") {
+
+    app.prefix_command();
+
+    std::vector<int> vals;
+    app.add_option("--vals", vals);
+
+    args = {"--vals", "1", "2", "3", "other"};
+
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "PrefixSeparation", "[subcom]") {
+
+    app.prefix_command();
+
+    std::vector<int> vals;
+    app.add_option("--vals", vals);
+
+    args = {"--vals", "1", "2", "3", "--", "other"};
+
+    run();
+
+    CHECK(std::vector<std::string>({"other"}) == app.remaining());
+    CHECK(std::vector<int>({1, 2, 3}) == vals);
+}
+
+TEST_CASE_METHOD(TApp, "PrefixSubcom", "[subcom]") {
+    auto subc = app.add_subcommand("subc");
+    subc->prefix_command();
+
+    app.add_flag("--simple");
+
+    args = {"--simple", "subc", "other", "--simple", "--mine"};
+    run();
+
+    CHECK(0u == app.remaining_size());
+    CHECK(3u == app.remaining_size(true));
+    CHECK(std::vector<std::string>({"other", "--simple", "--mine"}) == subc->remaining());
+}
+
+TEST_CASE_METHOD(TApp, "InheritHelpAllFlag", "[subcom]") {
+    app.set_help_all_flag("--help-all");
+    auto subc = app.add_subcommand("subc");
+    auto help_opt_list = subc->get_options([](const CLI::Option *opt) { return opt->get_name() == "--help-all"; });
+    CHECK(1u == help_opt_list.size());
+}
+
+TEST_CASE_METHOD(TApp, "RequiredPosInSubcommand", "[subcom]") {
+    app.require_subcommand();
+    std::string bar;
+
+    CLI::App *fooApp = app.add_subcommand("foo", "Foo a bar");
+    fooApp->add_option("bar", bar, "A bar to foo")->required();
+
+    CLI::App *bazApp = app.add_subcommand("baz", "Baz a bar");
+    bazApp->add_option("bar", bar, "A bar a baz")->required();
+
+    args = {"foo", "abc"};
+    run();
+    CHECK("abc" == bar);
+    args = {"baz", "cba"};
+    run();
+    CHECK("cba" == bar);
+
+    args = {};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+}
+
+TEST_CASE_METHOD(TApp, "invalidSubcommandName", "[subcom]") {
+
+    bool gotError{false};
+    try {
+        app.add_subcommand("!foo/foo", "Foo a bar");
+    } catch(const CLI::IncorrectConstruction &e) {
+        gotError = true;
+        CHECK_THAT(e.what(), Contains("!"));
+    }
+    CHECK(gotError);
+}
+
+struct SubcommandProgram : public TApp {
+
+    CLI::App *start{nullptr};
+    CLI::App *stop{nullptr};
+
+    int dummy{0};
+    std::string file{};
+    int count{0};
+
+    SubcommandProgram(const SubcommandProgram &) = delete;
+    SubcommandProgram &operator=(const SubcommandProgram &) = delete;
+
+    SubcommandProgram() {
+        app.set_help_all_flag("--help-all");
+
+        start = app.add_subcommand("start", "Start prog");
+        stop = app.add_subcommand("stop", "Stop prog");
+
+        app.add_flag("-d", dummy, "My dummy var");
+        start->add_option("-f,--file", file, "File name");
+        stop->add_flag("-c,--count", count, "Some flag opt");
+    }
+};
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand Working", "[subcom]") {
+    args = {"-d", "start", "-ffilename"};
+
+    run();
+
+    CHECK(dummy == 1);
+    CHECK(app.get_subcommands().at(0) == start);
+    CHECK(file == "filename");
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand Spare", "[subcom]") {
+    args = {"extra", "-d", "start", "-ffilename"};
+
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand SpareSub", "[subcom]") {
+    args = {"-d", "start", "spare", "-ffilename"};
+
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand Multiple", "[subcom]") {
+    args = {"-d", "start", "-ffilename", "stop"};
+
+    run();
+    CHECK(app.get_subcommands().size() == 2u);
+    CHECK(dummy == 1);
+    CHECK(file == "filename");
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand MultipleOtherOrder", "[subcom]") {
+    args = {"start", "-d", "-ffilename", "stop"};
+
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand MultipleArgs", "[subcom]") {
+    args = {"start", "stop"};
+
+    run();
+
+    CHECK(app.get_subcommands().size() == 2u);
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand CaseCheck", "[subcom]") {
+    args = {"Start"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+
+    args = {"start"};
+    run();
+
+    start->ignore_case();
+    run();
+
+    args = {"Start"};
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "SubcomInheritCaseCheck", "[subcom]") {
+    app.ignore_case();
+    auto sub1 = app.add_subcommand("sub1");
+    auto sub2 = app.add_subcommand("sub2");
+
+    run();
+    CHECK(app.get_subcommands().size() == 0u);
+    CHECK(app.get_subcommands({}).size() == 2u);
+    CHECK(app.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub1"; }).size() == 1u);
+
+    args = {"SuB1"};
+    run();
+    CHECK(app.get_subcommands().at(0) == sub1);
+    CHECK(app.get_subcommands().size() == 1u);
+
+    app.clear();
+    CHECK(app.get_subcommands().size() == 0u);
+
+    args = {"sUb2"};
+    run();
+    CHECK(app.get_subcommands().at(0) == sub2);
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand UnderscoreCheck", "[subcom]") {
+    args = {"start_"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+
+    args = {"start"};
+    run();
+
+    start->ignore_underscore();
+    run();
+
+    args = {"_start_"};
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "SubcomInheritUnderscoreCheck", "[subcom]") {
+    app.ignore_underscore();
+    auto sub1 = app.add_subcommand("sub_option1");
+    auto sub2 = app.add_subcommand("sub_option2");
+
+    run();
+    CHECK(app.get_subcommands().size() == 0u);
+    CHECK(app.get_subcommands({}).size() == 2u);
+    CHECK(app.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub_option1"; }).size() == 1u);
+
+    args = {"suboption1"};
+    run();
+    CHECK(app.get_subcommands().at(0) == sub1);
+    CHECK(app.get_subcommands().size() == 1u);
+
+    app.clear();
+    CHECK(app.get_subcommands().size() == 0u);
+
+    args = {"_suboption2"};
+    run();
+    CHECK(app.get_subcommands().at(0) == sub2);
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand HelpOrder", "[subcom]") {
+
+    args = {"-h"};
+    CHECK_THROWS_AS(run(), CLI::CallForHelp);
+
+    args = {"start", "-h"};
+    CHECK_THROWS_AS(run(), CLI::CallForHelp);
+
+    args = {"-h", "start"};
+    CHECK_THROWS_AS(run(), CLI::CallForHelp);
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand HelpAllOrder", "[subcom]") {
+
+    args = {"--help-all"};
+    CHECK_THROWS_AS(run(), CLI::CallForAllHelp);
+
+    args = {"start", "--help-all"};
+    CHECK_THROWS_AS(run(), CLI::CallForAllHelp);
+
+    args = {"--help-all", "start"};
+    CHECK_THROWS_AS(run(), CLI::CallForAllHelp);
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand Callbacks", "[subcom]") {
+
+    start->callback([]() { throw CLI::Success(); });
+
+    run();
+
+    args = {"start"};
+
+    CHECK_THROWS_AS(run(), CLI::Success);
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand Groups", "[subcom]") {
+
+    std::string help = app.help();
+    CHECK_THAT(help, !Contains("More Commands:"));
+    CHECK_THAT(help, Contains("Subcommands:"));
+
+    start->group("More Commands");
+    help = app.help();
+    CHECK_THAT(help, Contains("More Commands:"));
+    CHECK_THAT(help, Contains("Subcommands:"));
+
+    // Case is ignored but for the first subcommand in a group.
+    stop->group("more commands");
+    help = app.help();
+    CHECK_THAT(help, Contains("More Commands:"));
+    CHECK_THAT(help, !Contains("Subcommands:"));
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand ExtrasErrors", "[subcom]") {
+
+    args = {"one", "two", "start", "three", "four"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+
+    args = {"start", "three", "four"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+
+    args = {"one", "two"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand OrderedExtras", "[subcom]") {
+
+    app.allow_extras();
+    args = {"one", "two", "start", "three", "four"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+
+    start->allow_extras();
+
+    run();
+
+    CHECK(std::vector<std::string>({"one", "two"}) == app.remaining());
+    CHECK(std::vector<std::string>({"three", "four"}) == start->remaining());
+    CHECK(std::vector<std::string>({"one", "two", "three", "four"}) == app.remaining(true));
+
+    args = {"one", "two", "start", "three", "--", "four"};
+
+    run();
+
+    CHECK(std::vector<std::string>({"one", "two", "four"}) == app.remaining());
+    CHECK(std::vector<std::string>({"three"}) == start->remaining());
+    CHECK(std::vector<std::string>({"one", "two", "four", "three"}) == app.remaining(true));
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand MixedOrderExtras", "[subcom]") {
+
+    app.allow_extras();
+    start->allow_extras();
+    stop->allow_extras();
+
+    args = {"one", "two", "start", "three", "four", "stop", "five", "six"};
+    run();
+
+    CHECK(std::vector<std::string>({"one", "two"}) == app.remaining());
+    CHECK(std::vector<std::string>({"three", "four"}) == start->remaining());
+    CHECK(std::vector<std::string>({"five", "six"}) == stop->remaining());
+    CHECK(std::vector<std::string>({"one", "two", "three", "four", "five", "six"}) == app.remaining(true));
+
+    args = {"one", "two", "stop", "three", "four", "start", "five", "six"};
+    run();
+
+    CHECK(std::vector<std::string>({"one", "two"}) == app.remaining());
+    CHECK(std::vector<std::string>({"three", "four"}) == stop->remaining());
+    CHECK(std::vector<std::string>({"five", "six"}) == start->remaining());
+    CHECK(std::vector<std::string>({"one", "two", "three", "four", "five", "six"}) == app.remaining(true));
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand CallbackOrder", "[subcom]") {
+    std::vector<int> callback_order;
+    start->callback([&callback_order]() { callback_order.push_back(1); });
+    stop->callback([&callback_order]() { callback_order.push_back(2); });
+
+    args = {"start", "stop"};
+    run();
+    CHECK(std::vector<int>({1, 2}) == callback_order);
+
+    callback_order.clear();
+
+    args = {"stop", "start"};
+    run();
+    CHECK(std::vector<int>({2, 1}) == callback_order);
+}
+
+TEST_CASE_METHOD(SubcommandProgram, "Subcommand CallbackOrderImmediate", "[subcom]") {
+    std::vector<int> callback_order;
+    start->callback([&callback_order]() { callback_order.push_back(1); })->immediate_callback();
+    stop->callback([&callback_order]() { callback_order.push_back(2); });
+
+    args = {"start", "stop", "start"};
+    run();
+    CHECK(std::vector<int>({1, 1, 2}) == callback_order);
+
+    callback_order.clear();
+
+    args = {"stop", "start", "stop", "start"};
+    run();
+    CHECK(std::vector<int>({1, 1, 2}) == callback_order);
+}
+
+struct ManySubcommands : public TApp {
+
+    CLI::App *sub1{nullptr};
+    CLI::App *sub2{nullptr};
+    CLI::App *sub3{nullptr};
+    CLI::App *sub4{nullptr};
+
+    ManySubcommands() {
+        app.allow_extras();
+        sub1 = app.add_subcommand("sub1");
+        sub2 = app.add_subcommand("sub2");
+        sub3 = app.add_subcommand("sub3");
+        sub4 = app.add_subcommand("sub4");
+        args = {"sub1", "sub2", "sub3"};
+    }
+
+    ManySubcommands(const ManySubcommands &) = delete;
+    ManySubcommands &operator=(const ManySubcommands &) = delete;
+};
+
+TEST_CASE_METHOD(ManySubcommands, "Required1Exact", "[subcom]") {
+    app.require_subcommand(1);
+
+    run();
+    CHECK(vs_t({"sub2", "sub3"}) == sub1->remaining());
+    CHECK(vs_t({"sub2", "sub3"}) == app.remaining(true));
+}
+
+TEST_CASE_METHOD(ManySubcommands, "Required2Exact", "[subcom]") {
+    app.require_subcommand(2);
+
+    run();
+    CHECK(vs_t({"sub3"}) == sub2->remaining());
+}
+
+TEST_CASE_METHOD(ManySubcommands, "Required4Failure", "[subcom]") {
+    app.require_subcommand(4);
+
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+}
+
+TEST_CASE_METHOD(ManySubcommands, "RemoveSub", "[subcom]") {
+    run();
+    CHECK(0u == app.remaining_size(true));
+    app.remove_subcommand(sub1);
+    app.allow_extras();
+    run();
+    CHECK(1u == app.remaining_size(true));
+}
+
+TEST_CASE_METHOD(ManySubcommands, "RemoveSubFail", "[subcom]") {
+    auto sub_sub = sub1->add_subcommand("subsub");
+    CHECK(!app.remove_subcommand(sub_sub));
+    CHECK(sub1->remove_subcommand(sub_sub));
+    CHECK(!app.remove_subcommand(nullptr));
+}
+
+TEST_CASE_METHOD(ManySubcommands, "manyIndexQuery", "[subcom]") {
+    auto s1 = app.get_subcommand(0);
+    auto s2 = app.get_subcommand(1);
+    auto s3 = app.get_subcommand(2);
+    auto s4 = app.get_subcommand(3);
+    CHECK(sub1 == s1);
+    CHECK(sub2 == s2);
+    CHECK(sub3 == s3);
+    CHECK(sub4 == s4);
+    CHECK_THROWS_AS(app.get_subcommand(4), CLI::OptionNotFound);
+    auto s0 = app.get_subcommand();
+    CHECK(sub1 == s0);
+}
+
+TEST_CASE_METHOD(ManySubcommands, "manyIndexQueryPtr", "[subcom]") {
+    auto s1 = app.get_subcommand_ptr(0);
+    auto s2 = app.get_subcommand_ptr(1);
+    auto s3 = app.get_subcommand_ptr(2);
+    auto s4 = app.get_subcommand_ptr(3);
+    CHECK(sub1 == s1.get());
+    CHECK(sub2 == s2.get());
+    CHECK(sub3 == s3.get());
+    CHECK(sub4 == s4.get());
+    CHECK_THROWS_AS(app.get_subcommand_ptr(4), CLI::OptionNotFound);
+}
+
+TEST_CASE_METHOD(ManySubcommands, "Required1Fuzzy", "[subcom]") {
+
+    app.require_subcommand(0, 1);
+
+    run();
+    CHECK(vs_t({"sub2", "sub3"}) == sub1->remaining());
+
+    app.require_subcommand(-1);
+
+    run();
+    CHECK(vs_t({"sub2", "sub3"}) == sub1->remaining());
+}
+
+TEST_CASE_METHOD(ManySubcommands, "Required2Fuzzy", "[subcom]") {
+    app.require_subcommand(0, 2);
+
+    run();
+    CHECK(vs_t({"sub3"}) == sub2->remaining());
+    CHECK(vs_t({"sub3"}) == app.remaining(true));
+
+    app.require_subcommand(-2);
+
+    run();
+    CHECK(vs_t({"sub3"}) == sub2->remaining());
+}
+
+TEST_CASE_METHOD(ManySubcommands, "Unlimited", "[subcom]") {
+    run();
+    CHECK(vs_t() == app.remaining(true));
+
+    app.require_subcommand();
+
+    run();
+    CHECK(vs_t() == app.remaining(true));
+
+    app.require_subcommand(2, 0);  // 2 or more
+
+    run();
+    CHECK(vs_t() == app.remaining(true));
+}
+
+TEST_CASE_METHOD(ManySubcommands, "HelpFlags", "[subcom]") {
+
+    args = {"-h"};
+
+    CHECK_THROWS_AS(run(), CLI::CallForHelp);
+
+    args = {"sub2", "-h"};
+
+    CHECK_THROWS_AS(run(), CLI::CallForHelp);
+
+    args = {"-h", "sub2"};
+
+    CHECK_THROWS_AS(run(), CLI::CallForHelp);
+}
+
+TEST_CASE_METHOD(ManySubcommands, "MaxCommands", "[subcom]") {
+
+    app.require_subcommand(2);
+
+    args = {"sub1", "sub2"};
+    CHECK_NOTHROW(run());
+
+    // The extra subcommand counts as an extra
+    args = {"sub1", "sub2", "sub3"};
+    CHECK_NOTHROW(run());
+    CHECK(1u == sub2->remaining().size());
+    CHECK(2u == app.count_all());
+
+    // Currently, setting sub2 to throw causes an extras error
+    // In the future, would passing on up to app's extras be better?
+
+    app.allow_extras(false);
+    sub1->allow_extras(false);
+    sub2->allow_extras(false);
+
+    args = {"sub1", "sub2"};
+
+    CHECK_NOTHROW(run());
+
+    args = {"sub1", "sub2", "sub3"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(ManySubcommands, "SubcommandExclusion", "[subcom]") {
+
+    sub1->excludes(sub3);
+    sub2->excludes(sub3);
+    args = {"sub1", "sub2"};
+    CHECK_NOTHROW(run());
+
+    args = {"sub1", "sub2", "sub3"};
+    CHECK_THROWS_AS(run(), CLI::ExcludesError);
+
+    args = {"sub1", "sub2", "sub4"};
+    CHECK_NOTHROW(run());
+    CHECK(3u == app.count_all());
+
+    args = {"sub3", "sub4"};
+    CHECK_NOTHROW(run());
+}
+
+TEST_CASE_METHOD(ManySubcommands, "SubcommandOptionExclusion", "[subcom]") {
+
+    auto excluder_flag = app.add_flag("--exclude");
+    sub1->excludes(excluder_flag)->fallthrough();
+    sub2->excludes(excluder_flag)->fallthrough();
+    sub3->fallthrough();
+    sub4->fallthrough();
+    args = {"sub3", "sub4", "--exclude"};
+    CHECK_NOTHROW(run());
+
+    args = {"sub1", "sub3", "--exclude"};
+    CHECK_THROWS_AS(run(), CLI::ExcludesError);
+    CHECK(sub1->remove_excludes(excluder_flag));
+    CHECK_NOTHROW(run());
+    CHECK(!sub1->remove_excludes(excluder_flag));
+
+    args = {"--exclude", "sub2", "sub4"};
+    CHECK_THROWS_AS(run(), CLI::ExcludesError);
+    CHECK(sub1 == sub1->excludes(excluder_flag));
+    args = {"sub1", "--exclude", "sub2", "sub4"};
+    try {
+        run();
+    } catch(const CLI::ExcludesError &ee) {
+        CHECK(std::string::npos != std::string(ee.what()).find("sub1"));
+    }
+}
+
+TEST_CASE_METHOD(ManySubcommands, "SubcommandNeeds", "[subcom]") {
+
+    sub1->needs(sub2);
+    args = {"sub1", "sub2"};
+    CHECK_NOTHROW(run());
+
+    args = {"sub2"};
+    CHECK_NOTHROW(run());
+
+    args = {"sub1"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    sub1->needs(sub3);
+    args = {"sub1", "sub2", "sub3"};
+    CHECK_NOTHROW(run());
+
+    args = {"sub1", "sub2", "sub4"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"sub1", "sub2", "sub4"};
+    sub1->remove_needs(sub3);
+    CHECK_NOTHROW(run());
+}
+
+TEST_CASE_METHOD(ManySubcommands, "SubcommandNeedsOptions", "[subcom]") {
+
+    auto opt = app.add_flag("--subactive");
+    sub1->needs(opt);
+    sub1->fallthrough();
+    args = {"sub1", "--subactive"};
+    CHECK_NOTHROW(run());
+
+    args = {"sub1"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"--subactive"};
+    CHECK_NOTHROW(run());
+
+    auto opt2 = app.add_flag("--subactive2");
+
+    sub1->needs(opt2);
+    args = {"sub1", "--subactive"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+
+    args = {"--subactive", "--subactive2", "sub1"};
+    CHECK_NOTHROW(run());
+
+    sub1->remove_needs(opt2);
+    args = {"sub1", "--subactive"};
+    CHECK_NOTHROW(run());
+}
+
+TEST_CASE_METHOD(ManySubcommands, "SubcommandNeedsOptionsCallbackOrdering", "[subcom]") {
+    int count{0};
+    auto opt = app.add_flag("--subactive");
+    app.add_flag("--flag1");
+    sub1->needs(opt);
+    sub1->fallthrough();
+    sub1->parse_complete_callback([&count]() { ++count; });
+    args = {"sub1", "--flag1", "sub1", "--subactive"};
+    CHECK_THROWS_AS(run(), CLI::RequiresError);
+    // the subcommand has to pass validation by the first callback
+    sub1->immediate_callback(false);
+    // now since the callback executes after
+
+    CHECK_NOTHROW(run());
+    CHECK(1 == count);
+    sub1->immediate_callback();
+    args = {"--subactive", "sub1"};
+    // now the required is processed first
+    CHECK_NOTHROW(run());
+}
+
+TEST_CASE_METHOD(ManySubcommands, "SubcommandNeedsFail", "[subcom]") {
+
+    auto opt = app.add_flag("--subactive");
+    auto opt2 = app.add_flag("--dummy");
+    sub1->needs(opt);
+    CHECK_THROWS_AS(sub1->needs((CLI::Option *)nullptr), CLI::OptionNotFound);
+    CHECK_THROWS_AS(sub1->needs((CLI::App *)nullptr), CLI::OptionNotFound);
+    CHECK_THROWS_AS(sub1->needs(sub1), CLI::OptionNotFound);
+
+    CHECK(sub1->remove_needs(opt));
+    CHECK(!sub1->remove_needs(opt2));
+    CHECK(!sub1->remove_needs(sub1));
+}
+
+TEST_CASE_METHOD(ManySubcommands, "SubcommandRequired", "[subcom]") {
+
+    sub1->required();
+    args = {"sub1", "sub2"};
+    CHECK_NOTHROW(run());
+
+    args = {"sub1", "sub2", "sub3"};
+    CHECK_NOTHROW(run());
+
+    args = {"sub3", "sub4"};
+    CHECK_THROWS_AS(run(), CLI::RequiredError);
+}
+
+TEST_CASE_METHOD(ManySubcommands, "SubcommandDisabled", "[subcom]") {
+
+    sub3->disabled();
+    args = {"sub1", "sub2"};
+    CHECK_NOTHROW(run());
+
+    args = {"sub1", "sub2", "sub3"};
+    app.allow_extras(false);
+    sub2->allow_extras(false);
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+    args = {"sub3", "sub4"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+    sub3->disabled(false);
+    args = {"sub3", "sub4"};
+    CHECK_NOTHROW(run());
+}
+
+TEST_CASE_METHOD(ManySubcommands, "SubcommandTriggeredOff", "[subcom]") {
+
+    app.allow_extras(false);
+    sub1->allow_extras(false);
+    sub2->allow_extras(false);
+    CLI::TriggerOff(sub1, sub2);
+    args = {"sub1", "sub2"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+
+    args = {"sub2", "sub1", "sub3"};
+    CHECK_NOTHROW(run());
+    CLI::TriggerOff(sub1, {sub3, sub4});
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+    args = {"sub1", "sub2", "sub4"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(ManySubcommands, "SubcommandTriggeredOn", "[subcom]") {
+
+    app.allow_extras(false);
+    sub1->allow_extras(false);
+    sub2->allow_extras(false);
+    CLI::TriggerOn(sub1, sub2);
+    args = {"sub1", "sub2"};
+    CHECK_NOTHROW(run());
+
+    args = {"sub2", "sub1", "sub4"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+    CLI::TriggerOn(sub1, {sub3, sub4});
+    sub2->disabled_by_default(false);
+    sub2->disabled(false);
+    CHECK_NOTHROW(run());
+    args = {"sub3", "sub1", "sub2"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(ManySubcommands, "SubcommandSilence", "[subcom]") {
+
+    sub1->silent();
+    args = {"sub1", "sub2"};
+    CHECK_NOTHROW(run());
+
+    auto subs = app.get_subcommands();
+    CHECK(1U == subs.size());
+    sub1->silent(false);
+    CHECK(!sub1->get_silent());
+    run();
+    subs = app.get_subcommands();
+    CHECK(2U == subs.size());
+}
+
+TEST_CASE_METHOD(TApp, "UnnamedSub", "[subcom]") {
+    double val{0.0};
+    auto sub = app.add_subcommand("", "empty name");
+    auto opt = sub->add_option("-v,--value", val);
+    args = {"-v", "4.56"};
+
+    run();
+    CHECK(4.56 == val);
+    // make sure unnamed sub options can be found from the main app
+    auto opt2 = app.get_option("-v");
+    CHECK(opt2 == opt);
+
+    CHECK_THROWS_AS(app.get_option("--vvvv"), CLI::OptionNotFound);
+    // now test in the constant context
+    const auto &appC = app;
+    auto opt3 = appC.get_option("-v");
+    CHECK("--value" == opt3->get_name());
+    CHECK_THROWS_AS(appC.get_option("--vvvv"), CLI::OptionNotFound);
+}
+
+TEST_CASE_METHOD(TApp, "UnnamedSubMix", "[subcom]") {
+    double val{0.0}, val2{0.0}, val3{0.0};
+    app.add_option("-t", val2);
+    auto sub1 = app.add_subcommand("", "empty name");
+    sub1->add_option("-v,--value", val);
+    auto sub2 = app.add_subcommand("", "empty name2");
+    sub2->add_option("-m,--mix", val3);
+    args = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
+
+    run();
+    CHECK(-3.0 == val);
+    CHECK(5.93 == val2);
+    CHECK(4.56 == val3);
+    CHECK(3u == app.count_all());
+}
+
+TEST_CASE_METHOD(TApp, "UnnamedSubMixExtras", "[subcom]") {
+    double val{0.0}, val2{0.0};
+    app.add_option("-t", val2);
+    auto sub = app.add_subcommand("", "empty name");
+    sub->add_option("-v,--value", val);
+    args = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
+    app.allow_extras();
+    run();
+    CHECK(-3.0 == val);
+    CHECK(5.93 == val2);
+    CHECK(2u == app.remaining_size());
+    CHECK(0u == sub->remaining_size());
+}
+
+TEST_CASE_METHOD(TApp, "UnnamedSubNoExtras", "[subcom]") {
+    double val{0.0}, val2{0.0};
+    app.add_option("-t", val2);
+    auto sub = app.add_subcommand();
+    sub->add_option("-v,--value", val);
+    args = {"-t", "5.93", "-v", "-3"};
+    run();
+    CHECK(-3.0 == val);
+    CHECK(5.93 == val2);
+    CHECK(0u == app.remaining_size());
+    CHECK(0u == sub->remaining_size());
+}
+
+TEST_CASE_METHOD(TApp, "SubcommandAlias", "[subcom]") {
+    double val{0.0};
+    auto sub = app.add_subcommand("sub1");
+    sub->alias("sub2");
+    sub->alias("sub3");
+    sub->add_option("-v,--value", val);
+    args = {"sub1", "-v", "-3"};
+    run();
+    CHECK(-3.0 == val);
+
+    args = {"sub2", "--value", "-5"};
+    run();
+    CHECK(-5.0 == val);
+
+    args = {"sub3", "-v", "7"};
+    run();
+    CHECK(7 == val);
+
+    auto &al = sub->get_aliases();
+    REQUIRE(2U <= al.size());
+
+    CHECK("sub2" == al[0]);
+    CHECK("sub3" == al[1]);
+
+    sub->clear_aliases();
+    CHECK(al.empty());
+}
+
+TEST_CASE_METHOD(TApp, "SubcommandAliasIgnoreCaseUnderscore", "[subcom]") {
+    double val{0.0};
+    auto sub = app.add_subcommand("sub1");
+    sub->alias("sub2");
+    sub->alias("sub3");
+    sub->ignore_case();
+    sub->add_option("-v,--value", val);
+    args = {"sub1", "-v", "-3"};
+    run();
+    CHECK(-3.0 == val);
+
+    args = {"SUB2", "--value", "-5"};
+    run();
+    CHECK(-5.0 == val);
+
+    args = {"sUb3", "-v", "7"};
+    run();
+    CHECK(7 == val);
+    sub->ignore_underscore();
+    args = {"sub_1", "-v", "-3"};
+    run();
+    CHECK(-3.0 == val);
+
+    args = {"SUB_2", "--value", "-5"};
+    run();
+    CHECK(-5.0 == val);
+
+    args = {"sUb_3", "-v", "7"};
+    run();
+    CHECK(7 == val);
+
+    sub->ignore_case(false);
+    args = {"sub_1", "-v", "-3"};
+    run();
+    CHECK(-3.0 == val);
+
+    args = {"SUB_2", "--value", "-5"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+
+    args = {"sUb_3", "-v", "7"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+}
+
+TEST_CASE_METHOD(TApp, "OptionGroupAlias", "[subcom]") {
+    double val{0.0};
+    auto sub = app.add_option_group("sub1");
+    sub->alias("sub2");
+    sub->alias("sub3");
+    sub->add_option("-v,--value", val);
+    args = {"sub1", "-v", "-3"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+
+    args = {"sub2", "--value", "-5"};
+    run();
+    CHECK(-5.0 == val);
+
+    args = {"sub3", "-v", "7"};
+    run();
+    CHECK(7 == val);
+
+    args = {"-v", "-3"};
+    run();
+    CHECK(-3 == val);
+}
+
+TEST_CASE_METHOD(TApp, "OptionGroupAliasWithSpaces", "[subcom]") {
+    double val{0.0};
+    auto sub = app.add_option_group("sub1");
+    sub->alias("sub2 bb");
+    sub->alias("sub3/b");
+    sub->add_option("-v,--value", val);
+    args = {"sub1", "-v", "-3"};
+    CHECK_THROWS_AS(run(), CLI::ExtrasError);
+
+    args = {"sub2 bb", "--value", "-5"};
+    run();
+    CHECK(-5.0 == val);
+
+    args = {"sub3/b", "-v", "7"};
+    run();
+    CHECK(7 == val);
+
+    args = {"-v", "-3"};
+    run();
+    CHECK(-3 == val);
+}
+
+TEST_CASE_METHOD(TApp, "subcommand_help", "[subcom]") {
+    auto sub1 = app.add_subcommand("help")->silent();
+    bool flag{false};
+    app.add_flag("--one", flag, "FLAGGER");
+    sub1->parse_complete_callback([]() { throw CLI::CallForHelp(); });
+    bool called{false};
+    args = {"help"};
+    try {
+        run();
+    } catch(const CLI::CallForHelp &) {
+        called = true;
+    }
+    auto helpstr = app.help();
+    CHECK_THAT(helpstr, Contains("FLAGGER"));
+    CHECK(called);
+}
+
+TEST_CASE_METHOD(TApp, "AliasErrors", "[subcom]") {
+    auto sub1 = app.add_subcommand("sub1");
+    auto sub2 = app.add_subcommand("sub2");
+
+    CHECK_THROWS_AS(sub2->alias("this is a not\n a valid alias"), CLI::IncorrectConstruction);
+    CHECK_NOTHROW(sub2->alias("-alias"));  // this is allowed but would be unusable on command line parsers
+
+    CHECK_THROWS_AS(app.add_subcommand("--bad_subcommand_name", "documenting the bad subcommand"),
+                    CLI::IncorrectConstruction);
+
+    CHECK_THROWS_AS(app.add_subcommand("documenting a subcommand", "sub3"), CLI::IncorrectConstruction);
+    // cannot alias to an existing subcommand
+    CHECK_THROWS_AS(sub2->alias("sub1"), CLI::OptionAlreadyAdded);
+    CHECK_THROWS_AS(sub1->alias("sub2"), CLI::OptionAlreadyAdded);
+    // aliasing to an existing name should be allowed
+    CHECK_NOTHROW(sub1->alias(sub1->get_name()));
+
+    sub1->alias("les1")->alias("les2")->alias("les_3");
+    sub2->alias("s2les1")->alias("s2les2")->alias("s2les3");
+
+    CHECK_THROWS_AS(sub2->alias("les2"), CLI::OptionAlreadyAdded);
+    CHECK_THROWS_AS(sub1->alias("s2les2"), CLI::OptionAlreadyAdded);
+
+    CHECK_THROWS_AS(sub2->name("sub1"), CLI::OptionAlreadyAdded);
+    sub2->ignore_underscore();
+    CHECK_THROWS_AS(sub2->alias("les3"), CLI::OptionAlreadyAdded);
+}
+// test adding a subcommand via the pointer
+TEST_CASE_METHOD(TApp, "ExistingSubcommandMatch", "[subcom]") {
+    auto sshared = std::make_shared<CLI::App>("documenting the subcommand", "sub1");
+    sshared->alias("sub2")->alias("sub3");
+
+    CHECK("sub1" == sshared->get_name());
+    app.add_subcommand("sub1");
+
+    try {
+        app.add_subcommand(sshared);
+        // this should throw the next line should never be reached
+        CHECK(!true);
+    } catch(const CLI::OptionAlreadyAdded &oaa) {
+        CHECK_THAT(oaa.what(), Contains("sub1"));
+    }
+    sshared->name("osub");
+    app.add_subcommand("sub2");
+    // now check that the aliases don't overlap
+    try {
+        app.add_subcommand(sshared);
+        // this should throw the next line should never be reached
+        CHECK(!true);
+    } catch(const CLI::OptionAlreadyAdded &oaa) {
+        CHECK_THAT(oaa.what(), Contains("sub2"));
+    }
+    // now check that disabled subcommands can be added regardless of name
+    sshared->name("sub1");
+    sshared->disabled();
+    CHECK_NOTHROW(app.add_subcommand(sshared));
+}
+
+TEST_CASE_METHOD(TApp, "AliasErrorsInOptionGroup", "[subcom]") {
+    auto sub1 = app.add_subcommand("sub1");
+    auto g2 = app.add_option_group("g1");
+    auto sub2 = g2->add_subcommand("sub2");
+
+    // cannot alias to an existing subcommand even if it is in an option group
+    CHECK_THROWS_AS(sub2->alias("sub1"), CLI::OptionAlreadyAdded);
+    CHECK_THROWS_AS(sub1->alias("sub2"), CLI::OptionAlreadyAdded);
+
+    sub1->alias("les1")->alias("les2")->alias("les3");
+    sub2->alias("s2les1")->alias("s2les2")->alias("s2les3");
+
+    CHECK_THROWS_AS(sub2->alias("les2"), CLI::OptionAlreadyAdded);
+    CHECK_THROWS_AS(sub1->alias("s2les2"), CLI::OptionAlreadyAdded);
+
+    CHECK_THROWS_AS(sub2->name("sub1"), CLI::OptionAlreadyAdded);
+}
+
+TEST_CASE("SharedSubTests: SharedSubcommand", "[subcom]") {
+    double val{0.0}, val2{0.0}, val3{0.0}, val4{0.0};
+    CLI::App app1{"test program1"};
+
+    app1.add_option("-t", val2);
+    auto sub = app1.add_subcommand("", "empty name");
+    sub->add_option("-v,--value", val);
+    sub->add_option("-g", val4);
+    CLI::App app2{"test program2"};
+    app2.add_option("-m", val3);
+    // extract an owning ptr from app1 and add it to app2
+    auto subown = app1.get_subcommand_ptr(sub);
+    // add the extracted subcommand to a different app
+    app2.add_subcommand(std::move(subown));
+    CHECK_THROWS_AS(app2.add_subcommand(CLI::App_p{}), CLI::IncorrectConstruction);
+    input_t args1 = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
+    input_t args2 = {"-m", "4.56", "-g", "8.235"};
+    std::reverse(std::begin(args1), std::end(args1));
+    std::reverse(std::begin(args2), std::end(args2));
+    app1.allow_extras();
+    app1.parse(args1);
+
+    app2.parse(args2);
+
+    CHECK(-3.0 == val);
+    CHECK(5.93 == val2);
+    CHECK(4.56 == val3);
+    CHECK(8.235 == val4);
+}
+
+TEST_CASE("SharedSubTests: SharedSubIndependent", "[subcom]") {
+    double val{0.0}, val2{0.0}, val4{0.0};
+    CLI::App_p app1 = std::make_shared<CLI::App>("test program1");
+    app1->allow_extras();
+    app1->add_option("-t", val2);
+    auto sub = app1->add_subcommand("", "empty name");
+    sub->add_option("-v,--value", val);
+    sub->add_option("-g", val4);
+
+    // extract an owning ptr from app1 and add it to app2
+    auto subown = app1->get_subcommand_ptr(sub);
+
+    input_t args1 = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
+    input_t args2 = {"-m", "4.56", "-g", "8.235"};
+    std::reverse(std::begin(args1), std::end(args1));
+    std::reverse(std::begin(args2), std::end(args2));
+
+    app1->parse(args1);
+    // destroy the first parser
+    app1 = nullptr;
+    // parse with the extracted subcommand
+    subown->parse(args2);
+
+    CHECK(-3.0 == val);
+    CHECK(5.93 == val2);
+    CHECK(8.235 == val4);
+}
+
+TEST_CASE("SharedSubTests: SharedSubIndependentReuse", "[subcom]") {
+    double val{0.0}, val2{0.0}, val4{0.0};
+    CLI::App_p app1 = std::make_shared<CLI::App>("test program1");
+    app1->allow_extras();
+    app1->add_option("-t", val2);
+    auto sub = app1->add_subcommand("", "empty name");
+    sub->add_option("-v,--value", val);
+    sub->add_option("-g", val4);
+
+    // extract an owning ptr from app1 and add it to app2
+    auto subown = app1->get_subcommand_ptr(sub);
+
+    input_t args1 = {"-m", "4.56", "-t", "5.93", "-v", "-3"};
+    std::reverse(std::begin(args1), std::end(args1));
+    auto args2 = args1;
+    app1->parse(args1);
+
+    // parse with the extracted subcommand
+    subown->parse("program1 -m 4.56 -g 8.235", true);
+
+    CHECK(-3.0 == val);
+    CHECK(5.93 == val2);
+    CHECK(8.235 == val4);
+    val = 0.0;
+    val2 = 0.0;
+    CHECK("program1" == subown->get_name());
+    // this tests the name reset in subcommand since it was automatic
+    app1->parse(args2);
+    CHECK(-3.0 == val);
+    CHECK(5.93 == val2);
+}
+
+TEST_CASE_METHOD(ManySubcommands, "getSubtests", "[subcom]") {
+    CLI::App_p sub2p = app.get_subcommand_ptr(sub2);
+    CHECK(sub2 == sub2p.get());
+    CHECK_THROWS_AS(app.get_subcommand_ptr(nullptr), CLI::OptionNotFound);
+    CHECK_THROWS_AS(app.get_subcommand(nullptr), CLI::OptionNotFound);
+    CLI::App_p sub3p = app.get_subcommand_ptr(2);
+    CHECK(sub3 == sub3p.get());
+}
+
+TEST_CASE_METHOD(ManySubcommands, "defaultDisabledSubcommand", "[subcom]") {
+
+    sub1->fallthrough();
+    sub2->disabled_by_default();
+    run();
+    auto rem = app.remaining();
+    CHECK(1u == rem.size());
+    CHECK("sub2" == rem[0]);
+    CHECK(sub2->get_disabled_by_default());
+    sub2->disabled(false);
+    CHECK(!sub2->get_disabled());
+    run();
+    // this should disable it again even though it was disabled
+    rem = app.remaining();
+    CHECK(1u == rem.size());
+    CHECK("sub2" == rem[0]);
+    CHECK(sub2->get_disabled_by_default());
+    CHECK(sub2->get_disabled());
+}
+
+TEST_CASE_METHOD(ManySubcommands, "defaultEnabledSubcommand", "[subcom]") {
+
+    sub2->enabled_by_default();
+    run();
+    auto rem = app.remaining();
+    CHECK(0u == rem.size());
+    CHECK(sub2->get_enabled_by_default());
+    sub2->disabled();
+    CHECK(sub2->get_disabled());
+    run();
+    // this should disable it again even though it was disabled
+    rem = app.remaining();
+    CHECK(0u == rem.size());
+    CHECK(sub2->get_enabled_by_default());
+    CHECK(!sub2->get_disabled());
+}
+
+// #572
+TEST_CASE_METHOD(TApp, "MultiFinalCallbackCounts", "[subcom]") {
+
+    int app_compl = 0;
+    int sub_compl = 0;
+    int subsub_compl = 0;
+    int app_final = 0;
+    int sub_final = 0;
+    int subsub_final = 0;
+
+    app.parse_complete_callback([&app_compl]() { app_compl++; });
+    app.final_callback([&app_final]() { app_final++; });
+
+    auto *sub = app.add_subcommand("sub");
+
+    sub->parse_complete_callback([&sub_compl]() { sub_compl++; });
+    sub->final_callback([&sub_final]() { sub_final++; });
+
+    auto *subsub = sub->add_subcommand("subsub");
+
+    subsub->parse_complete_callback([&subsub_compl]() { subsub_compl++; });
+    subsub->final_callback([&subsub_final]() { subsub_final++; });
+
+    SECTION("No specified subcommands") {
+        args = {};
+        run();
+
+        CHECK(app_compl == 1);
+        CHECK(app_final == 1);
+        CHECK(sub_compl == 0);
+        CHECK(sub_final == 0);
+        CHECK(subsub_compl == 0);
+        CHECK(subsub_final == 0);
+    }
+
+    SECTION("One layer of subcommands") {
+        args = {"sub"};
+        run();
+
+        CHECK(app_compl == 1);
+        CHECK(app_final == 1);
+        CHECK(sub_compl == 1);
+        CHECK(sub_final == 1);
+        CHECK(subsub_compl == 0);
+        CHECK(subsub_final == 0);
+    }
+
+    SECTION("Fully specified subcommands") {
+        args = {"sub", "subsub"};
+        run();
+
+        CHECK(app_compl == 1);
+        CHECK(app_final == 1);
+        CHECK(sub_compl == 1);
+        CHECK(sub_final == 1);
+        CHECK(subsub_compl == 1);
+        CHECK(subsub_final == 1);
+    }
+}
diff --git a/external/CLI11/tests/TimerTest.cpp b/external/CLI11/tests/TimerTest.cpp
new file mode 100644
index 0000000..11e6e12
--- /dev/null
+++ b/external/CLI11/tests/TimerTest.cpp
@@ -0,0 +1,70 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "CLI/Timer.hpp"
+
+#include "catch.hpp"
+#include <chrono>
+#include <sstream>
+#include <string>
+#include <thread>
+
+using Catch::Matchers::Contains;
+
+TEST_CASE("Timer: MSTimes", "[timer]") {
+    CLI::Timer timer{"My Timer"};
+    std::this_thread::sleep_for(std::chrono::milliseconds(123));
+    std::string output = timer.to_string();
+    std::string new_output = (timer / 1000000).to_string();
+    CHECK_THAT(output, Contains("My Timer"));
+    CHECK_THAT(output, Contains(" ms"));
+    CHECK_THAT(new_output, Contains(" ns"));
+}
+
+/* Takes too long
+TEST_CASE("Timer: STimes", "[timer]") {
+    CLI::Timer timer;
+    std::this_thread::sleep_for(std::chrono::seconds(1));
+    std::string output = timer.to_string();
+    CHECK_THAT (output, Contains(" s"));
+}
+*/
+
+// Fails on Windows
+// TEST_CASE("Timer: UStimes", "[timer]") {
+//    CLI::Timer timer;
+//    std::this_thread::sleep_for(std::chrono::microseconds(2));
+//    std::string output = timer.to_string();
+//    CHECK_THAT (output, Contains(" ms"));
+//}
+
+TEST_CASE("Timer: BigTimer", "[timer]") {
+    CLI::Timer timer{"My Timer", CLI::Timer::Big};
+    std::string output = timer.to_string();
+    CHECK_THAT(output, Contains("Time ="));
+    CHECK_THAT(output, Contains("-----------"));
+}
+
+TEST_CASE("Timer: AutoTimer", "[timer]") {
+    CLI::AutoTimer timer;
+    std::string output = timer.to_string();
+    CHECK_THAT(output, Contains("Timer"));
+}
+
+TEST_CASE("Timer: PrintTimer", "[timer]") {
+    std::stringstream out;
+    CLI::AutoTimer timer;
+    out << timer;
+    std::string output = out.str();
+    CHECK_THAT(output, Contains("Timer"));
+}
+
+TEST_CASE("Timer: TimeItTimer", "[timer]") {
+    CLI::Timer timer;
+    std::string output = timer.time_it([]() { std::this_thread::sleep_for(std::chrono::milliseconds(10)); }, .1);
+    std::cout << output << std::endl;
+    CHECK_THAT(output, Contains("ms"));
+}
diff --git a/external/CLI11/tests/TransformTest.cpp b/external/CLI11/tests/TransformTest.cpp
new file mode 100644
index 0000000..76a1eff
--- /dev/null
+++ b/external/CLI11/tests/TransformTest.cpp
@@ -0,0 +1,970 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+
+#include <array>
+#include <chrono>
+#include <cstdint>
+#include <unordered_map>
+
+#if defined(CLI11_CPP17)
+#if defined(__has_include)
+#if __has_include(<string_view>)
+#include <string_view>
+#define CLI11_HAS_STRING_VIEW
+#endif
+#endif
+#endif
+
+TEST_CASE_METHOD(TApp, "SimpleTransform", "[transform]") {
+    int value{0};
+    auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", std::string("1")}}));
+    args = {"-s", "one"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(1 == value);
+}
+
+TEST_CASE_METHOD(TApp, "SimpleTransformInitList", "[transform]") {
+    int value{0};
+    auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}));
+    args = {"-s", "one"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(1 == value);
+}
+
+TEST_CASE_METHOD(TApp, "SimpleNumericalTransform", "[transform]") {
+    int value{0};
+    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(CLI::TransformPairs<int>{{"one", 1}}));
+    args = {"-s", "one"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(1 == value);
+}
+
+TEST_CASE_METHOD(TApp, "EnumTransform", "[transform]") {
+    enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17 };
+    test_cli value{test_cli::val2};
+    auto opt = app.add_option("-s", value)
+                   ->transform(CLI::Transformer(CLI::TransformPairs<test_cli>{
+                       {"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}}));
+    args = {"-s", "val1"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(test_cli::val1 == value);
+
+    args = {"-s", "val2"};
+    run();
+    CHECK(test_cli::val2 == value);
+
+    args = {"-s", "val3"};
+    run();
+    CHECK(test_cli::val3 == value);
+
+    args = {"-s", "val4"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    // transformer doesn't do any checking so this still works
+    args = {"-s", "5"};
+    run();
+    CHECK(std::int16_t(5) == static_cast<std::int16_t>(value));
+}
+
+TEST_CASE_METHOD(TApp, "EnumCheckedTransform", "[transform]") {
+    enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17 };
+    test_cli value{test_cli::val1};
+    auto opt = app.add_option("-s", value)
+                   ->transform(CLI::CheckedTransformer(CLI::TransformPairs<test_cli>{
+                       {"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}}));
+    args = {"-s", "val1"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(test_cli::val1 == value);
+
+    args = {"-s", "val2"};
+    run();
+    CHECK(test_cli::val2 == value);
+
+    args = {"-s", "val3"};
+    run();
+    CHECK(test_cli::val3 == value);
+
+    args = {"-s", "17"};
+    run();
+    CHECK(test_cli::val3 == value);
+
+    args = {"-s", "val4"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"-s", "5"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+// from jzakrzewski Issue #330
+TEST_CASE_METHOD(TApp, "EnumCheckedDefaultTransform", "[transform]") {
+    enum class existing : std::int16_t { abort, overwrite, remove };
+    app.add_option("--existing", "What to do if file already exists in the destination")
+        ->transform(
+            CLI::CheckedTransformer(std::unordered_map<std::string, existing>{{"abort", existing::abort},
+                                                                              {"overwrite", existing ::overwrite},
+                                                                              {"delete", existing::remove},
+                                                                              {"remove", existing::remove}}))
+        ->default_val("abort");
+    args = {"--existing", "overwrite"};
+    run();
+    CHECK(existing::overwrite == app.get_option("--existing")->as<existing>());
+    args.clear();
+    run();
+    CHECK(existing::abort == app.get_option("--existing")->as<existing>());
+}
+
+// test from https://github.com/CLIUtils/CLI11/issues/369  [Jakub Zakrzewski](https://github.com/jzakrzewski)
+TEST_CASE_METHOD(TApp, "EnumCheckedDefaultTransformCallback", "[transform]") {
+    enum class existing : std::int16_t { abort, overwrite, remove };
+    auto cmd = std::make_shared<CLI::App>("deploys the repository somewhere", "deploy");
+    cmd->add_option("--existing", "What to do if file already exists in the destination")
+        ->transform(
+            CLI::CheckedTransformer(std::unordered_map<std::string, existing>{{"abort", existing::abort},
+                                                                              {"overwrite", existing::overwrite},
+                                                                              {"delete", existing::remove},
+                                                                              {"remove", existing::remove}}))
+        ->default_val("abort");
+
+    cmd->callback([cmd]() { CHECK(cmd->get_option("--existing")->as<existing>() == existing::abort); });
+    app.add_subcommand(cmd);
+
+    args = {"deploy"};
+    run();
+}
+
+TEST_CASE_METHOD(TApp, "SimpleTransformFn", "[transform]") {
+    int value{0};
+    auto opt = app.add_option("-s", value)->transform(CLI::Transformer({{"one", "1"}}, CLI::ignore_case));
+    args = {"-s", "ONE"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(1 == value);
+}
+
+#if defined(CLI11_HAS_STRING_VIEW)
+TEST_CASE_METHOD(TApp, "StringViewTransformFn", "[transform]") {
+    std::string value;
+    std::map<std::string_view, std::string_view> map = {// key length > std::string().capacity() [SSO length]
+                                                        {"a-rather-long-argument", "mapped"}};
+    app.add_option("-s", value)->transform(CLI::CheckedTransformer(map));
+    args = {"-s", "a-rather-long-argument"};
+    run();
+    CHECK("mapped" == value);
+}
+
+#endif
+
+TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFn", "[transform]") {
+    int value{0};
+    auto opt =
+        app.add_option("-s", value)
+            ->transform(CLI::Transformer(std::vector<std::pair<std::string, int>>{{"one", 1}}, CLI::ignore_case));
+    args = {"-s", "ONe"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(1 == value);
+}
+
+TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFnVector", "[transform]") {
+    std::vector<std::pair<std::string, int>> conversions{{"one", 1}, {"two", 2}};
+    int value{0};
+    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(conversions, CLI::ignore_case));
+    args = {"-s", "ONe"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(1 == value);
+}
+
+TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFnArray", "[transform]") {
+    std::array<std::pair<std::string, int>, 2> conversions;
+    conversions[0] = std::make_pair(std::string("one"), 1);
+    conversions[1] = std::make_pair(std::string("two"), 2);
+
+    int value{0};
+    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(conversions, CLI::ignore_case));
+    args = {"-s", "ONe"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(1 == value);
+}
+
+#ifdef CLI11_CPP14
+// zero copy constexpr array operation with transformer example and test
+TEST_CASE_METHOD(TApp, "SimpleNumericalTransformFnconstexprArray", "[transform]") {
+    constexpr std::pair<const char *, int> p1{"one", 1};
+    constexpr std::pair<const char *, int> p2{"two", 2};
+    constexpr std::array<std::pair<const char *, int>, 2> conversions_c{{p1, p2}};
+
+    int value{0};
+    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(&conversions_c, CLI::ignore_case));
+    args = {"-s", "ONe"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(1 == value);
+
+    args = {"-s", "twO"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(2 == value);
+}
+#endif
+
+TEST_CASE_METHOD(TApp, "EnumTransformFn", "[transform]") {
+    enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17 };
+    test_cli value{test_cli::val2};
+    auto opt = app.add_option("-s", value)
+                   ->transform(CLI::Transformer(CLI::TransformPairs<test_cli>{{"val1", test_cli::val1},
+                                                                              {"val2", test_cli::val2},
+                                                                              {"val3", test_cli::val3}},
+                                                CLI::ignore_case,
+                                                CLI::ignore_underscore));
+    args = {"-s", "val_1"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(test_cli::val1 == value);
+
+    args = {"-s", "VAL_2"};
+    run();
+    CHECK(test_cli::val2 == value);
+
+    args = {"-s", "VAL3"};
+    run();
+    CHECK(test_cli::val3 == value);
+
+    args = {"-s", "val_4"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "EnumTransformFnMap", "[transform]") {
+    enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17 };
+    std::map<std::string, test_cli> map{{"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}};
+    test_cli value{test_cli::val3};
+    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(map, CLI::ignore_case, CLI::ignore_underscore));
+    args = {"-s", "val_1"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(test_cli::val1 == value);
+
+    args = {"-s", "VAL_2"};
+    run();
+    CHECK(test_cli::val2 == value);
+
+    args = {"-s", "VAL3"};
+    run();
+    CHECK(test_cli::val3 == value);
+
+    args = {"-s", "val_4"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+}
+
+TEST_CASE_METHOD(TApp, "EnumTransformFnPtrMap", "[transform]") {
+    enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17, val4 = 37 };
+    std::map<std::string, test_cli> map{{"val1", test_cli::val1}, {"val2", test_cli::val2}, {"val3", test_cli::val3}};
+    test_cli value{test_cli::val2};
+    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(&map, CLI::ignore_case, CLI::ignore_underscore));
+    args = {"-s", "val_1"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(test_cli::val1 == value);
+
+    args = {"-s", "VAL_2"};
+    run();
+    CHECK(test_cli::val2 == value);
+
+    args = {"-s", "VAL3"};
+    run();
+    CHECK(test_cli::val3 == value);
+
+    args = {"-s", "val_4"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    map["val4"] = test_cli::val4;
+    run();
+    CHECK(test_cli::val4 == value);
+}
+
+TEST_CASE_METHOD(TApp, "EnumTransformFnSharedPtrMap", "[transform]") {
+    enum class test_cli : std::int16_t { val1 = 3, val2 = 4, val3 = 17, val4 = 37 };
+    auto map = std::make_shared<std::unordered_map<std::string, test_cli>>();
+    auto &mp = *map;
+    mp["val1"] = test_cli::val1;
+    mp["val2"] = test_cli::val2;
+    mp["val3"] = test_cli::val3;
+
+    test_cli value{test_cli::val2};
+    auto opt = app.add_option("-s", value)->transform(CLI::Transformer(map, CLI::ignore_case, CLI::ignore_underscore));
+    args = {"-s", "val_1"};
+    run();
+    CHECK(app.count("-s") == 1u);
+    CHECK(opt->count() == 1u);
+    CHECK(test_cli::val1 == value);
+
+    args = {"-s", "VAL_2"};
+    run();
+    CHECK(test_cli::val2 == value);
+
+    args = {"-s", "VAL3"};
+    run();
+    CHECK(test_cli::val3 == value);
+
+    args = {"-s", "val_4"};
+    CHECK_THROWS_AS(run(), CLI::ConversionError);
+
+    mp["val4"] = test_cli::val4;
+    run();
+    CHECK(test_cli::val4 == value);
+}
+
+// Test a cascade of transform functions
+TEST_CASE_METHOD(TApp, "TransformCascade", "[transform]") {
+
+    std::string output;
+    auto opt = app.add_option("-s", output);
+    opt->transform(CLI::Transformer({{"abc", "abcd"}, {"bbc", "bbcd"}, {"cbc", "cbcd"}}, CLI::ignore_case));
+    opt->transform(
+        CLI::Transformer({{"ab", "abc"}, {"bc", "bbc"}, {"cb", "cbc"}}, CLI::ignore_case, CLI::ignore_underscore));
+    opt->transform(CLI::Transformer({{"a", "ab"}, {"b", "bb"}, {"c", "cb"}}, CLI::ignore_case));
+    opt->check(CLI::IsMember({"abcd", "bbcd", "cbcd"}));
+    args = {"-s", "abcd"};
+    run();
+    CHECK("abcd" == output);
+
+    args = {"-s", "Bbc"};
+    run();
+    CHECK("bbcd" == output);
+
+    args = {"-s", "C_B"};
+    run();
+    CHECK("cbcd" == output);
+
+    args = {"-s", "A"};
+    run();
+    CHECK("abcd" == output);
+}
+
+// Test a cascade of transform functions
+TEST_CASE_METHOD(TApp, "TransformCascadeDeactivate", "[transform]") {
+
+    std::string output;
+    auto opt = app.add_option("-s", output);
+    opt->transform(
+        CLI::Transformer({{"abc", "abcd"}, {"bbc", "bbcd"}, {"cbc", "cbcd"}}, CLI::ignore_case).name("tform1"));
+    opt->transform(
+        CLI::Transformer({{"ab", "abc"}, {"bc", "bbc"}, {"cb", "cbc"}}, CLI::ignore_case, CLI::ignore_underscore)
+            .name("tform2")
+            .active(false));
+    opt->transform(CLI::Transformer({{"a", "ab"}, {"b", "bb"}, {"c", "cb"}}, CLI::ignore_case).name("tform3"));
+    opt->check(CLI::IsMember({"abcd", "bbcd", "cbcd"}).name("check"));
+    args = {"-s", "abcd"};
+    run();
+    CHECK("abcd" == output);
+
+    args = {"-s", "Bbc"};
+    run();
+    CHECK("bbcd" == output);
+
+    args = {"-s", "C_B"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    auto validator = opt->get_validator("tform2");
+    CHECK(!validator->get_active());
+    CHECK("tform2" == validator->get_name());
+    validator->active();
+    CHECK(validator->get_active());
+    args = {"-s", "C_B"};
+    run();
+    CHECK("cbcd" == output);
+
+    opt->get_validator("check")->active(false);
+    args = {"-s", "gsdgsgs"};
+    run();
+    CHECK("gsdgsgs" == output);
+
+    CHECK_THROWS_AS(opt->get_validator("sdfsdf"), CLI::OptionNotFound);
+}
+
+TEST_CASE_METHOD(TApp, "IntTransformFn", "[transform]") {
+    std::string value;
+    app.add_option("-s", value)
+        ->transform(
+            CLI::CheckedTransformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}, [](int in) { return in - 10; }));
+    args = {"-s", "25"};
+    run();
+    CHECK("5" == value);
+
+    args = {"-s", "6"};
+    run();
+    CHECK("6" == value);
+
+    args = {"-s", "45"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"-s", "val_4"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "IntTransformNonConvertible", "[transform]") {
+    std::string value;
+    app.add_option("-s", value)->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}));
+    args = {"-s", "15"};
+    run();
+    CHECK("5" == value);
+
+    args = {"-s", "18"};
+    run();
+    CHECK("6" == value);
+
+    // value can't be converted to int so it is just ignored
+    args = {"-s", "abcd"};
+    run();
+    CHECK("abcd" == value);
+}
+
+TEST_CASE_METHOD(TApp, "IntTransformNonMerge", "[transform]") {
+    std::string value;
+    app.add_option("-s", value)
+        ->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}) &
+                        CLI::Transformer(std::map<int, int>{{25, 5}, {28, 6}, {31, 7}}),
+                    "merge");
+    args = {"-s", "15"};
+    run();
+    CHECK("5" == value);
+
+    args = {"-s", "18"};
+    run();
+    CHECK("6" == value);
+
+    // value can't be converted to int so it is just ignored
+    args = {"-s", "abcd"};
+    run();
+    CHECK("abcd" == value);
+
+    args = {"-s", "25"};
+    run();
+    CHECK("5" == value);
+
+    args = {"-s", "31"};
+    run();
+    CHECK("7" == value);
+
+    auto help = app.help();
+    CHECK(help.find("15->5") != std::string::npos);
+    CHECK(help.find("25->5") != std::string::npos);
+
+    auto validator = app.get_option("-s")->get_validator();
+    help = validator->get_description();
+    CHECK(help.find("15->5") != std::string::npos);
+    CHECK(help.find("25->5") != std::string::npos);
+
+    auto validator2 = app.get_option("-s")->get_validator("merge");
+    CHECK(validator == validator2);
+}
+
+TEST_CASE_METHOD(TApp, "IntTransformMergeWithCustomValidator", "[transform]") {
+    std::string value;
+    auto opt = app.add_option("-s", value)
+                   ->transform(CLI::Transformer(std::map<int, int>{{15, 5}, {18, 6}, {21, 7}}) |
+                                   CLI::Validator(
+                                       [](std::string &element) {
+                                           if(element == "frog") {
+                                               element = "hops";
+                                           }
+                                           return std::string{};
+                                       },
+                                       std::string{}),
+                               "check");
+    args = {"-s", "15"};
+    run();
+    CHECK("5" == value);
+
+    args = {"-s", "18"};
+    run();
+    CHECK("6" == value);
+
+    // value can't be converted to int so it is just ignored
+    args = {"-s", "frog"};
+    run();
+    CHECK("hops" == value);
+
+    args = {"-s", "25"};
+    run();
+    CHECK("25" == value);
+
+    auto help = app.help();
+    CHECK(help.find("15->5") != std::string::npos);
+    CHECK(help.find("OR") == std::string::npos);
+
+    auto validator = opt->get_validator("check");
+    CHECK("check" == validator->get_name());
+    validator->active(false);
+    help = app.help();
+    CHECK(help.find("15->5") == std::string::npos);
+}
+
+TEST_CASE_METHOD(TApp, "BoundTests", "[transform]") {
+    double value;
+    app.add_option("-s", value)->transform(CLI::Bound(3.4, 5.9));
+    args = {"-s", "15"};
+    run();
+    CHECK(5.9 == value);
+
+    args = {"-s", "3.689"};
+    run();
+    CHECK(std::stod("3.689") == value);
+
+    // value can't be converted to int so it is just ignored
+    args = {"-s", "abcd"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"-s", "2.5"};
+    run();
+    CHECK(3.4 == value);
+
+    auto help = app.help();
+    CHECK(help.find("bounded to") != std::string::npos);
+    CHECK(help.find("[3.4 - 5.9]") != std::string::npos);
+}
+
+TEST_CASE_METHOD(TApp, "NumberWithUnitCorrectlySplitNumber", "[transform]") {
+    std::map<std::string, int> mapping{{"a", 10}, {"b", 100}, {"cc", 1000}};
+
+    int value = 0;
+    app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
+
+    args = {"-n", "42"};
+    run();
+    CHECK(42 == value);
+
+    args = {"-n", "42a"};
+    run();
+    CHECK(420 == value);
+
+    args = {"-n", "  42  cc  "};
+    run();
+    CHECK(42000 == value);
+    args = {"-n", "  -42  cc  "};
+    run();
+    CHECK(-42000 == value);
+}
+
+TEST_CASE_METHOD(TApp, "NumberWithUnitFloatTest", "[transform]") {
+    std::map<std::string, double> mapping{{"a", 10}, {"b", 100}, {"cc", 1000}};
+    double value{0.0};
+    app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
+
+    args = {"-n", "42"};
+    run();
+    CHECK(42 == Approx(value));
+
+    args = {"-n", ".5"};
+    run();
+    CHECK(.5 == Approx(value));
+
+    args = {"-n", "42.5 a"};
+    run();
+    CHECK(425 == Approx(value));
+
+    args = {"-n", "42.cc"};
+    run();
+    CHECK(42000 == Approx(value));
+}
+
+TEST_CASE_METHOD(TApp, "NumberWithUnitCaseSensitive", "[transform]") {
+    std::map<std::string, int> mapping{{"a", 10}, {"A", 100}};
+
+    int value{0};
+    app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping, CLI::AsNumberWithUnit::CASE_SENSITIVE));
+
+    args = {"-n", "42a"};
+    run();
+    CHECK(420 == value);
+
+    args = {"-n", "42A"};
+    run();
+    CHECK(4200 == value);
+}
+
+TEST_CASE_METHOD(TApp, "NumberWithUnitCaseInsensitive", "[transform]") {
+    std::map<std::string, int> mapping{{"a", 10}, {"B", 100}};
+
+    int value{0};
+    app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping, CLI::AsNumberWithUnit::CASE_INSENSITIVE));
+
+    args = {"-n", "42a"};
+    run();
+    CHECK(420 == value);
+
+    args = {"-n", "42A"};
+    run();
+    CHECK(420 == value);
+
+    args = {"-n", "42b"};
+    run();
+    CHECK(4200 == value);
+
+    args = {"-n", "42B"};
+    run();
+    CHECK(4200 == value);
+}
+
+TEST_CASE_METHOD(TApp, "NumberWithUnitMandatoryUnit", "[transform]") {
+    std::map<std::string, int> mapping{{"a", 10}, {"A", 100}};
+
+    int value{0};
+    app.add_option("-n", value)
+        ->transform(CLI::AsNumberWithUnit(mapping,
+                                          CLI::AsNumberWithUnit::Options(CLI::AsNumberWithUnit::UNIT_REQUIRED |
+                                                                         CLI::AsNumberWithUnit::CASE_SENSITIVE)));
+
+    args = {"-n", "42a"};
+    run();
+    CHECK(420 == value);
+
+    args = {"-n", "42A"};
+    run();
+    CHECK(4200 == value);
+
+    args = {"-n", "42"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "NumberWithUnitMandatoryUnit2", "[transform]") {
+    std::map<std::string, int> mapping{{"a", 10}, {"B", 100}};
+
+    int value{0};
+    app.add_option("-n", value)
+        ->transform(CLI::AsNumberWithUnit(mapping,
+                                          CLI::AsNumberWithUnit::Options(CLI::AsNumberWithUnit::UNIT_REQUIRED |
+                                                                         CLI::AsNumberWithUnit::CASE_INSENSITIVE)));
+
+    args = {"-n", "42A"};
+    run();
+    CHECK(420 == value);
+
+    args = {"-n", "42b"};
+    run();
+    CHECK(4200 == value);
+
+    args = {"-n", "42"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "NumberWithUnitBadMapping", "[transform]") {
+    CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"A", 100}},
+                                          CLI::AsNumberWithUnit::CASE_INSENSITIVE),
+                    CLI::ValidationError);
+    CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"9", 100}}), CLI::ValidationError);
+    CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"AA A", 100}}), CLI::ValidationError);
+    CHECK_THROWS_AS(CLI::AsNumberWithUnit(std::map<std::string, int>{{"a", 10}, {"", 100}}), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "NumberWithUnitBadInput", "[transform]") {
+    std::map<std::string, int> mapping{{"a", 10}, {"b", 100}};
+
+    int value{0};
+    app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
+
+    args = {"-n", "13 a b"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+    args = {"-n", "13 c"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+    args = {"-n", "a"};
+    // Assume 1.0 unit
+    CHECK_NOTHROW(run());
+    args = {"-n", "12.0a"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+    args = {"-n", "a5"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+    args = {"-n", ""};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+    args = {"-n", "13 a-"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "NumberWithUnitIntOverflow", "[transform]") {
+    std::map<std::string, int> mapping{{"a", 1000000}, {"b", 100}, {"c", 101}};
+
+    std::int32_t value;
+    app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
+
+    args = {"-n", "1000 a"};
+    run();
+    CHECK(1000000000 == value);
+
+    args = {"-n", "1000000 a"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"-n", "-1000000 a"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"-n", "21474836 b"};
+    run();
+    CHECK(2147483600 == value);
+
+    args = {"-n", "21474836 c"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+}
+
+TEST_CASE_METHOD(TApp, "NumberWithUnitFloatOverflow", "[transform]") {
+    std::map<std::string, float> mapping{{"a", 2.f}, {"b", 1.f}, {"c", 0.f}};
+
+    float value{0.0F};
+    app.add_option("-n", value)->transform(CLI::AsNumberWithUnit(mapping));
+
+    args = {"-n", "3e+38 a"};
+    CHECK_THROWS_AS(run(), CLI::ValidationError);
+
+    args = {"-n", "3e+38 b"};
+    run();
+    CHECK(3e+38f == Approx(value));
+
+    args = {"-n", "3e+38 c"};
+    run();
+    CHECK(0.f == Approx(value));
+}
+
+TEST_CASE_METHOD(TApp, "AsSizeValue1000_1024", "[transform]") {
+    std::uint64_t value{0};
+    app.add_option("-s", value)->transform(CLI::AsSizeValue(true));
+
+    args = {"-s", "10240"};
+    run();
+    CHECK(10240u == value);
+
+    args = {"-s", "1b"};
+    run();
+    CHECK(1u == value);
+
+    std::uint64_t k_value{1000u};
+    std::uint64_t ki_value{1024u};
+    args = {"-s", "1k"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1kb"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1 Kb"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1ki"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1kib"};
+    run();
+    CHECK(ki_value == value);
+
+    k_value = 1000ull * 1000u;
+    ki_value = 1024ull * 1024u;
+    args = {"-s", "1m"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1mb"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1mi"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1mib"};
+    run();
+    CHECK(ki_value == value);
+
+    k_value = 1000ull * 1000u * 1000u;
+    ki_value = 1024ull * 1024u * 1024u;
+    args = {"-s", "1g"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1gb"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1gi"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1gib"};
+    run();
+    CHECK(ki_value == value);
+
+    k_value = 1000ull * 1000u * 1000u * 1000u;
+    ki_value = 1024ull * 1024u * 1024u * 1024u;
+    args = {"-s", "1t"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1tb"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1ti"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1tib"};
+    run();
+    CHECK(ki_value == value);
+
+    k_value = 1000ull * 1000u * 1000u * 1000u * 1000u;
+    ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u;
+    args = {"-s", "1p"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1pb"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1pi"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1pib"};
+    run();
+    CHECK(ki_value == value);
+
+    k_value = 1000ull * 1000u * 1000u * 1000u * 1000u * 1000u;
+    ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u * 1024u;
+    args = {"-s", "1e"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1eb"};
+    run();
+    CHECK(k_value == value);
+    args = {"-s", "1ei"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1eib"};
+    run();
+    CHECK(ki_value == value);
+}
+
+TEST_CASE_METHOD(TApp, "duration_test", "[transform]") {
+    std::chrono::seconds duration{1};
+
+    app.option_defaults()->ignore_case();
+    app.add_option_function<std::size_t>(
+           "--duration",
+           [&](size_t a_value) { duration = std::chrono::seconds{a_value}; },
+           "valid units: sec, min, h, day.")
+        ->capture_default_str()
+        ->transform(CLI::AsNumberWithUnit(
+            std::map<std::string, std::size_t>{{"sec", 1}, {"min", 60}, {"h", 3600}, {"day", 24 * 3600}}));
+    CHECK_NOTHROW(app.parse(std::vector<std::string>{"1 day", "--duration"}));
+
+    CHECK(std::chrono::seconds(86400) == duration);
+}
+
+TEST_CASE_METHOD(TApp, "AsSizeValue1024", "[transform]") {
+    std::uint64_t value{0};
+    app.add_option("-s", value)->transform(CLI::AsSizeValue(false));
+
+    args = {"-s", "10240"};
+    run();
+    CHECK(10240u == value);
+
+    args = {"-s", "1b"};
+    run();
+    CHECK(1u == value);
+
+    std::uint64_t ki_value{1024u};
+    args = {"-s", "1k"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1kb"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1 Kb"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1ki"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1kib"};
+    run();
+    CHECK(ki_value == value);
+
+    ki_value = 1024ull * 1024u;
+    args = {"-s", "1m"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1mb"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1mi"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1mib"};
+    run();
+    CHECK(ki_value == value);
+
+    ki_value = 1024ull * 1024u * 1024u;
+    args = {"-s", "1g"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1gb"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1gi"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1gib"};
+    run();
+    CHECK(ki_value == value);
+
+    ki_value = 1024ull * 1024u * 1024u * 1024u;
+    args = {"-s", "1t"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1tb"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1ti"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1tib"};
+    run();
+    CHECK(ki_value == value);
+
+    ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u;
+    args = {"-s", "1p"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1pb"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1pi"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1pib"};
+    run();
+    CHECK(ki_value == value);
+
+    ki_value = 1024ull * 1024u * 1024u * 1024u * 1024u * 1024u;
+    args = {"-s", "1e"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1eb"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1ei"};
+    run();
+    CHECK(ki_value == value);
+    args = {"-s", "1eib"};
+    run();
+    CHECK(ki_value == value);
+}
diff --git a/external/CLI11/tests/TrueFalseTest.cpp b/external/CLI11/tests/TrueFalseTest.cpp
new file mode 100644
index 0000000..c3b556c
--- /dev/null
+++ b/external/CLI11/tests/TrueFalseTest.cpp
@@ -0,0 +1,29 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+
+TEST_CASE_METHOD(TApp, "True Bool Option", "[bool][flag]") {
+    // Strings needed here due to MSVC 2015.
+    auto param = GENERATE(as<std::string>{}, "true", "on", "True", "ON");
+    bool value{false};  // Not used, but set just in case
+    app.add_option("-b,--bool", value);
+    args = {"--bool", param};
+    run();
+    CHECK(app.count("--bool") == 1u);
+    CHECK(value);
+}
+
+TEST_CASE_METHOD(TApp, "False Bool Option", "[bool][flag]") {
+    auto param = GENERATE(as<std::string>{}, "false", "off", "False", "OFF");
+
+    bool value{true};  // Not used, but set just in case
+    app.add_option("-b,--bool", value);
+    args = {"--bool", param};
+    run();
+    CHECK(app.count("--bool") == 1u);
+    CHECK_FALSE(value);
+}
diff --git a/external/CLI11/tests/WindowsTest.cpp b/external/CLI11/tests/WindowsTest.cpp
new file mode 100644
index 0000000..5a75908
--- /dev/null
+++ b/external/CLI11/tests/WindowsTest.cpp
@@ -0,0 +1,19 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "app_helper.hpp"
+#include <Windows.h>
+
+// This test verifies that CLI11 still works if
+// Windows.h is included. #145
+
+TEST_CASE_METHOD(TApp, "WindowsTestSimple", "[windows]") {
+    app.add_flag("-c,--count");
+    args = {"-c"};
+    run();
+    CHECK(app.count("-c") == 1u);
+    CHECK(app.count("--count") == 1u);
+}
diff --git a/external/CLI11/tests/app_helper.hpp b/external/CLI11/tests/app_helper.hpp
new file mode 100644
index 0000000..9199168
--- /dev/null
+++ b/external/CLI11/tests/app_helper.hpp
@@ -0,0 +1,67 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+#ifdef CLI11_SINGLE_FILE
+#include "CLI11.hpp"
+#else
+#include "CLI/CLI.hpp"
+#endif
+
+#include "catch.hpp"
+#include <iostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+using input_t = std::vector<std::string>;
+
+class TApp {
+  public:
+    CLI::App app{"My Test Program"};
+    input_t args{};
+    virtual ~TApp() = default;
+    void run() {
+        // It is okay to re-parse - clear is called automatically before a parse.
+        input_t newargs = args;
+        std::reverse(std::begin(newargs), std::end(newargs));
+        app.parse(newargs);
+    }
+};
+
+class TempFile {
+    std::string _name{};
+
+  public:
+    explicit TempFile(std::string name) : _name(std::move(name)) {
+        if(!CLI::NonexistentPath(_name).empty())
+            throw std::runtime_error(_name);
+    }
+
+    ~TempFile() {
+        std::remove(_name.c_str());  // Doesn't matter if returns 0 or not
+    }
+
+    operator const std::string &() const { return _name; }
+    const char *c_str() const { return _name.c_str(); }
+};
+
+inline void put_env(std::string name, std::string value) {
+#ifdef _WIN32
+    _putenv_s(name.c_str(), value.c_str());
+#else
+    setenv(name.c_str(), value.c_str(), 1);
+#endif
+}
+
+inline void unset_env(std::string name) {
+#ifdef _WIN32
+    _putenv_s(name.c_str(), "");
+#else
+    unsetenv(name.c_str());
+#endif
+}
diff --git a/external/CLI11/tests/catch.hpp b/external/CLI11/tests/catch.hpp
new file mode 100644
index 0000000..2aaeae7
--- /dev/null
+++ b/external/CLI11/tests/catch.hpp
@@ -0,0 +1,9 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+#include <catch2/catch.hpp>
diff --git a/external/CLI11/tests/informational.cpp b/external/CLI11/tests/informational.cpp
new file mode 100644
index 0000000..f0fc70b
--- /dev/null
+++ b/external/CLI11/tests/informational.cpp
@@ -0,0 +1,56 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifdef CLI11_SINGLE_FILE
+#include "CLI11.hpp"
+#else
+#include "CLI/CLI.hpp"
+#endif
+
+#include <iostream>
+
+int main() {
+    std::cout << "\nCLI11 information:\n";
+
+    std::cout << "  C++ standard: ";
+#if defined(CLI11_CPP20)
+    std::cout << 20;
+#elif defined(CLI11_CPP17)
+    std::cout << 17;
+#elif defined(CLI11_CPP14)
+    std::cout << 14;
+#else
+    std::cout << 11;
+#endif
+    std::cout << "\n";
+
+    std::cout << "  __has_include: ";
+#ifdef __has_include
+    std::cout << "yes\n";
+#else
+    std::cout << "no\n";
+#endif
+
+#if CLI11_OPTIONAL
+    std::cout << "  [Available as CLI::optional]";
+#else
+    std::cout << "  No optional library found\n";
+#endif
+
+#if CLI11_STD_OPTIONAL
+    std::cout << "  std::optional support active\n";
+#endif
+
+#if CLI11_EXPERIMENTAL_OPTIONAL
+    std::cout << "  std::experimental::optional support active\n";
+#endif
+
+#if CLI11_BOOST_OPTIONAL
+    std::cout << "  boost::optional support active\n";
+#endif
+
+    std::cout << std::endl;
+}
diff --git a/external/CLI11/tests/link_test_1.cpp b/external/CLI11/tests/link_test_1.cpp
new file mode 100644
index 0000000..447afbf
--- /dev/null
+++ b/external/CLI11/tests/link_test_1.cpp
@@ -0,0 +1,10 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "CLI/CLI.hpp"
+#include "CLI/Timer.hpp"
+
+int do_nothing() { return 7; }
diff --git a/external/CLI11/tests/link_test_2.cpp b/external/CLI11/tests/link_test_2.cpp
new file mode 100644
index 0000000..abce549
--- /dev/null
+++ b/external/CLI11/tests/link_test_2.cpp
@@ -0,0 +1,17 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "CLI/CLI.hpp"
+#include "CLI/Timer.hpp"
+#include "catch.hpp"
+
+int do_nothing();
+
+// Verifies there are no unguarded inlines
+TEST_CASE("Link: DoNothing", "[link]") {
+    int a = do_nothing();
+    CHECK(a == 7);
+}
diff --git a/external/CLI11/tests/main.cpp b/external/CLI11/tests/main.cpp
new file mode 100644
index 0000000..f5fbebd
--- /dev/null
+++ b/external/CLI11/tests/main.cpp
@@ -0,0 +1,8 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#define CATCH_CONFIG_MAIN
+#include "catch.hpp"
diff --git a/external/CLI11/tests/mesonTest/README.md b/external/CLI11/tests/mesonTest/README.md
new file mode 100644
index 0000000..ec08dc5
--- /dev/null
+++ b/external/CLI11/tests/mesonTest/README.md
@@ -0,0 +1,10 @@
+# CLI11 Meson test / example
+
+Requirements: meson, ninja
+
+## Build
+
+```bash
+meson build
+ninja -C build
+```
diff --git a/external/CLI11/tests/mesonTest/main.cpp b/external/CLI11/tests/mesonTest/main.cpp
new file mode 100644
index 0000000..64d45eb
--- /dev/null
+++ b/external/CLI11/tests/mesonTest/main.cpp
@@ -0,0 +1,17 @@
+// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner
+// under NSF AWARD 1414736 and by the respective contributors.
+// All rights reserved.
+//
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <CLI/CLI.hpp>
+
+int main(int argc, char **argv) {
+    CLI::App app{"App description"};
+
+    std::string filename = "default";
+    app.add_option("-f,--file", filename, "A help string");
+
+    CLI11_PARSE(app, argc, argv);
+    return 0;
+}
diff --git a/external/CLI11/tests/mesonTest/meson.build b/external/CLI11/tests/mesonTest/meson.build
new file mode 100644
index 0000000..56ebadb
--- /dev/null
+++ b/external/CLI11/tests/mesonTest/meson.build
@@ -0,0 +1,5 @@
+project('mesonTest', ['c', 'cpp'], default_options: ['cpp_std=c++11'])
+
+cli11_dep = subproject('CLI11').get_variable('CLI11_dep')
+
+mainExe = executable('main', ['main.cpp'], dependencies: [cli11_dep])
diff --git a/external/CLI11/tests/mesonTest/subprojects/CLI11 b/external/CLI11/tests/mesonTest/subprojects/CLI11
new file mode 120000
index 0000000..a8a4f8c
--- /dev/null
+++ b/external/CLI11/tests/mesonTest/subprojects/CLI11
@@ -0,0 +1 @@
+../../..
\ No newline at end of file
-- 
GitLab