From fb8a4c10f7bf059dd253aba950d4aea7a7193250 Mon Sep 17 00:00:00 2001
From: Mark Roszko <mark.roszko@gmail.com>
Date: Tue, 4 Oct 2022 01:53:37 +0000
Subject: [PATCH] Shove kicad2step into pcbnew itself with a new cli

---
 CMakeLists.txt                                |    4 -
 bitmap2component/CMakeLists.txt               |    1 +
 common/CMakeLists.txt                         |    8 +
 common/cli/command.h                          |   48 +
 common/cli/command_export_kicad_pcbnew.cpp    |   31 +
 common/cli/command_export_kicad_pcbnew.h      |   36 +
 common/cli/command_export_pcb_svg.cpp         |  126 +
 common/cli/command_export_pcb_svg.h           |   36 +
 common/cli/command_export_pcbnew.cpp          |   30 +
 common/cli/command_export_pcbnew.h            |   36 +
 common/cli/command_export_step.cpp            |  174 +
 common/cli/command_export_step.h              |   36 +
 common/cli/exit_codes.h                       |   38 +
 common/jobs/job.h                             |   42 +
 common/jobs/job_dispatcher.cpp                |   38 +
 common/jobs/job_dispatcher.h                  |   39 +
 common/jobs/job_export_pcb_svg.h              |   49 +
 common/jobs/job_export_step.cpp               |   34 +
 common/jobs/job_export_step.h                 |   45 +
 common/kiway.cpp                              |    8 +
 common/pgm_base.cpp                           |   24 +
 common/single_top.cpp                         |  160 +-
 eeschema/CMakeLists.txt                       |    3 +-
 eeschema/eeschema.cpp                         |    7 +
 gerbview/CMakeLists.txt                       |    1 +
 include/kiway.h                               |    8 +
 include/kiway_player.h                        |   14 -
 include/pgm_base.h                            |   10 +
 kicad/CMakeLists.txt                          |    7 +-
 kicad/kicad.cpp                               |   87 +
 libs/kiplatform/msw/app.cpp                   |   25 +-
 pagelayout_editor/CMakeLists.txt              |    1 +
 pcb_calculator/CMakeLists.txt                 |    1 +
 pcbnew/CMakeLists.txt                         |   14 +-
 pcbnew/dialogs/dialog_export_step.cpp         |   68 +-
 pcbnew/dialogs/dialog_export_svg.cpp          |   81 +-
 .../exporters/step}/CMakeLists.txt            |   36 +-
 .../exporters/step}/kicad2step.cpp            |  146 +-
 .../exporters/step}/kicad2step.h              |   11 +
 .../exporters/step}/kicad2step_app.cpp        |    0
 .../exporters/step}/kicad2step_frame_base.cpp |    4 +-
 .../exporters/step}/kicad2step_frame_base.fbp |    6 +-
 .../exporters/step}/kicad2step_frame_base.h   |    3 +-
 .../exporters/step}/panel_kicad2step.h        |    4 -
 .../exporters/step}/pcb/3d_resolver.cpp       |    2 +-
 .../exporters/step}/pcb/3d_resolver.h         |    0
 .../exporters/step}/pcb/base.cpp              |    0
 .../exporters/step}/pcb/base.h                |    0
 .../exporters/step}/pcb/kicadcurve.cpp        |    0
 .../exporters/step}/pcb/kicadcurve.h          |    0
 .../exporters/step}/pcb/kicadfootprint.cpp    |    0
 .../exporters/step}/pcb/kicadfootprint.h      |    0
 .../exporters/step}/pcb/kicadmodel.cpp        |    0
 .../exporters/step}/pcb/kicadmodel.h          |    0
 .../exporters/step}/pcb/kicadpad.cpp          |    0
 .../exporters/step}/pcb/kicadpad.h            |    0
 .../exporters/step}/pcb/kicadpcb.cpp          |    0
 .../exporters/step}/pcb/kicadpcb.h            |    0
 .../exporters/step}/pcb/oce_utils.cpp         |    0
 .../exporters/step}/pcb/oce_utils.h           |    0
 pcbnew/pcb_plot_svg.cpp                       |  106 +
 pcbnew/pcb_plot_svg.h                         |   43 +
 pcbnew/pcbnew.cpp                             |   13 +
 pcbnew/pcbnew_jobs_handler.cpp                |  102 +
 pcbnew/pcbnew_jobs_handler.h                  |   34 +
 qa/tools/pcbnew_tools/CMakeLists.txt          |    5 +-
 resources/msw/cmd-wrappers/kicad.cmd          |    1 +
 resources/msw/cmd-wrappers/pcbnew.cmd         |    1 +
 thirdparty/CMakeLists.txt                     |    1 +
 thirdparty/argparse/.clang-format             |  117 +
 thirdparty/argparse/.clang-tidy               |   21 +
 thirdparty/argparse/.github/workflows/ci.yml  |   87 +
 thirdparty/argparse/.gitignore                |  273 +
 thirdparty/argparse/.travis.yml               |   39 +
 thirdparty/argparse/CMakeLists.txt            |   98 +
 thirdparty/argparse/CONTRIBUTING.md           |   17 +
 thirdparty/argparse/LICENSE                   |    7 +
 thirdparty/argparse/README.md                 | 1143 +++
 thirdparty/argparse/clang_format.bash         |    1 +
 thirdparty/argparse/conanfile.py              |   10 +
 .../argparse/include/argparse/argparse.hpp    | 1652 ++++
 thirdparty/argparse/packaging/pkgconfig.pc.in |    7 +
 thirdparty/argparse/samples/CMakeLists.txt    |   46 +
 .../argparse/samples/compound_arguments.cpp   |   36 +
 .../samples/custom_assignment_characters.cpp  |   27 +
 .../samples/custom_prefix_characters.cpp      |   31 +
 .../samples/description_epilog_metavar.cpp    |   17 +
 .../samples/gathering_remaining_arguments.cpp |   24 +
 thirdparty/argparse/samples/is_used.cpp       |   26 +
 .../joining_repeated_optional_arguments.cpp   |   28 +
 .../argparse/samples/list_of_arguments.cpp    |   30 +
 .../argparse/samples/negative_numbers.cpp     |   32 +
 .../samples/optional_flag_argument.cpp        |   22 +
 .../argparse/samples/parse_known_args.cpp     |   26 +
 .../argparse/samples/positional_argument.cpp  |   28 +
 .../repeating_argument_to_increase_value.cpp  |   17 +
 .../samples/required_optional_argument.cpp    |   19 +
 thirdparty/argparse/samples/subcommands.cpp   |   65 +
 thirdparty/argparse/test/.gitignore           |    2 +
 thirdparty/argparse/test/CMakeLists.txt       |   63 +
 thirdparty/argparse/test/README.md            |   25 +
 thirdparty/argparse/test/doctest.hpp          | 6816 +++++++++++++++++
 thirdparty/argparse/test/main.cpp             |    1 +
 thirdparty/argparse/test/test_actions.cpp     |  173 +
 thirdparty/argparse/test/test_append.cpp      |   40 +
 .../argparse/test/test_compound_arguments.cpp |  110 +
 .../argparse/test/test_const_correct.cpp      |   27 +
 .../test/test_container_arguments.cpp         |   80 +
 .../argparse/test/test_default_args.cpp       |   19 +
 thirdparty/argparse/test/test_equals_form.cpp |   39 +
 thirdparty/argparse/test/test_get.cpp         |   35 +
 thirdparty/argparse/test/test_help.cpp        |   75 +
 .../argparse/test/test_invalid_arguments.cpp  |   40 +
 thirdparty/argparse/test/test_is_used.cpp     |   20 +
 thirdparty/argparse/test/test_issue_37.cpp    |   43 +
 .../argparse/test/test_negative_numbers.cpp   |  255 +
 .../argparse/test/test_optional_arguments.cpp |  200 +
 .../argparse/test/test_parent_parsers.cpp     |   36 +
 thirdparty/argparse/test/test_parse_args.cpp  |  219 +
 .../argparse/test/test_parse_known_args.cpp   |   82 +
 .../test/test_positional_arguments.cpp        |  262 +
 .../argparse/test/test_prefix_chars.cpp       |   41 +
 thirdparty/argparse/test/test_repr.cpp        |   56 +
 .../argparse/test/test_required_arguments.cpp |   63 +
 thirdparty/argparse/test/test_scan.cpp        |  348 +
 thirdparty/argparse/test/test_subparsers.cpp  |  202 +
 thirdparty/argparse/test/test_utility.hpp     |   17 +
 .../argparse/test/test_value_semantics.cpp    |   96 +
 thirdparty/argparse/test/test_version.cpp     |   36 +
 thirdparty/argparse/tools/build.bat           |    4 +
 thirdparty/argparse/tools/build.sh            |    4 +
 utils/CMakeLists.txt                          |    1 -
 132 files changed, 15052 insertions(+), 312 deletions(-)
 create mode 100644 common/cli/command.h
 create mode 100644 common/cli/command_export_kicad_pcbnew.cpp
 create mode 100644 common/cli/command_export_kicad_pcbnew.h
 create mode 100644 common/cli/command_export_pcb_svg.cpp
 create mode 100644 common/cli/command_export_pcb_svg.h
 create mode 100644 common/cli/command_export_pcbnew.cpp
 create mode 100644 common/cli/command_export_pcbnew.h
 create mode 100644 common/cli/command_export_step.cpp
 create mode 100644 common/cli/command_export_step.h
 create mode 100644 common/cli/exit_codes.h
 create mode 100644 common/jobs/job.h
 create mode 100644 common/jobs/job_dispatcher.cpp
 create mode 100644 common/jobs/job_dispatcher.h
 create mode 100644 common/jobs/job_export_pcb_svg.h
 create mode 100644 common/jobs/job_export_step.cpp
 create mode 100644 common/jobs/job_export_step.h
 rename {utils/kicad2step => pcbnew/exporters/step}/CMakeLists.txt (54%)
 rename {utils/kicad2step => pcbnew/exporters/step}/kicad2step.cpp (74%)
 rename {utils/kicad2step => pcbnew/exporters/step}/kicad2step.h (91%)
 rename {utils/kicad2step => pcbnew/exporters/step}/kicad2step_app.cpp (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/kicad2step_frame_base.cpp (86%)
 rename {utils/kicad2step => pcbnew/exporters/step}/kicad2step_frame_base.fbp (96%)
 rename {utils/kicad2step => pcbnew/exporters/step}/kicad2step_frame_base.h (91%)
 rename {utils/kicad2step => pcbnew/exporters/step}/panel_kicad2step.h (95%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/3d_resolver.cpp (99%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/3d_resolver.h (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/base.cpp (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/base.h (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/kicadcurve.cpp (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/kicadcurve.h (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/kicadfootprint.cpp (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/kicadfootprint.h (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/kicadmodel.cpp (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/kicadmodel.h (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/kicadpad.cpp (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/kicadpad.h (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/kicadpcb.cpp (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/kicadpcb.h (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/oce_utils.cpp (100%)
 rename {utils/kicad2step => pcbnew/exporters/step}/pcb/oce_utils.h (100%)
 create mode 100644 pcbnew/pcb_plot_svg.cpp
 create mode 100644 pcbnew/pcb_plot_svg.h
 create mode 100644 pcbnew/pcbnew_jobs_handler.cpp
 create mode 100644 pcbnew/pcbnew_jobs_handler.h
 create mode 100644 resources/msw/cmd-wrappers/kicad.cmd
 create mode 100644 resources/msw/cmd-wrappers/pcbnew.cmd
 create mode 100644 thirdparty/argparse/.clang-format
 create mode 100644 thirdparty/argparse/.clang-tidy
 create mode 100644 thirdparty/argparse/.github/workflows/ci.yml
 create mode 100644 thirdparty/argparse/.gitignore
 create mode 100644 thirdparty/argparse/.travis.yml
 create mode 100644 thirdparty/argparse/CMakeLists.txt
 create mode 100644 thirdparty/argparse/CONTRIBUTING.md
 create mode 100644 thirdparty/argparse/LICENSE
 create mode 100644 thirdparty/argparse/README.md
 create mode 100644 thirdparty/argparse/clang_format.bash
 create mode 100644 thirdparty/argparse/conanfile.py
 create mode 100644 thirdparty/argparse/include/argparse/argparse.hpp
 create mode 100644 thirdparty/argparse/packaging/pkgconfig.pc.in
 create mode 100644 thirdparty/argparse/samples/CMakeLists.txt
 create mode 100644 thirdparty/argparse/samples/compound_arguments.cpp
 create mode 100644 thirdparty/argparse/samples/custom_assignment_characters.cpp
 create mode 100644 thirdparty/argparse/samples/custom_prefix_characters.cpp
 create mode 100644 thirdparty/argparse/samples/description_epilog_metavar.cpp
 create mode 100644 thirdparty/argparse/samples/gathering_remaining_arguments.cpp
 create mode 100644 thirdparty/argparse/samples/is_used.cpp
 create mode 100644 thirdparty/argparse/samples/joining_repeated_optional_arguments.cpp
 create mode 100644 thirdparty/argparse/samples/list_of_arguments.cpp
 create mode 100644 thirdparty/argparse/samples/negative_numbers.cpp
 create mode 100644 thirdparty/argparse/samples/optional_flag_argument.cpp
 create mode 100644 thirdparty/argparse/samples/parse_known_args.cpp
 create mode 100644 thirdparty/argparse/samples/positional_argument.cpp
 create mode 100644 thirdparty/argparse/samples/repeating_argument_to_increase_value.cpp
 create mode 100644 thirdparty/argparse/samples/required_optional_argument.cpp
 create mode 100644 thirdparty/argparse/samples/subcommands.cpp
 create mode 100644 thirdparty/argparse/test/.gitignore
 create mode 100644 thirdparty/argparse/test/CMakeLists.txt
 create mode 100644 thirdparty/argparse/test/README.md
 create mode 100644 thirdparty/argparse/test/doctest.hpp
 create mode 100644 thirdparty/argparse/test/main.cpp
 create mode 100644 thirdparty/argparse/test/test_actions.cpp
 create mode 100644 thirdparty/argparse/test/test_append.cpp
 create mode 100644 thirdparty/argparse/test/test_compound_arguments.cpp
 create mode 100644 thirdparty/argparse/test/test_const_correct.cpp
 create mode 100644 thirdparty/argparse/test/test_container_arguments.cpp
 create mode 100644 thirdparty/argparse/test/test_default_args.cpp
 create mode 100644 thirdparty/argparse/test/test_equals_form.cpp
 create mode 100644 thirdparty/argparse/test/test_get.cpp
 create mode 100644 thirdparty/argparse/test/test_help.cpp
 create mode 100644 thirdparty/argparse/test/test_invalid_arguments.cpp
 create mode 100644 thirdparty/argparse/test/test_is_used.cpp
 create mode 100644 thirdparty/argparse/test/test_issue_37.cpp
 create mode 100644 thirdparty/argparse/test/test_negative_numbers.cpp
 create mode 100644 thirdparty/argparse/test/test_optional_arguments.cpp
 create mode 100644 thirdparty/argparse/test/test_parent_parsers.cpp
 create mode 100644 thirdparty/argparse/test/test_parse_args.cpp
 create mode 100644 thirdparty/argparse/test/test_parse_known_args.cpp
 create mode 100644 thirdparty/argparse/test/test_positional_arguments.cpp
 create mode 100644 thirdparty/argparse/test/test_prefix_chars.cpp
 create mode 100644 thirdparty/argparse/test/test_repr.cpp
 create mode 100644 thirdparty/argparse/test/test_required_arguments.cpp
 create mode 100644 thirdparty/argparse/test/test_scan.cpp
 create mode 100644 thirdparty/argparse/test/test_subparsers.cpp
 create mode 100644 thirdparty/argparse/test/test_utility.hpp
 create mode 100644 thirdparty/argparse/test/test_value_semantics.cpp
 create mode 100644 thirdparty/argparse/test/test_version.cpp
 create mode 100644 thirdparty/argparse/tools/build.bat
 create mode 100644 thirdparty/argparse/tools/build.sh

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8dcaeef530..cb02859712 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -155,10 +155,6 @@ cmake_dependent_option( KICAD_WIN32_INSTALL_PDBS
     OFF "WIN32"
     OFF )
 
-option( KICAD_STEP_EXPORT_LIB
-    "Build and use kicad2step as a library, meant for debugging"
-    OFF )
-
 option( KICAD_USE_3DCONNEXION
     "Build KiCad with support for 3Dconnexion devices (Currently only for Mac/MSW)"
     OFF )
diff --git a/bitmap2component/CMakeLists.txt b/bitmap2component/CMakeLists.txt
index 3b45e9d348..078f31f179 100644
--- a/bitmap2component/CMakeLists.txt
+++ b/bitmap2component/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable( bitmap2component WIN32 MACOSX_BUNDLE
 
 target_link_libraries( bitmap2component
     common
+    argparse::argparse
     ${wxWidgets_LIBRARIES}
     potrace
     )
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index d8361589dc..2680b39ac1 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -287,6 +287,12 @@ set( COMMON_SRCS
     ${PLUGINS_CADSTAR_SRCS}
     ${PLUGINS_EAGLE_SRCS}
 	${FONT_SRCS}
+    cli/command_export_kicad_pcbnew.cpp
+    cli/command_export_pcb_svg.cpp
+    cli/command_export_pcbnew.cpp
+    cli/command_export_step.cpp
+    jobs/job_export_step.cpp
+    jobs/job_dispatcher.cpp
     advanced_config.cpp
     array_axis.cpp
     array_options.cpp
@@ -481,6 +487,7 @@ target_link_libraries( common
     compoundfilereader
     pcm_settings
     nanodbc # for now; maybe hoist out of common
+    argparse::argparse
     ${Boost_LIBRARIES}
     ${CURL_LIBRARIES}
     ${wxWidgets_LIBRARIES}
@@ -574,6 +581,7 @@ set( PCB_COMMON_SRCS
     ${CMAKE_SOURCE_DIR}/pcbnew/pcb_painter.cpp
     ${CMAKE_SOURCE_DIR}/pcbnew/plugins/kicad/pcb_parser.cpp
     ${CMAKE_SOURCE_DIR}/pcbnew/pcb_plot_params.cpp
+    ${CMAKE_SOURCE_DIR}/pcbnew/pcb_plot_svg.cpp
     ${CMAKE_SOURCE_DIR}/pcbnew/pcb_screen.cpp
     ${CMAKE_SOURCE_DIR}/pcbnew/pcb_view.cpp
     ${CMAKE_SOURCE_DIR}/pcbnew/pcbnew_settings.cpp
diff --git a/common/cli/command.h b/common/cli/command.h
new file mode 100644
index 0000000000..11f7de01a5
--- /dev/null
+++ b/common/cli/command.h
@@ -0,0 +1,48 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CLI_COMMAND_H
+#define CLI_COMMAND_H
+
+#include <argparse/argparse.hpp>
+#include <kiway.h>
+
+namespace CLI
+{
+
+struct COMMAND
+{
+    COMMAND( std::string aName ) : m_name( aName ), m_argParser( aName ){};
+
+    virtual int Perform( KIWAY& aKiway ) const = 0;
+
+    virtual ~COMMAND() = default;
+
+    argparse::ArgumentParser& GetArgParser() { return m_argParser; }
+    const std::string&        GetName() const { return m_name; }
+
+protected:
+    std::string              m_name;
+    argparse::ArgumentParser m_argParser;
+};
+
+}
+
+#endif
\ No newline at end of file
diff --git a/common/cli/command_export_kicad_pcbnew.cpp b/common/cli/command_export_kicad_pcbnew.cpp
new file mode 100644
index 0000000000..126e660a02
--- /dev/null
+++ b/common/cli/command_export_kicad_pcbnew.cpp
@@ -0,0 +1,31 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "command_export_kicad_pcbnew.h"
+
+CLI::EXPORT_KICAD_PCBNEW_COMMAND::EXPORT_KICAD_PCBNEW_COMMAND() : COMMAND( "export-pcb" )
+{
+}
+
+
+int CLI::EXPORT_KICAD_PCBNEW_COMMAND::Perform( KIWAY& aKiway ) const
+{
+    return 0;
+}
\ No newline at end of file
diff --git a/common/cli/command_export_kicad_pcbnew.h b/common/cli/command_export_kicad_pcbnew.h
new file mode 100644
index 0000000000..efb9048f19
--- /dev/null
+++ b/common/cli/command_export_kicad_pcbnew.h
@@ -0,0 +1,36 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMMAND_EXPORT_KICAD_PCBNEW_H
+#define COMMAND_EXPORT_KICAD_PCBNEW_H
+
+#include "command.h"
+
+namespace CLI
+{
+struct EXPORT_KICAD_PCBNEW_COMMAND : public COMMAND
+{
+    EXPORT_KICAD_PCBNEW_COMMAND();
+
+    int Perform( KIWAY& aKiway ) const override;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/common/cli/command_export_pcb_svg.cpp b/common/cli/command_export_pcb_svg.cpp
new file mode 100644
index 0000000000..fbbbf445ad
--- /dev/null
+++ b/common/cli/command_export_pcb_svg.cpp
@@ -0,0 +1,126 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "command_export_pcb_svg.h"
+#include "exit_codes.h"
+#include "jobs/job_export_pcb_svg.h"
+#include <kiface_base.h>
+#include <layer_ids.h>
+#include <regex>
+
+#include <macros.h>
+#include <wx/tokenzr.h>
+
+#define ARG_PAGE_SIZE "--page-size-mode"
+#define ARG_MIRROR "--mirror"
+#define ARG_BLACKANDWHITE "--black-and-white"
+#define ARG_OUTPUT "--output"
+#define ARG_THEME "--theme"
+#define ARG_LAYERS "--layers"
+#define ARG_INPUT "input"
+
+
+CLI::EXPORT_PCB_SVG_COMMAND::EXPORT_PCB_SVG_COMMAND() : COMMAND( "svg" )
+{
+    m_argParser.add_argument( "-o", ARG_OUTPUT )
+            .default_value( std::string() )
+            .help( "output file name" );
+
+    m_argParser.add_argument( "-l", ARG_LAYERS )
+            .default_value( std::string() )
+            .help( "comma separated list of untranslated layer names to include such as F.Cu,B.Cu" );
+
+    m_argParser.add_argument( "-m", ARG_MIRROR )
+            .help( "Mirror the board (useful for trying to show bottom layers)" )
+            .implicit_value( true )
+            .default_value( false );
+
+    m_argParser.add_argument( "-t", ARG_THEME )
+            .default_value( std::string() )
+            .help( "Color theme to use (will default to pcbnew settings)" );
+
+    m_argParser.add_argument( ARG_BLACKANDWHITE )
+            .help( "Black and white only" )
+            .implicit_value( true )
+            .default_value( false );
+
+    m_argParser.add_argument( ARG_PAGE_SIZE )
+            .help( "Set page sizing mode (0 = page with frame and title block, 1 = current page size, 2 = board area only)" )
+            .default_value( 0 );
+
+    m_argParser.add_argument( ARG_INPUT ).help( "input file" );
+}
+
+
+int CLI::EXPORT_PCB_SVG_COMMAND::Perform( KIWAY& aKiway ) const
+{
+    std::map<std::string, LSET> layerMasks;
+    for( int layer = 0; layer < PCB_LAYER_ID_COUNT; ++layer )
+    {
+        std::string untranslated = TO_UTF8( wxString( LSET::Name( PCB_LAYER_ID( layer ) ) ) );
+
+        //m_layerIndices[untranslated] = PCB_LAYER_ID( layer );
+        layerMasks[untranslated] = LSET( PCB_LAYER_ID( layer ) );
+    }
+    layerMasks["*.Cu"] = LSET::AllCuMask();
+    layerMasks["*In.Cu"] = LSET::InternalCuMask();
+    layerMasks["F&B.Cu"] = LSET( 2, F_Cu, B_Cu );
+    layerMasks["*.Adhes"] = LSET( 2, B_Adhes, F_Adhes );
+    layerMasks["*.Paste"] = LSET( 2, B_Paste, F_Paste );
+    layerMasks["*.Mask"] = LSET( 2, B_Mask, F_Mask );
+    layerMasks["*.SilkS"] = LSET( 2, B_SilkS, F_SilkS );
+    layerMasks["*.Fab"] = LSET( 2, B_Fab, F_Fab );
+    layerMasks["*.CrtYd"] = LSET( 2, B_CrtYd, F_CrtYd );
+
+    JOB_EXPORT_PCB_SVG* svgJob = new JOB_EXPORT_PCB_SVG( true );
+
+    svgJob->m_mirror = m_argParser.get<bool>( ARG_MIRROR );
+    svgJob->m_blackAndWhite = m_argParser.get<bool>( ARG_BLACKANDWHITE );
+    svgJob->m_pageSizeMode = m_argParser.get<int>( ARG_PAGE_SIZE );
+
+    svgJob->m_filename = FROM_UTF8( m_argParser.get<std::string>( ARG_INPUT ).c_str() );
+    svgJob->m_outputFile = FROM_UTF8( m_argParser.get<std::string>( ARG_OUTPUT ).c_str() );
+    svgJob->m_colorTheme = FROM_UTF8( m_argParser.get<std::string>( ARG_THEME ).c_str() );
+
+    wxString layers = FROM_UTF8( m_argParser.get<std::string>( ARG_LAYERS ).c_str() );
+
+    LSET              layerMask = LSET::AllCuMask();
+
+    if( !layers.IsEmpty() )
+    {
+        layerMask.reset();
+        wxStringTokenizer layerTokens( layers, "," );
+        while( layerTokens.HasMoreTokens() )
+        {
+            std::string token = TO_UTF8(layerTokens.GetNextToken());
+            if( layerMasks.count( token ) )
+            {
+                layerMask |= layerMasks[token];
+            }
+        }
+    }
+
+    svgJob->m_printMaskLayer = layerMask;
+
+    int exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, svgJob );
+
+    return exitCode;
+}
\ No newline at end of file
diff --git a/common/cli/command_export_pcb_svg.h b/common/cli/command_export_pcb_svg.h
new file mode 100644
index 0000000000..de99d2f2d5
--- /dev/null
+++ b/common/cli/command_export_pcb_svg.h
@@ -0,0 +1,36 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMMAND_EXPORT_PCB_SVG_H
+#define COMMAND_EXPORT_PCB_SVG_H
+
+#include "command.h"
+
+namespace CLI
+{
+struct EXPORT_PCB_SVG_COMMAND : public COMMAND
+{
+    EXPORT_PCB_SVG_COMMAND();
+
+    int Perform( KIWAY& aKiway ) const override;
+};
+} // namespace CLI
+
+#endif
\ No newline at end of file
diff --git a/common/cli/command_export_pcbnew.cpp b/common/cli/command_export_pcbnew.cpp
new file mode 100644
index 0000000000..309f092997
--- /dev/null
+++ b/common/cli/command_export_pcbnew.cpp
@@ -0,0 +1,30 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "command_export_pcbnew.h"
+
+CLI::EXPORT_PCBNEW_COMMAND::EXPORT_PCBNEW_COMMAND() : COMMAND( "export" )
+{
+}
+
+int CLI::EXPORT_PCBNEW_COMMAND::Perform( KIWAY& aKiway ) const
+{
+    return 0;
+}
\ No newline at end of file
diff --git a/common/cli/command_export_pcbnew.h b/common/cli/command_export_pcbnew.h
new file mode 100644
index 0000000000..eb797ee169
--- /dev/null
+++ b/common/cli/command_export_pcbnew.h
@@ -0,0 +1,36 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMMAND_EXPORT_PCBNEW_H
+#define COMMAND_EXPORT_PCBNEW_H
+
+#include "command.h"
+
+namespace CLI
+{
+struct EXPORT_PCBNEW_COMMAND : public COMMAND
+{
+    EXPORT_PCBNEW_COMMAND();
+
+    int Perform( KIWAY& aKiway ) const override;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/common/cli/command_export_step.cpp b/common/cli/command_export_step.cpp
new file mode 100644
index 0000000000..884f5c7f1b
--- /dev/null
+++ b/common/cli/command_export_step.cpp
@@ -0,0 +1,174 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "command_export_step.h"
+#include "exit_codes.h"
+#include "jobs/job_export_step.h"
+#include <kiface_base.h>
+#include <regex>
+
+#include <macros.h>
+
+#define ARG_DRILL_ORIGIN "--drill-origin"
+#define ARG_GRID_ORIGIN "--grid-origin"
+#define ARG_NO_VIRTUAL "--no-virtual"
+#define ARG_SUBST_MODELS "--subst-models"
+#define ARG_FORCE "--force"
+#define ARG_OUTPUT "--output"
+#define ARG_INPUT "input"
+#define ARG_MIN_DISTANCE "--min-distance"
+#define ARG_USER_ORIGIN "--user-origin"
+#define ARG_GUI "--gui"
+
+#define REGEX_QUANTITY "([\\s]*[+-]?[\\d]*[.]?[\\d]*)"
+#define REGEX_DELIMITER "(?:[\\s]*x)"
+#define REGEX_UNIT "([m]{2}|(?:in))"
+
+CLI::EXPORT_STEP_COMMAND::EXPORT_STEP_COMMAND() : COMMAND( "step" )
+{
+    m_argParser.add_argument( ARG_DRILL_ORIGIN )
+            .help( "Use Drill Origin for output origin" )
+            .implicit_value( true )
+            .default_value( false );
+
+    m_argParser.add_argument( ARG_GRID_ORIGIN )
+            .help( "Use Grid Origin for output origin" )
+            .implicit_value( true )
+            .default_value( false );
+
+    m_argParser.add_argument( ARG_NO_VIRTUAL )
+            .help( "Exclude 3D models for components with 'virtual' attribute" )
+            .implicit_value( true )
+            .default_value( false );
+
+    m_argParser.add_argument( "--subst-models" )
+            .help( "Substitute STEP or IGS models with the same name in place of VRML models" )
+            .implicit_value( true )
+            .default_value( false );
+
+    m_argParser.add_argument( ARG_FORCE, "-f" )
+            .help( "overwrite output file" )
+            .implicit_value( true )
+            .default_value( false );
+
+    m_argParser.add_argument( ARG_GUI )
+            .help( "Show GUI (log window)" )
+            .implicit_value( true )
+            .default_value( false );
+
+    m_argParser.add_argument( ARG_MIN_DISTANCE )
+            .default_value( std::string() )
+            .help( "Minimum distance between points to treat them as separate ones (default 0.01mm)" );
+
+    m_argParser.add_argument( ARG_USER_ORIGIN )
+            .default_value( std::string() )
+            .help( "User-specified output origin ex. 1x1in, 1x1inch, 25.4x25.4mm (default mm)" );
+
+    m_argParser.add_argument( "-o", ARG_OUTPUT )
+            .default_value( std::string() )
+            .help( "output file name" );
+
+    m_argParser.add_argument( ARG_INPUT ).help( "input file" );
+}
+
+int CLI::EXPORT_STEP_COMMAND::Perform( KIWAY& aKiway ) const
+{
+    JOB_EXPORT_STEP* step = new JOB_EXPORT_STEP( true );
+
+    step->m_useDrillOrigin = m_argParser.get<bool>( ARG_DRILL_ORIGIN );
+    step->m_useGridOrigin = m_argParser.get<bool>( ARG_GRID_ORIGIN );
+    step->m_includeVirtual = !m_argParser.get<bool>( ARG_NO_VIRTUAL );
+    step->m_substModels = m_argParser.get<bool>( ARG_SUBST_MODELS );
+    step->m_overwrite = m_argParser.get<bool>( ARG_FORCE );
+    step->m_filename = FROM_UTF8( m_argParser.get<std::string>( ARG_INPUT ).c_str() );
+    step->m_outputFile = FROM_UTF8( m_argParser.get<std::string>( ARG_OUTPUT ).c_str() );
+    step->m_gui = m_argParser.get<bool>( ARG_GUI );
+
+    wxString userOrigin = FROM_UTF8( m_argParser.get<std::string>( ARG_USER_ORIGIN ).c_str() );
+    if( !userOrigin.IsEmpty() )
+    {
+        std::regex  re_pattern( REGEX_QUANTITY REGEX_DELIMITER REGEX_QUANTITY REGEX_UNIT,
+                                std::regex_constants::icase );
+        std::smatch sm;
+        std::string str( userOrigin.ToUTF8() );
+        std::regex_search( str, sm, re_pattern );
+        step->m_xOrigin = atof( sm.str( 1 ).c_str() );
+        step->m_yOrigin = atof( sm.str( 2 ).c_str() );
+
+        std::string tunit( sm[3] );
+
+        if( tunit.size() > 0 ) // No unit accepted ( default = mm )
+        {
+            if( ( !sm.str( 1 ).compare( " " ) || !sm.str( 2 ).compare( " " ) )
+                || ( sm.size() != 4 ) )
+            {
+                std::cout << m_argParser;
+                return CLI::EXIT_CODES::ERR_ARGS;
+            }
+
+            // only in, inch and mm are valid:
+            if( !tunit.compare( "in" ) || !tunit.compare( "inch" ) )
+            {
+                step->m_xOrigin *= 25.4;
+                step->m_yOrigin *= 25.4;
+            }
+            else if( tunit.compare( "mm" ) )
+            {
+                std::cout << m_argParser;
+                return CLI::EXIT_CODES::ERR_ARGS;
+            }
+        }
+    }
+
+    wxString minDistance = FROM_UTF8( m_argParser.get<std::string>( ARG_MIN_DISTANCE ).c_str() );
+    if( !minDistance.IsEmpty() )
+    {
+        std::istringstream istr;
+        istr.str( std::string( minDistance.ToUTF8() ) );
+        istr >> step->m_minDistance;
+
+        if( istr.fail() )
+        {
+            std::cout << m_argParser;
+            return CLI::EXIT_CODES::ERR_ARGS;
+        }
+
+        if( !istr.eof() )
+        {
+            std::string tunit;
+            istr >> tunit;
+
+            if( !tunit.compare( "in" ) || !tunit.compare( "inch" ) )
+            {
+                step->m_minDistance *= 25.4;
+            }
+            else if( tunit.compare( "mm" ) )
+            {
+                std::cout << m_argParser;
+                return CLI::EXIT_CODES::ERR_ARGS;
+            }
+        }
+    }
+
+    int exitCode = aKiway.ProcessJob( KIWAY::FACE_PCB, step );
+
+    return exitCode;
+}
\ No newline at end of file
diff --git a/common/cli/command_export_step.h b/common/cli/command_export_step.h
new file mode 100644
index 0000000000..543c0aec81
--- /dev/null
+++ b/common/cli/command_export_step.h
@@ -0,0 +1,36 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMMAND_EXPORT_STEP_H
+#define COMMAND_EXPORT_STEP_H
+
+#include "command.h"
+
+namespace CLI
+{
+struct EXPORT_STEP_COMMAND : public COMMAND
+{
+    EXPORT_STEP_COMMAND();
+
+    int Perform( KIWAY& aKiway ) const override;
+};
+}
+
+#endif
\ No newline at end of file
diff --git a/common/cli/exit_codes.h b/common/cli/exit_codes.h
new file mode 100644
index 0000000000..c7fa9b4310
--- /dev/null
+++ b/common/cli/exit_codes.h
@@ -0,0 +1,38 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CLI_EXIT_CODES_H
+#define CLI_EXIT_CODES_H
+
+namespace CLI
+{
+    namespace EXIT_CODES
+    {
+        static const int  AVOID_CLOSING = -1;
+        static const int SUCCESS = 0;
+        static const int  OK = 0;
+        static const int ERR_ARGS = 1;
+        static const int ERR_UNKNOWN = 2;
+        static const int  ERR_INVALID_INPUT_FILE = 3;
+        static const int  ERR_INVALID_OUTPUT_CONFLICT = 4;
+    };
+}
+
+#endif
\ No newline at end of file
diff --git a/common/jobs/job.h b/common/jobs/job.h
new file mode 100644
index 0000000000..996b217211
--- /dev/null
+++ b/common/jobs/job.h
@@ -0,0 +1,42 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef JOB_H
+#define JOB_H
+
+/**
+ * An simple container class that lets us dispatch output jobs to kifaces
+ */
+class JOB
+{
+public:
+    JOB( const std::string& aType, bool aIsCli ) : m_type( aType ), m_isCli( aIsCli ) {}
+
+    virtual ~JOB() {}
+
+    const std::string& GetType() const { return m_type; };
+    bool IsCli() const { return m_isCli; };
+
+private:
+    std::string m_type;
+    bool m_isCli;
+};
+
+#endif
\ No newline at end of file
diff --git a/common/jobs/job_dispatcher.cpp b/common/jobs/job_dispatcher.cpp
new file mode 100644
index 0000000000..c722719137
--- /dev/null
+++ b/common/jobs/job_dispatcher.cpp
@@ -0,0 +1,38 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cli/exit_codes.h>
+#include <jobs/job_dispatcher.h>
+
+void JOB_DISPATCHER::Register( const std::string&             aJobTypeName,
+                               std::function<int( JOB* job )> aHandler )
+{
+    m_jobHandlers.emplace( aJobTypeName, aHandler );
+}
+
+int JOB_DISPATCHER::RunJob( JOB* job )
+{
+    if( m_jobHandlers.count( job->GetType() ) )
+    {
+        return m_jobHandlers[job->GetType()]( job );
+    }
+
+    return CLI::EXIT_CODES::ERR_UNKNOWN;
+}
\ No newline at end of file
diff --git a/common/jobs/job_dispatcher.h b/common/jobs/job_dispatcher.h
new file mode 100644
index 0000000000..f7a8f701a8
--- /dev/null
+++ b/common/jobs/job_dispatcher.h
@@ -0,0 +1,39 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef JOB_DISPATCHER_H
+#define JOB_DISPATCHER_H
+
+#include <functional>
+#include <string>
+#include <map>
+#include <jobs/job.h>
+
+class JOB_DISPATCHER
+{
+public:
+    void Register( const std::string& aJobTypeName, std::function<int( JOB* job )> aHandler );
+    int RunJob( JOB* job );
+
+private:
+    std::map<std::string, std::function<int( JOB* job )>> m_jobHandlers;
+};
+
+#endif
\ No newline at end of file
diff --git a/common/jobs/job_export_pcb_svg.h b/common/jobs/job_export_pcb_svg.h
new file mode 100644
index 0000000000..d28a14b98b
--- /dev/null
+++ b/common/jobs/job_export_pcb_svg.h
@@ -0,0 +1,49 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef JOB_EXPORT_PCB_SVG_H
+#define JOB_EXPORT_PCB_SVG_H
+
+#include <layer_ids.h>
+#include <wx/string.h>
+#include "job.h"
+
+class JOB_EXPORT_PCB_SVG : public JOB
+{
+public:
+    JOB_EXPORT_PCB_SVG( bool aIsCli ) :
+            JOB( "svg", aIsCli ), m_filename(), m_outputFile(), m_colorTheme(), m_mirror( false ),
+            m_blackAndWhite( false ), m_pageSizeMode( 0 ), m_printMaskLayer()
+    {
+    }
+
+    wxString m_filename;
+    wxString m_outputFile;
+    wxString m_colorTheme;
+
+    bool m_mirror;
+    bool m_blackAndWhite;
+
+    int m_pageSizeMode;
+
+    LSET m_printMaskLayer;
+};
+
+#endif
\ No newline at end of file
diff --git a/common/jobs/job_export_step.cpp b/common/jobs/job_export_step.cpp
new file mode 100644
index 0000000000..1dcdf1ff3f
--- /dev/null
+++ b/common/jobs/job_export_step.cpp
@@ -0,0 +1,34 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "jobs/job_export_step.h"
+
+JOB_EXPORT_STEP::JOB_EXPORT_STEP( bool aIsCli ) : JOB( "step", aIsCli )
+{
+    m_overwrite = false;
+    m_useGridOrigin = false;
+    m_useDrillOrigin = false;
+    m_includeVirtual = true;
+    m_substModels = false;
+    m_xOrigin = 0.0;
+    m_yOrigin = 0.0;
+    m_minDistance = 0.0;
+    m_gui = false;
+}
\ No newline at end of file
diff --git a/common/jobs/job_export_step.h b/common/jobs/job_export_step.h
new file mode 100644
index 0000000000..401e7f92ae
--- /dev/null
+++ b/common/jobs/job_export_step.h
@@ -0,0 +1,45 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef JOB_EXPORT_STEP_H
+#define JOB_EXPORT_STEP_H
+
+#include <wx/string.h>
+#include "job.h"
+
+class JOB_EXPORT_STEP : public JOB
+{
+public:
+    JOB_EXPORT_STEP( bool aIsCli );
+
+    bool     m_overwrite;
+    bool     m_useGridOrigin;
+    bool     m_useDrillOrigin;
+    bool     m_includeVirtual;
+    bool     m_substModels;
+    wxString m_filename;
+    wxString m_outputFile;
+    double   m_xOrigin;
+    double   m_yOrigin;
+    double   m_minDistance;
+    bool     m_gui;
+};
+
+#endif
\ No newline at end of file
diff --git a/common/kiway.cpp b/common/kiway.cpp
index f427cded3c..a9c48b9bdd 100644
--- a/common/kiway.cpp
+++ b/common/kiway.cpp
@@ -654,6 +654,14 @@ bool KIWAY::ProcessEvent( wxEvent& aEvent )
 }
 
 
+int KIWAY::ProcessJob( KIWAY::FACE_T aFace, JOB* job )
+{
+    KIFACE* kiface = KiFACE( aFace );
+
+    return kiface->HandleJob( job );
+}
+
+
 void KIWAY::OnKiCadExit()
 {
     if( m_ctl & KFCTL_CPP_PROJECT_SUITE )
diff --git a/common/pgm_base.cpp b/common/pgm_base.cpp
index be267713bc..e1d1046caf 100644
--- a/common/pgm_base.cpp
+++ b/common/pgm_base.cpp
@@ -124,6 +124,8 @@ PGM_BASE::PGM_BASE()
     m_Printing = false;
     m_Quitting = false;
     m_ModalDialogCount = 0;
+    m_argcUtf8 = 0;
+    m_argvUtf8 = nullptr;
 
     setLanguageId( wxLANGUAGE_DEFAULT );
 
@@ -134,6 +136,13 @@ PGM_BASE::PGM_BASE()
 PGM_BASE::~PGM_BASE()
 {
     Destroy();
+
+    for( size_t n = 0; n < m_argcUtf8; n++ )
+    {
+        delete m_argvUtf8[n];
+    }
+
+    delete m_argvUtf8;
 }
 
 
@@ -370,6 +379,21 @@ void PGM_BASE::sentryPrompt()
 #endif
 
 
+void PGM_BASE::BuildArgvUtf8()
+{
+    const wxArrayString& argArray = App().argv.GetArguments();
+    m_argcUtf8 = argArray.size();
+
+    m_argvUtf8 = new char*[m_argcUtf8 + 1];
+    for( size_t n = 0; n < m_argcUtf8; n++ )
+    {
+        m_argvUtf8[n] = wxStrdup( argArray[n].ToUTF8() );
+    }
+
+    m_argvUtf8[m_argcUtf8] = NULL;  // null terminator at end of argv
+}
+
+
 bool PGM_BASE::InitPgm( bool aHeadless, bool aSkipPyInit )
 {
     // Just make sure we init precreate any folders early for later code
diff --git a/common/single_top.cpp b/common/single_top.cpp
index bfb2ed4055..d84c6fc303 100644
--- a/common/single_top.cpp
+++ b/common/single_top.cpp
@@ -40,6 +40,7 @@
 #include <wx/stdpaths.h>
 #include <wx/snglinst.h>
 #include <wx/html/htmlwin.h>
+#include <argparse/argparse.hpp>
 
 #include <kiway.h>
 #include <pgm_base.h>
@@ -48,9 +49,14 @@
 #include <confirm.h>
 #include <settings/settings_manager.h>
 
+#include <kicad_build_version.h>
 #include <kiplatform/app.h>
 #include <kiplatform/environment.h>
 
+#include "cli/command_export_pcbnew.h"
+#include "cli/command_export_pcb_svg.h"
+#include "cli/command_export_step.h"
+#include "cli/exit_codes.h"
 
 // Only a single KIWAY is supported in this single_top top level component,
 // which is dedicated to loading only a single DSO.
@@ -274,8 +280,36 @@ struct APP_SINGLE_TOP : public wxApp
 IMPLEMENT_APP( APP_SINGLE_TOP )
 
 
+
+struct COMMAND_ENTRY
+{
+    CLI::COMMAND* handler;
+
+    std::vector<COMMAND_ENTRY> subCommands;
+
+    COMMAND_ENTRY( CLI::COMMAND* aHandler ) : handler( aHandler ){};
+    COMMAND_ENTRY( CLI::COMMAND* aHandler, std::vector<COMMAND_ENTRY> aSub ) :
+            handler( aHandler ), subCommands( aSub ) {};
+};
+
+#ifdef PCBNEW
+static CLI::EXPORT_STEP_COMMAND   stepCmd{};
+static CLI::EXPORT_PCB_SVG_COMMAND   svgCmd{};
+static CLI::EXPORT_PCBNEW_COMMAND exportCmd{};
+
+static std::vector<COMMAND_ENTRY> commandStack = {
+    { &exportCmd, { &stepCmd, &svgCmd } }
+};
+#else
+
+static std::vector<COMMAND_ENTRY> commandStack = {
+};
+#endif
+
 bool PGM_SINGLE_TOP::OnPgmInit()
 {
+    PGM_BASE::BuildArgvUtf8();
+
 #if defined(DEBUG)
     wxString absoluteArgv0 = wxStandardPaths::Get().GetExecutablePath();
 
@@ -285,10 +319,62 @@ bool PGM_SINGLE_TOP::OnPgmInit()
         return false;
     }
 #endif
+    wxString pgm_name;
+    if( App().argc == 0 )
+        pgm_name = wxT( "kicad" );
+    else
+        pgm_name = wxFileName( App().argv[0] ).GetName().Lower();
+
+    argparse::ArgumentParser argParser( std::string( pgm_name.utf8_str() ), KICAD_MAJOR_MINOR_VERSION );
+
+    for(COMMAND_ENTRY& entry : commandStack)
+    {
+        argParser.add_subparser( entry.handler->GetArgParser() );
+
+        for( COMMAND_ENTRY& subentry : entry.subCommands )
+        {
+            entry.handler->GetArgParser().add_subparser( subentry.handler->GetArgParser() );
+        }
+    }
+
+    try
+    {
+        argParser.parse_args( m_argcUtf8, m_argvUtf8 );
+    }
+    catch( const std::runtime_error& err )
+    {
+        // this provides the nice pretty print
+        std::cerr << err.what() << std::endl;
+        std::cerr << argParser;
+
+        std::exit( CLI::EXIT_CODES::ERR_ARGS );
+    }
+
+    bool cliCmdRequested = false;
+    CLI::COMMAND* cliCmd = nullptr;
+    for( COMMAND_ENTRY& entry : commandStack )
+    {
+        if( argParser.is_subcommand_used( entry.handler->GetName() ) )
+        {
+            for( COMMAND_ENTRY& subentry : entry.subCommands )
+            {
+                if( entry.handler->GetArgParser().is_subcommand_used( subentry.handler->GetName() ) )
+                {
+                    cliCmd = subentry.handler;
+                    cliCmdRequested = true;
+                }
+            }
+
+            if( !cliCmdRequested )
+            {
+                cliCmd = entry.handler;
+            }
+        }
+    }
 
     // Not all kicad applications use the python stuff. skip python init
     // for these apps.
-    bool skip_python_initialization = false;
+    bool skip_python_initialization = cliCmdRequested;
 #if defined( BITMAP_2_CMP ) || defined( PL_EDITOR ) || defined( GERBVIEW ) ||\
         defined( PCB_CALCULATOR_BUILD )
     skip_python_initialization = true;
@@ -320,53 +406,29 @@ bool PGM_SINGLE_TOP::OnPgmInit()
     Kiway.set_kiface( KIWAY::KifaceType( TOP_FRAME ), kiface );
 #endif
 
-    static const wxCmdLineEntryDesc desc[] = {
-        { wxCMD_LINE_OPTION, "f", "frame", "Frame to load", wxCMD_LINE_VAL_STRING, 0 },
-        { wxCMD_LINE_PARAM, nullptr, nullptr, "File to load", wxCMD_LINE_VAL_STRING,
-                wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_PARAM_OPTIONAL },
-        { wxCMD_LINE_NONE, nullptr, nullptr, nullptr, wxCMD_LINE_VAL_NONE, 0 }
-    };
-
-    wxCmdLineParser parser( App().argc, App().argv );
-    parser.SetDesc( desc );
-    parser.Parse( false );
-
     FRAME_T appType = TOP_FRAME;
 
-    const struct
-    {
-        wxString name;
-        FRAME_T type;
-    } frameTypes[] = {
-        { wxT( "pcb" ),    FRAME_PCB_EDITOR },
-        { wxT( "fpedit" ), FRAME_FOOTPRINT_EDITOR },
-        { wxT( "" ),       FRAME_T_COUNT }
-    };
-
-    wxString frameName;
-
-    if( parser.Found( "frame", &frameName ) )
-    {
-        appType = FRAME_T_COUNT;
-
-        for( const auto& it : frameTypes )
-        {
-            if( it.name == frameName )
-                appType = it.type;
-        }
-
-        if( appType == FRAME_T_COUNT )
-        {
-            wxLogError( wxT( "Unknown frame: %s" ), frameName );
-            // Clean up
-            OnPgmExit();
-            return false;
-        }
-    }
-
     // Tell the settings manager about the current Kiway
     GetSettingsManager().SetKiway( &Kiway );
 
+    if( cliCmdRequested )
+    {
+        int exitCode = CLI::EXIT_CODES::ERR_UNKNOWN;
+        if( cliCmd )
+        {
+            exitCode = cliCmd->Perform( Kiway );
+        }
+
+        if( exitCode != CLI::EXIT_CODES::AVOID_CLOSING )
+        {
+            std::exit( exitCode );
+        }
+        else
+        {
+            return true;
+        }
+    }
+
     // Use KIWAY to create a top window, which registers its existence also.
     // "TOP_FRAME" is a macro that is passed on compiler command line from CMake,
     // and is one of the types in FRAME_T.
@@ -390,14 +452,10 @@ bool PGM_SINGLE_TOP::OnPgmInit()
     frame->Show();
     wxSafeYield();
 
-    // Individual frames may provide additional option/switch processing, but for compatibility,
-    // any positional arguments are treated as a list of files to pass to OpenProjectFiles
-    frame->ParseArgs( parser );
-
     // Now after the frame processing, the rest of the positional args are files
     std::vector<wxString> fileArgs;
 
-    if( parser.GetParamCount() )
+    if( App().argc > 1 )
     {
         /*
             gerbview handles multiple project data files, i.e. gerber files on
@@ -408,15 +466,15 @@ bool PGM_SINGLE_TOP::OnPgmInit()
             launcher.
         */
 
-        for( size_t i = 0; i < parser.GetParamCount(); i++ )
-            fileArgs.push_back( parser.GetParam( i ) );
+        for( size_t i = 1; i < App().argc; i++ )
+            fileArgs.push_back( App().argv[i] );
 
         // special attention to a single argument: argv[1] (==argSet[0])
         if( fileArgs.size() == 1 )
         {
             wxFileName argv1( fileArgs[0] );
 
-#if defined(PGM_DATA_FILE_EXT)
+#if defined( PGM_DATA_FILE_EXT )
             // PGM_DATA_FILE_EXT, if present, may be different for each compile,
             // it may come from CMake on the compiler command line, but often does not.
             // This facility is mostly useful for those program footprints
diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt
index 9e28d84569..68c0471a28 100644
--- a/eeschema/CMakeLists.txt
+++ b/eeschema/CMakeLists.txt
@@ -278,7 +278,7 @@ set( EESCHEMA_SRCS
 
     ${CMAKE_SOURCE_DIR}/pcbnew/ibis/ibis_parser.cpp
     ${CMAKE_SOURCE_DIR}/pcbnew/ibis/kibis.cpp
-    
+
     # Some simulation features must be built even if libngspice is not linked.
     sim/sim_library.cpp
     sim/sim_library_spice.cpp
@@ -427,6 +427,7 @@ target_link_libraries( eeschema
     # There's way too much crap coming in from common yet.
     gal
     common
+    argparse::argparse
     ${wxWidgets_LIBRARIES}
     )
 
diff --git a/eeschema/eeschema.cpp b/eeschema/eeschema.cpp
index e5329b88a8..e9b30313eb 100644
--- a/eeschema/eeschema.cpp
+++ b/eeschema/eeschema.cpp
@@ -298,6 +298,9 @@ static struct IFACE : public KIFACE_BASE
                      const wxString& aNewProjectBasePath, const wxString& aNewProjectName,
                      const wxString& aSrcFilePath, wxString& aErrors ) override;
 
+
+    int HandleJob( JOB* aJob ) override;
+
 } kiface( "eeschema", KIWAY::FACE_SCH );
 
 } // namespace
@@ -560,3 +563,7 @@ void IFACE::SaveFileAs( const wxString& aProjectBasePath, const wxString& aProje
     }
 }
 
+int IFACE::HandleJob( JOB* aJob )
+{
+    return 0;
+}
\ No newline at end of file
diff --git a/gerbview/CMakeLists.txt b/gerbview/CMakeLists.txt
index d21fbc0868..fb658c69a8 100644
--- a/gerbview/CMakeLists.txt
+++ b/gerbview/CMakeLists.txt
@@ -112,6 +112,7 @@ target_link_libraries( gerbview
     gal
     common
     nlohmann_json
+    argparse::argparse
     ${wxWidgets_LIBRARIES}
     )
 
diff --git a/include/kiway.h b/include/kiway.h
index 2e83922197..55773a18a6 100644
--- a/include/kiway.h
+++ b/include/kiway.h
@@ -104,6 +104,7 @@
 #include <frame_type.h>
 #include <mail_type.h>
 #include <ki_exception.h>
+#include <jobs/job.h>
 
 
 #define KIFACE_VERSION      1
@@ -233,6 +234,11 @@ struct KIFACE
      * Append this Kiface's registered actions to the given list.
      */
     virtual void GetActions( std::vector<TOOL_ACTION*>& aActions ) const = 0;
+
+    virtual int HandleJob( JOB* aJob )
+    {
+        return 0;
+    }
 };
 
 
@@ -403,6 +409,8 @@ public:
 
     bool ProcessEvent( wxEvent& aEvent ) override;
 
+    int ProcessJob( KIWAY::FACE_T aFace, JOB* job );
+
     /**
      * Gets the window pointer to the blocking dialog (to send it signals)
      * @return Pointer to blocking dialog window or null if none
diff --git a/include/kiway_player.h b/include/kiway_player.h
index d377d1e1b0..584ad67d36 100644
--- a/include/kiway_player.h
+++ b/include/kiway_player.h
@@ -127,20 +127,6 @@ public:
         return false;
     }
 
-    /**
-     * Handle command-line arguments in a frame-specific way.
-     *
-     * The given argument parser has already been initialized with the command line and any
-     * options/switches that are handled by the top-level launcher before passing control to
-     * the child frame.
-     *
-     * @param aParser is the argument parser created by the top-level launcher.
-     */
-    virtual void ParseArgs( wxCmdLineParser& aParser )
-    {
-        WXUNUSED( aParser );
-    }
-
 
     /**
      * Show this wxFrame as if it were a modal dialog, with all other instantiated wxFrames
diff --git a/include/pgm_base.h b/include/pgm_base.h
index 3eb7e780e6..6d149c0787 100644
--- a/include/pgm_base.h
+++ b/include/pgm_base.h
@@ -118,6 +118,11 @@ public:
     virtual void OnPgmExit() = 0;           // call this from wxApp::OnExit()
 #endif
 
+    /**
+     * Builds the UTF8 based argv variable
+     */
+    void BuildArgvUtf8();
+
     /**
      * Specific to MacOSX (not used under Linux or Windows).
      *
@@ -342,6 +347,11 @@ protected:
     wxFileName      m_sentry_uid_fn;
     wxString        m_sentryUid;
 #endif
+
+    char** m_argvUtf8;                      /// argv parameters converted to utf8 form, because wxwidgets has opinions
+                                            /// and will return argv as either force converted to ascii in char* or wchar_t only
+
+    int m_argcUtf8;
 };
 
 
diff --git a/kicad/CMakeLists.txt b/kicad/CMakeLists.txt
index 5bc2bd16bc..c3f292f8b7 100644
--- a/kicad/CMakeLists.txt
+++ b/kicad/CMakeLists.txt
@@ -107,11 +107,12 @@ if( KICAD_WIN32_INSTALL_PDBS )
     install(FILES $<TARGET_PDB_FILE:kicad> DESTINATION ${KICAD_BIN})
 endif()
 
-if( MSVC )
-    # Allow for MSVC to run from the build directory
+if( WIN32 )
     add_custom_command( TARGET kicad POST_BUILD
-        COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/pcm/schemas" "${CMAKE_BINARY_DIR}/schemas"
+        COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/resources/msw/cmd-wrappers/kicad.cmd" "$<TARGET_FILE_DIR:kicad>"
         )
+
+    install(FILES "${CMAKE_SOURCE_DIR}/resources/msw/cmd-wrappers/kicad.cmd" DESTINATION ${KICAD_BIN})
 endif()
 
 if( APPLE )
diff --git a/kicad/kicad.cpp b/kicad/kicad.cpp
index 9d0fe603fa..04f2c77258 100644
--- a/kicad/kicad.cpp
+++ b/kicad/kicad.cpp
@@ -51,9 +51,13 @@
 #include "pgm_kicad.h"
 #include "kicad_manager_frame.h"
 
+#include <kicad_build_version.h>
 #include <kiplatform/app.h>
 #include <kiplatform/environment.h>
 
+#include "cli/command_export_kicad_pcbnew.h"
+#include "cli/command_export_step.h"
+#include "cli/exit_codes.h"
 
 // a dummy to quiet linking with EDA_BASE_FRAME::config();
 #include <kiface_base.h>
@@ -89,9 +93,25 @@ PGM_KICAD& PgmTop()
     return program;
 }
 
+struct COMMAND_ENTRY
+{
+    CLI::COMMAND* handler;
+
+    std::vector<COMMAND_ENTRY> subCommands;
+
+    COMMAND_ENTRY( CLI::COMMAND* aHandler ) : handler( aHandler ){};
+    COMMAND_ENTRY( CLI::COMMAND* aHandler, std::vector<COMMAND_ENTRY> aSub ) :
+            handler( aHandler ), subCommands( aSub ){};
+};
+
+static CLI::EXPORT_STEP_COMMAND   stepCmd{};
+static CLI::EXPORT_KICAD_PCBNEW_COMMAND exportPcbCmd{};
+
+static std::vector<COMMAND_ENTRY> commandStack = { { &exportPcbCmd, { &stepCmd } } };
 
 bool PGM_KICAD::OnPgmInit()
 {
+    PGM_BASE::BuildArgvUtf8();
     App().SetAppDisplayName( wxT( "KiCad" ) );
 
 #if defined(DEBUG)
@@ -104,6 +124,55 @@ bool PGM_KICAD::OnPgmInit()
     }
 #endif
 
+    argparse::ArgumentParser argParser( std::string( "kicad" ),
+                                        KICAD_MAJOR_MINOR_VERSION );
+
+    for( COMMAND_ENTRY& entry : commandStack )
+    {
+        argParser.add_subparser( entry.handler->GetArgParser() );
+
+        for( COMMAND_ENTRY& subentry : entry.subCommands )
+        {
+            entry.handler->GetArgParser().add_subparser( subentry.handler->GetArgParser() );
+        }
+    }
+
+    try
+    {
+        argParser.parse_args( m_argcUtf8, m_argvUtf8 );
+    }
+    catch( const std::runtime_error& err )
+    {
+        // this provides the nice pretty print
+        std::cerr << err.what() << std::endl;
+        std::cerr << argParser;
+
+        std::exit( CLI::EXIT_CODES::ERR_ARGS );
+    }
+
+    bool          cliCmdRequested = false;
+    CLI::COMMAND* cliCmd = nullptr;
+    for( COMMAND_ENTRY& entry : commandStack )
+    {
+        if( argParser.is_subcommand_used( entry.handler->GetName() ) )
+        {
+            for( COMMAND_ENTRY& subentry : entry.subCommands )
+            {
+                if( entry.handler->GetArgParser().is_subcommand_used(
+                            subentry.handler->GetName() ) )
+                {
+                    cliCmd = subentry.handler;
+                    cliCmdRequested = true;
+                }
+            }
+
+            if( !cliCmdRequested )
+            {
+                cliCmd = entry.handler;
+            }
+        }
+    }
+
     if( !InitPgm() )
         return false;
 
@@ -148,6 +217,24 @@ bool PGM_KICAD::OnPgmInit()
             m_bm.m_search.Insert( it->second.GetValue(), 0 );
     }
 
+    if( cliCmdRequested )
+    {
+        int exitCode = CLI::EXIT_CODES::ERR_UNKNOWN;
+        if( cliCmd )
+        {
+            exitCode = cliCmd->Perform( Kiway );
+        }
+
+        if( exitCode != CLI::EXIT_CODES::AVOID_CLOSING )
+        {
+            std::exit( exitCode );
+        }
+        else
+        {
+            return true;
+        }
+    }
+
     KICAD_MANAGER_FRAME* frame = new KICAD_MANAGER_FRAME( nullptr, wxT( "KiCad" ),
                                                           wxDefaultPosition, wxSize( 775, -1 ) );
     App().SetTopWindow( frame );
diff --git a/libs/kiplatform/msw/app.cpp b/libs/kiplatform/msw/app.cpp
index f70959f710..4fdb54a010 100644
--- a/libs/kiplatform/msw/app.cpp
+++ b/libs/kiplatform/msw/app.cpp
@@ -28,6 +28,8 @@
 #include <strsafe.h>
 #include <config.h>
 #include <VersionHelpers.h>
+#include <iostream>
+#include <cstdio>
 
 #if defined( _MSC_VER )
 #include <werapi.h>     // issues on msys2
@@ -63,22 +65,35 @@ bool KIPLATFORM::APP::Init()
 
     // In order to support GUI and CLI
     // Let's attach to console when it's possible
-    HANDLE handleStdOut, handleStdErr;
+    HANDLE handle;
     if( AttachConsole( ATTACH_PARENT_PROCESS ) )
     {
-        handleStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
-        if( handleStdOut != INVALID_HANDLE_VALUE )
+        if( GetStdHandle( STD_INPUT_HANDLE ) != INVALID_HANDLE_VALUE )
+        {
+            freopen( "CONIN$", "r", stdin );
+            setvbuf( stdin, NULL, _IONBF, 0 );
+        }
+
+        if( GetStdHandle( STD_OUTPUT_HANDLE ) != INVALID_HANDLE_VALUE )
         {
             freopen( "CONOUT$", "w", stdout );
             setvbuf( stdout, NULL, _IONBF, 0 );
         }
 
-        handleStdErr = GetStdHandle( STD_ERROR_HANDLE );
-        if( handleStdErr != INVALID_HANDLE_VALUE )
+        if( GetStdHandle( STD_ERROR_HANDLE ) != INVALID_HANDLE_VALUE )
         {
             freopen( "CONOUT$", "w", stderr );
             setvbuf( stderr, NULL, _IONBF, 0 );
         }
+
+        std::ios::sync_with_stdio( true );
+
+        std::wcout.clear();
+        std::cout.clear();
+        std::wcerr.clear();
+        std::cerr.clear();
+        std::wcin.clear();
+        std::cin.clear();
     }
 
     return true;
diff --git a/pagelayout_editor/CMakeLists.txt b/pagelayout_editor/CMakeLists.txt
index 5f503d479e..72ce38fde7 100644
--- a/pagelayout_editor/CMakeLists.txt
+++ b/pagelayout_editor/CMakeLists.txt
@@ -85,6 +85,7 @@ target_link_libraries( pl_editor
     # There's way too much crap coming in from common yet.
     gal
     common
+    argparse::argparse
     ${wxWidgets_LIBRARIES}
     )
 
diff --git a/pcb_calculator/CMakeLists.txt b/pcb_calculator/CMakeLists.txt
index f58e348d27..e4ca0b9607 100644
--- a/pcb_calculator/CMakeLists.txt
+++ b/pcb_calculator/CMakeLists.txt
@@ -90,6 +90,7 @@ target_link_libraries( pcb_calculator
     #singletop  # replaces common, giving us restrictive control and link warnings.
     # There's way too much crap coming in from common yet.
     common
+    argparse::argparse
     ${wxWidgets_LIBRARIES}
     )
 
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index 6406eb33f6..158bc232a2 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -13,6 +13,7 @@ add_definitions( -DPCBNEW )
 add_subdirectory(connectivity)
 
 add_subdirectory(router)
+add_subdirectory(exporters/step)
 
 # psnrouter depends on make_lexer outputs in common (bug #1285878 )
 add_dependencies( pnsrouter pcbcommon )
@@ -313,6 +314,7 @@ set( PCBNEW_CLASS_SRCS
     pcb_layer_box_selector.cpp
     pcb_edit_frame.cpp
     pcbnew_config.cpp
+    pcbnew_jobs_handler.cpp
     pcbnew_printout.cpp
     pcbnew_settings.cpp
     pcbplot.cpp
@@ -639,6 +641,7 @@ target_link_libraries( pcbnew
     scripting
     nlohmann_json
     rectpack2d
+    argparse::argparse
     ${wxWidgets_LIBRARIES}
     )
 
@@ -674,16 +677,13 @@ target_link_libraries( pcbnew_kiface_objects
         tinyspline_lib
         nlohmann_json
         rectpack2d
+        kicad2step_lib
     )
 
 target_include_directories( pcbnew_kiface_objects PRIVATE
     $<TARGET_PROPERTY:thread-pool,INTERFACE_INCLUDE_DIRECTORIES>
     )
 
-if( KICAD_STEP_EXPORT_LIB )
-    add_definitions( -DKICAD_STEP_EXPORT_LIB )
-endif()
-
 add_library( pcbnew_kiface MODULE )
 
 set_target_properties( pcbnew_kiface PROPERTIES
@@ -756,6 +756,12 @@ if( WIN32 )
     add_custom_command( TARGET pcbnew POST_BUILD
         COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:kicad_3dsg>" "$<TARGET_FILE_DIR:pcbnew>"
         )
+
+    add_custom_command( TARGET pcbnew POST_BUILD
+        COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/resources/msw/cmd-wrappers/pcbnew.cmd" "$<TARGET_FILE_DIR:pcbnew>"
+        )
+
+    install(FILES "${CMAKE_SOURCE_DIR}/resources/msw/cmd-wrappers/pcbnew.cmd" DESTINATION ${KICAD_BIN})
 endif()
 
 # these 2 binaries are a matched set, keep them together:
diff --git a/pcbnew/dialogs/dialog_export_step.cpp b/pcbnew/dialogs/dialog_export_step.cpp
index a7645f7af1..800e65b604 100644
--- a/pcbnew/dialogs/dialog_export_step.cpp
+++ b/pcbnew/dialogs/dialog_export_step.cpp
@@ -46,9 +46,7 @@
 #include <filename_resolver.h>
 
 
-#ifdef KICAD_STEP_EXPORT_LIB
 #include <kicad2step.h>
-#endif
 
 
 class DIALOG_EXPORT_STEP : public DIALOG_EXPORT_STEP_BASE
@@ -371,7 +369,6 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent )
     double xOrg = 0.0;
     double yOrg = 0.0;
 
-#ifndef KICAD_STEP_EXPORT_LIB
     wxFileName appK2S( wxStandardPaths::Get().GetExecutablePath() );
 #ifdef __WXMAC__
     // On macOS, we have standalone applications inside the main bundle, so we handle that here:
@@ -385,12 +382,16 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent )
     }
 #endif
 
-    appK2S.SetName( wxT( "kicad2step" ) );
+    appK2S.SetName( wxT( "pcbnew" ) );
 
     wxString cmdK2S = wxT( "\"" );
     cmdK2S.Append( appK2S.GetFullPath() );
     cmdK2S.Append( wxT( "\"" ) );
 
+    cmdK2S.Append( wxT( " export" ) );
+    cmdK2S.Append( wxT( " step" ) );
+    cmdK2S.Append( wxT( " --gui" ) );
+
     if( GetNoVirtOption() )
         cmdK2S.Append( wxT( " --no-virtual" ) );
 
@@ -458,65 +459,8 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent )
     // Output file path.
     cmdK2S.Append( wxString::Format( wxT( " %c%s%c" ), quote,  m_boardPath, quote ) );
 
-    wxLogTrace( traceKiCad2Step, wxT( "KiCad2Step command: %s" ), cmdK2S );
+    wxLogTrace( traceKiCad2Step, wxT( "export step command: %s" ), cmdK2S );
     wxExecute( cmdK2S, wxEXEC_ASYNC  | wxEXEC_SHOW_CONSOLE );
 
-#else
-
-    KICAD2MCAD_PRMS params;
-    params.m_filename = m_boardPath;
-    params.m_outputFile = m_filePickerSTEP->GetPath();
-
-    params.m_includeVirtual = !GetNoVirtOption();
-
-    params.m_substModels = GetSubstOption();
-    params.m_minDistance = tolerance;
-    params.m_overwrite = true;
-
-    switch( orgOpt )
-    {
-    case DIALOG_EXPORT_STEP::STEP_ORG_0:
-        break;
-
-    case DIALOG_EXPORT_STEP::STEP_ORG_PLOT_AXIS:
-        params.m_useDrillOrigin = true;
-        break;
-
-    case DIALOG_EXPORT_STEP::STEP_ORG_GRID_AXIS:
-        params.m_useGridOrigin = true;
-        break;
-
-    case DIALOG_EXPORT_STEP::STEP_ORG_USER:
-    {
-        xOrg = GetXOrg();
-        yOrg = GetYOrg();
-
-        if( GetOrgUnitsChoice() == 1 )
-        {
-            // selected reference unit is in inches, and STEP units are mm
-            xOrg *= 25.4;
-            yOrg *= 25.4;
-        }
-
-        params.m_xOrigin = xOrg;
-        params.m_yOrigin = yOrg;
-        break;
-    }
-
-    case DIALOG_EXPORT_STEP::STEP_ORG_BOARD_CENTER:
-    {
-        BOX2I bbox = m_parent->GetBoard()->ComputeBoundingBox( true );
-        xOrg = pcbIUScale.IUTomm( bbox.GetCenter().x );
-        yOrg = pcbIUScale.IUTomm( bbox.GetCenter().y );
-        params.m_xOrigin = xOrg;
-        params.m_yOrigin = yOrg;
-        break;
-    }
-    }
-
-    KICAD2STEP converter( params );
-    converter.Run();
-#endif
-
     aEvent.Skip(); // Close the dialog
 }
diff --git a/pcbnew/dialogs/dialog_export_svg.cpp b/pcbnew/dialogs/dialog_export_svg.cpp
index 275d19505a..d4b0adf7a0 100644
--- a/pcbnew/dialogs/dialog_export_svg.cpp
+++ b/pcbnew/dialogs/dialog_export_svg.cpp
@@ -41,6 +41,7 @@
 #include <plotters/plotters_pslike.h>
 #include <wx/dirdlg.h>
 #include <pgm_base.h>
+#include <pcb_plot_svg.h>
 
 class DIALOG_EXPORT_SVG : public DIALOG_EXPORT_SVG_BASE
 {
@@ -68,8 +69,6 @@ private:
     void OnOutputDirectoryBrowseClicked( wxCommandEvent& event ) override;
     void ExportSVGFile( bool aOnlyOneFile );
 
-    bool CreateSVGFile( const wxString& FullFileName );
-
     LSET getCheckBoxSelectedLayers() const;
 };
 
@@ -281,6 +280,13 @@ void DIALOG_EXPORT_SVG::ExportSVGFile( bool aOnlyOneFile )
 
     LSET all_selected = getCheckBoxSelectedLayers();
 
+    PCB_PLOT_SVG_OPTIONS svgPlotOptions;
+    svgPlotOptions.m_blackAndWhite = m_printBW;
+    svgPlotOptions.m_printMaskLayer = m_printMaskLayer;
+    svgPlotOptions.m_pageSizeMode = m_rbSvgPageSizeOpt->GetSelection();
+    svgPlotOptions.m_colorTheme = "";   // will use default
+    svgPlotOptions.m_mirror = m_printMirror;
+
     for( LSEQ seq = all_selected.Seq();  seq;  ++seq )
     {
         PCB_LAYER_ID layer = *seq;
@@ -295,7 +301,10 @@ void DIALOG_EXPORT_SVG::ExportSVGFile( bool aOnlyOneFile )
         if( m_checkboxEdgesOnAllPages->GetValue() )
             m_printMaskLayer.set( Edge_Cuts );
 
-        if( CreateSVGFile( svgPath ) )
+        svgPlotOptions.m_outputFile = svgPath;
+        svgPlotOptions.m_printMaskLayer = m_printMaskLayer;
+
+        if( PCB_PLOT_SVG::Plot(m_board, svgPlotOptions ) )
         {
             reporter.Report( wxString::Format( _( "Exported '%s'." ), svgPath ),
                              RPT_SEVERITY_ACTION );
@@ -312,72 +321,6 @@ void DIALOG_EXPORT_SVG::ExportSVGFile( bool aOnlyOneFile )
 }
 
 
-// Actual SVG file export function.
-bool DIALOG_EXPORT_SVG::CreateSVGFile( const wxString& aFullFileName )
-{
-    PCB_PLOT_PARAMS plot_opts;
-
-    plot_opts.SetPlotFrameRef( m_rbSvgPageSizeOpt->GetSelection() == 0 );
-
-    // Adding drill marks, for copper layers
-    if( ( m_printMaskLayer & LSET::AllCuMask() ).any() )
-        plot_opts.SetDrillMarksType( PCB_PLOT_PARAMS::FULL_DRILL_SHAPE );
-    else
-        plot_opts.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE );
-
-    plot_opts.SetSkipPlotNPTH_Pads( false );
-
-    plot_opts.SetMirror( m_printMirror );
-    plot_opts.SetFormat( PLOT_FORMAT::SVG );
-    // coord format: 4 digits in mantissa (units always in mm). This is a good choice.
-    plot_opts.SetSvgPrecision( 4 );
-
-    PAGE_INFO   savedPageInfo = m_board->GetPageSettings();
-    VECTOR2I  savedAuxOrigin = m_board->GetDesignSettings().GetAuxOrigin();
-
-    if( m_rbSvgPageSizeOpt->GetSelection() == 2 )   // Page is board boundary size
-    {
-        BOX2I     bbox = m_board->ComputeBoundingBox();
-        PAGE_INFO currpageInfo = m_board->GetPageSettings();
-
-        currpageInfo.SetWidthMils(  bbox.GetWidth() / pcbIUScale.IU_PER_MILS );
-        currpageInfo.SetHeightMils( bbox.GetHeight() / pcbIUScale.IU_PER_MILS );
-        m_board->SetPageSettings( currpageInfo );
-        plot_opts.SetUseAuxOrigin( true );
-        VECTOR2I origin = bbox.GetOrigin();
-        m_board->GetDesignSettings().SetAuxOrigin( origin );
-    }
-
-    SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
-    PCBNEW_SETTINGS*  cfg = mgr.GetAppSettings<PCBNEW_SETTINGS>();
-
-    plot_opts.SetColorSettings( mgr.GetColorSettings( cfg->m_ColorTheme ) );
-
-    LOCALE_IO    toggle;
-
-    //@todo allow controlling the sheet name and path that will be displayed in the title block
-    // Leave blank for now
-    SVG_PLOTTER* plotter = (SVG_PLOTTER*) StartPlotBoard( m_board, &plot_opts, UNDEFINED_LAYER,
-                                                          aFullFileName, wxEmptyString,
-                                                          wxEmptyString );
-
-    if( plotter )
-    {
-        plotter->SetColorMode( !m_printBW );
-        PlotBoardLayers( m_board, plotter, m_printMaskLayer.SeqStackupBottom2Top(), plot_opts );
-        plotter->EndPlot();
-    }
-
-    delete plotter;
-
-    // reset to the values saved earlier
-    m_board->GetDesignSettings().SetAuxOrigin( savedAuxOrigin );
-    m_board->SetPageSettings( savedPageInfo );
-
-    return true;
-}
-
-
 void DIALOG_EXPORT_SVG::OnButtonPlot( wxCommandEvent& event )
 {
     m_oneFileOnly = !m_checkboxPagePerLayer->GetValue();
diff --git a/utils/kicad2step/CMakeLists.txt b/pcbnew/exporters/step/CMakeLists.txt
similarity index 54%
rename from utils/kicad2step/CMakeLists.txt
rename to pcbnew/exporters/step/CMakeLists.txt
index 2bc9c94e29..18262551fb 100644
--- a/utils/kicad2step/CMakeLists.txt
+++ b/pcbnew/exporters/step/CMakeLists.txt
@@ -16,7 +16,6 @@ set( KS2_LIB_FILES
     pcb/oce_utils.cpp
 )
 
-
 # Break the library out for re-use by both kicad2step and any qa that needs it
 # In future, this could move for re-use by other programs needing s-expr support (?)
 add_library( kicad2step_lib STATIC
@@ -33,45 +32,14 @@ target_include_directories( kicad2step_lib PUBLIC
 
 target_link_libraries( kicad2step_lib
     sexpr
+    common
     ${wxWidgets_LIBRARIES}
     ${OCC_LIBRARIES}
     ${ZLIB_LIBRARIES}
     kimath
 )
 
-set( K2S_FILES
-    kicad2step_app.cpp
-)
-
-if( MINGW )
-    list( APPEND K2S_FILES ${CMAKE_SOURCE_DIR}/common/streamwrapper.cpp )
-endif( MINGW )
-
-add_executable( kicad2step_bin WIN32 ${K2S_FILES} )
-
-target_link_libraries( kicad2step_bin
-    kicad2step_lib )
 
 target_include_directories( kicad2step_lib PRIVATE
     $<TARGET_PROPERTY:gzip-hpp,INTERFACE_INCLUDE_DIRECTORIES>
-    )
-
-set_target_properties( kicad2step_bin
-        PROPERTIES OUTPUT_NAME kicad2step)
-
-if( APPLE )
-    # puts binaries into the *.app bundle while linking
-    set_target_properties( kicad2step_bin PROPERTIES
-            RUNTIME_OUTPUT_DIRECTORY ${OSX_BUNDLE_BUILD_BIN_DIR}
-            )
-else()
-    install( TARGETS kicad2step_bin
-            RUNTIME DESTINATION ${KICAD_BIN}
-            ARCHIVE DESTINATION ${KICAD_LIB}
-            COMPONENT binary )
-endif()
-
-if( KICAD_WIN32_INSTALL_PDBS )
-    # Get the PDBs to copy over for MSVC
-    install(FILES $<TARGET_PDB_FILE:kicad2step_bin> DESTINATION ${KICAD_BIN})
-endif()
+    )
\ No newline at end of file
diff --git a/utils/kicad2step/kicad2step.cpp b/pcbnew/exporters/step/kicad2step.cpp
similarity index 74%
rename from utils/kicad2step/kicad2step.cpp
rename to pcbnew/exporters/step/kicad2step.cpp
index c9669632c9..732d38b804 100644
--- a/utils/kicad2step/kicad2step.cpp
+++ b/pcbnew/exporters/step/kicad2step.cpp
@@ -28,11 +28,14 @@
 #include <wx/msgdlg.h>
 #include <wx/string.h>
 #include <wx/filename.h>
+#include <wx/crt.h>
 #include <algorithm>
 #include <sstream>
 #include <iostream>
 #include <sstream>
 
+#include <cli/exit_codes.h>
+#include <pgm_base.h>
 #include "kicad2step.h"
 #include "pcb/kicadpcb.h"
 #include "kicad2step_frame_base.h"
@@ -52,26 +55,33 @@
 class KICAD2STEP_FRAME : public KICAD2STEP_FRAME_BASE
 {
 public:
-    KICAD2STEP_FRAME( const wxString& title );
+    KICAD2STEP_FRAME( KICAD2STEP* aConverter, const wxString& title );
 
 protected:
     virtual void OnOKButtonClick( wxCommandEvent& aEvent ) override;
+    virtual void OnIdle( wxIdleEvent& aEvent ) override;
+
+    KICAD2STEP* m_converter;
+    bool        m_running;
 };
 
 
 // Horrible hack until we decouple things more
-static PANEL_KICAD2STEP* openPanel = nullptr;
+static KICAD2STEP* k2sInstance = nullptr;
 
 
 void ReportMessage( const wxString& aMessage )
 {
-    if( openPanel != nullptr )
-        openPanel->AppendMessage( aMessage );
+    if( k2sInstance != nullptr )
+        k2sInstance->ReportMessage( aMessage );
 }
 
 
 class KiCadPrinter : public Message_Printer
 {
+public:
+    KiCadPrinter( KICAD2STEP* aConverter ) : m_converter( aConverter ) {}
+
 protected:
 #if OCC_VERSION_HEX < OCC_VERSION_MIN
     virtual void Send( const TCollection_ExtendedString& theString,
@@ -91,24 +101,28 @@ protected:
     {
       if( theGravity >= Message_Info )
       {
-          ReportMessage( theString.ToCString() );
+          m_converter->ReportMessage( theString.ToCString() );
 
 #if OCC_VERSION_HEX < OCC_VERSION_MIN
           if( theToPutEol )
               ReportMessage( wxT( "\n" ) );
 #else
-          ReportMessage( wxT( "\n" ) );
+          m_converter->ReportMessage( wxT( "\n" ) );
 #endif
       }
 
       if( theGravity >= Message_Alarm )
-          openPanel->m_error = true;
+          m_converter->SetError();
 
       if( theGravity == Message_Fail )
-          openPanel->m_fail = true;
+          m_converter->SetFail();
     }
+
+private:
+    KICAD2STEP* m_converter;
 };
 
+
 KICAD2MCAD_PRMS::KICAD2MCAD_PRMS()
 {
 #ifdef SUPPORTS_IGES
@@ -126,8 +140,8 @@ KICAD2MCAD_PRMS::KICAD2MCAD_PRMS()
 }
 
 
-KICAD2STEP_FRAME::KICAD2STEP_FRAME( const wxString& title ) :
-        KICAD2STEP_FRAME_BASE( nullptr, wxID_ANY, title )
+KICAD2STEP_FRAME::KICAD2STEP_FRAME( KICAD2STEP* aConverter, const wxString& title ) :
+        KICAD2STEP_FRAME_BASE( nullptr, wxID_ANY, title ), m_converter( aConverter ), m_running( false )
 {
 }
 
@@ -138,9 +152,21 @@ void KICAD2STEP_FRAME::OnOKButtonClick( wxCommandEvent& aEvent )
 }
 
 
-PANEL_KICAD2STEP::PANEL_KICAD2STEP( wxWindow* parent, wxWindowID id, const wxPoint& pos,
+void KICAD2STEP_FRAME::OnIdle( wxIdleEvent& aEvent )
+{
+    if( !m_running )
+    {
+        m_running = true;
+        m_converter->DoRun();
+    }
+}
+
+
+PANEL_KICAD2STEP::PANEL_KICAD2STEP( wxWindow* parent, wxWindowID id,
+                                    const wxPoint& pos,
                                     const wxSize& size, long style ) :
-        wxPanel( parent, id, pos, size, style ), m_error( false ), m_fail( false )
+        wxPanel( parent, id, pos, size, style ),
+        m_error( false ), m_fail( false )
 {
 	wxBoxSizer* bSizer = new wxBoxSizer( wxVERTICAL );
 
@@ -161,14 +187,33 @@ void PANEL_KICAD2STEP::AppendMessage( const wxString& aMessage )
 }
 
 
-int PANEL_KICAD2STEP::RunConverter()
+
+wxString KICAD2MCAD_PRMS::getOutputExt() const
+{
+#ifdef SUPPORTS_IGES
+    if( m_fmtIGES )
+        return wxT( "igs" );
+    else
+#endif
+        return wxT( "step" );
+}
+
+
+KICAD2STEP::KICAD2STEP( KICAD2MCAD_PRMS aParams ) :
+        m_params( aParams ), m_panel( nullptr ), m_error( false ), m_fail( false )
+{
+}
+
+
+int KICAD2STEP::DoRun()
 {
     wxFileName fname( m_params.m_filename );
 
     if( !fname.FileExists() )
     {
-        wxMessageBox( wxString::Format( _( "No such file: %s" ), m_params.m_filename ) );
-        return -1;
+        ReportMessage( wxString::Format(  _( "No such file: %s" ), m_params.m_filename ) );
+
+        return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
     }
 
     wxFileName out_fname;
@@ -192,7 +237,7 @@ int PANEL_KICAD2STEP::RunConverter()
         ReportMessage( _( "** Output already exists. Export aborted. **\n"
                           "Enable the force overwrite flag to overwrite it." ) );
 
-        return -1;
+        return CLI::EXIT_CODES::ERR_INVALID_OUTPUT_CONFLICT;
     }
 
     wxString outfile = out_fname.GetFullPath();
@@ -203,7 +248,7 @@ int PANEL_KICAD2STEP::RunConverter()
     ReportMessage( wxString::Format( _( "Read file: '%s'\n" ), m_params.m_filename ) );
 
     Message::DefaultMessenger()->RemovePrinters( STANDARD_TYPE( Message_PrinterOStream ) );
-    Message::DefaultMessenger()->AddPrinter( new KiCadPrinter );
+    Message::DefaultMessenger()->AddPrinter( new KiCadPrinter( this ) );
 
     if( pcb.ReadFile( m_params.m_filename ) )
     {
@@ -224,7 +269,8 @@ int PANEL_KICAD2STEP::RunConverter()
             if( !res )
             {
                 ReportMessage( _( "\n** Error building STEP board model. Export aborted. **\n" ) );
-                return -1;
+
+                return CLI::EXIT_CODES::ERR_UNKNOWN;
             }
 
             ReportMessage( _( "Write STEP file\n" ) );
@@ -239,7 +285,7 @@ int PANEL_KICAD2STEP::RunConverter()
             if( !res )
             {
                 ReportMessage( _( "\n** Error writing STEP file. **\n" ) );
-                return -1;
+                return CLI::EXIT_CODES::ERR_UNKNOWN;
             }
             else
             {
@@ -250,18 +296,18 @@ int PANEL_KICAD2STEP::RunConverter()
         {
             ReportMessage( e.GetMessageString() );
             ReportMessage( _( "\n** Error exporting STEP file. Export aborted. **\n" ) );
-            return -1;
+            return CLI::EXIT_CODES::ERR_UNKNOWN;
         }
         catch( ... )
         {
             ReportMessage( _( "\n** Error exporting STEP file. Export aborted. **\n" ) );
-            return -1;
+            return CLI::EXIT_CODES::ERR_INVALID_INPUT_FILE;
         }
     }
     else
     {
         ReportMessage( _( "\n** Error reading kicad_pcb file. **\n" ) );
-        return -1;
+        return CLI::EXIT_CODES::ERR_UNKNOWN;
     }
 
     wxString msg;
@@ -278,49 +324,45 @@ int PANEL_KICAD2STEP::RunConverter()
 
     ReportMessage( msg );
 
-    return 0;
-}
-
-
-wxString KICAD2MCAD_PRMS::getOutputExt() const
-{
-#ifdef SUPPORTS_IGES
-    if( m_fmtIGES )
-        return wxT( "igs" );
-    else
-#endif
-        return wxT( "step" );
-}
-
-
-KICAD2STEP::KICAD2STEP( KICAD2MCAD_PRMS aParams ) : m_params( aParams ), m_panel( nullptr )
-{
+    return CLI::EXIT_CODES::SUCCESS;
 }
 
 
 int KICAD2STEP::Run()
 {
-    // create the main application window
-    KICAD2STEP_FRAME* frame = new KICAD2STEP_FRAME( wxT( "Kicad2step" ) );
+    k2sInstance = this;
 
-    m_panel = frame->m_panelKicad2Step;
-    m_panel->m_params = m_params;
+    if( m_params.m_gui )
+    {
+        // create the main application window
+        KICAD2STEP_FRAME* frame = new KICAD2STEP_FRAME( this, wxT( "PCB Step Export" ) );
 
-    // and show it (a wxFrame is not shown when created initially)
-    frame->Show( true );
-    frame->Iconize( false );
+        m_panel = frame->m_panelKicad2Step;
+        m_panel->m_params = m_params;
 
-    openPanel = m_panel;
+        Pgm().App().SetTopWindow( frame );
 
-    int diag = m_panel->RunConverter();
+        frame->Iconize( false );
+        frame->Show( true );
 
-    openPanel = nullptr;
-
-    return diag;
+        return CLI::EXIT_CODES::AVOID_CLOSING;
+    }
+    else
+    {
+        int diag = DoRun();
+        return diag;
+    }
 }
 
 
 void KICAD2STEP::ReportMessage( const wxString& aMessage )
 {
-    m_panel->AppendMessage( aMessage );
+    if( m_params.m_gui )
+    {
+        m_panel->AppendMessage( aMessage );
+    }
+    else
+    {
+        wxPrintf( aMessage );
+    }
 }
diff --git a/utils/kicad2step/kicad2step.h b/pcbnew/exporters/step/kicad2step.h
similarity index 91%
rename from utils/kicad2step/kicad2step.h
rename to pcbnew/exporters/step/kicad2step.h
index e8286cebda..9f9b91b569 100644
--- a/utils/kicad2step/kicad2step.h
+++ b/pcbnew/exporters/step/kicad2step.h
@@ -51,6 +51,7 @@ public:
     double   m_xOrigin;
     double   m_yOrigin;
     double   m_minDistance;
+    bool     m_gui;
 };
 
 class APIEXPORT KICAD2STEP
@@ -61,9 +62,19 @@ public:
     int Run();
     void ReportMessage( const wxString& aMessage );
 
+    void SetError() { m_error = true; }
+    void SetFail() { m_error = true; }
+
 private:
+    int DoRun();
+
     KICAD2MCAD_PRMS m_params;
     PANEL_KICAD2STEP* m_panel;
+
+    bool m_error;
+    bool m_fail;
+
+    friend class KICAD2STEP_FRAME;
 };
 
 #endif
\ No newline at end of file
diff --git a/utils/kicad2step/kicad2step_app.cpp b/pcbnew/exporters/step/kicad2step_app.cpp
similarity index 100%
rename from utils/kicad2step/kicad2step_app.cpp
rename to pcbnew/exporters/step/kicad2step_app.cpp
diff --git a/utils/kicad2step/kicad2step_frame_base.cpp b/pcbnew/exporters/step/kicad2step_frame_base.cpp
similarity index 86%
rename from utils/kicad2step/kicad2step_frame_base.cpp
rename to pcbnew/exporters/step/kicad2step_frame_base.cpp
index 942d205cf9..b3dc61c250 100644
--- a/utils/kicad2step/kicad2step_frame_base.cpp
+++ b/pcbnew/exporters/step/kicad2step_frame_base.cpp
@@ -1,5 +1,5 @@
 ///////////////////////////////////////////////////////////////////////////
-// C++ code generated with wxFormBuilder (version Oct 26 2018)
+// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
 // http://www.wxformbuilder.org/
 //
 // PLEASE DO *NOT* EDIT THIS FILE!
@@ -34,12 +34,14 @@ KICAD2STEP_FRAME_BASE::KICAD2STEP_FRAME_BASE( wxWindow* parent, wxWindowID id, c
 	this->Centre( wxBOTH );
 
 	// Connect Events
+	this->Connect( wxEVT_IDLE, wxIdleEventHandler( KICAD2STEP_FRAME_BASE::OnIdle ) );
 	m_sdbSizer1OK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( KICAD2STEP_FRAME_BASE::OnOKButtonClick ), NULL, this );
 }
 
 KICAD2STEP_FRAME_BASE::~KICAD2STEP_FRAME_BASE()
 {
 	// Disconnect Events
+	this->Disconnect( wxEVT_IDLE, wxIdleEventHandler( KICAD2STEP_FRAME_BASE::OnIdle ) );
 	m_sdbSizer1OK->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( KICAD2STEP_FRAME_BASE::OnOKButtonClick ), NULL, this );
 
 }
diff --git a/utils/kicad2step/kicad2step_frame_base.fbp b/pcbnew/exporters/step/kicad2step_frame_base.fbp
similarity index 96%
rename from utils/kicad2step/kicad2step_frame_base.fbp
rename to pcbnew/exporters/step/kicad2step_frame_base.fbp
index f10e35bad8..855e91b652 100644
--- a/utils/kicad2step/kicad2step_frame_base.fbp
+++ b/pcbnew/exporters/step/kicad2step_frame_base.fbp
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
 <wxFormBuilder_Project>
-    <FileVersion major="1" minor="15" />
+    <FileVersion major="1" minor="16" />
     <object class="Project" expanded="1">
         <property name="class_decoration"></property>
         <property name="code_generation">C++</property>
@@ -14,6 +14,7 @@
         <property name="file">kicad2step_frame_base</property>
         <property name="first_id">1000</property>
         <property name="help_provider">none</property>
+        <property name="image_path_wrapper_function_name"></property>
         <property name="indent_with_spaces"></property>
         <property name="internationalize">1</property>
         <property name="name">kicad2step_frame</property>
@@ -25,6 +26,7 @@
         <property name="skip_php_events">1</property>
         <property name="skip_python_events">1</property>
         <property name="ui_table">UI</property>
+        <property name="use_array_enum">0</property>
         <property name="use_enum">0</property>
         <property name="use_microsoft_bom">0</property>
         <object class="Frame" expanded="1">
@@ -50,10 +52,12 @@
             <property name="subclass">; forward_declare; forward_declare</property>
             <property name="title">Kicad2step Converter</property>
             <property name="tooltip"></property>
+            <property name="two_step_creation">0</property>
             <property name="window_extra_style"></property>
             <property name="window_name"></property>
             <property name="window_style">wxTAB_TRAVERSAL</property>
             <property name="xrc_skip_sizer">1</property>
+            <event name="OnIdle">OnIdle</event>
             <object class="wxBoxSizer" expanded="1">
                 <property name="minimum_size"></property>
                 <property name="name">bSizerMain</property>
diff --git a/utils/kicad2step/kicad2step_frame_base.h b/pcbnew/exporters/step/kicad2step_frame_base.h
similarity index 91%
rename from utils/kicad2step/kicad2step_frame_base.h
rename to pcbnew/exporters/step/kicad2step_frame_base.h
index bb1d2b56f6..224a55bddd 100644
--- a/utils/kicad2step/kicad2step_frame_base.h
+++ b/pcbnew/exporters/step/kicad2step_frame_base.h
@@ -1,5 +1,5 @@
 ///////////////////////////////////////////////////////////////////////////
-// C++ code generated with wxFormBuilder (version Oct 26 2018)
+// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
 // http://www.wxformbuilder.org/
 //
 // PLEASE DO *NOT* EDIT THIS FILE!
@@ -36,6 +36,7 @@ class KICAD2STEP_FRAME_BASE : public wxFrame
 		wxButton* m_sdbSizer1OK;
 
 		// Virtual event handlers, override them in your derived class
+		virtual void OnIdle( wxIdleEvent& event ) { event.Skip(); }
 		virtual void OnOKButtonClick( wxCommandEvent& event ) { event.Skip(); }
 
 
diff --git a/utils/kicad2step/panel_kicad2step.h b/pcbnew/exporters/step/panel_kicad2step.h
similarity index 95%
rename from utils/kicad2step/panel_kicad2step.h
rename to pcbnew/exporters/step/panel_kicad2step.h
index cc4c05b2c7..39ad4e3cb7 100644
--- a/utils/kicad2step/panel_kicad2step.h
+++ b/pcbnew/exporters/step/panel_kicad2step.h
@@ -44,10 +44,6 @@ public:
                       const wxSize& size = wxSize( 500,300 ),
                       long style = wxTAB_TRAVERSAL );
 
-    /**
-     * Run the KiCad to STEP converter.
-     */
-    int RunConverter();
 
     /**
      * Add a message to m_tcMessages.
diff --git a/utils/kicad2step/pcb/3d_resolver.cpp b/pcbnew/exporters/step/pcb/3d_resolver.cpp
similarity index 99%
rename from utils/kicad2step/pcb/3d_resolver.cpp
rename to pcbnew/exporters/step/pcb/3d_resolver.cpp
index c4971debbf..20d21d9200 100644
--- a/utils/kicad2step/pcb/3d_resolver.cpp
+++ b/pcbnew/exporters/step/pcb/3d_resolver.cpp
@@ -374,7 +374,7 @@ bool S3D_RESOLVER::readPathList( void )
 
     if( !wxFileName::Exists( cfgname ) )
     {
-        wxLogTrace( trace3dResolver, wxT( "%s:%s:d\n * no 3D configuration file '%s'" ),
+        wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * no 3D configuration file '%s'" ),
                     __FILE__, __FUNCTION__, __LINE__, cfgname );
 
         return false;
diff --git a/utils/kicad2step/pcb/3d_resolver.h b/pcbnew/exporters/step/pcb/3d_resolver.h
similarity index 100%
rename from utils/kicad2step/pcb/3d_resolver.h
rename to pcbnew/exporters/step/pcb/3d_resolver.h
diff --git a/utils/kicad2step/pcb/base.cpp b/pcbnew/exporters/step/pcb/base.cpp
similarity index 100%
rename from utils/kicad2step/pcb/base.cpp
rename to pcbnew/exporters/step/pcb/base.cpp
diff --git a/utils/kicad2step/pcb/base.h b/pcbnew/exporters/step/pcb/base.h
similarity index 100%
rename from utils/kicad2step/pcb/base.h
rename to pcbnew/exporters/step/pcb/base.h
diff --git a/utils/kicad2step/pcb/kicadcurve.cpp b/pcbnew/exporters/step/pcb/kicadcurve.cpp
similarity index 100%
rename from utils/kicad2step/pcb/kicadcurve.cpp
rename to pcbnew/exporters/step/pcb/kicadcurve.cpp
diff --git a/utils/kicad2step/pcb/kicadcurve.h b/pcbnew/exporters/step/pcb/kicadcurve.h
similarity index 100%
rename from utils/kicad2step/pcb/kicadcurve.h
rename to pcbnew/exporters/step/pcb/kicadcurve.h
diff --git a/utils/kicad2step/pcb/kicadfootprint.cpp b/pcbnew/exporters/step/pcb/kicadfootprint.cpp
similarity index 100%
rename from utils/kicad2step/pcb/kicadfootprint.cpp
rename to pcbnew/exporters/step/pcb/kicadfootprint.cpp
diff --git a/utils/kicad2step/pcb/kicadfootprint.h b/pcbnew/exporters/step/pcb/kicadfootprint.h
similarity index 100%
rename from utils/kicad2step/pcb/kicadfootprint.h
rename to pcbnew/exporters/step/pcb/kicadfootprint.h
diff --git a/utils/kicad2step/pcb/kicadmodel.cpp b/pcbnew/exporters/step/pcb/kicadmodel.cpp
similarity index 100%
rename from utils/kicad2step/pcb/kicadmodel.cpp
rename to pcbnew/exporters/step/pcb/kicadmodel.cpp
diff --git a/utils/kicad2step/pcb/kicadmodel.h b/pcbnew/exporters/step/pcb/kicadmodel.h
similarity index 100%
rename from utils/kicad2step/pcb/kicadmodel.h
rename to pcbnew/exporters/step/pcb/kicadmodel.h
diff --git a/utils/kicad2step/pcb/kicadpad.cpp b/pcbnew/exporters/step/pcb/kicadpad.cpp
similarity index 100%
rename from utils/kicad2step/pcb/kicadpad.cpp
rename to pcbnew/exporters/step/pcb/kicadpad.cpp
diff --git a/utils/kicad2step/pcb/kicadpad.h b/pcbnew/exporters/step/pcb/kicadpad.h
similarity index 100%
rename from utils/kicad2step/pcb/kicadpad.h
rename to pcbnew/exporters/step/pcb/kicadpad.h
diff --git a/utils/kicad2step/pcb/kicadpcb.cpp b/pcbnew/exporters/step/pcb/kicadpcb.cpp
similarity index 100%
rename from utils/kicad2step/pcb/kicadpcb.cpp
rename to pcbnew/exporters/step/pcb/kicadpcb.cpp
diff --git a/utils/kicad2step/pcb/kicadpcb.h b/pcbnew/exporters/step/pcb/kicadpcb.h
similarity index 100%
rename from utils/kicad2step/pcb/kicadpcb.h
rename to pcbnew/exporters/step/pcb/kicadpcb.h
diff --git a/utils/kicad2step/pcb/oce_utils.cpp b/pcbnew/exporters/step/pcb/oce_utils.cpp
similarity index 100%
rename from utils/kicad2step/pcb/oce_utils.cpp
rename to pcbnew/exporters/step/pcb/oce_utils.cpp
diff --git a/utils/kicad2step/pcb/oce_utils.h b/pcbnew/exporters/step/pcb/oce_utils.h
similarity index 100%
rename from utils/kicad2step/pcb/oce_utils.h
rename to pcbnew/exporters/step/pcb/oce_utils.h
diff --git a/pcbnew/pcb_plot_svg.cpp b/pcbnew/pcb_plot_svg.cpp
new file mode 100644
index 0000000000..f28fff6aa3
--- /dev/null
+++ b/pcbnew/pcb_plot_svg.cpp
@@ -0,0 +1,106 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 Jean-Pierre Charras, jp.charras at wanadoo.fr
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <board.h>
+#include <board_design_settings.h>
+#include <locale_io.h>
+#include <pcbnew_settings.h>
+#include <pcb_plot_params.h>
+#include <pcb_plot_svg.h>
+#include <pcbplot.h>
+#include <pgm_base.h>
+#include <plotters/plotters_pslike.h>
+
+
+bool PCB_PLOT_SVG::Plot( BOARD* aBoard, const PCB_PLOT_SVG_OPTIONS& aSvgPlotOptions )
+{
+    PCB_PLOT_PARAMS plot_opts;
+
+    plot_opts.SetPlotFrameRef( aSvgPlotOptions.m_pageSizeMode );
+
+    // Adding drill marks, for copper layers
+    if( ( aSvgPlotOptions.m_printMaskLayer & LSET::AllCuMask() ).any() )
+        plot_opts.SetDrillMarksType( PCB_PLOT_PARAMS::FULL_DRILL_SHAPE );
+    else
+        plot_opts.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE );
+
+    plot_opts.SetSkipPlotNPTH_Pads( false );
+
+    plot_opts.SetMirror( aSvgPlotOptions.m_mirror );
+    plot_opts.SetFormat( PLOT_FORMAT::SVG );
+    // coord format: 4 digits in mantissa (units always in mm). This is a good choice.
+    plot_opts.SetSvgPrecision( 4 );
+
+    PAGE_INFO savedPageInfo = aBoard->GetPageSettings();
+    VECTOR2I  savedAuxOrigin = aBoard->GetDesignSettings().GetAuxOrigin();
+
+    if( aSvgPlotOptions.m_pageSizeMode == 2 ) // Page is board boundary size
+    {
+        BOX2I     bbox = aBoard->ComputeBoundingBox();
+        PAGE_INFO currpageInfo = aBoard->GetPageSettings();
+
+        currpageInfo.SetWidthMils( bbox.GetWidth() / pcbIUScale.IU_PER_MILS );
+        currpageInfo.SetHeightMils( bbox.GetHeight() / pcbIUScale.IU_PER_MILS );
+        aBoard->SetPageSettings( currpageInfo );
+        plot_opts.SetUseAuxOrigin( true );
+        VECTOR2I origin = bbox.GetOrigin();
+        aBoard->GetDesignSettings().SetAuxOrigin( origin );
+    }
+
+    SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
+    PCBNEW_SETTINGS*  cfg = mgr.GetAppSettings<PCBNEW_SETTINGS>();
+
+    plot_opts.SetColorSettings( mgr.GetColorSettings( cfg->m_ColorTheme ) );
+
+    COLOR_SETTINGS* theme = nullptr;
+    if( !aSvgPlotOptions.m_colorTheme.IsEmpty() )
+    {
+        theme = mgr.GetColorSettings( aSvgPlotOptions.m_colorTheme );
+    }
+
+    if( !theme )
+    {
+        theme = mgr.GetColorSettings( cfg->m_ColorTheme );
+    }
+
+    LOCALE_IO toggle;
+
+    //@todo allow controlling the sheet name and path that will be displayed in the title block
+    // Leave blank for now
+    SVG_PLOTTER* plotter = (SVG_PLOTTER*) StartPlotBoard( aBoard, &plot_opts, UNDEFINED_LAYER,
+                                                          aSvgPlotOptions.m_outputFile,
+                                                          wxEmptyString, wxEmptyString );
+
+    if( plotter )
+    {
+        plotter->SetColorMode( !aSvgPlotOptions.m_blackAndWhite );
+        PlotBoardLayers( aBoard, plotter, aSvgPlotOptions.m_printMaskLayer.SeqStackupBottom2Top(),
+                         plot_opts );
+        plotter->EndPlot();
+    }
+
+    delete plotter;
+
+    // reset to the values saved earlier
+    aBoard->GetDesignSettings().SetAuxOrigin( savedAuxOrigin );
+    aBoard->SetPageSettings( savedPageInfo );
+
+    return true;
+}
diff --git a/pcbnew/pcb_plot_svg.h b/pcbnew/pcb_plot_svg.h
new file mode 100644
index 0000000000..064e43a70d
--- /dev/null
+++ b/pcbnew/pcb_plot_svg.h
@@ -0,0 +1,43 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012 Jean-Pierre Charras, jp.charras at wanadoo.fr
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PCB_PLOT_SVG_H
+#define PCB_PLOT_SVG_H
+
+struct PCB_PLOT_SVG_OPTIONS
+{
+    wxString m_outputFile;
+    wxString m_colorTheme;
+
+    bool m_mirror;
+    bool m_blackAndWhite;
+
+    int m_pageSizeMode;
+
+    LSET m_printMaskLayer;
+};
+
+class PCB_PLOT_SVG
+{
+public:
+    static bool Plot( BOARD* aBoard, const PCB_PLOT_SVG_OPTIONS& aSvgPlotOptions );
+};
+
+#endif
\ No newline at end of file
diff --git a/pcbnew/pcbnew.cpp b/pcbnew/pcbnew.cpp
index be3db8a972..7678ddafac 100644
--- a/pcbnew/pcbnew.cpp
+++ b/pcbnew/pcbnew.cpp
@@ -60,6 +60,7 @@
 
 #include "invoke_pcb_dialog.h"
 #include <wildcards_and_files_ext.h>
+#include "pcbnew_jobs_handler.h"
 
 
 /* init functions defined by swig */
@@ -290,6 +291,11 @@ static struct IFACE : public KIFACE_BASE
                      const wxString& aNewProjectBasePath, const wxString& aNewProjectName,
                      const wxString& aSrcFilePath, wxString& aErrors ) override;
 
+    int HandleJob( JOB* aJob ) override;
+
+private:
+    std::unique_ptr<PCBNEW_JOBS_HANDLER> m_jobHandler;
+
 } kiface( "pcbnew", KIWAY::FACE_PCB );
 
 } // namespace
@@ -381,6 +387,8 @@ bool IFACE::OnKifaceStart( PGM_BASE* aProgram, int aCtlBits )
         }
     }
 
+    m_jobHandler = std::make_unique<PCBNEW_JOBS_HANDLER>();
+
     return true;
 }
 
@@ -478,3 +486,8 @@ void IFACE::SaveFileAs( const wxString& aProjectBasePath, const wxString& aSrcPr
     }
 }
 
+
+int IFACE::HandleJob( JOB* aJob )
+{
+    return m_jobHandler->RunJob( aJob );
+}
\ No newline at end of file
diff --git a/pcbnew/pcbnew_jobs_handler.cpp b/pcbnew/pcbnew_jobs_handler.cpp
new file mode 100644
index 0000000000..6383c518e2
--- /dev/null
+++ b/pcbnew/pcbnew_jobs_handler.cpp
@@ -0,0 +1,102 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pcbnew_jobs_handler.h"
+#include <kicad2step.h>
+#include <jobs/job_export_pcb_svg.h>
+#include <jobs/job_export_step.h>
+#include <cli/exit_codes.h>
+#include <plotters/plotters_pslike.h>
+#include <pgm_base.h>
+#include <pcbplot.h>
+#include <board_design_settings.h>
+#include <pcbnew_settings.h>
+#include <wx/crt.h>
+#include <pcb_plot_svg.h>
+
+#include "pcbnew_scripting_helpers.h"
+
+PCBNEW_JOBS_HANDLER::PCBNEW_JOBS_HANDLER()
+{
+    Register( "step",
+              std::bind( &PCBNEW_JOBS_HANDLER::JobExportStep, this, std::placeholders::_1 ) );
+    Register( "svg",
+              std::bind( &PCBNEW_JOBS_HANDLER::JobExportSvg, this, std::placeholders::_1 ) );
+}
+
+
+int PCBNEW_JOBS_HANDLER::JobExportStep( JOB* aJob )
+{
+    JOB_EXPORT_STEP* aStepJob = dynamic_cast<JOB_EXPORT_STEP*>( aJob );
+
+    if( aStepJob == nullptr )
+        return CLI::EXIT_CODES::ERR_UNKNOWN;
+
+    KICAD2MCAD_PRMS params;
+    params.m_useDrillOrigin = aStepJob->m_useDrillOrigin;
+    params.m_useGridOrigin = aStepJob->m_useGridOrigin;
+    params.m_overwrite = aStepJob->m_overwrite;
+    params.m_includeVirtual = aStepJob->m_includeVirtual;
+    params.m_filename = aStepJob->m_filename;
+    params.m_outputFile = aStepJob->m_outputFile;
+    params.m_xOrigin = aStepJob->m_xOrigin;
+    params.m_yOrigin = aStepJob->m_yOrigin;
+    params.m_minDistance = aStepJob->m_minDistance;
+    params.m_substModels = aStepJob->m_substModels;
+    params.m_gui = aStepJob->m_gui;
+
+    // we might need the lifetime of the converter to continue until frame destruction
+    // due to the gui parameter
+    KICAD2STEP* converter = new KICAD2STEP( params );
+
+    return converter->Run();
+}
+
+
+int PCBNEW_JOBS_HANDLER::JobExportSvg( JOB* aJob )
+{
+    JOB_EXPORT_PCB_SVG* aSvgJob = dynamic_cast<JOB_EXPORT_PCB_SVG*>( aJob );
+
+    if( aSvgJob == nullptr )
+        return CLI::EXIT_CODES::ERR_UNKNOWN;
+
+    PCB_PLOT_SVG_OPTIONS svgPlotOptions;
+    svgPlotOptions.m_blackAndWhite = aSvgJob->m_blackAndWhite;
+    svgPlotOptions.m_colorTheme = aSvgJob->m_colorTheme;
+    svgPlotOptions.m_outputFile = aSvgJob->m_outputFile;
+    svgPlotOptions.m_mirror = aSvgJob->m_mirror;
+    svgPlotOptions.m_pageSizeMode = aSvgJob->m_pageSizeMode;
+    svgPlotOptions.m_printMaskLayer = aSvgJob->m_printMaskLayer;
+
+    if( aJob->IsCli() )
+        wxPrintf( _( "Loading board\n" ) );
+
+    BOARD* brd = LoadBoard( aSvgJob->m_filename );
+
+    if( aJob->IsCli() )
+    {
+        if( PCB_PLOT_SVG::Plot( brd, svgPlotOptions ) )
+            wxPrintf( _( "Successfully created svg file" ) );
+        else
+            wxPrintf( _( "Error creating svg file" ) );
+    }
+
+    return CLI::EXIT_CODES::OK;
+}
\ No newline at end of file
diff --git a/pcbnew/pcbnew_jobs_handler.h b/pcbnew/pcbnew_jobs_handler.h
new file mode 100644
index 0000000000..c3b94bef92
--- /dev/null
+++ b/pcbnew/pcbnew_jobs_handler.h
@@ -0,0 +1,34 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2022 Mark Roszko <mark.roszko@gmail.com>
+ * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PCBNEW_JOBS_HANDLER_H
+#define PCBNEW_JOBS_HANDLER_H
+
+#include <jobs/job_dispatcher.h>
+
+class PCBNEW_JOBS_HANDLER : public JOB_DISPATCHER
+{
+public:
+    PCBNEW_JOBS_HANDLER();
+    int JobExportStep( JOB* aJob );
+    int JobExportSvg( JOB* aJob );
+};
+
+#endif
\ No newline at end of file
diff --git a/qa/tools/pcbnew_tools/CMakeLists.txt b/qa/tools/pcbnew_tools/CMakeLists.txt
index c30dad905c..19fe8221c2 100644
--- a/qa/tools/pcbnew_tools/CMakeLists.txt
+++ b/qa/tools/pcbnew_tools/CMakeLists.txt
@@ -29,10 +29,6 @@ add_executable( qa_pcbnew_tools
     tools/polygon_generator/polygon_generator.cpp
 
     tools/polygon_triangulation/polygon_triangulation.cpp
-
-    # Older CMakes cannot link OBJECT libraries
-    # https://cmake.org/pipermail/cmake/2013-November/056263.html
-    $<TARGET_OBJECTS:pcbnew_kiface_objects>
 )
 
 # Anytime we link to the kiface_objects, we have to add a dependency on the last object
@@ -41,6 +37,7 @@ add_executable( qa_pcbnew_tools
 add_dependencies( qa_pcbnew_tools pcbnew )
 
 target_link_libraries( qa_pcbnew_tools
+    pcbnew_kiface_objects
     qa_pcbnew_utils
     3d-viewer
     connectivity
diff --git a/resources/msw/cmd-wrappers/kicad.cmd b/resources/msw/cmd-wrappers/kicad.cmd
new file mode 100644
index 0000000000..8e901e2438
--- /dev/null
+++ b/resources/msw/cmd-wrappers/kicad.cmd
@@ -0,0 +1 @@
+@"%~dp0kicad.exe" %*
\ No newline at end of file
diff --git a/resources/msw/cmd-wrappers/pcbnew.cmd b/resources/msw/cmd-wrappers/pcbnew.cmd
new file mode 100644
index 0000000000..6530ba7d60
--- /dev/null
+++ b/resources/msw/cmd-wrappers/pcbnew.cmd
@@ -0,0 +1 @@
+@"%~dp0pcbnew.exe" %*
\ No newline at end of file
diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt
index 77a38b2761..3a74883cf7 100644
--- a/thirdparty/CMakeLists.txt
+++ b/thirdparty/CMakeLists.txt
@@ -23,6 +23,7 @@
 
 # Note: The glew folder isn't added here because it is added inside the main CMakeLists.txt
 
+add_subdirectory( argparse )
 add_subdirectory( clipper )
 add_subdirectory( compoundfilereader )
 add_subdirectory( delaunator )
diff --git a/thirdparty/argparse/.clang-format b/thirdparty/argparse/.clang-format
new file mode 100644
index 0000000000..2cfd8b190e
--- /dev/null
+++ b/thirdparty/argparse/.clang-format
@@ -0,0 +1,117 @@
+---
+Language:        Cpp
+# BasedOnStyle:  LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Right
+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: true
+BinPackParameters: true
+BraceWrapping:
+  AfterClass:      false
+  AfterControlStatement: false
+  AfterEnum:       false
+  AfterFunction:   false
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  AfterExternBlock: false
+  BeforeCatch:     false
+  BeforeElse:      false
+  IndentBraces:    false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit:     80
+CommentPragmas:  '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat:   false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IncludeBlocks:   Preserve
+IncludeCategories:
+  - Regex:           '^"(llvm|llvm-c|clang|clang-c)/'
+    Priority:        2
+  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
+    Priority:        3
+  - Regex:           '.*'
+    Priority:        1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+IndentPPDirectives: None
+IndentWidth:     2
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Right
+RawStringFormats:
+  - Language: TextProto
+    Delimiters:
+      - 'pb'
+      - 'proto'
+    BasedOnStyle: google
+ReflowComments:  true
+SortIncludes:    true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard:        c++17
+TabWidth:        8
+UseTab:          Never
+...
+
diff --git a/thirdparty/argparse/.clang-tidy b/thirdparty/argparse/.clang-tidy
new file mode 100644
index 0000000000..d0b28f4f79
--- /dev/null
+++ b/thirdparty/argparse/.clang-tidy
@@ -0,0 +1,21 @@
+Checks:
+  -*,
+  clang-analyzer-*,
+  cppcoreguidelines-avoid-c-arrays,
+  cppcoreguidelines-special-member-functions,
+  readability-*,
+
+CheckOptions:
+  - { key: readability-identifier-naming.ClassCase, value: CamelCase }
+  - { key: readability-identifier-naming.ConstexprVariableCase, value: lower_case }
+  - { key: readability-identifier-naming.ConstexprVariableIgnoredRegexp, value: "^Is.+" }
+  - { key: readability-identifier-naming.FunctionCase, value: lower_case }
+  - { key: readability-identifier-naming.NamespaceCase, value: lower_case }
+  - { key: readability-identifier-naming.ParameterCase, value: lower_case }
+  - { key: readability-identifier-naming.PrivateMemberCase, value: lower_case }
+  - { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ }
+  - { key: readability-identifier-naming.StructCase, value: CamelCase }
+  - { key: readability-identifier-naming.StructIgnoredRegexp, value: "parse_number" }
+  - { key: readability-identifier-naming.VariableCase, value: lower_case }
+
+HeaderFilterRegex: '.*'
diff --git a/thirdparty/argparse/.github/workflows/ci.yml b/thirdparty/argparse/.github/workflows/ci.yml
new file mode 100644
index 0000000000..4324179b24
--- /dev/null
+++ b/thirdparty/argparse/.github/workflows/ci.yml
@@ -0,0 +1,87 @@
+
+name: CI
+
+on: pull_request
+
+jobs:
+
+  test:
+
+    name: ${{ matrix.toolchain }}
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+
+      matrix:
+
+        toolchain:
+          - macos-latest-clang
+          - macos-12-clang
+          - ubuntu-latest-clang
+          - ubuntu-latest-gcc
+          - windows-2019-msvc
+          - windows-latest-msvc
+          - windows-latest-clang
+
+        include:
+          - toolchain: macos-latest-clang
+            os: macos-latest
+            c_compiler: clang
+            cxx_compiler: clang++
+
+          - toolchain: macos-12-clang
+            os: macos-latest
+            c_compiler: clang
+            cxx_compiler: clang++
+
+          - toolchain: ubuntu-latest-clang
+            os: ubuntu-latest
+            c_compiler: clang
+            cxx_compiler: clang++
+
+          - toolchain: ubuntu-latest-gcc
+            os: ubuntu-latest
+            c_compiler: cc
+            cxx_compiler: g++
+
+          - toolchain: windows-2019-msvc
+            os: windows-2019
+            c_compiler: msvc
+            cxx_compiler: msvc
+
+          - toolchain: windows-latest-msvc
+            os: windows-latest
+            c_compiler: msvc
+            cxx_compiler: msvc
+
+          - toolchain: windows-latest-clang
+            os: windows-latest
+            c_compiler: clang-cl
+            cxx_compiler: clang-cl
+            cmake_opts: -T ClangCL
+
+    steps:
+
+    - name: Checkout Code
+      uses: actions/checkout@v2
+
+    - name: Configure
+      working-directory: test
+      run: cmake -S . -B build ${{ matrix.cmake_opts }}
+      env:
+        CC: ${{ matrix.c_compiler }}
+        CXX: ${{ matrix.cxx_compiler }}
+
+    - name: Build for ${{ matrix.os }} with ${{ matrix.compiler }}
+      working-directory: test
+      run: cmake --build build
+
+    - name: Test
+      if: ${{ ! startsWith(matrix.os, 'windows') }}
+      working-directory: test/build
+      run: ./tests
+
+    - name: Test (Windows)
+      if: ${{ startsWith(matrix.os, 'windows') }}
+      working-directory: test/build
+      run: ./Debug/tests.exe
diff --git a/thirdparty/argparse/.gitignore b/thirdparty/argparse/.gitignore
new file mode 100644
index 0000000000..cdcb6f62e2
--- /dev/null
+++ b/thirdparty/argparse/.gitignore
@@ -0,0 +1,273 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+.vscode/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# CMake build directory
+build
+
+# Cppcheck build directory
+analysis-cppcheck-build-dir
+
+# Ideas directory
+ideas
+
+desktop.iniimages/
diff --git a/thirdparty/argparse/.travis.yml b/thirdparty/argparse/.travis.yml
new file mode 100644
index 0000000000..620daeb6e5
--- /dev/null
+++ b/thirdparty/argparse/.travis.yml
@@ -0,0 +1,39 @@
+matrix:
+  include:
+    - os: linux
+      dist: bionic
+      language: cpp
+      compiler: gcc
+      addons:
+        apt:
+          sources:
+            - ubuntu-toolchain-r-test
+          packages:
+            - g++-8
+      env: CXX=g++-8 CC=gcc-8
+    - os: osx
+      osx_image: xcode10.2
+      language: cpp
+      compiler: clang
+    - os: windows
+      language: bash
+      env: CXX=cl.exe
+install:
+  - |
+    if [[ $TRAVIS_OS_NAME == 'windows' ]]; then
+      choco install ninja cmake
+    elif [[ $TRAVIS_OS_NAME == 'osx' ]]; then
+      export PATH=~/Library/Python/3.7/bin:$PATH
+      pip3 install --user ninja cmake
+    else
+      pipenv global 3.6
+      pip install --user ninja cmake
+    fi
+script:
+  - |
+    if [[ $TRAVIS_OS_NAME == 'windows' ]]; then
+      tools/build.bat
+    else
+      sh tools/build.sh
+    fi
+  - ./build/test/tests
diff --git a/thirdparty/argparse/CMakeLists.txt b/thirdparty/argparse/CMakeLists.txt
new file mode 100644
index 0000000000..cb359d5fca
--- /dev/null
+++ b/thirdparty/argparse/CMakeLists.txt
@@ -0,0 +1,98 @@
+cmake_minimum_required(VERSION 3.12.4)
+
+project(argparse
+        VERSION 2.9.0 
+        DESCRIPTION "A single header argument parser for C++17"
+        HOMEPAGE_URL "https://github.com/p-ranav/argparse"
+        LANGUAGES CXX
+)
+
+option(ARGPARSE_BUILD_TESTS OFF)
+option(ARGPARSE_LONG_VERSION_ARG_ONLY OFF)
+
+include(GNUInstallDirs)
+include(CMakePackageConfigHelpers)
+string(REPLACE "/${CMAKE_LIBRARY_ARCHITECTURE}" "" CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}")
+
+
+add_library(argparse INTERFACE)
+add_library(argparse::argparse ALIAS argparse)
+
+
+if (ARGPARSE_LONG_VERSION_ARG_ONLY)
+	target_compile_definitions(argparse INTERFACE ARGPARSE_LONG_VERSION_ARG_ONLY=true)
+endif ()
+
+target_compile_features(argparse INTERFACE cxx_std_17)
+target_include_directories(argparse INTERFACE
+  $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>)
+
+if(ARGPARSE_BUILD_SAMPLES)
+  add_subdirectory(samples)
+endif()
+
+if(ARGPARSE_BUILD_TESTS)
+  add_subdirectory(test)
+endif()
+
+install(TARGETS argparse EXPORT argparseConfig)
+install(EXPORT argparseConfig
+        NAMESPACE argparse::
+        DESTINATION ${CMAKE_INSTALL_LIBDIR_ARCHIND}/cmake/${PROJECT_NAME})
+install(FILES ${CMAKE_CURRENT_LIST_DIR}/include/argparse/argparse.hpp
+        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/argparse)
+
+
+set(CONFIG_FILE_NAME_WITHOUT_EXT "${PROJECT_NAME}Config")
+set(CMAKE_CONFIG_FILE_BASENAME "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE_NAME_WITHOUT_EXT}")
+set(CMAKE_CONFIG_VERSION_FILE_NAME "${CMAKE_CONFIG_FILE_BASENAME}-version.cmake")
+set(CMAKE_CONFIG_FILE_NAME "${CMAKE_CONFIG_FILE_BASENAME}.cmake")
+
+if(${CMAKE_VERSION} VERSION_GREATER "3.14")
+	set(OPTIONAL_ARCH_INDEPENDENT "ARCH_INDEPENDENT")
+endif()
+
+write_basic_package_version_file("${CMAKE_CONFIG_VERSION_FILE_NAME}"
+    COMPATIBILITY ExactVersion
+    ${OPTIONAL_ARCH_INDEPENDENT}
+)
+
+export(EXPORT argparseConfig
+       NAMESPACE argparse::)
+
+install(FILES "${CMAKE_CONFIG_VERSION_FILE_NAME}"
+       DESTINATION "${CMAKE_INSTALL_LIBDIR_ARCHIND}/cmake/${PROJECT_NAME}")
+
+set(PackagingTemplatesDir "${CMAKE_CURRENT_SOURCE_DIR}/packaging")
+
+set(CPACK_PACKAGE_NAME "${PROJECT_NAME}")
+set(CPACK_PACKAGE_VENDOR "argparse (C++) developers")
+set(CPACK_PACKAGE_DESCRIPTION "${PROJECT_DESCRIPTION}")
+set(CPACK_DEBIAN_PACKAGE_NAME "${CPACK_PACKAGE_NAME}")
+set(CPACK_RPM_PACKAGE_NAME "${CPACK_PACKAGE_NAME}")
+set(CPACK_PACKAGE_HOMEPAGE_URL "${PROJECT_HOMEPAGE_URL}")
+set(CPACK_PACKAGE_MAINTAINER "Pranav Srinivas Kumar")
+set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_MAINTAINER}")
+set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
+set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
+
+set(CPACK_DEBIAN_PACKAGE_NAME "lib${PROJECT_NAME}-dev")
+set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6-dev")
+set(CPACK_DEBIAN_PACKAGE_SUGGESTS "cmake, pkg-config, pkg-conf")
+
+set(CPACK_RPM_PACKAGE_NAME "lib${PROJECT_NAME}-devel")
+set(CPACK_RPM_PACKAGE_SUGGESTS "${CPACK_DEBIAN_PACKAGE_SUGGESTS}")
+
+set(CPACK_DEB_COMPONENT_INSTALL ON)
+set(CPACK_RPM_COMPONENT_INSTALL ON)
+set(CPACK_NSIS_COMPONENT_INSTALL ON)
+set(CPACK_DEBIAN_COMPRESSION_TYPE "xz")
+
+set(PKG_CONFIG_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc")
+configure_file("${PackagingTemplatesDir}/pkgconfig.pc.in" "${PKG_CONFIG_FILE_NAME}" @ONLY)
+install(FILES "${PKG_CONFIG_FILE_NAME}"
+        DESTINATION "${CMAKE_INSTALL_LIBDIR_ARCHIND}/pkgconfig"
+)
+
+include(CPack)
diff --git a/thirdparty/argparse/CONTRIBUTING.md b/thirdparty/argparse/CONTRIBUTING.md
new file mode 100644
index 0000000000..9d9678bc58
--- /dev/null
+++ b/thirdparty/argparse/CONTRIBUTING.md
@@ -0,0 +1,17 @@
+# Contributing
+Contributions are welcomed. Open a pull-request or an issue.
+
+## Code of conduct
+This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.
+
+[code-of-conduct]: https://github.com/spotify/code-of-conduct/blob/master/code-of-conduct.md
+
+## Code Style
+
+This project prefers, but does not strictly enforce, a specific source code style. The style is described in `.clang-format` and `.clang-tidy`.
+
+To generate a clang-tidy report:
+
+```bash
+clang-tidy --extra-arg=-std=c++17 --config-file=.clang-tidy include/argparse/argparse.hpp
+```
diff --git a/thirdparty/argparse/LICENSE b/thirdparty/argparse/LICENSE
new file mode 100644
index 0000000000..a9ebaa374e
--- /dev/null
+++ b/thirdparty/argparse/LICENSE
@@ -0,0 +1,7 @@
+Copyright (c) 2018 Pranav Srinivas Kumar <pranav.srinivas.kumar@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/thirdparty/argparse/README.md b/thirdparty/argparse/README.md
new file mode 100644
index 0000000000..74265f90be
--- /dev/null
+++ b/thirdparty/argparse/README.md
@@ -0,0 +1,1143 @@
+<p align="center">
+  <img height="100" src="https://i.imgur.com/oDXeMUQ.png" alt="argparse"/>
+</p>
+
+<p align="center">
+  <img src="https://travis-ci.org/p-ranav/argparse.svg?branch=master" alt="travis"/>
+  <a href="https://github.com/p-ranav/argparse/blob/master/LICENSE">
+    <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="license"/>
+  </a>
+  <img src="https://img.shields.io/badge/version-2.9-blue.svg?cacheSeconds=2592000" alt="version"/>
+</p>
+
+## Highlights
+
+* Single header file
+* Requires C++17
+* MIT License
+
+## Table of Contents
+
+*    [Quick Start](#quick-start)
+     *    [Positional Arguments](#positional-arguments)
+     *    [Optional Arguments](#optional-arguments)
+          *    [Requiring optional arguments](#requiring-optional-arguments)
+          *    [Accessing optional arguments without default values](#accessing-optional-arguments-without-default-values)
+          *    [Deciding if the value was given by the user](#deciding-if-the-value-was-given-by-the-user)
+          *    [Joining values of repeated optional arguments](#joining-values-of-repeated-optional-arguments)
+          *    [Repeating an argument to increase a value](#repeating-an-argument-to-increase-a-value)
+     *    [Negative Numbers](#negative-numbers)
+     *    [Combining Positional and Optional Arguments](#combining-positional-and-optional-arguments)
+     *    [Printing Help](#printing-help)
+     *    [Adding a description and an epilog to help](#adding-a-description-and-an-epilog-to-help)
+     *    [List of Arguments](#list-of-arguments)
+     *    [Compound Arguments](#compound-arguments)
+     *    [Converting to Numeric Types](#converting-to-numeric-types)
+     *    [Default Arguments](#default-arguments)
+     *    [Gathering Remaining Arguments](#gathering-remaining-arguments)
+     *    [Parent Parsers](#parent-parsers)
+     *    [Subcommands](#subcommands)
+     *    [Parse Known Args](#parse-known-args)
+     *    [Custom Prefix Characters](#custom-prefix-characters)
+     *    [Custom Assignment Characters](#custom-assignment-characters)
+*    [Further Examples](#further-examples)
+     *    [Construct a JSON object from a filename argument](#construct-a-json-object-from-a-filename-argument)
+     *    [Positional Arguments with Compound Toggle Arguments](#positional-arguments-with-compound-toggle-arguments)
+     *    [Restricting the set of values for an argument](#restricting-the-set-of-values-for-an-argument)
+     *    [Using `option=value` syntax](#using-optionvalue-syntax)
+*    [CMake Integration](#cmake-integration)
+*    [Building, Installing, and Testing](#building-installing-and-testing)
+*    [Supported Toolchains](#supported-toolchains)
+*    [Contributing](#contributing)
+*    [License](#license)
+
+## Quick Start
+
+Simply include argparse.hpp and you're good to go.
+
+```cpp
+#include <argparse/argparse.hpp>
+```
+
+To start parsing command-line arguments, create an ```ArgumentParser```.
+
+```cpp
+argparse::ArgumentParser program("program_name");
+```
+
+**NOTE:** There is an optional second argument to the `ArgumentParser` which is the program version. Example: `argparse::ArgumentParser program("libfoo", "1.9.0");`
+
+To add a new argument, simply call ```.add_argument(...)```. You can provide a variadic list of argument names that you want to group together, e.g., ```-v``` and ```--verbose```
+
+```cpp
+program.add_argument("foo");
+program.add_argument("-v", "--verbose"); // parameter packing
+```
+
+Argparse supports a variety of argument types including positional, optional, and compound arguments. Below you can see how to configure each of these types:
+
+### Positional Arguments
+
+Here's an example of a ***positional argument***:
+
+```cpp
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("program_name");
+
+  program.add_argument("square")
+    .help("display the square of a given integer")
+    .scan<'i', int>();
+
+  try {
+    program.parse_args(argc, argv);
+  }
+  catch (const std::runtime_error& err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  auto input = program.get<int>("square");
+  std::cout << (input * input) << std::endl;
+
+  return 0;
+}
+```
+
+And running the code:
+
+```console
+foo@bar:/home/dev/$ ./main 15
+225
+```
+
+Here's what's happening:
+
+* The ```add_argument()``` method is used to specify which command-line options the program is willing to accept. In this case, I’ve named it square so that it’s in line with its function.
+* Command-line arguments are strings. To square the argument and print the result, we need to convert this argument to a number. In order to do this, we use the ```.scan``` method to convert user input into an integer.
+* We can get the value stored by the parser for a given argument using ```parser.get<T>(key)``` method.
+
+### Optional Arguments
+
+Now, let's look at ***optional arguments***. Optional arguments start with ```-``` or ```--```, e.g., ```--verbose``` or ```-a```. Optional arguments can be placed anywhere in the input sequence.
+
+
+```cpp
+argparse::ArgumentParser program("test");
+
+program.add_argument("--verbose")
+  .help("increase output verbosity")
+  .default_value(false)
+  .implicit_value(true);
+
+try {
+  program.parse_args(argc, argv);
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+if (program["--verbose"] == true) {
+  std::cout << "Verbosity enabled" << std::endl;
+}
+```
+
+```console
+foo@bar:/home/dev/$ ./main --verbose
+Verbosity enabled
+```
+
+Here's what's happening:
+* The program is written so as to display something when --verbose is specified and display nothing when not.
+* Since the argument is actually optional, no error is thrown when running the program without ```--verbose```. Note that by using ```.default_value(false)```, if the optional argument isn’t used, it's value is automatically set to false.
+* By using ```.implicit_value(true)```, the user specifies that this option is more of a flag than something that requires a value. When the user provides the --verbose option, it's value is set to true.
+
+#### Requiring optional arguments
+
+There are scenarios where you would like to make an optional argument ***required***. As discussed above, optional arguments either begin with `-` or `--`. You can make these types of arguments required like so:
+
+```cpp
+program.add_argument("-o", "--output")
+  .required()
+  .help("specify the output file.");
+```
+
+If the user does not provide a value for this parameter, an exception is thrown.
+
+Alternatively, you could provide a default value like so:
+
+```cpp
+program.add_argument("-o", "--output")
+  .default_value(std::string("-"))
+  .required()
+  .help("specify the output file.");
+```
+
+#### Accessing optional arguments without default values
+
+If you require an optional argument to be present but have no good default value for it, you can combine testing and accessing the argument as following:
+
+```cpp
+if (auto fn = program.present("-o")) {
+    do_something_with(*fn);
+}
+```
+
+Similar to `get`, the `present` method also accepts a template argument.  But rather than returning `T`, `parser.present<T>(key)` returns `std::optional<T>`, so that when the user does not provide a value to this parameter, the return value compares equal to `std::nullopt`.
+
+#### Deciding if the value was given by the user
+
+If you want to know whether the user supplied a value for an argument that has a ```.default_value```, check whether the argument ```.is_used()```.
+
+```cpp
+program.add_argument("--color")
+  .default_value(std::string{"orange"})   // might otherwise be type const char* leading to an error when trying program.get<std::string>
+  .help("specify the cat's fur color");
+
+try {
+  program.parse_args(argc, argv);    // Example: ./main --color orange
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+auto color = program.get<std::string>("--color");  // "orange"
+auto explicit_color = program.is_used("--color");  // true, user provided orange
+```
+
+#### Joining values of repeated optional arguments
+
+You may want to allow an optional argument to be repeated and gather all values in one place.
+
+```cpp
+program.add_argument("--color")
+  .default_value<std::vector<std::string>>({ "orange" })
+  .append()
+  .help("specify the cat's fur color");
+
+try {
+  program.parse_args(argc, argv);    // Example: ./main --color red --color green --color blue
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+auto colors = program.get<std::vector<std::string>>("--color");  // {"red", "green", "blue"}
+```
+
+Notice that ```.default_value``` is given an explicit template parameter to match the type you want to ```.get```.
+
+#### Repeating an argument to increase a value
+
+A common pattern is to repeat an argument to indicate a greater value.
+
+```cpp
+int verbosity = 0;
+program.add_argument("-V", "--verbose")
+  .action([&](const auto &) { ++verbosity; })
+  .append()
+  .default_value(false)
+  .implicit_value(true)
+  .nargs(0);
+
+program.parse_args(argc, argv);    // Example: ./main -VVVV
+
+std::cout << "verbose level: " << verbosity << std::endl;    // verbose level: 4
+```
+
+### Negative Numbers
+
+Optional arguments start with ```-```. Can ```argparse``` handle negative numbers? The answer is yes!
+
+```cpp
+argparse::ArgumentParser program;
+
+program.add_argument("integer")
+  .help("Input number")
+  .scan<'i', int>();
+
+program.add_argument("floats")
+  .help("Vector of floats")
+  .nargs(4)
+  .scan<'g', float>();
+
+try {
+  program.parse_args(argc, argv);
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+// Some code to print arguments
+```
+
+```console
+foo@bar:/home/dev/$ ./main -5 -1.1 -3.1415 -3.1e2 -4.51329E3
+integer : -5
+floats  : -1.1 -3.1415 -310 -4513.29
+```
+
+As you can see here, ```argparse``` supports negative integers, negative floats and scientific notation.
+
+### Combining Positional and Optional Arguments
+
+```cpp
+argparse::ArgumentParser program("main");
+
+program.add_argument("square")
+  .help("display the square of a given number")
+  .scan<'i', int>();
+
+program.add_argument("--verbose")
+  .default_value(false)
+  .implicit_value(true);
+
+try {
+  program.parse_args(argc, argv);
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+int input = program.get<int>("square");
+
+if (program["--verbose"] == true) {
+  std::cout << "The square of " << input << " is " << (input * input) << std::endl;
+}
+else {
+  std::cout << (input * input) << std::endl;
+}
+```
+
+```console
+foo@bar:/home/dev/$ ./main 4
+16
+
+foo@bar:/home/dev/$ ./main 4 --verbose
+The square of 4 is 16
+
+foo@bar:/home/dev/$ ./main --verbose 4
+The square of 4 is 16
+```
+
+### Printing Help
+
+`std::cout << program` prints a help message, including the program usage and information about the arguments registered with the `ArgumentParser`. For the previous example, here's the default help message:
+
+```
+foo@bar:/home/dev/$ ./main --help
+Usage: main [-h] [--verbose] square
+
+Positional arguments:
+  square       	display the square of a given number
+
+Optional arguments:
+  -h, --help   	shows help message and exits
+  -v, --version	prints version information and exits
+  --verbose
+```
+
+You may also get the help message in string via `program.help().str()`.
+
+#### Adding a description and an epilog to help
+
+`ArgumentParser::add_description` will add text before the detailed argument
+information. `ArgumentParser::add_epilog` will add text after all other help output.
+
+```cpp
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("main");
+  program.add_argument("thing").help("Thing to use.").metavar("THING");
+  program.add_argument("--member").help("The alias for the member to pass to.").metavar("ALIAS");
+  program.add_argument("--verbose").default_value(false).implicit_value(true);
+
+  program.add_description("Forward a thing to the next member.");
+  program.add_epilog("Possible things include betingalw, chiz, and res.");
+
+  program.parse_args(argc, argv);
+
+  std::cout << program << std::endl;
+}
+```
+
+```console
+Usage: main [-h] [--member ALIAS] [--verbose] THING
+
+Forward a thing to the next member.
+
+Positional arguments:
+  THING         	Thing to use.
+
+Optional arguments:
+  -h, --help    	shows help message and exits
+  -v, --version 	prints version information and exits
+  --member ALIAS	The alias for the member to pass to.
+  --verbose     	
+
+Possible things include betingalw, chiz, and res.
+```
+
+### List of Arguments
+
+ArgumentParser objects usually associate a single command-line argument with a single action to be taken. The ```.nargs``` associates a different number of command-line arguments with a single action. When using ```nargs(N)```, N arguments from the command line will be gathered together into a list.
+
+```cpp
+argparse::ArgumentParser program("main");
+
+program.add_argument("--input_files")
+  .help("The list of input files")
+  .nargs(2);
+
+try {
+  program.parse_args(argc, argv);   // Example: ./main --input_files config.yml System.xml
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+auto files = program.get<std::vector<std::string>>("--input_files");  // {"config.yml", "System.xml"}
+```
+
+```ArgumentParser.get<T>()``` has specializations for ```std::vector``` and ```std::list```. So, the following variant, ```.get<std::list>```, will also work.
+
+```cpp
+auto files = program.get<std::list<std::string>>("--input_files");  // {"config.yml", "System.xml"}
+```
+
+Using ```.scan```, one can quickly build a list of desired value types from command line arguments. Here's an example:
+
+```cpp
+argparse::ArgumentParser program("main");
+
+program.add_argument("--query_point")
+  .help("3D query point")
+  .nargs(3)
+  .default_value(std::vector<double>{0.0, 0.0, 0.0})
+  .scan<'g', double>();
+
+try {
+  program.parse_args(argc, argv); // Example: ./main --query_point 3.5 4.7 9.2
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+auto query_point = program.get<std::vector<double>>("--query_point");  // {3.5, 4.7, 9.2}
+```
+
+You can also make a variable length list of arguments with the ```.nargs```.
+Below are some examples.
+
+```cpp
+program.add_argument("--input_files")
+  .nargs(1, 3);  // This accepts 1 to 3 arguments.
+```
+
+Some useful patterns are defined like "?", "*", "+" of argparse in Python.
+
+```cpp
+program.add_argument("--input_files")
+  .nargs(argparse::nargs_pattern::any);  // "*" in Python. This accepts any number of arguments including 0.
+```
+```cpp
+program.add_argument("--input_files")
+  .nargs(argparse::nargs_pattern::at_least_one);  // "+" in Python. This accepts one or more number of arguments.
+```
+```cpp
+program.add_argument("--input_files")
+  .nargs(argparse::nargs_pattern::optional);  // "?" in Python. This accepts an argument optionally.
+```
+
+### Compound Arguments
+
+Compound arguments are optional arguments that are combined and provided as a single argument. Example: ```ps -aux```
+
+```cpp
+argparse::ArgumentParser program("test");
+
+program.add_argument("-a")
+  .default_value(false)
+  .implicit_value(true);
+
+program.add_argument("-b")
+  .default_value(false)
+  .implicit_value(true);
+
+program.add_argument("-c")
+  .nargs(2)
+  .default_value(std::vector<float>{0.0f, 0.0f})
+  .scan<'g', float>();
+
+try {
+  program.parse_args(argc, argv);                  // Example: ./main -abc 1.95 2.47
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+auto a = program.get<bool>("-a");                  // true
+auto b = program.get<bool>("-b");                  // true
+auto c = program.get<std::vector<float>>("-c");    // {1.95, 2.47}
+
+/// Some code that prints parsed arguments
+```
+
+```console
+foo@bar:/home/dev/$ ./main -ac 3.14 2.718
+a = true
+b = false
+c = {3.14, 2.718}
+
+foo@bar:/home/dev/$ ./main -cb
+a = false
+b = true
+c = {0.0, 0.0}
+```
+
+Here's what's happening:
+* We have three optional arguments ```-a```, ```-b``` and ```-c```.
+* ```-a``` and ```-b``` are toggle arguments.
+* ```-c``` requires 2 floating point numbers from the command-line.
+* argparse can handle compound arguments, e.g., ```-abc``` or ```-bac``` or ```-cab```. This only works with short single-character argument names.
+  - ```-a``` and ```-b``` become true.
+  - argv is further parsed to identify the inputs mapped to ```-c```.
+  - If argparse cannot find any arguments to map to c, then c defaults to {0.0, 0.0} as defined by ```.default_value```
+
+### Converting to Numeric Types
+
+For inputs, users can express a primitive type for the value.
+
+The ```.scan<Shape, T>``` method attempts to convert the incoming `std::string` to `T` following the `Shape` conversion specifier. An `std::invalid_argument` or `std::range_error` exception is thrown for errors.
+
+```cpp
+program.add_argument("-x")
+       .scan<'d', int>();
+
+program.add_argument("scale")
+       .scan<'g', double>();
+```
+
+`Shape` specifies what the input "looks like", and the type template argument specifies the return value of the predefined action. Acceptable types are floating point (i.e float, double, long double) and integral (i.e. signed char, short, int, long, long long).
+
+The grammar follows `std::from_chars`, but does not exactly duplicate it. For example, hexadecimal numbers may begin with `0x` or `0X` and numbers with a leading zero may be handled as octal values.
+
+| Shape      | interpretation                            |
+| :--------: | ----------------------------------------- |
+| 'a' or 'A' | hexadecimal floating point                |
+| 'e' or 'E' | scientific notation (floating point)      |
+| 'f' or 'F' | fixed notation (floating point)           |
+| 'g' or 'G' | general form (either fixed or scientific) |
+|            |                                           |
+| 'd'        | decimal                                   |
+| 'i'        | `std::from_chars` grammar with base == 0  |
+| 'o'        | octal (unsigned)                          |
+| 'u'        | decimal (unsigned)                        |
+| 'x' or 'X' | hexadecimal (unsigned)                    |
+
+### Default Arguments
+
+`argparse` provides predefined arguments and actions for `-h`/`--help` and `-v`/`--version`. These default actions exit the program after displaying a help or version message, respectively. These defaults arguments can be disabled during `ArgumentParser` creation so that you can handle these arguments in your own way. (Note that a program name and version must be included when choosing default arguments.)
+
+```cpp
+argparse::ArgumentParser program("test", "1.0", default_arguments::none);
+
+program.add_argument("-h", "--help")
+  .action([=](const std::string& s) {
+    std::cout << help().str();
+  })
+  .default_value(false)
+  .help("shows help message")
+  .implicit_value(true)
+  .nargs(0);
+```
+
+The above code snippet outputs a help message and continues to run. It does not support a `--version` argument.
+
+The default is `default_arguments::all` for included arguments. No default arguments will be added with `default_arguments::none`. `default_arguments::help` and `default_arguments::version` will individually add `--help` and `--version`.
+
+### Gathering Remaining Arguments
+
+`argparse` supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler:
+
+```console
+foo@bar:/home/dev/$ compiler file1 file2 file3
+```
+
+To enable this, simply create an argument and mark it as `remaining`. All remaining arguments passed to argparse are gathered here.
+
+```cpp
+argparse::ArgumentParser program("compiler");
+
+program.add_argument("files")
+  .remaining();
+
+try {
+  program.parse_args(argc, argv);
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+try {
+  auto files = program.get<std::vector<std::string>>("files");
+  std::cout << files.size() << " files provided" << std::endl;
+  for (auto& file : files)
+    std::cout << file << std::endl;
+} catch (std::logic_error& e) {
+  std::cout << "No files provided" << std::endl;
+}
+```
+
+When no arguments are provided:
+
+```console
+foo@bar:/home/dev/$ ./compiler
+No files provided
+```
+
+and when multiple arguments are provided:
+
+```console
+foo@bar:/home/dev/$ ./compiler foo.txt bar.txt baz.txt
+3 files provided
+foo.txt
+bar.txt
+baz.txt
+```
+
+The process of gathering remaining arguments plays nicely with optional arguments too:
+
+```cpp
+argparse::ArgumentParser program("compiler");
+
+program.add_arguments("-o")
+  .default_value(std::string("a.out"));
+
+program.add_argument("files")
+  .remaining();
+
+try {
+  program.parse_args(argc, argv);
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+auto output_filename = program.get<std::string>("-o");
+std::cout << "Output filename: " << output_filename << std::endl;
+
+try {
+  auto files = program.get<std::vector<std::string>>("files");
+  std::cout << files.size() << " files provided" << std::endl;
+  for (auto& file : files)
+    std::cout << file << std::endl;
+} catch (std::logic_error& e) {
+  std::cout << "No files provided" << std::endl;
+}
+
+```
+
+```console
+foo@bar:/home/dev/$ ./compiler -o main foo.cpp bar.cpp baz.cpp
+Output filename: main
+3 files provided
+foo.cpp
+bar.cpp
+baz.cpp
+```
+
+***NOTE***: Remember to place all optional arguments BEFORE the remaining argument. If the optional argument is placed after the remaining arguments, it too will be deemed remaining:
+
+```console
+foo@bar:/home/dev/$ ./compiler foo.cpp bar.cpp baz.cpp -o main
+5 arguments provided
+foo.cpp
+bar.cpp
+baz.cpp
+-o
+main
+```
+
+### Parent Parsers
+
+Sometimes, several parsers share a common set of arguments. Rather than repeating the definitions of these arguments, a single parser with all the common arguments can be added as a parent to another ArgumentParser instance. The ```.add_parents``` method takes a list of ArgumentParser objects, collects all the positional and optional actions from them, and adds these actions to the ArgumentParser object being constructed:
+
+```cpp
+argparse::ArgumentParser parent_parser("main");
+parent_parser.add_argument("--parent")
+  .default_value(0)
+  .scan<'i', int>();
+
+argparse::ArgumentParser foo_parser("foo");
+foo_parser.add_argument("foo");
+foo_parser.add_parents(parent_parser);
+foo_parser.parse_args({ "./main", "--parent", "2", "XXX" });   // parent = 2, foo = XXX
+
+argparse::ArgumentParser bar_parser("bar");
+bar_parser.add_argument("--bar");
+bar_parser.parse_args({ "./main", "--bar", "YYY" });           // bar = YYY
+```
+
+Note You must fully initialize the parsers before passing them via ```.add_parents```. If you change the parent parsers after the child parser, those changes will not be reflected in the child.
+
+### Subcommands
+
+Many programs split up their functionality into a number of sub-commands, for example, the `git` program can invoke sub-commands like `git checkout`, `git add`, and `git commit`. Splitting up functionality this way can be a particularly good idea when a program performs several different functions which require different kinds of command-line arguments. `ArgumentParser` supports the creation of such sub-commands with the `add_subparser()` member function.
+
+```cpp
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("git");
+
+  // git add subparser
+  argparse::ArgumentParser add_command("add");
+  add_command.add_description("Add file contents to the index");
+  add_command.add_argument("files")
+    .help("Files to add content from. Fileglobs (e.g.  *.c) can be given to add all matching files.")
+    .remaining();
+
+  // git commit subparser
+  argparse::ArgumentParser commit_command("commit");
+  commit_command.add_description("Record changes to the repository");
+  commit_command.add_argument("-a", "--all")
+    .help("Tell the command to automatically stage files that have been modified and deleted.")
+    .default_value(false)
+    .implicit_value(true);
+
+  commit_command.add_argument("-m", "--message")
+    .help("Use the given <msg> as the commit message.");
+
+  // git cat-file subparser
+  argparse::ArgumentParser catfile_command("cat-file");
+  catfile_command.add_description("Provide content or type and size information for repository objects");
+  catfile_command.add_argument("-t")
+    .help("Instead of the content, show the object type identified by <object>.");
+
+  catfile_command.add_argument("-p")
+    .help("Pretty-print the contents of <object> based on its type.");
+
+  // git submodule subparser
+  argparse::ArgumentParser submodule_command("submodule");
+  submodule_command.add_description("Initialize, update or inspect submodules");
+  argparse::ArgumentParser submodule_update_command("update");
+  submodule_update_command.add_description("Update the registered submodules to match what the superproject expects");
+  submodule_update_command.add_argument("--init")
+    .default_value(false)
+    .implicit_value(true);
+  submodule_update_command.add_argument("--recursive")
+    .default_value(false)
+    .implicit_value(true);
+  submodule_command.add_subparser(submodule_update_command);
+
+  program.add_subparser(add_command);
+  program.add_subparser(commit_command);
+  program.add_subparser(catfile_command);
+  program.add_subparser(submodule_command);
+
+  try {
+    program.parse_args(argc, argv);
+  }
+  catch (const std::runtime_error& err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  // Use arguments
+}
+```
+
+```console
+foo@bar:/home/dev/$ ./git --help
+Usage: git [-h] {add,cat-file,commit,submodule}
+
+Optional arguments:
+  -h, --help   	shows help message and exits
+  -v, --version	prints version information and exits
+
+Subcommands:
+  add           Add file contents to the index
+  cat-file      Provide content or type and size information for repository objects
+  commit        Record changes to the repository
+  submodule     Initialize, update or inspect submodules
+
+foo@bar:/home/dev/$ ./git add --help
+Usage: add [-h] files
+
+Add file contents to the index
+
+Positional arguments:
+  files        	Files to add content from. Fileglobs (e.g.  *.c) can be given to add all matching files.
+
+Optional arguments:
+  -h, --help   	shows help message and exits
+  -v, --version	prints version information and exits
+
+foo@bar:/home/dev/$ ./git commit --help
+Usage: commit [-h] [--all] [--message VAR]
+
+Record changes to the repository
+
+Optional arguments:
+  -h, --help   	shows help message and exits
+  -v, --version	prints version information and exits
+  -a, --all    	Tell the command to automatically stage files that have been modified and deleted.
+  -m, --message	Use the given <msg> as the commit message.
+
+foo@bar:/home/dev/$ ./git submodule --help
+Usage: submodule [-h] {update}
+
+Initialize, update or inspect submodules
+
+Optional arguments:
+  -h, --help   	shows help message and exits
+  -v, --version	prints version information and exits
+
+Subcommands:
+  update        Update the registered submodules to match what the superproject expects
+```
+
+When a help message is requested from a subparser, only the help for that particular parser will be printed. The help message will not include parent parser or sibling parser messages.
+
+Additionally, every parser has a `.is_subcommand_used("<command_name>")` member function to check if a subcommand was used. 
+
+### Parse Known Args
+
+Sometimes a program may only parse a few of the command-line arguments, passing the remaining arguments on to another script or program. In these cases, the `parse_known_args()` function can be useful. It works much like `parse_args()` except that it does not produce an error when extra arguments are present. Instead, it returns a list of remaining argument strings.
+
+```cpp
+#include <argparse/argparse.hpp>
+#include <cassert>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--foo").implicit_value(true).default_value(false);
+  program.add_argument("bar");
+
+  auto unknown_args =
+    program.parse_known_args({"test", "--foo", "--badger", "BAR", "spam"});
+
+  assert(program.get<bool>("--foo") == true);
+  assert(program.get<std::string>("bar") == std::string{"BAR"});
+  assert((unknown_args == std::vector<std::string>{"--badger", "spam"}));
+}
+```
+
+### Custom Prefix Characters 
+
+Most command-line options will use `-` as the prefix, e.g. `-f/--foo`. Parsers that need to support different or additional prefix characters, e.g. for options like `+f` or `/foo`, may specify them using the `set_prefix_chars()`.
+
+The default prefix character is `-`.
+
+```cpp
+#include <argparse/argparse.hpp>
+#include <cassert>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+  program.set_prefix_chars("-+/");
+
+  program.add_argument("+f");
+  program.add_argument("--bar");
+  program.add_argument("/foo");
+
+  try {
+    program.parse_args(argc, argv);
+  }
+  catch (const std::runtime_error& err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  if (program.is_used("+f")) {
+    std::cout << "+f    : " << program.get("+f") << "\n";
+  }
+
+  if (program.is_used("--bar")) {
+    std::cout << "--bar : " << program.get("--bar") << "\n";
+  }
+
+  if (program.is_used("/foo")) {
+    std::cout << "/foo  : " << program.get("/foo") << "\n";
+  }  
+}
+```
+
+```console
+foo@bar:/home/dev/$ ./main +f 5 --bar 3.14f /foo "Hello"
++f    : 5
+--bar : 3.14f
+/foo  : Hello
+```
+
+### Custom Assignment Characters 
+
+In addition to prefix characters, custom 'assign' characters can be set. This setting is used to allow invocations like `./test --foo=Foo /B:Bar`.
+
+The default assign character is `=`.
+
+```cpp
+#include <argparse/argparse.hpp>
+#include <cassert>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+  program.set_prefix_chars("-+/");
+  program.set_assign_chars("=:");
+
+  program.add_argument("--foo");
+  program.add_argument("/B");
+
+  try {
+    program.parse_args(argc, argv);
+  }
+  catch (const std::runtime_error& err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  if (program.is_used("--foo")) {
+    std::cout << "--foo : " << program.get("--foo") << "\n";
+  }
+
+  if (program.is_used("/B")) {
+    std::cout << "/B    : " << program.get("/B") << "\n";
+  }
+}
+```
+
+```console
+foo@bar:/home/dev/$ ./main --foo=Foo /B:Bar
+--foo : Foo
+/B    : Bar
+```
+
+## Further Examples
+
+### Construct a JSON object from a filename argument
+
+```cpp
+argparse::ArgumentParser program("json_test");
+
+program.add_argument("config")
+  .action([](const std::string& value) {
+    // read a JSON file
+    std::ifstream stream(value);
+    nlohmann::json config_json;
+    stream >> config_json;
+    return config_json;
+  });
+
+try {
+  program.parse_args({"./test", "config.json"});
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+nlohmann::json config = program.get<nlohmann::json>("config");
+```
+
+### Positional Arguments with Compound Toggle Arguments
+
+```cpp
+argparse::ArgumentParser program("test");
+
+program.add_argument("numbers")
+  .nargs(3)
+  .scan<'i', int>();
+
+program.add_argument("-a")
+  .default_value(false)
+  .implicit_value(true);
+
+program.add_argument("-b")
+  .default_value(false)
+  .implicit_value(true);
+
+program.add_argument("-c")
+  .nargs(2)
+  .scan<'g', float>();
+
+program.add_argument("--files")
+  .nargs(3);
+
+try {
+  program.parse_args(argc, argv);
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+auto numbers = program.get<std::vector<int>>("numbers");        // {1, 2, 3}
+auto a = program.get<bool>("-a");                               // true
+auto b = program.get<bool>("-b");                               // true
+auto c = program.get<std::vector<float>>("-c");                 // {3.14f, 2.718f}
+auto files = program.get<std::vector<std::string>>("--files");  // {"a.txt", "b.txt", "c.txt"}
+
+/// Some code that prints parsed arguments
+```
+
+```console
+foo@bar:/home/dev/$ ./main 1 2 3 -abc 3.14 2.718 --files a.txt b.txt c.txt
+numbers = {1, 2, 3}
+a = true
+b = true
+c = {3.14, 2.718}
+files = {"a.txt", "b.txt", "c.txt"}
+```
+
+### Restricting the set of values for an argument
+
+```cpp
+argparse::ArgumentParser program("test");
+
+program.add_argument("input")
+  .default_value(std::string{"baz"})
+  .action([](const std::string& value) {
+    static const std::vector<std::string> choices = { "foo", "bar", "baz" };
+    if (std::find(choices.begin(), choices.end(), value) != choices.end()) {
+      return value;
+    }
+    return std::string{ "baz" };
+  });
+
+try {
+  program.parse_args(argc, argv);
+}
+catch (const std::runtime_error& err) {
+  std::cerr << err.what() << std::endl;
+  std::cerr << program;
+  std::exit(1);
+}
+
+auto input = program.get("input");
+std::cout << input << std::endl;
+```
+
+```console
+foo@bar:/home/dev/$ ./main fex
+baz
+```
+
+## Using `option=value` syntax
+
+```cpp
+#include "argparse.hpp"
+#include <cassert>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--foo").implicit_value(true).default_value(false);
+  program.add_argument("--bar");
+
+  try {
+    program.parse_args(argc, argv);
+  }
+  catch (const std::runtime_error& err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  if (program.is_used("--foo")) {
+    std::cout << "--foo: " << std::boolalpha << program.get<bool>("--foo") << "\n";
+  }
+
+  if (program.is_used("--bar")) {
+    std::cout << "--bar: " << program.get("--bar") << "\n";
+  }  
+}
+```
+
+```console
+foo@bar:/home/dev/$ ./test --bar=BAR --foo
+--foo: true
+--bar: BAR
+```
+
+## CMake Integration 
+
+Use the latest argparse in your CMake project without copying any content.  
+
+```cmake
+cmake_minimum_required(VERSION 3.11)
+
+PROJECT(myproject)
+
+# fetch latest argparse
+include(FetchContent)
+FetchContent_Declare(
+    argparse
+    GIT_REPOSITORY https://github.com/p-ranav/argparse.git
+)
+FetchContent_MakeAvailable(argparse)
+
+add_executable(myproject main.cpp)
+target_link_libraries(myproject argparse)
+```
+
+## Building, Installing, and Testing
+
+```bash
+# Clone the repository
+git clone https://github.com/p-ranav/argparse
+cd argparse
+
+# Build the tests
+mkdir build
+cd build
+cmake -DARGPARSE_BUILD_SAMPLES=on -DARGPARSE_BUILD_TESTS=on ..
+make
+
+# Run tests
+./test/tests
+
+# Install the library
+sudo make install
+```
+
+## Supported Toolchains
+
+| Compiler             | Standard Library | Test Environment   |
+| :------------------- | :--------------- | :----------------- |
+| GCC >= 8.3.0         | libstdc++        | Ubuntu 18.04       |
+| Clang >= 7.0.0       | libc++           | Xcode 10.2         |
+| MSVC >= 14.16        | Microsoft STL    | Visual Studio 2017 |
+
+## Contributing
+Contributions are welcome, have a look at the [CONTRIBUTING.md](CONTRIBUTING.md) document for more information.
+
+## License
+The project is available under the [MIT](https://opensource.org/licenses/MIT) license.
diff --git a/thirdparty/argparse/clang_format.bash b/thirdparty/argparse/clang_format.bash
new file mode 100644
index 0000000000..518de183ef
--- /dev/null
+++ b/thirdparty/argparse/clang_format.bash
@@ -0,0 +1 @@
+clang-format -i include/argparse/*.hpp test/*.cpp samples/*.cpp
diff --git a/thirdparty/argparse/conanfile.py b/thirdparty/argparse/conanfile.py
new file mode 100644
index 0000000000..743e5f3f77
--- /dev/null
+++ b/thirdparty/argparse/conanfile.py
@@ -0,0 +1,10 @@
+from conans import ConanFile
+
+class ArgparseConan(ConanFile):
+    name = "argparse"
+    version = "2.9"
+    exports_sources = "include/argparse.hpp"
+    no_copy_source = True
+
+    def package(self):
+        self.copy("argparse.hpp")
diff --git a/thirdparty/argparse/include/argparse/argparse.hpp b/thirdparty/argparse/include/argparse/argparse.hpp
new file mode 100644
index 0000000000..5e826c58b2
--- /dev/null
+++ b/thirdparty/argparse/include/argparse/argparse.hpp
@@ -0,0 +1,1652 @@
+/*
+  __ _ _ __ __ _ _ __   __ _ _ __ ___  ___
+ / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++
+| (_| | | | (_| | |_) | (_| | |  \__ \  __/ http://github.com/p-ranav/argparse
+ \__,_|_|  \__, | .__/ \__,_|_|  |___/\___|
+           |___/|_|
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+SPDX-License-Identifier: MIT
+Copyright (c) 2019-2022 Pranav Srinivas Kumar <pranav.srinivas.kumar@gmail.com>
+and other contributors.
+
+Permission is hereby  granted, free of charge, to any  person obtaining a copy
+of this software and associated  documentation files (the "Software"), to deal
+in the Software  without restriction, including without  limitation the rights
+to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
+copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
+IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
+FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
+AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
+LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+#pragma once
+#include <algorithm>
+#include <any>
+#include <array>
+#include <cerrno>
+#include <charconv>
+#include <cstdlib>
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <iterator>
+#include <limits>
+#include <list>
+#include <map>
+#include <numeric>
+#include <optional>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace argparse {
+
+namespace details { // namespace for helper methods
+
+template <typename T, typename = void>
+struct HasContainerTraits : std::false_type {};
+
+template <> struct HasContainerTraits<std::string> : std::false_type {};
+
+template <typename T>
+struct HasContainerTraits<
+    T, std::void_t<typename T::value_type, decltype(std::declval<T>().begin()),
+                   decltype(std::declval<T>().end()),
+                   decltype(std::declval<T>().size())>> : std::true_type {};
+
+template <typename T>
+static constexpr bool IsContainer = HasContainerTraits<T>::value;
+
+template <typename T, typename = void>
+struct HasStreamableTraits : std::false_type {};
+
+template <typename T>
+struct HasStreamableTraits<
+    T,
+    std::void_t<decltype(std::declval<std::ostream &>() << std::declval<T>())>>
+    : std::true_type {};
+
+template <typename T>
+static constexpr bool IsStreamable = HasStreamableTraits<T>::value;
+
+constexpr std::size_t repr_max_container_size = 5;
+
+template <typename T> std::string repr(T const &val) {
+  if constexpr (std::is_same_v<T, bool>) {
+    return val ? "true" : "false";
+  } else if constexpr (std::is_convertible_v<T, std::string_view>) {
+    return '"' + std::string{std::string_view{val}} + '"';
+  } else if constexpr (IsContainer<T>) {
+    std::stringstream out;
+    out << "{";
+    const auto size = val.size();
+    if (size > 1) {
+      out << repr(*val.begin());
+      std::for_each(
+          std::next(val.begin()),
+          std::next(
+              val.begin(),
+              static_cast<typename T::iterator::difference_type>(
+                  std::min<std::size_t>(size, repr_max_container_size) - 1)),
+          [&out](const auto &v) { out << " " << repr(v); });
+      if (size <= repr_max_container_size) {
+        out << " ";
+      } else {
+        out << "...";
+      }
+    }
+    if (size > 0) {
+      out << repr(*std::prev(val.end()));
+    }
+    out << "}";
+    return out.str();
+  } else if constexpr (IsStreamable<T>) {
+    std::stringstream out;
+    out << val;
+    return out.str();
+  } else {
+    return "<not representable>";
+  }
+}
+
+namespace {
+
+template <typename T> constexpr bool standard_signed_integer = false;
+template <> constexpr bool standard_signed_integer<signed char> = true;
+template <> constexpr bool standard_signed_integer<short int> = true;
+template <> constexpr bool standard_signed_integer<int> = true;
+template <> constexpr bool standard_signed_integer<long int> = true;
+template <> constexpr bool standard_signed_integer<long long int> = true;
+
+template <typename T> constexpr bool standard_unsigned_integer = false;
+template <> constexpr bool standard_unsigned_integer<unsigned char> = true;
+template <> constexpr bool standard_unsigned_integer<unsigned short int> = true;
+template <> constexpr bool standard_unsigned_integer<unsigned int> = true;
+template <> constexpr bool standard_unsigned_integer<unsigned long int> = true;
+template <>
+constexpr bool standard_unsigned_integer<unsigned long long int> = true;
+
+} // namespace
+
+constexpr int radix_8 = 8;
+constexpr int radix_10 = 10;
+constexpr int radix_16 = 16;
+
+template <typename T>
+constexpr bool standard_integer =
+    standard_signed_integer<T> || standard_unsigned_integer<T>;
+
+template <class F, class Tuple, class Extra, std::size_t... I>
+constexpr decltype(auto)
+apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x,
+                    std::index_sequence<I...> /*unused*/) {
+  return std::invoke(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...,
+                     std::forward<Extra>(x));
+}
+
+template <class F, class Tuple, class Extra>
+constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) {
+  return details::apply_plus_one_impl(
+      std::forward<F>(f), std::forward<Tuple>(t), std::forward<Extra>(x),
+      std::make_index_sequence<
+          std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
+}
+
+constexpr auto pointer_range(std::string_view s) noexcept {
+  return std::tuple(s.data(), s.data() + s.size());
+}
+
+template <class CharT, class Traits>
+constexpr bool starts_with(std::basic_string_view<CharT, Traits> prefix,
+                           std::basic_string_view<CharT, Traits> s) noexcept {
+  return s.substr(0, prefix.size()) == prefix;
+}
+
+enum class chars_format {
+  scientific = 0x1,
+  fixed = 0x2,
+  hex = 0x4,
+  general = fixed | scientific
+};
+
+struct ConsumeHexPrefixResult {
+  bool is_hexadecimal;
+  std::string_view rest;
+};
+
+using namespace std::literals;
+
+constexpr auto consume_hex_prefix(std::string_view s)
+    -> ConsumeHexPrefixResult {
+  if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
+    s.remove_prefix(2);
+    return {true, s};
+  }
+  return {false, s};
+}
+
+template <class T, auto Param>
+inline auto do_from_chars(std::string_view s) -> T {
+  T x;
+  auto [first, last] = pointer_range(s);
+  auto [ptr, ec] = std::from_chars(first, last, x, Param);
+  if (ec == std::errc()) {
+    if (ptr == last) {
+      return x;
+    }
+    throw std::invalid_argument{"pattern does not match to the end"};
+  }
+  if (ec == std::errc::invalid_argument) {
+    throw std::invalid_argument{"pattern not found"};
+  }
+  if (ec == std::errc::result_out_of_range) {
+    throw std::range_error{"not representable"};
+  }
+  return x; // unreachable
+}
+
+template <class T, auto Param = 0> struct parse_number {
+  auto operator()(std::string_view s) -> T {
+    return do_from_chars<T, Param>(s);
+  }
+};
+
+template <class T> struct parse_number<T, radix_16> {
+  auto operator()(std::string_view s) -> T {
+    if (auto [ok, rest] = consume_hex_prefix(s); ok) {
+      return do_from_chars<T, radix_16>(rest);
+    }
+    throw std::invalid_argument{"pattern not found"};
+  }
+};
+
+template <class T> struct parse_number<T> {
+  auto operator()(std::string_view s) -> T {
+    auto [ok, rest] = consume_hex_prefix(s);
+    if (ok) {
+      return do_from_chars<T, radix_16>(rest);
+    }
+    if (starts_with("0"sv, s)) {
+      return do_from_chars<T, radix_8>(rest);
+    }
+    return do_from_chars<T, radix_10>(rest);
+  }
+};
+
+namespace {
+
+template <class T> inline const auto generic_strtod = nullptr;
+template <> inline const auto generic_strtod<float> = strtof;
+template <> inline const auto generic_strtod<double> = strtod;
+template <> inline const auto generic_strtod<long double> = strtold;
+
+} // namespace
+
+template <class T> inline auto do_strtod(std::string const &s) -> T {
+  if (isspace(static_cast<unsigned char>(s[0])) || s[0] == '+') {
+    throw std::invalid_argument{"pattern not found"};
+  }
+
+  auto [first, last] = pointer_range(s);
+  char *ptr;
+
+  errno = 0;
+  auto x = generic_strtod<T>(first, &ptr);
+  if (errno == 0) {
+    if (ptr == last) {
+      return x;
+    }
+    throw std::invalid_argument{"pattern does not match to the end"};
+  }
+  if (errno == ERANGE) {
+    throw std::range_error{"not representable"};
+  }
+  return x; // unreachable
+}
+
+template <class T> struct parse_number<T, chars_format::general> {
+  auto operator()(std::string const &s) -> T {
+    if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
+      throw std::invalid_argument{
+          "chars_format::general does not parse hexfloat"};
+    }
+
+    return do_strtod<T>(s);
+  }
+};
+
+template <class T> struct parse_number<T, chars_format::hex> {
+  auto operator()(std::string const &s) -> T {
+    if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) {
+      throw std::invalid_argument{"chars_format::hex parses hexfloat"};
+    }
+
+    return do_strtod<T>(s);
+  }
+};
+
+template <class T> struct parse_number<T, chars_format::scientific> {
+  auto operator()(std::string const &s) -> T {
+    if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
+      throw std::invalid_argument{
+          "chars_format::scientific does not parse hexfloat"};
+    }
+    if (s.find_first_of("eE") == std::string::npos) {
+      throw std::invalid_argument{
+          "chars_format::scientific requires exponent part"};
+    }
+
+    return do_strtod<T>(s);
+  }
+};
+
+template <class T> struct parse_number<T, chars_format::fixed> {
+  auto operator()(std::string const &s) -> T {
+    if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
+      throw std::invalid_argument{
+          "chars_format::fixed does not parse hexfloat"};
+    }
+    if (s.find_first_of("eE") != std::string::npos) {
+      throw std::invalid_argument{
+          "chars_format::fixed does not parse exponent part"};
+    }
+
+    return do_strtod<T>(s);
+  }
+};
+
+template <typename StrIt>
+std::string join(StrIt first, StrIt last, const std::string &separator) {
+  if (first == last) {
+    return "";
+  }
+  std::stringstream value;
+  value << *first;
+  ++first;
+  while (first != last) {
+    value << separator << *first;
+    ++first;
+  }
+  return value.str();
+}
+
+} // namespace details
+
+enum class nargs_pattern { optional, any, at_least_one };
+
+enum class default_arguments : unsigned int {
+  none = 0,
+  help = 1,
+  version = 2,
+  all = help | version,
+};
+
+inline default_arguments operator&(const default_arguments &a,
+                                   const default_arguments &b) {
+  return static_cast<default_arguments>(
+      static_cast<std::underlying_type<default_arguments>::type>(a) &
+      static_cast<std::underlying_type<default_arguments>::type>(b));
+}
+
+class ArgumentParser;
+
+class Argument {
+  friend class ArgumentParser;
+  friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
+      -> std::ostream &;
+
+  template <std::size_t N, std::size_t... I>
+  explicit Argument(std::string_view prefix_chars,
+                    std::array<std::string_view, N> &&a,
+                    std::index_sequence<I...> /*unused*/)
+      : m_is_optional((is_optional(a[I], prefix_chars) || ...)),
+        m_is_required(false), m_is_repeatable(false), m_is_used(false),
+        m_prefix_chars(prefix_chars) {
+    ((void)m_names.emplace_back(a[I]), ...);
+    std::sort(
+        m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) {
+          return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size();
+        });
+  }
+
+public:
+  template <std::size_t N>
+  explicit Argument(std::string_view prefix_chars,
+                    std::array<std::string_view, N> &&a)
+      : Argument(prefix_chars, std::move(a), std::make_index_sequence<N>{}) {}
+
+  Argument &help(std::string help_text) {
+    m_help = std::move(help_text);
+    return *this;
+  }
+
+  Argument &metavar(std::string metavar) {
+    m_metavar = std::move(metavar);
+    return *this;
+  }
+
+  template <typename T> Argument &default_value(T &&value) {
+    m_default_value_repr = details::repr(value);
+    m_default_value = std::forward<T>(value);
+    return *this;
+  }
+
+  Argument &required() {
+    m_is_required = true;
+    return *this;
+  }
+
+  Argument &implicit_value(std::any value) {
+    m_implicit_value = std::move(value);
+    m_num_args_range = NArgsRange{0, 0};
+    return *this;
+  }
+
+  template <class F, class... Args>
+  auto action(F &&callable, Args &&... bound_args)
+      -> std::enable_if_t<std::is_invocable_v<F, Args..., std::string const>,
+                          Argument &> {
+    using action_type = std::conditional_t<
+        std::is_void_v<std::invoke_result_t<F, Args..., std::string const>>,
+        void_action, valued_action>;
+    if constexpr (sizeof...(Args) == 0) {
+      m_action.emplace<action_type>(std::forward<F>(callable));
+    } else {
+      m_action.emplace<action_type>(
+          [f = std::forward<F>(callable),
+           tup = std::make_tuple(std::forward<Args>(bound_args)...)](
+              std::string const &opt) mutable {
+            return details::apply_plus_one(f, tup, opt);
+          });
+    }
+    return *this;
+  }
+
+  auto &append() {
+    m_is_repeatable = true;
+    return *this;
+  }
+
+  template <char Shape, typename T>
+  auto scan() -> std::enable_if_t<std::is_arithmetic_v<T>, Argument &> {
+    static_assert(!(std::is_const_v<T> || std::is_volatile_v<T>),
+                  "T should not be cv-qualified");
+    auto is_one_of = [](char c, auto... x) constexpr {
+      return ((c == x) || ...);
+    };
+
+    if constexpr (is_one_of(Shape, 'd') && details::standard_integer<T>) {
+      action(details::parse_number<T, details::radix_10>());
+    } else if constexpr (is_one_of(Shape, 'i') &&
+                         details::standard_integer<T>) {
+      action(details::parse_number<T>());
+    } else if constexpr (is_one_of(Shape, 'u') &&
+                         details::standard_unsigned_integer<T>) {
+      action(details::parse_number<T, details::radix_10>());
+    } else if constexpr (is_one_of(Shape, 'o') &&
+                         details::standard_unsigned_integer<T>) {
+      action(details::parse_number<T, details::radix_8>());
+    } else if constexpr (is_one_of(Shape, 'x', 'X') &&
+                         details::standard_unsigned_integer<T>) {
+      action(details::parse_number<T, details::radix_16>());
+    } else if constexpr (is_one_of(Shape, 'a', 'A') &&
+                         std::is_floating_point_v<T>) {
+      action(details::parse_number<T, details::chars_format::hex>());
+    } else if constexpr (is_one_of(Shape, 'e', 'E') &&
+                         std::is_floating_point_v<T>) {
+      action(details::parse_number<T, details::chars_format::scientific>());
+    } else if constexpr (is_one_of(Shape, 'f', 'F') &&
+                         std::is_floating_point_v<T>) {
+      action(details::parse_number<T, details::chars_format::fixed>());
+    } else if constexpr (is_one_of(Shape, 'g', 'G') &&
+                         std::is_floating_point_v<T>) {
+      action(details::parse_number<T, details::chars_format::general>());
+    } else {
+      static_assert(alignof(T) == 0, "No scan specification for T");
+    }
+
+    return *this;
+  }
+
+  Argument &nargs(std::size_t num_args) {
+    m_num_args_range = NArgsRange{num_args, num_args};
+    return *this;
+  }
+
+  Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) {
+    m_num_args_range = NArgsRange{num_args_min, num_args_max};
+    return *this;
+  }
+
+  Argument &nargs(nargs_pattern pattern) {
+    switch (pattern) {
+    case nargs_pattern::optional:
+      m_num_args_range = NArgsRange{0, 1};
+      break;
+    case nargs_pattern::any:
+      m_num_args_range = NArgsRange{0, std::numeric_limits<std::size_t>::max()};
+      break;
+    case nargs_pattern::at_least_one:
+      m_num_args_range = NArgsRange{1, std::numeric_limits<std::size_t>::max()};
+      break;
+    }
+    return *this;
+  }
+
+  Argument &remaining() {
+    m_accepts_optional_like_value = true;
+    return nargs(nargs_pattern::any);
+  }
+
+  template <typename Iterator>
+  Iterator consume(Iterator start, Iterator end,
+                   std::string_view used_name = {}) {
+    if (!m_is_repeatable && m_is_used) {
+      throw std::runtime_error("Duplicate argument");
+    }
+    m_is_used = true;
+    m_used_name = used_name;
+
+    const auto num_args_max = m_num_args_range.get_max();
+    const auto num_args_min = m_num_args_range.get_min();
+    std::size_t dist = 0;
+    if (num_args_max == 0) {
+      m_values.emplace_back(m_implicit_value);
+      std::visit([](const auto &f) { f({}); }, m_action);
+      return start;
+    }
+    if ((dist = static_cast<std::size_t>(std::distance(start, end))) >=
+        num_args_min) {
+      if (num_args_max < dist) {
+        end = std::next(start, static_cast<typename Iterator::difference_type>(
+                                   num_args_max));
+      }
+      if (!m_accepts_optional_like_value) {
+        end = std::find_if(
+            start, end,
+            std::bind(is_optional, std::placeholders::_1, m_prefix_chars));
+        dist = static_cast<std::size_t>(std::distance(start, end));
+        if (dist < num_args_min) {
+          throw std::runtime_error("Too few arguments");
+        }
+      }
+
+      struct ActionApply {
+        void operator()(valued_action &f) {
+          std::transform(first, last, std::back_inserter(self.m_values), f);
+        }
+
+        void operator()(void_action &f) {
+          std::for_each(first, last, f);
+          if (!self.m_default_value.has_value()) {
+            if (!self.m_accepts_optional_like_value) {
+              self.m_values.resize(
+                  static_cast<std::size_t>(std::distance(first, last)));
+            }
+          }
+        }
+
+        Iterator first, last;
+        Argument &self;
+      };
+      std::visit(ActionApply{start, end, *this}, m_action);
+      return end;
+    }
+    if (m_default_value.has_value()) {
+      return start;
+    }
+    throw std::runtime_error("Too few arguments for '" +
+                             std::string(m_used_name) + "'.");
+  }
+
+  /*
+   * @throws std::runtime_error if argument values are not valid
+   */
+  void validate() const {
+    if (m_is_optional) {
+      // TODO: check if an implicit value was programmed for this argument
+      if (!m_is_used && !m_default_value.has_value() && m_is_required) {
+        throw_required_arg_not_used_error();
+      }
+      if (m_is_used && m_is_required && m_values.empty()) {
+        throw_required_arg_no_value_provided_error();
+      }
+    } else {
+      if (!m_num_args_range.contains(m_values.size()) &&
+          !m_default_value.has_value()) {
+        throw_nargs_range_validation_error();
+      }
+    }
+  }
+
+  std::string get_inline_usage() const {
+    std::stringstream usage;
+    // Find the longest variant to show in the usage string
+    std::string longest_name = m_names[0];
+    for (const auto &s : m_names) {
+      if (s.size() > longest_name.size()) {
+        longest_name = s;
+      }
+    }
+    if (!m_is_required) {
+      usage << "[";
+    }
+    usage << longest_name;
+    const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR";
+    if (m_num_args_range.get_max() > 0) {
+      usage << " " << metavar;
+      if (m_num_args_range.get_max() > 1) {
+        usage << "...";
+      }
+    }
+    if (!m_is_required) {
+      usage << "]";
+    }
+    return usage.str();
+  }
+
+  std::size_t get_arguments_length() const {
+
+    std::size_t names_size = std::accumulate(
+        std::begin(m_names), std::end(m_names), std::size_t(0),
+        [](const auto &sum, const auto &s) { return sum + s.size(); });
+
+    if (is_positional(m_names.front(), m_prefix_chars)) {
+      // A set metavar means this replaces the names
+      if (!m_metavar.empty()) {
+        // Indent and metavar
+        return 2 + m_metavar.size();
+      }
+
+      // Indent and space-separated
+      return 2 + names_size + (m_names.size() - 1);
+    }
+    // Is an option - include both names _and_ metavar
+    // size = text + (", " between names)
+    std::size_t size = names_size + 2 * (m_names.size() - 1);
+    if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) {
+      size += m_metavar.size() + 1;
+    }
+    return size + 2; // indent
+  }
+
+  friend std::ostream &operator<<(std::ostream &stream,
+                                  const Argument &argument) {
+    std::stringstream name_stream;
+    name_stream << "  "; // indent
+    if (argument.is_positional(argument.m_names.front(),
+                               argument.m_prefix_chars)) {
+      if (!argument.m_metavar.empty()) {
+        name_stream << argument.m_metavar;
+      } else {
+        name_stream << details::join(argument.m_names.begin(),
+                                     argument.m_names.end(), " ");
+      }
+    } else {
+      name_stream << details::join(argument.m_names.begin(),
+                                   argument.m_names.end(), ", ");
+      // If we have a metavar, and one narg - print the metavar
+      if (!argument.m_metavar.empty() &&
+          argument.m_num_args_range == NArgsRange{1, 1}) {
+        name_stream << " " << argument.m_metavar;
+      }
+    }
+    stream << name_stream.str() << "\t" << argument.m_help;
+
+    // print nargs spec
+    if (!argument.m_help.empty()) {
+      stream << " ";
+    }
+    stream << argument.m_num_args_range;
+
+    if (argument.m_default_value.has_value() &&
+        argument.m_num_args_range != NArgsRange{0, 0}) {
+      stream << "[default: " << argument.m_default_value_repr << "]";
+    } else if (argument.m_is_required) {
+      stream << "[required]";
+    }
+    stream << "\n";
+    return stream;
+  }
+
+  template <typename T> bool operator!=(const T &rhs) const {
+    return !(*this == rhs);
+  }
+
+  /*
+   * Compare to an argument value of known type
+   * @throws std::logic_error in case of incompatible types
+   */
+  template <typename T> bool operator==(const T &rhs) const {
+    if constexpr (!details::IsContainer<T>) {
+      return get<T>() == rhs;
+    } else {
+      auto lhs = get<T>();
+      return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs),
+                        std::end(rhs),
+                        [](const auto &a, const auto &b) { return a == b; });
+    }
+  }
+
+private:
+  class NArgsRange {
+    std::size_t m_min;
+    std::size_t m_max;
+
+  public:
+    NArgsRange(std::size_t minimum, std::size_t maximum)
+        : m_min(minimum), m_max(maximum) {
+      if (minimum > maximum) {
+        throw std::logic_error("Range of number of arguments is invalid");
+      }
+    }
+
+    bool contains(std::size_t value) const {
+      return value >= m_min && value <= m_max;
+    }
+
+    bool is_exact() const { return m_min == m_max; }
+
+    bool is_right_bounded() const {
+      return m_max < std::numeric_limits<std::size_t>::max();
+    }
+
+    std::size_t get_min() const { return m_min; }
+
+    std::size_t get_max() const { return m_max; }
+
+    // Print help message
+    friend auto operator<<(std::ostream &stream, const NArgsRange &range)
+        -> std::ostream & {
+      if (range.m_min == range.m_max) {
+        if (range.m_min != 0 && range.m_min != 1) {
+          stream << "[nargs: " << range.m_min << "] ";
+        }
+      } else {
+        if (range.m_max == std::numeric_limits<std::size_t>::max()) {
+          stream << "[nargs: " << range.m_min << " or more] ";
+        } else {
+          stream << "[nargs=" << range.m_min << ".." << range.m_max << "] ";
+        }
+      }
+      return stream;
+    }
+
+    bool operator==(const NArgsRange &rhs) const {
+      return rhs.m_min == m_min && rhs.m_max == m_max;
+    }
+
+    bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); }
+  };
+
+  void throw_nargs_range_validation_error() const {
+    std::stringstream stream;
+    if (!m_used_name.empty()) {
+      stream << m_used_name << ": ";
+    }
+    if (m_num_args_range.is_exact()) {
+      stream << m_num_args_range.get_min();
+    } else if (m_num_args_range.is_right_bounded()) {
+      stream << m_num_args_range.get_min() << " to "
+             << m_num_args_range.get_max();
+    } else {
+      stream << m_num_args_range.get_min() << " or more";
+    }
+    stream << " argument(s) expected. " << m_values.size() << " provided.";
+    throw std::runtime_error(stream.str());
+  }
+
+  void throw_required_arg_not_used_error() const {
+    std::stringstream stream;
+    stream << m_names[0] << ": required.";
+    throw std::runtime_error(stream.str());
+  }
+
+  void throw_required_arg_no_value_provided_error() const {
+    std::stringstream stream;
+    stream << m_used_name << ": no value provided.";
+    throw std::runtime_error(stream.str());
+  }
+
+  static constexpr int eof = std::char_traits<char>::eof();
+
+  static auto lookahead(std::string_view s) -> int {
+    if (s.empty()) {
+      return eof;
+    }
+    return static_cast<int>(static_cast<unsigned char>(s[0]));
+  }
+
+  /*
+   * decimal-literal:
+   *    '0'
+   *    nonzero-digit digit-sequence_opt
+   *    integer-part fractional-part
+   *    fractional-part
+   *    integer-part '.' exponent-part_opt
+   *    integer-part exponent-part
+   *
+   * integer-part:
+   *    digit-sequence
+   *
+   * fractional-part:
+   *    '.' post-decimal-point
+   *
+   * post-decimal-point:
+   *    digit-sequence exponent-part_opt
+   *
+   * exponent-part:
+   *    'e' post-e
+   *    'E' post-e
+   *
+   * post-e:
+   *    sign_opt digit-sequence
+   *
+   * sign: one of
+   *    '+' '-'
+   */
+  static bool is_decimal_literal(std::string_view s) {
+    auto is_digit = [](auto c) constexpr {
+      switch (c) {
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+      case '6':
+      case '7':
+      case '8':
+      case '9':
+        return true;
+      default:
+        return false;
+      }
+    };
+
+    // precondition: we have consumed or will consume at least one digit
+    auto consume_digits = [=](std::string_view sd) {
+      // NOLINTNEXTLINE(readability-qualified-auto)
+      auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit);
+      return sd.substr(static_cast<std::size_t>(it - std::begin(sd)));
+    };
+
+    switch (lookahead(s)) {
+    case '0': {
+      s.remove_prefix(1);
+      if (s.empty()) {
+        return true;
+      }
+      goto integer_part;
+    }
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9': {
+      s = consume_digits(s);
+      if (s.empty()) {
+        return true;
+      }
+      goto integer_part_consumed;
+    }
+    case '.': {
+      s.remove_prefix(1);
+      goto post_decimal_point;
+    }
+    default:
+      return false;
+    }
+
+  integer_part:
+    s = consume_digits(s);
+  integer_part_consumed:
+    switch (lookahead(s)) {
+    case '.': {
+      s.remove_prefix(1);
+      if (is_digit(lookahead(s))) {
+        goto post_decimal_point;
+      } else {
+        goto exponent_part_opt;
+      }
+    }
+    case 'e':
+    case 'E': {
+      s.remove_prefix(1);
+      goto post_e;
+    }
+    default:
+      return false;
+    }
+
+  post_decimal_point:
+    if (is_digit(lookahead(s))) {
+      s = consume_digits(s);
+      goto exponent_part_opt;
+    }
+    return false;
+
+  exponent_part_opt:
+    switch (lookahead(s)) {
+    case eof:
+      return true;
+    case 'e':
+    case 'E': {
+      s.remove_prefix(1);
+      goto post_e;
+    }
+    default:
+      return false;
+    }
+
+  post_e:
+    switch (lookahead(s)) {
+    case '-':
+    case '+':
+      s.remove_prefix(1);
+    }
+    if (is_digit(lookahead(s))) {
+      s = consume_digits(s);
+      return s.empty();
+    }
+    return false;
+  }
+
+  static bool is_optional(std::string_view name,
+                          std::string_view prefix_chars) {
+    return !is_positional(name, prefix_chars);
+  }
+
+  /*
+   * positional:
+   *    _empty_
+   *    '-'
+   *    '-' decimal-literal
+   *    !'-' anything
+   */
+  static bool is_positional(std::string_view name,
+                            std::string_view prefix_chars) {
+    auto first = lookahead(name);
+
+    if (first == eof) {
+      return true;
+    } else if (prefix_chars.find(static_cast<char>(first)) !=
+               std::string_view::npos) {
+      name.remove_prefix(1);
+      if (name.empty()) {
+        return true;
+      }
+      return is_decimal_literal(name);
+    }
+    return true;
+  }
+
+  /*
+   * Get argument value given a type
+   * @throws std::logic_error in case of incompatible types
+   */
+  template <typename T>
+  auto get() const
+      -> std::conditional_t<details::IsContainer<T>, T, const T &> {
+    if (!m_values.empty()) {
+      if constexpr (details::IsContainer<T>) {
+        return any_cast_container<T>(m_values);
+      } else {
+        return *std::any_cast<T>(&m_values.front());
+      }
+    }
+    if (m_default_value.has_value()) {
+      return *std::any_cast<T>(&m_default_value);
+    }
+    if constexpr (details::IsContainer<T>) {
+      if (!m_accepts_optional_like_value) {
+        return any_cast_container<T>(m_values);
+      }
+    }
+
+    throw std::logic_error("No value provided for '" + m_names.back() + "'.");
+  }
+
+  /*
+   * Get argument value given a type.
+   * @pre The object has no default value.
+   * @returns The stored value if any, std::nullopt otherwise.
+   */
+  template <typename T> auto present() const -> std::optional<T> {
+    if (m_default_value.has_value()) {
+      throw std::logic_error("Argument with default value always presents");
+    }
+    if (m_values.empty()) {
+      return std::nullopt;
+    }
+    if constexpr (details::IsContainer<T>) {
+      return any_cast_container<T>(m_values);
+    }
+    return std::any_cast<T>(m_values.front());
+  }
+
+  template <typename T>
+  static auto any_cast_container(const std::vector<std::any> &operand) -> T {
+    using ValueType = typename T::value_type;
+
+    T result;
+    std::transform(
+        std::begin(operand), std::end(operand), std::back_inserter(result),
+        [](const auto &value) { return *std::any_cast<ValueType>(&value); });
+    return result;
+  }
+
+  std::vector<std::string> m_names;
+  std::string_view m_used_name;
+  std::string m_help;
+  std::string m_metavar;
+  std::any m_default_value;
+  std::string m_default_value_repr;
+  std::any m_implicit_value;
+  using valued_action = std::function<std::any(const std::string &)>;
+  using void_action = std::function<void(const std::string &)>;
+  std::variant<valued_action, void_action> m_action{
+      std::in_place_type<valued_action>,
+      [](const std::string &value) { return value; }};
+  std::vector<std::any> m_values;
+  NArgsRange m_num_args_range{1, 1};
+  bool m_accepts_optional_like_value = false;
+  bool m_is_optional : true;
+  bool m_is_required : true;
+  bool m_is_repeatable : true;
+  bool m_is_used : true; // True if the optional argument is used by user
+  std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars
+};
+
+class ArgumentParser {
+public:
+  explicit ArgumentParser(std::string program_name = {},
+                          std::string version = "1.0",
+                          default_arguments add_args = default_arguments::all)
+      : m_program_name(std::move(program_name)), m_version(std::move(version)),
+        m_parser_path(m_program_name) {
+    if ((add_args & default_arguments::help) == default_arguments::help) {
+      add_argument("-h", "--help")
+          .action([&](const auto & /*unused*/) {
+            std::cout << help().str();
+            std::exit(0);
+          })
+          .default_value(false)
+          .help("shows help message and exits")
+          .implicit_value(true)
+          .nargs(0);
+    }
+    if ((add_args & default_arguments::version) == default_arguments::version) {
+      add_argument("-v", "--version")
+          .action([&](const auto & /*unused*/) {
+            std::cout << m_version << std::endl;
+            std::exit(0);
+          })
+          .default_value(false)
+          .help("prints version information and exits")
+          .implicit_value(true)
+          .nargs(0);
+    }
+  }
+
+  ArgumentParser(ArgumentParser &&) noexcept = default;
+  ArgumentParser &operator=(ArgumentParser &&) = default;
+
+  ArgumentParser(const ArgumentParser &other)
+      : m_program_name(other.m_program_name), m_version(other.m_version),
+        m_description(other.m_description), m_epilog(other.m_epilog),
+        m_prefix_chars(other.m_prefix_chars),
+        m_assign_chars(other.m_assign_chars), m_is_parsed(other.m_is_parsed),
+        m_positional_arguments(other.m_positional_arguments),
+        m_optional_arguments(other.m_optional_arguments),
+        m_parser_path(other.m_parser_path), m_subparsers(other.m_subparsers) {
+    for (auto it = std::begin(m_positional_arguments);
+         it != std::end(m_positional_arguments); ++it) {
+      index_argument(it);
+    }
+    for (auto it = std::begin(m_optional_arguments);
+         it != std::end(m_optional_arguments); ++it) {
+      index_argument(it);
+    }
+    for (auto it = std::begin(m_subparsers); it != std::end(m_subparsers);
+         ++it) {
+      m_subparser_map.insert_or_assign(it->get().m_program_name, it);
+      m_subparser_used.insert_or_assign(it->get().m_program_name, false);
+    }
+  }
+
+  ~ArgumentParser() = default;
+
+  ArgumentParser &operator=(const ArgumentParser &other) {
+    auto tmp = other;
+    std::swap(*this, tmp);
+    return *this;
+  }
+
+  // Parameter packing
+  // Call add_argument with variadic number of string arguments
+  template <typename... Targs> Argument &add_argument(Targs... f_args) {
+    using array_of_sv = std::array<std::string_view, sizeof...(Targs)>;
+    auto argument =
+        m_optional_arguments.emplace(std::cend(m_optional_arguments),
+                                     m_prefix_chars, array_of_sv{f_args...});
+
+    if (!argument->m_is_optional) {
+      m_positional_arguments.splice(std::cend(m_positional_arguments),
+                                    m_optional_arguments, argument);
+    }
+
+    index_argument(argument);
+    return *argument;
+  }
+
+  // Parameter packed add_parents method
+  // Accepts a variadic number of ArgumentParser objects
+  template <typename... Targs>
+  ArgumentParser &add_parents(const Targs &... f_args) {
+    for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) {
+      for (const auto &argument : parent_parser.m_positional_arguments) {
+        auto it = m_positional_arguments.insert(
+            std::cend(m_positional_arguments), argument);
+        index_argument(it);
+      }
+      for (const auto &argument : parent_parser.m_optional_arguments) {
+        auto it = m_optional_arguments.insert(std::cend(m_optional_arguments),
+                                              argument);
+        index_argument(it);
+      }
+    }
+    return *this;
+  }
+
+  ArgumentParser &add_description(std::string description) {
+    m_description = std::move(description);
+    return *this;
+  }
+
+  ArgumentParser &add_epilog(std::string epilog) {
+    m_epilog = std::move(epilog);
+    return *this;
+  }
+
+  ArgumentParser &set_prefix_chars(std::string prefix_chars) {
+    m_prefix_chars = std::move(prefix_chars);
+    return *this;
+  }
+
+  ArgumentParser &set_assign_chars(std::string assign_chars) {
+    m_assign_chars = std::move(assign_chars);
+    return *this;
+  }
+
+  /* Call parse_args_internal - which does all the work
+   * Then, validate the parsed arguments
+   * This variant is used mainly for testing
+   * @throws std::runtime_error in case of any invalid argument
+   */
+  void parse_args(const std::vector<std::string> &arguments) {
+    parse_args_internal(arguments);
+    // Check if all arguments are parsed
+    for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
+      argument->validate();
+    }
+  }
+
+  /* Call parse_known_args_internal - which does all the work
+   * Then, validate the parsed arguments
+   * This variant is used mainly for testing
+   * @throws std::runtime_error in case of any invalid argument
+   */
+  std::vector<std::string>
+  parse_known_args(const std::vector<std::string> &arguments) {
+    auto unknown_arguments = parse_known_args_internal(arguments);
+    // Check if all arguments are parsed
+    for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
+      argument->validate();
+    }
+    return unknown_arguments;
+  }
+
+  /* Main entry point for parsing command-line arguments using this
+   * ArgumentParser
+   * @throws std::runtime_error in case of any invalid argument
+   */
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
+  void parse_args(int argc, const char *const argv[]) {
+    parse_args({argv, argv + argc});
+  }
+
+  /* Main entry point for parsing command-line arguments using this
+   * ArgumentParser
+   * @throws std::runtime_error in case of any invalid argument
+   */
+  // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
+  auto parse_known_args(int argc, const char *const argv[]) {
+    return parse_known_args({argv, argv + argc});
+  }
+
+  /* Getter for options with default values.
+   * @throws std::logic_error if parse_args() has not been previously called
+   * @throws std::logic_error if there is no such option
+   * @throws std::logic_error if the option has no value
+   * @throws std::bad_any_cast if the option is not of type T
+   */
+  template <typename T = std::string>
+  auto get(std::string_view arg_name) const
+      -> std::conditional_t<details::IsContainer<T>, T, const T &> {
+    if (!m_is_parsed) {
+      throw std::logic_error("Nothing parsed, no arguments are available.");
+    }
+    return (*this)[arg_name].get<T>();
+  }
+
+  /* Getter for options without default values.
+   * @pre The option has no default value.
+   * @throws std::logic_error if there is no such option
+   * @throws std::bad_any_cast if the option is not of type T
+   */
+  template <typename T = std::string>
+  auto present(std::string_view arg_name) const -> std::optional<T> {
+    return (*this)[arg_name].present<T>();
+  }
+
+  /* Getter that returns true for user-supplied options. Returns false if not
+   * user-supplied, even with a default value.
+   */
+  auto is_used(std::string_view arg_name) const {
+    return (*this)[arg_name].m_is_used;
+  }
+
+  /* Getter that returns true for user-supplied options. Returns false if not
+   * user-supplied, even with a default value.
+   */
+  auto is_subcommand_used(std::string_view subcommand_name) const {
+    return m_subparser_used.at(subcommand_name);
+  }
+
+  /* Indexing operator. Return a reference to an Argument object
+   * Used in conjuction with Argument.operator== e.g., parser["foo"] == true
+   * @throws std::logic_error in case of an invalid argument name
+   */
+  Argument &operator[](std::string_view arg_name) const {
+    auto it = m_argument_map.find(arg_name);
+    if (it != m_argument_map.end()) {
+      return *(it->second);
+    }
+    if (!is_valid_prefix_char(arg_name.front())) {
+      std::string name(arg_name);
+      const auto legal_prefix_char = get_any_valid_prefix_char();
+      const auto prefix = std::string(1, legal_prefix_char);
+
+      // "-" + arg_name
+      name = prefix + name;
+      it = m_argument_map.find(name);
+      if (it != m_argument_map.end()) {
+        return *(it->second);
+      }
+      // "--" + arg_name
+      name = prefix + name;
+      it = m_argument_map.find(name);
+      if (it != m_argument_map.end()) {
+        return *(it->second);
+      }
+    }
+    throw std::logic_error("No such argument: " + std::string(arg_name));
+  }
+
+  // Print help message
+  friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
+      -> std::ostream & {
+    stream.setf(std::ios_base::left);
+
+    auto longest_arg_length = parser.get_length_of_longest_argument();
+
+    stream << parser.usage() << "\n\n";
+
+    if (!parser.m_description.empty()) {
+      stream << parser.m_description << "\n\n";
+    }
+
+    if (!parser.m_positional_arguments.empty()) {
+      stream << "Positional arguments:\n";
+    }
+
+    for (const auto &argument : parser.m_positional_arguments) {
+      stream.width(static_cast<std::streamsize>(longest_arg_length));
+      stream << argument;
+    }
+
+    if (!parser.m_optional_arguments.empty()) {
+      stream << (parser.m_positional_arguments.empty() ? "" : "\n")
+             << "Optional arguments:\n";
+    }
+
+    for (const auto &argument : parser.m_optional_arguments) {
+      stream.width(static_cast<std::streamsize>(longest_arg_length));
+      stream << argument;
+    }
+
+    if (!parser.m_subparser_map.empty()) {
+      stream << (parser.m_positional_arguments.empty()
+                     ? (parser.m_optional_arguments.empty() ? "" : "\n")
+                     : "\n")
+             << "Subcommands:\n";
+      for (const auto &[command, subparser] : parser.m_subparser_map) {
+        stream << std::setw(2) << " ";
+        stream << std::setw(static_cast<int>(longest_arg_length - 2))
+               << command;
+        stream << " " << subparser->get().m_description << "\n";
+      }
+    }
+
+    if (!parser.m_epilog.empty()) {
+      stream << '\n';
+      stream << parser.m_epilog << "\n\n";
+    }
+
+    return stream;
+  }
+
+  // Format help message
+  auto help() const -> std::stringstream {
+    std::stringstream out;
+    out << *this;
+    return out;
+  }
+
+  // Format usage part of help only
+  auto usage() const -> std::string {
+    std::stringstream stream;
+
+    stream << "Usage: " << this->m_program_name;
+
+    // Add any options inline here
+    for (const auto &argument : this->m_optional_arguments) {
+      if (argument.m_names[0] == "-v") {
+        continue;
+      } else if (argument.m_names[0] == "-h") {
+        stream << " [-h]";
+      } else {
+        stream << " " << argument.get_inline_usage();
+      }
+    }
+    // Put positional arguments after the optionals
+    for (const auto &argument : this->m_positional_arguments) {
+      if (!argument.m_metavar.empty()) {
+        stream << " " << argument.m_metavar;
+      } else {
+        stream << " " << argument.m_names.front();
+      }
+    }
+    // Put subcommands after positional arguments
+    if (!m_subparser_map.empty()) {
+      stream << " {";
+      std::size_t i{0};
+      for (const auto &[command, unused] : m_subparser_map) {
+        if (i == 0) {
+          stream << command;
+        } else {
+          stream << "," << command;
+        }
+        ++i;
+      }
+      stream << "}";
+    }
+
+    return stream.str();
+  }
+
+  // Printing the one and only help message
+  // I've stuck with a simple message format, nothing fancy.
+  [[deprecated("Use cout << program; instead.  See also help().")]] std::string
+  print_help() const {
+    auto out = help();
+    std::cout << out.rdbuf();
+    return out.str();
+  }
+
+  void add_subparser(ArgumentParser &parser) {
+    parser.m_parser_path = m_program_name + " " + parser.m_program_name;
+    auto it = m_subparsers.emplace(std::cend(m_subparsers), parser);
+    m_subparser_map.insert_or_assign(parser.m_program_name, it);
+    m_subparser_used.insert_or_assign(parser.m_program_name, false);
+  }
+
+private:
+  bool is_valid_prefix_char(char c) const {
+    return m_prefix_chars.find(c) != std::string::npos;
+  }
+
+  char get_any_valid_prefix_char() const { return m_prefix_chars[0]; }
+
+  /*
+   * Pre-process this argument list. Anything starting with "--", that
+   * contains an =, where the prefix before the = has an entry in the
+   * options table, should be split.
+   */
+  std::vector<std::string>
+  preprocess_arguments(const std::vector<std::string> &raw_arguments) const {
+    std::vector<std::string> arguments{};
+    for (const auto &arg : raw_arguments) {
+
+      const auto argument_starts_with_prefix_chars =
+          [this](const std::string &a) -> bool {
+        if (!a.empty()) {
+
+          const auto legal_prefix = [this](char c) -> bool {
+            return m_prefix_chars.find(c) != std::string::npos;
+          };
+
+          // Windows-style
+          // if '/' is a legal prefix char
+          // then allow single '/' followed by argument name, followed by an
+          // assign char, e.g., ':' e.g., 'test.exe /A:Foo'
+          const auto windows_style = legal_prefix('/');
+
+          if (windows_style) {
+            if (legal_prefix(a[0])) {
+              return true;
+            }
+          } else {
+            // Slash '/' is not a legal prefix char
+            // For all other characters, only support long arguments
+            // i.e., the argument must start with 2 prefix chars, e.g,
+            // '--foo' e,g, './test --foo=Bar -DARG=yes'
+            if (a.size() > 1) {
+              return (legal_prefix(a[0]) && legal_prefix(a[1]));
+            }
+          }
+        }
+        return false;
+      };
+
+      // Check that:
+      // - We don't have an argument named exactly this
+      // - The argument starts with a prefix char, e.g., "--"
+      // - The argument contains an assign char, e.g., "="
+      auto assign_char_pos = arg.find_first_of(m_assign_chars);
+
+      if (m_argument_map.find(arg) == m_argument_map.end() &&
+          argument_starts_with_prefix_chars(arg) &&
+          assign_char_pos != std::string::npos) {
+        // Get the name of the potential option, and check it exists
+        std::string opt_name = arg.substr(0, assign_char_pos);
+        if (m_argument_map.find(opt_name) != m_argument_map.end()) {
+          // This is the name of an option! Split it into two parts
+          arguments.push_back(std::move(opt_name));
+          arguments.push_back(arg.substr(assign_char_pos + 1));
+          continue;
+        }
+      }
+      // If we've fallen through to here, then it's a standard argument
+      arguments.push_back(arg);
+    }
+    return arguments;
+  }
+
+  /*
+   * @throws std::runtime_error in case of any invalid argument
+   */
+  void parse_args_internal(const std::vector<std::string> &raw_arguments) {
+    auto arguments = preprocess_arguments(raw_arguments);
+    if (m_program_name.empty() && !arguments.empty()) {
+      m_program_name = arguments.front();
+    }
+    auto end = std::end(arguments);
+    auto positional_argument_it = std::begin(m_positional_arguments);
+    for (auto it = std::next(std::begin(arguments)); it != end;) {
+      const auto &current_argument = *it;
+      if (Argument::is_positional(current_argument, m_prefix_chars)) {
+        if (positional_argument_it == std::end(m_positional_arguments)) {
+
+          std::string_view maybe_command = current_argument;
+
+          // Check sub-parsers
+          auto subparser_it = m_subparser_map.find(maybe_command);
+          if (subparser_it != m_subparser_map.end()) {
+
+            // build list of remaining args
+            const auto unprocessed_arguments =
+                std::vector<std::string>(it, end);
+
+            // invoke subparser
+            m_is_parsed = true;
+            m_subparser_used[maybe_command] = true;
+            return subparser_it->second->get().parse_args(
+                unprocessed_arguments);
+          }
+
+          throw std::runtime_error(
+              "Maximum number of positional arguments exceeded");
+        }
+        auto argument = positional_argument_it++;
+        it = argument->consume(it, end);
+        continue;
+      }
+
+      auto arg_map_it = m_argument_map.find(current_argument);
+      if (arg_map_it != m_argument_map.end()) {
+        auto argument = arg_map_it->second;
+        it = argument->consume(std::next(it), end, arg_map_it->first);
+      } else if (const auto &compound_arg = current_argument;
+                 compound_arg.size() > 1 &&
+                 is_valid_prefix_char(compound_arg[0]) &&
+                 !is_valid_prefix_char(compound_arg[1])) {
+        ++it;
+        for (std::size_t j = 1; j < compound_arg.size(); j++) {
+          auto hypothetical_arg = std::string{'-', compound_arg[j]};
+          auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
+          if (arg_map_it2 != m_argument_map.end()) {
+            auto argument = arg_map_it2->second;
+            it = argument->consume(it, end, arg_map_it2->first);
+          } else {
+            throw std::runtime_error("Unknown argument: " + current_argument);
+          }
+        }
+      } else {
+        throw std::runtime_error("Unknown argument: " + current_argument);
+      }
+    }
+    m_is_parsed = true;
+  }
+
+  /*
+   * Like parse_args_internal but collects unused args into a vector<string>
+   */
+  std::vector<std::string>
+  parse_known_args_internal(const std::vector<std::string> &raw_arguments) {
+    auto arguments = preprocess_arguments(raw_arguments);
+
+    std::vector<std::string> unknown_arguments{};
+
+    if (m_program_name.empty() && !arguments.empty()) {
+      m_program_name = arguments.front();
+    }
+    auto end = std::end(arguments);
+    auto positional_argument_it = std::begin(m_positional_arguments);
+    for (auto it = std::next(std::begin(arguments)); it != end;) {
+      const auto &current_argument = *it;
+      if (Argument::is_positional(current_argument, m_prefix_chars)) {
+        if (positional_argument_it == std::end(m_positional_arguments)) {
+
+          std::string_view maybe_command = current_argument;
+
+          // Check sub-parsers
+          auto subparser_it = m_subparser_map.find(maybe_command);
+          if (subparser_it != m_subparser_map.end()) {
+
+            // build list of remaining args
+            const auto unprocessed_arguments =
+                std::vector<std::string>(it, end);
+
+            // invoke subparser
+            m_is_parsed = true;
+            m_subparser_used[maybe_command] = true;
+            return subparser_it->second->get().parse_known_args_internal(
+                unprocessed_arguments);
+          }
+
+          // save current argument as unknown and go to next argument
+          unknown_arguments.push_back(current_argument);
+          ++it;
+        } else {
+          // current argument is the value of a positional argument
+          // consume it
+          auto argument = positional_argument_it++;
+          it = argument->consume(it, end);
+        }
+        continue;
+      }
+
+      auto arg_map_it = m_argument_map.find(current_argument);
+      if (arg_map_it != m_argument_map.end()) {
+        auto argument = arg_map_it->second;
+        it = argument->consume(std::next(it), end, arg_map_it->first);
+      } else if (const auto &compound_arg = current_argument;
+                 compound_arg.size() > 1 &&
+                 is_valid_prefix_char(compound_arg[0]) &&
+                 !is_valid_prefix_char(compound_arg[1])) {
+        ++it;
+        for (std::size_t j = 1; j < compound_arg.size(); j++) {
+          auto hypothetical_arg = std::string{'-', compound_arg[j]};
+          auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
+          if (arg_map_it2 != m_argument_map.end()) {
+            auto argument = arg_map_it2->second;
+            it = argument->consume(it, end, arg_map_it2->first);
+          } else {
+            unknown_arguments.push_back(current_argument);
+            break;
+          }
+        }
+      } else {
+        // current argument is an optional-like argument that is unknown
+        // save it and move to next argument
+        unknown_arguments.push_back(current_argument);
+        ++it;
+      }
+    }
+    m_is_parsed = true;
+    return unknown_arguments;
+  }
+
+  // Used by print_help.
+  std::size_t get_length_of_longest_argument() const {
+    if (m_argument_map.empty()) {
+      return 0;
+    }
+    std::size_t max_size = 0;
+    for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
+      max_size = std::max(max_size, argument->get_arguments_length());
+    }
+    for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) {
+      max_size = std::max(max_size, command.size());
+    }
+    return max_size;
+  }
+
+  using argument_it = std::list<Argument>::iterator;
+  using argument_parser_it =
+      std::list<std::reference_wrapper<ArgumentParser>>::iterator;
+
+  void index_argument(argument_it it) {
+    for (const auto &name : std::as_const(it->m_names)) {
+      m_argument_map.insert_or_assign(name, it);
+    }
+  }
+
+  std::string m_program_name;
+  std::string m_version;
+  std::string m_description;
+  std::string m_epilog;
+  std::string m_prefix_chars{"-"};
+  std::string m_assign_chars{"="};
+  bool m_is_parsed = false;
+  std::list<Argument> m_positional_arguments;
+  std::list<Argument> m_optional_arguments;
+  std::map<std::string_view, argument_it, std::less<>> m_argument_map;
+  std::string m_parser_path;
+  std::list<std::reference_wrapper<ArgumentParser>> m_subparsers;
+  std::map<std::string_view, argument_parser_it, std::less<>> m_subparser_map;
+  std::map<std::string_view, bool, std::less<>> m_subparser_used;
+};
+
+} // namespace argparse
diff --git a/thirdparty/argparse/packaging/pkgconfig.pc.in b/thirdparty/argparse/packaging/pkgconfig.pc.in
new file mode 100644
index 0000000000..9b6a44c632
--- /dev/null
+++ b/thirdparty/argparse/packaging/pkgconfig.pc.in
@@ -0,0 +1,7 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
+
+Name: @PROJECT_NAME@
+Description: @PROJECT_DESCRIPTION@
+Version: @PROJECT_VERSION@
+Cflags: -I${includedir}
diff --git a/thirdparty/argparse/samples/CMakeLists.txt b/thirdparty/argparse/samples/CMakeLists.txt
new file mode 100644
index 0000000000..b146b2d251
--- /dev/null
+++ b/thirdparty/argparse/samples/CMakeLists.txt
@@ -0,0 +1,46 @@
+cmake_minimum_required(VERSION 3.6)
+project(argparse_samples)
+
+if(MSVC)
+  # Force to always compile with W4
+  if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
+    string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+  else()
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
+  endif()
+elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
+  # Update if necessary
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic -Wsign-conversion -Wshadow -Wconversion")
+endif()
+
+if(NOT CMAKE_BUILD_TYPE)
+  set(CMAKE_BUILD_TYPE Release)
+endif()
+
+# Disable deprecation for windows
+if (WIN32)
+	add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
+endif()
+
+function(add_sample NAME)
+  ADD_EXECUTABLE(ARGPARSE_SAMPLE_${NAME} ${NAME}.cpp)
+  INCLUDE_DIRECTORIES("../include" ".")
+  TARGET_LINK_LIBRARIES(ARGPARSE_SAMPLE_${NAME} PRIVATE argparse::argparse)
+  set_target_properties(ARGPARSE_SAMPLE_${NAME} PROPERTIES OUTPUT_NAME ${NAME})
+endfunction()
+
+add_sample(positional_argument)
+add_sample(optional_flag_argument)
+add_sample(required_optional_argument)
+add_sample(is_used)
+add_sample(joining_repeated_optional_arguments)
+add_sample(repeating_argument_to_increase_value)
+add_sample(negative_numbers)
+add_sample(description_epilog_metavar)
+add_sample(list_of_arguments)
+add_sample(compound_arguments)
+add_sample(gathering_remaining_arguments)
+add_sample(subcommands)
+add_sample(parse_known_args)
+add_sample(custom_prefix_characters)
+add_sample(custom_assignment_characters)
\ No newline at end of file
diff --git a/thirdparty/argparse/samples/compound_arguments.cpp b/thirdparty/argparse/samples/compound_arguments.cpp
new file mode 100644
index 0000000000..c44d02d9f0
--- /dev/null
+++ b/thirdparty/argparse/samples/compound_arguments.cpp
@@ -0,0 +1,36 @@
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+
+  program.add_argument("-a").default_value(false).implicit_value(true);
+
+  program.add_argument("-b").default_value(false).implicit_value(true);
+
+  program.add_argument("-c")
+      .nargs(2)
+      .default_value(std::vector<float>{0.0f, 0.0f})
+      .scan<'g', float>();
+
+  try {
+    program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47
+  } catch (const std::runtime_error &err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  auto a = program.get<bool>("-a");               // true
+  auto b = program.get<bool>("-b");               // true
+  auto c = program.get<std::vector<float>>("-c"); // {1.95, 2.47}
+
+  std::cout << "a: " << std::boolalpha << a << "\n";
+  std::cout << "b: " << b << "\n";
+  if (!c.empty()) {
+    std::cout << "c: ";
+    for (auto &v : c) {
+      std::cout << v << " ";
+    }
+    std::cout << std::endl;
+  }
+}
\ No newline at end of file
diff --git a/thirdparty/argparse/samples/custom_assignment_characters.cpp b/thirdparty/argparse/samples/custom_assignment_characters.cpp
new file mode 100644
index 0000000000..7d9ab39d42
--- /dev/null
+++ b/thirdparty/argparse/samples/custom_assignment_characters.cpp
@@ -0,0 +1,27 @@
+#include <argparse/argparse.hpp>
+#include <cassert>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+  program.set_prefix_chars("-+/");
+  program.set_assign_chars("=:");
+
+  program.add_argument("--foo");
+  program.add_argument("/B");
+
+  try {
+    program.parse_args(argc, argv);
+  } catch (const std::runtime_error &err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  if (program.is_used("--foo")) {
+    std::cout << "--foo : " << program.get("--foo") << "\n";
+  }
+
+  if (program.is_used("/B")) {
+    std::cout << "/B    : " << program.get("/B") << "\n";
+  }
+}
\ No newline at end of file
diff --git a/thirdparty/argparse/samples/custom_prefix_characters.cpp b/thirdparty/argparse/samples/custom_prefix_characters.cpp
new file mode 100644
index 0000000000..cdce04de67
--- /dev/null
+++ b/thirdparty/argparse/samples/custom_prefix_characters.cpp
@@ -0,0 +1,31 @@
+#include <argparse/argparse.hpp>
+#include <cassert>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+  program.set_prefix_chars("-+/");
+
+  program.add_argument("+f");
+  program.add_argument("--bar");
+  program.add_argument("/foo");
+
+  try {
+    program.parse_args(argc, argv);
+  } catch (const std::runtime_error &err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  if (program.is_used("+f")) {
+    std::cout << "+f    : " << program.get("+f") << "\n";
+  }
+
+  if (program.is_used("--bar")) {
+    std::cout << "--bar : " << program.get("--bar") << "\n";
+  }
+
+  if (program.is_used("/foo")) {
+    std::cout << "/foo  : " << program.get("/foo") << "\n";
+  }
+}
\ No newline at end of file
diff --git a/thirdparty/argparse/samples/description_epilog_metavar.cpp b/thirdparty/argparse/samples/description_epilog_metavar.cpp
new file mode 100644
index 0000000000..ef7f11eaf8
--- /dev/null
+++ b/thirdparty/argparse/samples/description_epilog_metavar.cpp
@@ -0,0 +1,17 @@
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("main");
+  program.add_argument("thing").help("Thing to use.").metavar("THING");
+  program.add_argument("--member")
+      .help("The alias for the member to pass to.")
+      .metavar("ALIAS");
+  program.add_argument("--verbose").default_value(false).implicit_value(true);
+
+  program.add_description("Forward a thing to the next member.");
+  program.add_epilog("Possible things include betingalw, chiz, and res.");
+
+  program.parse_args(argc, argv);
+
+  std::cout << program << std::endl;
+}
\ No newline at end of file
diff --git a/thirdparty/argparse/samples/gathering_remaining_arguments.cpp b/thirdparty/argparse/samples/gathering_remaining_arguments.cpp
new file mode 100644
index 0000000000..e3fb84eede
--- /dev/null
+++ b/thirdparty/argparse/samples/gathering_remaining_arguments.cpp
@@ -0,0 +1,24 @@
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("compiler");
+
+  program.add_argument("files").remaining();
+
+  try {
+    program.parse_args(argc, argv);
+  } catch (const std::runtime_error &err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  try {
+    auto files = program.get<std::vector<std::string>>("files");
+    std::cout << files.size() << " files provided" << std::endl;
+    for (auto &file : files)
+      std::cout << file << std::endl;
+  } catch (std::logic_error &e) {
+    std::cout << "No files provided" << std::endl;
+  }
+}
\ No newline at end of file
diff --git a/thirdparty/argparse/samples/is_used.cpp b/thirdparty/argparse/samples/is_used.cpp
new file mode 100644
index 0000000000..a2f0a149c9
--- /dev/null
+++ b/thirdparty/argparse/samples/is_used.cpp
@@ -0,0 +1,26 @@
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+
+  program.add_argument("--color")
+      .default_value(std::string{
+          "orange"}) // might otherwise be type const char* leading to an error
+                     // when trying program.get<std::string>
+      .help("specify the cat's fur color");
+
+  try {
+    program.parse_args(argc, argv); // Example: ./main --color orange
+  } catch (const std::runtime_error &err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  auto color = program.get<std::string>("--color"); // "orange"
+  auto explicit_color =
+      program.is_used("--color"); // true, user provided orange
+  std::cout << "Color: " << color << "\n";
+  std::cout << "Argument was explicitly provided by user? " << std::boolalpha
+            << explicit_color << "\n";
+}
diff --git a/thirdparty/argparse/samples/joining_repeated_optional_arguments.cpp b/thirdparty/argparse/samples/joining_repeated_optional_arguments.cpp
new file mode 100644
index 0000000000..ab819c8fc6
--- /dev/null
+++ b/thirdparty/argparse/samples/joining_repeated_optional_arguments.cpp
@@ -0,0 +1,28 @@
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+
+  program.add_argument("--color")
+      .default_value<std::vector<std::string>>({"orange"})
+      .append()
+      .help("specify the cat's fur color");
+
+  try {
+    program.parse_args(
+        argc, argv); // Example: ./main --color red --color green --color blue
+  } catch (const std::runtime_error &err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  auto colors = program.get<std::vector<std::string>>(
+      "--color"); // {"red", "green", "blue"}
+
+  std::cout << "Colors: ";
+  for (const auto &c : colors) {
+    std::cout << c << " ";
+  }
+  std::cout << "\n";
+}
diff --git a/thirdparty/argparse/samples/list_of_arguments.cpp b/thirdparty/argparse/samples/list_of_arguments.cpp
new file mode 100644
index 0000000000..ef4d421051
--- /dev/null
+++ b/thirdparty/argparse/samples/list_of_arguments.cpp
@@ -0,0 +1,30 @@
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+
+  argparse::ArgumentParser program("main");
+
+  program.add_argument("--input_files")
+      .help("The list of input files")
+      .nargs(2);
+
+  try {
+    program.parse_args(
+        argc, argv); // Example: ./main --input_files config.yml System.xml
+  } catch (const std::runtime_error &err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  auto files = program.get<std::vector<std::string>>(
+      "--input_files"); // {"config.yml", "System.xml"}
+
+  if (!files.empty()) {
+    std::cout << "Files: ";
+    for (auto &file : files) {
+      std::cout << file << " ";
+    }
+    std::cout << std::endl;
+  }
+}
\ No newline at end of file
diff --git a/thirdparty/argparse/samples/negative_numbers.cpp b/thirdparty/argparse/samples/negative_numbers.cpp
new file mode 100644
index 0000000000..bb1c888f77
--- /dev/null
+++ b/thirdparty/argparse/samples/negative_numbers.cpp
@@ -0,0 +1,32 @@
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+
+  program.add_argument("integer").help("Input number").scan<'i', int>();
+
+  program.add_argument("floats")
+      .help("Vector of floats")
+      .nargs(4)
+      .scan<'g', float>();
+
+  try {
+    program.parse_args(argc, argv);
+  } catch (const std::runtime_error &err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  if (program.is_used("integer")) {
+    std::cout << "Integer : " << program.get<int>("integer") << "\n";
+  }
+
+  if (program.is_used("floats")) {
+    std::cout << "Floats  : ";
+    for (const auto &f : program.get<std::vector<float>>("floats")) {
+      std::cout << f << " ";
+    }
+    std::cout << std::endl;
+  }
+}
diff --git a/thirdparty/argparse/samples/optional_flag_argument.cpp b/thirdparty/argparse/samples/optional_flag_argument.cpp
new file mode 100644
index 0000000000..fff720a9e1
--- /dev/null
+++ b/thirdparty/argparse/samples/optional_flag_argument.cpp
@@ -0,0 +1,22 @@
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+
+  program.add_argument("--verbose")
+      .help("increase output verbosity")
+      .default_value(false)
+      .implicit_value(true);
+
+  try {
+    program.parse_args(argc, argv);
+  } catch (const std::runtime_error &err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  if (program["--verbose"] == true) {
+    std::cout << "Verbosity enabled" << std::endl;
+  }
+}
diff --git a/thirdparty/argparse/samples/parse_known_args.cpp b/thirdparty/argparse/samples/parse_known_args.cpp
new file mode 100644
index 0000000000..17b5f4f2c5
--- /dev/null
+++ b/thirdparty/argparse/samples/parse_known_args.cpp
@@ -0,0 +1,26 @@
+#include <argparse/argparse.hpp>
+#include <cassert>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--foo").implicit_value(true).default_value(false);
+  program.add_argument("bar");
+
+  auto unknown_args = program.parse_known_args(argc, argv);
+
+  if (program.is_used("--foo")) {
+    std::cout << "--foo        : " << program.get<bool>("--foo") << "\n";
+  }
+
+  if (program.is_used("bar")) {
+    std::cout << "bar          : " << program.get<std::string>("bar") << "\n";
+  }
+
+  if (!unknown_args.empty()) {
+    std::cout << "Unknown args : ";
+    for (const auto &u : unknown_args) {
+      std::cout << u << " ";
+    }
+    std::cout << std::endl;
+  }
+}
\ No newline at end of file
diff --git a/thirdparty/argparse/samples/positional_argument.cpp b/thirdparty/argparse/samples/positional_argument.cpp
new file mode 100644
index 0000000000..646ddc378b
--- /dev/null
+++ b/thirdparty/argparse/samples/positional_argument.cpp
@@ -0,0 +1,28 @@
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("main");
+
+  program.add_argument("square")
+      .help("display the square of a given number")
+      .scan<'i', int>();
+
+  program.add_argument("--verbose").default_value(false).implicit_value(true);
+
+  try {
+    program.parse_args(argc, argv);
+  } catch (const std::runtime_error &err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  int input = program.get<int>("square");
+
+  if (program["--verbose"] == true) {
+    std::cout << "The square of " << input << " is " << (input * input)
+              << std::endl;
+  } else {
+    std::cout << (input * input) << std::endl;
+  }
+}
diff --git a/thirdparty/argparse/samples/repeating_argument_to_increase_value.cpp b/thirdparty/argparse/samples/repeating_argument_to_increase_value.cpp
new file mode 100644
index 0000000000..e0de41e9ff
--- /dev/null
+++ b/thirdparty/argparse/samples/repeating_argument_to_increase_value.cpp
@@ -0,0 +1,17 @@
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+
+  int verbosity = 0;
+  program.add_argument("-V", "--verbose")
+      .action([&](const auto &) { ++verbosity; })
+      .append()
+      .default_value(false)
+      .implicit_value(true)
+      .nargs(0);
+
+  program.parse_args(argc, argv); // Example: ./main -VVVV
+
+  std::cout << "verbose level: " << verbosity << std::endl; // verbose level: 4
+}
diff --git a/thirdparty/argparse/samples/required_optional_argument.cpp b/thirdparty/argparse/samples/required_optional_argument.cpp
new file mode 100644
index 0000000000..1fe2dccc40
--- /dev/null
+++ b/thirdparty/argparse/samples/required_optional_argument.cpp
@@ -0,0 +1,19 @@
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("test");
+
+  program.add_argument("-o", "--output")
+      .required()
+      .help("specify the output file.");
+
+  try {
+    program.parse_args(argc, argv);
+  } catch (const std::runtime_error &err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  std::cout << "Output written to " << program.get("-o") << "\n";
+}
diff --git a/thirdparty/argparse/samples/subcommands.cpp b/thirdparty/argparse/samples/subcommands.cpp
new file mode 100644
index 0000000000..f88f05cec1
--- /dev/null
+++ b/thirdparty/argparse/samples/subcommands.cpp
@@ -0,0 +1,65 @@
+#include <argparse/argparse.hpp>
+
+int main(int argc, char *argv[]) {
+  argparse::ArgumentParser program("git");
+
+  // git add subparser
+  argparse::ArgumentParser add_command("add");
+  add_command.add_description("Add file contents to the index");
+  add_command.add_argument("files")
+      .help("Files to add content from. Fileglobs (e.g.  *.c) can be given to "
+            "add all matching files.")
+      .remaining();
+
+  // git commit subparser
+  argparse::ArgumentParser commit_command("commit");
+  commit_command.add_description("Record changes to the repository");
+  commit_command.add_argument("-a", "--all")
+      .help("Tell the command to automatically stage files that have been "
+            "modified and deleted.")
+      .default_value(false)
+      .implicit_value(true);
+
+  commit_command.add_argument("-m", "--message")
+      .help("Use the given <msg> as the commit message.");
+
+  // git cat-file subparser
+  argparse::ArgumentParser catfile_command("cat-file");
+  catfile_command.add_description(
+      "Provide content or type and size information for repository objects");
+  catfile_command.add_argument("-t").help(
+      "Instead of the content, show the object type identified by <object>.");
+
+  catfile_command.add_argument("-p").help(
+      "Pretty-print the contents of <object> based on its type.");
+
+  // git submodule subparser
+  argparse::ArgumentParser submodule_command("submodule");
+  submodule_command.add_description("Initialize, update or inspect submodules");
+  argparse::ArgumentParser submodule_update_command("update");
+  submodule_update_command.add_description(
+      "Update the registered submodules to match what the superproject "
+      "expects");
+  submodule_update_command.add_argument("--init")
+      .default_value(false)
+      .implicit_value(true);
+  submodule_update_command.add_argument("--recursive")
+      .default_value(false)
+      .implicit_value(true);
+  submodule_command.add_subparser(submodule_update_command);
+
+  program.add_subparser(add_command);
+  program.add_subparser(commit_command);
+  program.add_subparser(catfile_command);
+  program.add_subparser(submodule_command);
+
+  try {
+    program.parse_args(argc, argv);
+  } catch (const std::runtime_error &err) {
+    std::cerr << err.what() << std::endl;
+    std::cerr << program;
+    std::exit(1);
+  }
+
+  // Use arguments
+}
diff --git a/thirdparty/argparse/test/.gitignore b/thirdparty/argparse/test/.gitignore
new file mode 100644
index 0000000000..1fa72e71ba
--- /dev/null
+++ b/thirdparty/argparse/test/.gitignore
@@ -0,0 +1,2 @@
+build/
+build_linux/
\ No newline at end of file
diff --git a/thirdparty/argparse/test/CMakeLists.txt b/thirdparty/argparse/test/CMakeLists.txt
new file mode 100644
index 0000000000..c7a56572bc
--- /dev/null
+++ b/thirdparty/argparse/test/CMakeLists.txt
@@ -0,0 +1,63 @@
+cmake_minimum_required(VERSION 3.6)
+project(argparse_tests)
+
+if(MSVC)
+  # Force to always compile with W4
+  if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
+    string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+  else()
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
+  endif()
+elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
+  # Update if necessary
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic -Wsign-conversion -Wshadow -Wconversion")
+endif()
+
+if(NOT CMAKE_BUILD_TYPE)
+  set(CMAKE_BUILD_TYPE Release)
+endif()
+
+# Disable deprecation for windows
+if (WIN32)
+	add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
+endif()
+
+# ARGPARSE executable
+file(GLOB ARGPARSE_TEST_SOURCES
+    main.cpp
+    test_actions.cpp
+    test_append.cpp
+    test_compound_arguments.cpp
+    test_container_arguments.cpp
+    test_const_correct.cpp
+    test_default_args.cpp
+    test_get.cpp
+    test_help.cpp
+    test_invalid_arguments.cpp
+    test_is_used.cpp
+    test_issue_37.cpp
+    test_negative_numbers.cpp
+    test_optional_arguments.cpp
+    test_parent_parsers.cpp
+    test_parse_args.cpp
+    test_positional_arguments.cpp
+    test_repr.cpp
+    test_required_arguments.cpp
+    test_scan.cpp
+    test_value_semantics.cpp
+    test_version.cpp
+    test_subparsers.cpp
+    test_parse_known_args.cpp
+    test_equals_form.cpp
+    test_prefix_chars.cpp
+)
+set_source_files_properties(main.cpp
+    PROPERTIES
+    COMPILE_DEFINITIONS DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN)
+ADD_EXECUTABLE(ARGPARSE_TESTS ${ARGPARSE_TEST_SOURCES})
+INCLUDE_DIRECTORIES("../include" ".")
+set_target_properties(ARGPARSE_TESTS PROPERTIES OUTPUT_NAME tests)
+set_property(TARGET ARGPARSE_TESTS PROPERTY CXX_STANDARD 17)
+
+# Set ${PROJECT_NAME} as the startup project
+set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ARGPARSE_TESTS)
diff --git a/thirdparty/argparse/test/README.md b/thirdparty/argparse/test/README.md
new file mode 100644
index 0000000000..491cf1a674
--- /dev/null
+++ b/thirdparty/argparse/test/README.md
@@ -0,0 +1,25 @@
+# Argparse Tests
+
+## Linux
+
+```bash
+$ mkdir build
+$ cd build
+$ cmake ../.
+$ make
+$ ./tests
+```
+
+## Windows
+
+1. Generate Visual Studio solution
+
+```bash
+$ mkdir build
+$ cd build
+$ cmake ../. -G "Visual Studio 15 2017"
+```
+
+2. Open ARGPARSE.sln
+3. Build tests in RELEASE | x64
+4. Run tests.exe
diff --git a/thirdparty/argparse/test/doctest.hpp b/thirdparty/argparse/test/doctest.hpp
new file mode 100644
index 0000000000..d25f526827
--- /dev/null
+++ b/thirdparty/argparse/test/doctest.hpp
@@ -0,0 +1,6816 @@
+// ====================================================================== lgtm [cpp/missing-header-guard]
+// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! ==
+// ======================================================================
+//
+// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD
+//
+// Copyright (c) 2016-2021 Viktor Kirilov
+//
+// Distributed under the MIT Software License
+// See accompanying file LICENSE.txt or copy at
+// https://opensource.org/licenses/MIT
+//
+// The documentation can be found at the library's page:
+// https://github.com/doctest/doctest/blob/master/doc/markdown/readme.md
+//
+// =================================================================================================
+// =================================================================================================
+// =================================================================================================
+//
+// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2
+// which uses the Boost Software License - Version 1.0
+// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt
+//
+// The concept of subcases (sections in Catch) and expression decomposition are from there.
+// Some parts of the code are taken directly:
+// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<>
+// - the Approx() helper class for floating point comparison
+// - colors in the console
+// - breaking into a debugger
+// - signal / SEH handling
+// - timer
+// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste)
+//
+// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest
+// which uses the Boost Software License - Version 1.0
+// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt
+//
+// =================================================================================================
+// =================================================================================================
+// =================================================================================================
+
+#ifndef DOCTEST_LIBRARY_INCLUDED
+#define DOCTEST_LIBRARY_INCLUDED
+
+// =================================================================================================
+// == VERSION ======================================================================================
+// =================================================================================================
+
+#define DOCTEST_VERSION_MAJOR 2
+#define DOCTEST_VERSION_MINOR 4
+#define DOCTEST_VERSION_PATCH 8
+
+// util we need here
+#define DOCTEST_TOSTR_IMPL(x) #x
+#define DOCTEST_TOSTR(x) DOCTEST_TOSTR_IMPL(x)
+
+#define DOCTEST_VERSION_STR                                                                        \
+    DOCTEST_TOSTR(DOCTEST_VERSION_MAJOR) "."                                                       \
+    DOCTEST_TOSTR(DOCTEST_VERSION_MINOR) "."                                                       \
+    DOCTEST_TOSTR(DOCTEST_VERSION_PATCH)
+
+#define DOCTEST_VERSION                                                                            \
+    (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH)
+
+// =================================================================================================
+// == COMPILER VERSION =============================================================================
+// =================================================================================================
+
+// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect
+
+#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH))
+
+// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl...
+#if defined(_MSC_VER) && defined(_MSC_FULL_VER)
+#if _MSC_VER == _MSC_FULL_VER / 10000
+#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000)
+#else // MSVC
+#define DOCTEST_MSVC                                                                               \
+    DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000)
+#endif // MSVC
+#endif // MSVC
+#if defined(__clang__) && defined(__clang_minor__)
+#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__)
+#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) &&              \
+        !defined(__INTEL_COMPILER)
+#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
+#endif // GCC
+
+#ifndef DOCTEST_MSVC
+#define DOCTEST_MSVC 0
+#endif // DOCTEST_MSVC
+#ifndef DOCTEST_CLANG
+#define DOCTEST_CLANG 0
+#endif // DOCTEST_CLANG
+#ifndef DOCTEST_GCC
+#define DOCTEST_GCC 0
+#endif // DOCTEST_GCC
+
+// =================================================================================================
+// == COMPILER WARNINGS HELPERS ====================================================================
+// =================================================================================================
+
+#if DOCTEST_CLANG
+#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x)
+#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push")
+#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w)
+#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop")
+#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w)                                                \
+    DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w)
+#else // DOCTEST_CLANG
+#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+#define DOCTEST_CLANG_SUPPRESS_WARNING(w)
+#define DOCTEST_CLANG_SUPPRESS_WARNING_POP
+#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w)
+#endif // DOCTEST_CLANG
+
+#if DOCTEST_GCC
+#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x)
+#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push")
+#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w)
+#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop")
+#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w)                                                  \
+    DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w)
+#else // DOCTEST_GCC
+#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+#define DOCTEST_GCC_SUPPRESS_WARNING(w)
+#define DOCTEST_GCC_SUPPRESS_WARNING_POP
+#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w)
+#endif // DOCTEST_GCC
+
+#if DOCTEST_MSVC
+#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push))
+#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w))
+#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop))
+#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w)                                                 \
+    DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w)
+#else // DOCTEST_MSVC
+#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+#define DOCTEST_MSVC_SUPPRESS_WARNING(w)
+#define DOCTEST_MSVC_SUPPRESS_WARNING_POP
+#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w)
+#endif // DOCTEST_MSVC
+
+// =================================================================================================
+// == COMPILER WARNINGS ============================================================================
+// =================================================================================================
+
+// both the header and the implementation suppress all of these,
+// so it only makes sense to aggregrate them like so
+#define DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH                                                      \
+    DOCTEST_CLANG_SUPPRESS_WARNING_PUSH                                                            \
+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas")                                            \
+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables")                                               \
+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded")                                                     \
+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes")                                         \
+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef")                                       \
+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat")                                               \
+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")                                      \
+                                                                                                   \
+    DOCTEST_GCC_SUPPRESS_WARNING_PUSH                                                              \
+    DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas")                                              \
+    DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas")                                                      \
+    DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++")                                                       \
+    DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow")                                              \
+    DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing")                                              \
+    DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations")                                         \
+    DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs")                                        \
+    DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast")                                                 \
+    DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept")                                                     \
+                                                                                                   \
+    DOCTEST_MSVC_SUPPRESS_WARNING_PUSH                                                             \
+    /* these 4 also disabled globally via cmake: */                                                \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */        \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4571) /* SEH related */                                          \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */                                 \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/                \
+    /* */                                                                                          \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */                             \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */                             \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */    \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */             \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */                   \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */                                              \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */              \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */           \
+    DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */          \
+    DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */              \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */  \
+    DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */                   \
+    /* static analysis */                                                                          \
+    DOCTEST_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */       \
+    DOCTEST_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */                 \
+    DOCTEST_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */                             \
+    DOCTEST_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */  \
+    DOCTEST_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */
+
+#define DOCTEST_SUPPRESS_COMMON_WARNINGS_POP                                                       \
+    DOCTEST_CLANG_SUPPRESS_WARNING_POP                                                             \
+    DOCTEST_GCC_SUPPRESS_WARNING_POP                                                               \
+    DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH
+
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated")
+
+DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo")
+
+DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted
+
+#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN                                 \
+    DOCTEST_MSVC_SUPPRESS_WARNING_PUSH                                                             \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */       \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */     \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */      \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */                \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */                  \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */                             \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */                   \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4820) /* padding */                                              \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */              \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */           \
+    DOCTEST_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */          \
+    DOCTEST_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */              \
+    DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */           \
+    DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \
+    DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */                   \
+    DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */
+
+#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+// =================================================================================================
+// == FEATURE DETECTION ============================================================================
+// =================================================================================================
+
+// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support
+// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx
+// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html
+// MSVC version table:
+// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering
+// MSVC++ 14.3 (17) _MSC_VER == 1930 (Visual Studio 2022)
+// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019)
+// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017)
+// MSVC++ 14.0      _MSC_VER == 1900 (Visual Studio 2015)
+// MSVC++ 12.0      _MSC_VER == 1800 (Visual Studio 2013)
+// MSVC++ 11.0      _MSC_VER == 1700 (Visual Studio 2012)
+// MSVC++ 10.0      _MSC_VER == 1600 (Visual Studio 2010)
+// MSVC++ 9.0       _MSC_VER == 1500 (Visual Studio 2008)
+// MSVC++ 8.0       _MSC_VER == 1400 (Visual Studio 2005)
+
+// Universal Windows Platform support
+#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
+#define DOCTEST_CONFIG_NO_WINDOWS_SEH
+#endif // WINAPI_FAMILY
+#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH)
+#define DOCTEST_CONFIG_WINDOWS_SEH
+#endif // MSVC
+#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH)
+#undef DOCTEST_CONFIG_WINDOWS_SEH
+#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH
+
+#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) &&             \
+        !defined(__EMSCRIPTEN__)
+#define DOCTEST_CONFIG_POSIX_SIGNALS
+#endif // _WIN32
+#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS)
+#undef DOCTEST_CONFIG_POSIX_SIGNALS
+#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND)
+#define DOCTEST_CONFIG_NO_EXCEPTIONS
+#endif // no exceptions
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+#define DOCTEST_CONFIG_NO_EXCEPTIONS
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+
+#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS)
+#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+
+#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT)
+#define DOCTEST_CONFIG_IMPLEMENT
+#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+#if DOCTEST_MSVC
+#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport)
+#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport)
+#else // MSVC
+#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport))
+#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport))
+#endif // MSVC
+#else  // _WIN32
+#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default")))
+#define DOCTEST_SYMBOL_IMPORT
+#endif // _WIN32
+
+#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
+#ifdef DOCTEST_CONFIG_IMPLEMENT
+#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT
+#else // DOCTEST_CONFIG_IMPLEMENT
+#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT
+#endif // DOCTEST_CONFIG_IMPLEMENT
+#else  // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
+#define DOCTEST_INTERFACE
+#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
+
+#define DOCTEST_EMPTY
+
+#if DOCTEST_MSVC
+#define DOCTEST_NOINLINE __declspec(noinline)
+#define DOCTEST_UNUSED
+#define DOCTEST_ALIGNMENT(x)
+#elif DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 5, 0)
+#define DOCTEST_NOINLINE
+#define DOCTEST_UNUSED
+#define DOCTEST_ALIGNMENT(x)
+#else
+#define DOCTEST_NOINLINE __attribute__((noinline))
+#define DOCTEST_UNUSED __attribute__((unused))
+#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x)))
+#endif
+
+#ifndef DOCTEST_NORETURN
+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
+#define DOCTEST_NORETURN
+#else // DOCTEST_MSVC
+#define DOCTEST_NORETURN [[noreturn]]
+#endif // DOCTEST_MSVC
+#endif // DOCTEST_NORETURN
+
+#ifndef DOCTEST_NOEXCEPT
+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
+#define DOCTEST_NOEXCEPT
+#else // DOCTEST_MSVC
+#define DOCTEST_NOEXCEPT noexcept
+#endif // DOCTEST_MSVC
+#endif // DOCTEST_NOEXCEPT
+
+#ifndef DOCTEST_CONSTEXPR
+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
+#define DOCTEST_CONSTEXPR const
+#else // DOCTEST_MSVC
+#define DOCTEST_CONSTEXPR constexpr
+#endif // DOCTEST_MSVC
+#endif // DOCTEST_CONSTEXPR
+
+// =================================================================================================
+// == FEATURE DETECTION END ========================================================================
+// =================================================================================================
+
+// internal macros for string concatenation and anonymous variable name generation
+#define DOCTEST_CAT_IMPL(s1, s2) s1##s2
+#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2)
+#ifdef __COUNTER__ // not standard and may be missing for some compilers
+#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__)
+#else // __COUNTER__
+#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__)
+#endif // __COUNTER__
+
+#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
+#define DOCTEST_REF_WRAP(x) x&
+#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
+#define DOCTEST_REF_WRAP(x) x
+#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
+
+// not using __APPLE__ because... this is how Catch does it
+#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
+#define DOCTEST_PLATFORM_MAC
+#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
+#define DOCTEST_PLATFORM_IPHONE
+#elif defined(_WIN32)
+#define DOCTEST_PLATFORM_WINDOWS
+#else // DOCTEST_PLATFORM
+#define DOCTEST_PLATFORM_LINUX
+#endif // DOCTEST_PLATFORM
+
+namespace doctest { namespace detail {
+    static DOCTEST_CONSTEXPR int consume(const int*, int) { return 0; }
+}}
+
+#define DOCTEST_GLOBAL_NO_WARNINGS(var, ...)                                                       \
+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors")                              \
+    static const int var = doctest::detail::consume(&var, __VA_ARGS__);                            \
+    DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+#ifndef DOCTEST_BREAK_INTO_DEBUGGER
+// should probably take a look at https://github.com/scottt/debugbreak
+#ifdef DOCTEST_PLATFORM_LINUX
+#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))
+// Break at the location of the failing check if possible
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler)
+#else
+#include <signal.h>
+#define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP)
+#endif
+#elif defined(DOCTEST_PLATFORM_MAC)
+#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386)
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler)
+#else
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT (hicpp-no-assembler)
+#endif
+#elif DOCTEST_MSVC
+#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak()
+#elif defined(__MINGW32__)
+DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls")
+extern "C" __declspec(dllimport) void __stdcall DebugBreak();
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak()
+#else // linux
+#define DOCTEST_BREAK_INTO_DEBUGGER() (static_cast<void>(0))
+#endif // linux
+#endif // DOCTEST_BREAK_INTO_DEBUGGER
+
+// this is kept here for backwards compatibility since the config option was changed
+#ifdef DOCTEST_CONFIG_USE_IOSFWD
+#define DOCTEST_CONFIG_USE_STD_HEADERS
+#endif // DOCTEST_CONFIG_USE_IOSFWD
+
+// for clang - always include ciso646 (which drags some std stuff) because
+// we want to check if we are using libc++ with the _LIBCPP_VERSION macro in
+// which case we don't want to forward declare stuff from std - for reference:
+// https://github.com/doctest/doctest/issues/126
+// https://github.com/doctest/doctest/issues/356
+#if DOCTEST_CLANG
+#include <ciso646>
+#ifdef _LIBCPP_VERSION
+#define DOCTEST_CONFIG_USE_STD_HEADERS
+#endif // _LIBCPP_VERSION
+#endif // clang
+
+#ifdef DOCTEST_CONFIG_USE_STD_HEADERS
+#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#include <cstddef>
+#include <ostream>
+#include <istream>
+#else // DOCTEST_CONFIG_USE_STD_HEADERS
+
+// Forward declaring 'X' in namespace std is not permitted by the C++ Standard.
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643)
+
+namespace std { // NOLINT (cert-dcl58-cpp)
+typedef decltype(nullptr) nullptr_t;
+template <class charT>
+struct char_traits;
+template <>
+struct char_traits<char>;
+template <class charT, class traits>
+class basic_ostream;
+typedef basic_ostream<char, char_traits<char>> ostream;
+template <class charT, class traits>
+class basic_istream;
+typedef basic_istream<char, char_traits<char>> istream;
+template <class... Types>
+class tuple;
+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
+// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183
+template <class Ty>
+class allocator;
+template <class Elem, class Traits, class Alloc>
+class basic_string;
+using string = basic_string<char, char_traits<char>, allocator<char>>;
+#endif // VS 2019
+} // namespace std
+
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+#endif // DOCTEST_CONFIG_USE_STD_HEADERS
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#include <type_traits>
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+namespace doctest {
+
+DOCTEST_INTERFACE extern bool is_running_in_test;
+
+// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length
+// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for:
+// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128)
+// - if small - capacity left before going on the heap - using the lowest 5 bits
+// - if small - 2 bits are left unused - the second and third highest ones
+// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator)
+//              and the "is small" bit remains "0" ("as well as the capacity left") so its OK
+// Idea taken from this lecture about the string implementation of facebook/folly - fbstring
+// https://www.youtube.com/watch?v=kPR8h4-qZdk
+// TODO:
+// - optimizations - like not deleting memory unnecessarily in operator= and etc.
+// - resize/reserve/clear
+// - substr
+// - replace
+// - back/front
+// - iterator stuff
+// - find & friends
+// - push_back/pop_back
+// - assign/insert/erase
+// - relational operators as free functions - taking const char* as one of the params
+class DOCTEST_INTERFACE String
+{
+    static const unsigned len  = 24;      //!OCLINT avoid private static members
+    static const unsigned last = len - 1; //!OCLINT avoid private static members
+
+    struct view // len should be more than sizeof(view) - because of the final byte for flags
+    {
+        char*    ptr;
+        unsigned size;
+        unsigned capacity;
+    };
+
+    union
+    {
+        char buf[len];
+        view data;
+    };
+
+    char* allocate(unsigned sz);
+
+    bool isOnStack() const { return (buf[last] & 128) == 0; }
+    void setOnHeap();
+    void setLast(unsigned in = last);
+
+    void copy(const String& other);
+
+public:
+    String();
+    ~String();
+
+    // cppcheck-suppress noExplicitConstructor
+    String(const char* in);
+    String(const char* in, unsigned in_size);
+
+    String(std::istream& in, unsigned in_size);
+
+    String(const String& other);
+    String& operator=(const String& other);
+
+    String& operator+=(const String& other);
+
+    String(String&& other);
+    String& operator=(String&& other);
+
+    char  operator[](unsigned i) const;
+    char& operator[](unsigned i);
+
+    // the only functions I'm willing to leave in the interface - available for inlining
+    const char* c_str() const { return const_cast<String*>(this)->c_str(); } // NOLINT
+    char*       c_str() {
+        if(isOnStack())
+            return reinterpret_cast<char*>(buf);
+        return data.ptr;
+    }
+
+    unsigned size() const;
+    unsigned capacity() const;
+
+    int compare(const char* other, bool no_case = false) const;
+    int compare(const String& other, bool no_case = false) const;
+};
+
+DOCTEST_INTERFACE String operator+(const String& lhs, const String& rhs);
+
+DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs);
+DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs);
+
+DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in);
+
+namespace Color {
+    enum Enum
+    {
+        None = 0,
+        White,
+        Red,
+        Green,
+        Blue,
+        Cyan,
+        Yellow,
+        Grey,
+
+        Bright = 0x10,
+
+        BrightRed   = Bright | Red,
+        BrightGreen = Bright | Green,
+        LightGrey   = Bright | Grey,
+        BrightWhite = Bright | White
+    };
+
+    DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code);
+} // namespace Color
+
+namespace assertType {
+    enum Enum
+    {
+        // macro traits
+
+        is_warn    = 1,
+        is_check   = 2 * is_warn,
+        is_require = 2 * is_check,
+
+        is_normal      = 2 * is_require,
+        is_throws      = 2 * is_normal,
+        is_throws_as   = 2 * is_throws,
+        is_throws_with = 2 * is_throws_as,
+        is_nothrow     = 2 * is_throws_with,
+
+        is_false = 2 * is_nothrow,
+        is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types
+
+        is_eq = 2 * is_unary,
+        is_ne = 2 * is_eq,
+
+        is_lt = 2 * is_ne,
+        is_gt = 2 * is_lt,
+
+        is_ge = 2 * is_gt,
+        is_le = 2 * is_ge,
+
+        // macro types
+
+        DT_WARN    = is_normal | is_warn,
+        DT_CHECK   = is_normal | is_check,
+        DT_REQUIRE = is_normal | is_require,
+
+        DT_WARN_FALSE    = is_normal | is_false | is_warn,
+        DT_CHECK_FALSE   = is_normal | is_false | is_check,
+        DT_REQUIRE_FALSE = is_normal | is_false | is_require,
+
+        DT_WARN_THROWS    = is_throws | is_warn,
+        DT_CHECK_THROWS   = is_throws | is_check,
+        DT_REQUIRE_THROWS = is_throws | is_require,
+
+        DT_WARN_THROWS_AS    = is_throws_as | is_warn,
+        DT_CHECK_THROWS_AS   = is_throws_as | is_check,
+        DT_REQUIRE_THROWS_AS = is_throws_as | is_require,
+
+        DT_WARN_THROWS_WITH    = is_throws_with | is_warn,
+        DT_CHECK_THROWS_WITH   = is_throws_with | is_check,
+        DT_REQUIRE_THROWS_WITH = is_throws_with | is_require,
+        
+        DT_WARN_THROWS_WITH_AS    = is_throws_with | is_throws_as | is_warn,
+        DT_CHECK_THROWS_WITH_AS   = is_throws_with | is_throws_as | is_check,
+        DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require,
+
+        DT_WARN_NOTHROW    = is_nothrow | is_warn,
+        DT_CHECK_NOTHROW   = is_nothrow | is_check,
+        DT_REQUIRE_NOTHROW = is_nothrow | is_require,
+
+        DT_WARN_EQ    = is_normal | is_eq | is_warn,
+        DT_CHECK_EQ   = is_normal | is_eq | is_check,
+        DT_REQUIRE_EQ = is_normal | is_eq | is_require,
+
+        DT_WARN_NE    = is_normal | is_ne | is_warn,
+        DT_CHECK_NE   = is_normal | is_ne | is_check,
+        DT_REQUIRE_NE = is_normal | is_ne | is_require,
+
+        DT_WARN_GT    = is_normal | is_gt | is_warn,
+        DT_CHECK_GT   = is_normal | is_gt | is_check,
+        DT_REQUIRE_GT = is_normal | is_gt | is_require,
+
+        DT_WARN_LT    = is_normal | is_lt | is_warn,
+        DT_CHECK_LT   = is_normal | is_lt | is_check,
+        DT_REQUIRE_LT = is_normal | is_lt | is_require,
+
+        DT_WARN_GE    = is_normal | is_ge | is_warn,
+        DT_CHECK_GE   = is_normal | is_ge | is_check,
+        DT_REQUIRE_GE = is_normal | is_ge | is_require,
+
+        DT_WARN_LE    = is_normal | is_le | is_warn,
+        DT_CHECK_LE   = is_normal | is_le | is_check,
+        DT_REQUIRE_LE = is_normal | is_le | is_require,
+
+        DT_WARN_UNARY    = is_normal | is_unary | is_warn,
+        DT_CHECK_UNARY   = is_normal | is_unary | is_check,
+        DT_REQUIRE_UNARY = is_normal | is_unary | is_require,
+
+        DT_WARN_UNARY_FALSE    = is_normal | is_false | is_unary | is_warn,
+        DT_CHECK_UNARY_FALSE   = is_normal | is_false | is_unary | is_check,
+        DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require,
+    };
+} // namespace assertType
+
+DOCTEST_INTERFACE const char* assertString(assertType::Enum at);
+DOCTEST_INTERFACE const char* failureString(assertType::Enum at);
+DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file);
+
+struct DOCTEST_INTERFACE TestCaseData
+{
+    String      m_file;       // the file in which the test was registered (using String - see #350)
+    unsigned    m_line;       // the line where the test was registered
+    const char* m_name;       // name of the test case
+    const char* m_test_suite; // the test suite in which the test was added
+    const char* m_description;
+    bool        m_skip;
+    bool        m_no_breaks;
+    bool        m_no_output;
+    bool        m_may_fail;
+    bool        m_should_fail;
+    int         m_expected_failures;
+    double      m_timeout;
+};
+
+struct DOCTEST_INTERFACE AssertData
+{
+    // common - for all asserts
+    const TestCaseData* m_test_case;
+    assertType::Enum    m_at;
+    const char*         m_file;
+    int                 m_line;
+    const char*         m_expr;
+    bool                m_failed;
+
+    // exception-related - for all asserts
+    bool   m_threw;
+    String m_exception;
+
+    // for normal asserts
+    String m_decomp;
+
+    // for specific exception-related asserts
+    bool        m_threw_as;
+    const char* m_exception_type;
+    const char* m_exception_string;
+};
+
+struct DOCTEST_INTERFACE MessageData
+{
+    String           m_string;
+    const char*      m_file;
+    int              m_line;
+    assertType::Enum m_severity;
+};
+
+struct DOCTEST_INTERFACE SubcaseSignature
+{
+    String      m_name;
+    const char* m_file;
+    int         m_line;
+
+    bool operator<(const SubcaseSignature& other) const;
+};
+
+struct DOCTEST_INTERFACE IContextScope
+{
+    IContextScope();
+    virtual ~IContextScope();
+    virtual void stringify(std::ostream*) const = 0;
+};
+
+namespace detail {
+    struct DOCTEST_INTERFACE TestCase;
+} // namespace detail
+
+struct ContextOptions //!OCLINT too many fields
+{
+    std::ostream* cout = nullptr; // stdout stream
+    String        binary_name;    // the test binary name
+
+    const detail::TestCase* currentTest = nullptr;
+
+    // == parameters from the command line
+    String   out;       // output filename
+    String   order_by;  // how tests should be ordered
+    unsigned rand_seed; // the seed for rand ordering
+
+    unsigned first; // the first (matching) test to be executed
+    unsigned last;  // the last (matching) test to be executed
+
+    int abort_after;           // stop tests after this many failed assertions
+    int subcase_filter_levels; // apply the subcase filters for the first N levels
+
+    bool success;              // include successful assertions in output
+    bool case_sensitive;       // if filtering should be case sensitive
+    bool exit;                 // if the program should be exited after the tests are ran/whatever
+    bool duration;             // print the time duration of each test case
+    bool minimal;              // minimal console output (only test failures)
+    bool quiet;                // no console output
+    bool no_throw;             // to skip exceptions-related assertion macros
+    bool no_exitcode;          // if the framework should return 0 as the exitcode
+    bool no_run;               // to not run the tests at all (can be done with an "*" exclude)
+    bool no_intro;             // to not print the intro of the framework
+    bool no_version;           // to not print the version of the framework
+    bool no_colors;            // if output to the console should be colorized
+    bool force_colors;         // forces the use of colors even when a tty cannot be detected
+    bool no_breaks;            // to not break into the debugger
+    bool no_skip;              // don't skip test cases which are marked to be skipped
+    bool gnu_file_line;        // if line numbers should be surrounded with :x: and not (x):
+    bool no_path_in_filenames; // if the path to files should be removed from the output
+    bool no_line_numbers;      // if source code line numbers should be omitted from the output
+    bool no_debug_output;      // no output in the debug console when a debugger is attached
+    bool no_skipped_summary;   // don't print "skipped" in the summary !!! UNDOCUMENTED !!!
+    bool no_time_in_output;    // omit any time/timestamps from output !!! UNDOCUMENTED !!!
+
+    bool help;             // to print the help
+    bool version;          // to print the version
+    bool count;            // if only the count of matching tests is to be retrieved
+    bool list_test_cases;  // to list all tests matching the filters
+    bool list_test_suites; // to list all suites matching the filters
+    bool list_reporters;   // lists all registered reporters
+};
+
+namespace detail {
+    template <bool CONDITION, typename TYPE = void>
+    struct enable_if
+    {};
+
+    template <typename TYPE>
+    struct enable_if<true, TYPE>
+    { typedef TYPE type; };
+
+    // clang-format off
+    template<class T> struct remove_reference      { typedef T type; };
+    template<class T> struct remove_reference<T&>  { typedef T type; };
+    template<class T> struct remove_reference<T&&> { typedef T type; };
+
+    template<typename T, typename U = T&&> U declval(int); 
+
+    template<typename T> T declval(long); 
+
+    template<typename T> auto declval() DOCTEST_NOEXCEPT -> decltype(declval<T>(0)) ;
+
+    template<class T> struct is_lvalue_reference { const static bool value=false; };
+    template<class T> struct is_lvalue_reference<T&> { const static bool value=true; };
+
+    template<class T> struct is_rvalue_reference { const static bool value=false; };
+    template<class T> struct is_rvalue_reference<T&&> { const static bool value=true; };
+
+    template <class T>
+    inline T&& forward(typename remove_reference<T>::type& t) DOCTEST_NOEXCEPT
+    {
+        return static_cast<T&&>(t);
+    }
+
+    template <class T>
+    inline T&& forward(typename remove_reference<T>::type&& t) DOCTEST_NOEXCEPT
+    {
+        static_assert(!is_lvalue_reference<T>::value,
+                        "Can not forward an rvalue as an lvalue.");
+        return static_cast<T&&>(t);
+    }
+
+    template<class T> struct remove_const          { typedef T type; };
+    template<class T> struct remove_const<const T> { typedef T type; };
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+    template<class T> struct is_enum : public std::is_enum<T> {};
+    template<class T> struct underlying_type : public std::underlying_type<T> {};
+#else
+    // Use compiler intrinsics
+    template<class T> struct is_enum { DOCTEST_CONSTEXPR static bool value = __is_enum(T); };
+    template<class T> struct underlying_type { typedef __underlying_type(T) type; };
+#endif
+    // clang-format on
+
+    template <typename T>
+    struct deferred_false
+    // cppcheck-suppress unusedStructMember
+    { static const bool value = false; };
+
+    namespace has_insertion_operator_impl {
+        std::ostream &os();
+        template<class T>
+        DOCTEST_REF_WRAP(T) val();
+
+        template<class, class = void>
+        struct check {
+            static DOCTEST_CONSTEXPR bool value = false;
+        };
+
+        template<class T>
+        struct check<T, decltype(os() << val<T>(), void())> {
+            static DOCTEST_CONSTEXPR bool value = true;
+        };
+    } // namespace has_insertion_operator_impl
+
+    template<class T>
+    using has_insertion_operator = has_insertion_operator_impl::check<const T>;
+
+    DOCTEST_INTERFACE std::ostream* tlssPush();
+    DOCTEST_INTERFACE String tlssPop();
+
+
+    template <bool C>
+    struct StringMakerBase
+    {
+        template <typename T>
+        static String convert(const DOCTEST_REF_WRAP(T)) {
+            return "{?}";
+        }
+    };
+
+    // Vector<int> and various type other than pointer or array.
+    template<typename T>
+    struct filldata
+    {
+        static void fill(std::ostream* stream, const T &in) {
+          *stream << in;
+        }
+    };
+
+    template<typename T,unsigned long N>
+    struct filldata<T[N]>
+    {
+        static void fill(std::ostream* stream, const T (&in)[N]) {
+            for (unsigned long i = 0; i < N; i++) {
+                *stream << in[i];
+            }
+        }
+    };
+
+    // Specialized since we don't want the terminating null byte!
+    template<unsigned long N>
+    struct filldata<const char[N]>
+    {
+        static void fill(std::ostream* stream, const char(&in)[N]) {
+            *stream << in;
+        }
+    };
+
+    template<typename T>
+    void filloss(std::ostream* stream, const T& in) {
+        filldata<T>::fill(stream, in);
+    }
+
+    template<typename T,unsigned long N>
+    void filloss(std::ostream* stream, const T (&in)[N]) {
+        // T[N], T(&)[N], T(&&)[N] have same behaviour.
+        // Hence remove reference.
+        filldata<typename remove_reference<decltype(in)>::type>::fill(stream, in);
+    }
+
+    template <>
+    struct StringMakerBase<true>
+    {
+        template <typename T>
+        static String convert(const DOCTEST_REF_WRAP(T) in) {
+            /* When parameter "in" is a null terminated const char* it works.
+             * When parameter "in" is a T arr[N] without '\0' we can fill the
+             * stringstream with N objects (T=char).If in is char pointer *
+             * without '\0' , it would cause segfault
+             * stepping over unaccessible memory.
+             */
+
+            std::ostream* stream = tlssPush();
+            filloss(stream, in);
+            return tlssPop();
+        }
+    };
+
+    DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size);
+
+    template <typename T>
+    String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) {
+        return rawMemoryToString(&object, sizeof(object));
+    }
+
+    template <typename T>
+    const char* type_to_string() {
+        return "<>";
+    }
+} // namespace detail
+
+template <typename T>
+struct StringMaker : public detail::StringMakerBase<detail::has_insertion_operator<T>::value>
+{};
+
+template <typename T>
+struct StringMaker<T*>
+{
+    template <typename U>
+    static String convert(U* p) {
+        if(p)
+            return detail::rawMemoryToString(p);
+        return "NULL";
+    }
+};
+
+template <typename R, typename C>
+struct StringMaker<R C::*>
+{
+    static String convert(R C::*p) {
+        if(p)
+            return detail::rawMemoryToString(p);
+        return "NULL";
+    }
+};
+
+template <typename T, typename detail::enable_if<!detail::is_enum<T>::value, bool>::type = true>
+String toString(const DOCTEST_REF_WRAP(T) value) {
+    return StringMaker<T>::convert(value);
+}
+
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+DOCTEST_INTERFACE String toString(char* in);
+DOCTEST_INTERFACE String toString(const char* in);
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+DOCTEST_INTERFACE String toString(bool in);
+DOCTEST_INTERFACE String toString(float in);
+DOCTEST_INTERFACE String toString(double in);
+DOCTEST_INTERFACE String toString(double long in);
+
+DOCTEST_INTERFACE String toString(char in);
+DOCTEST_INTERFACE String toString(char signed in);
+DOCTEST_INTERFACE String toString(char unsigned in);
+DOCTEST_INTERFACE String toString(int short in);
+DOCTEST_INTERFACE String toString(int short unsigned in);
+DOCTEST_INTERFACE String toString(int in);
+DOCTEST_INTERFACE String toString(int unsigned in);
+DOCTEST_INTERFACE String toString(int long in);
+DOCTEST_INTERFACE String toString(int long unsigned in);
+DOCTEST_INTERFACE String toString(int long long in);
+DOCTEST_INTERFACE String toString(int long long unsigned in);
+DOCTEST_INTERFACE String toString(std::nullptr_t in);
+
+template <typename T, typename detail::enable_if<detail::is_enum<T>::value, bool>::type = true>
+String toString(const DOCTEST_REF_WRAP(T) value) {
+    typedef typename detail::underlying_type<T>::type UT;
+    return toString(static_cast<UT>(value));
+}
+
+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
+// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183
+DOCTEST_INTERFACE String toString(const std::string& in);
+#endif // VS 2019
+
+class DOCTEST_INTERFACE Approx
+{
+public:
+    explicit Approx(double value);
+
+    Approx operator()(double value) const;
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+    template <typename T>
+    explicit Approx(const T& value,
+                    typename detail::enable_if<std::is_constructible<double, T>::value>::type* =
+                            static_cast<T*>(nullptr)) {
+        *this = Approx(static_cast<double>(value));
+    }
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+    Approx& epsilon(double newEpsilon);
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+    template <typename T>
+    typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type epsilon(
+            const T& newEpsilon) {
+        m_epsilon = static_cast<double>(newEpsilon);
+        return *this;
+    }
+#endif //  DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+    Approx& scale(double newScale);
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+    template <typename T>
+    typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type scale(
+            const T& newScale) {
+        m_scale = static_cast<double>(newScale);
+        return *this;
+    }
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+    // clang-format off
+    DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs);
+    DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs);
+    DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs);
+    DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs);
+    DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs);
+    DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs);
+    DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs);
+    DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs);
+    DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs);
+    DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs);
+    DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs);
+    DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs);
+
+    DOCTEST_INTERFACE friend String toString(const Approx& in);
+
+#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#define DOCTEST_APPROX_PREFIX \
+    template <typename T> friend typename detail::enable_if<std::is_constructible<double, T>::value, bool>::type
+
+    DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); }
+    DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); }
+    DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); }
+    DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); }
+    DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; }
+    DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; }
+    DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; }
+    DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; }
+    DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; }
+    DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; }
+    DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; }
+    DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; }
+#undef DOCTEST_APPROX_PREFIX
+#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+
+    // clang-format on
+
+private:
+    double m_epsilon;
+    double m_scale;
+    double m_value;
+};
+
+DOCTEST_INTERFACE String toString(const Approx& in);
+
+DOCTEST_INTERFACE const ContextOptions* getContextOptions();
+
+#if !defined(DOCTEST_CONFIG_DISABLE)
+
+namespace detail {
+    // clang-format off
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+    template<class T>               struct decay_array       { typedef T type; };
+    template<class T, unsigned N>   struct decay_array<T[N]> { typedef T* type; };
+    template<class T>               struct decay_array<T[]>  { typedef T* type; };
+
+    template<class T>   struct not_char_pointer              { enum { value = 1 }; };
+    template<>          struct not_char_pointer<char*>       { enum { value = 0 }; };
+    template<>          struct not_char_pointer<const char*> { enum { value = 0 }; };
+
+    template<class T> struct can_use_op : public not_char_pointer<typename decay_array<T>::type> {};
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+    // clang-format on
+
+    struct DOCTEST_INTERFACE TestFailureException
+    {
+    };
+
+    DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at);
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+    DOCTEST_NORETURN
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+    DOCTEST_INTERFACE void throwException();
+
+    struct DOCTEST_INTERFACE Subcase
+    {
+        SubcaseSignature m_signature;
+        bool             m_entered = false;
+
+        Subcase(const String& name, const char* file, int line);
+        ~Subcase();
+
+        operator bool() const;
+    };
+
+    template <typename L, typename R>
+    String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op,
+                               const DOCTEST_REF_WRAP(R) rhs) {
+        // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+        return toString(lhs) + op + toString(rhs);
+    }
+
+#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0)
+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison")
+#endif
+
+// This will check if there is any way it could find a operator like member or friend and uses it.
+// If not it doesn't find the operator or if the operator at global scope is defined after
+// this template, the template won't be instantiated due to SFINAE. Once the template is not
+// instantiated it can look for global operator using normal conversions.
+#define SFINAE_OP(ret,op) decltype((void)(doctest::detail::declval<L>() op doctest::detail::declval<R>()),ret{})
+
+#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro)                              \
+    template <typename R>                                                                          \
+    DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(const R&& rhs) {                             \
+    bool res = op_macro(doctest::detail::forward<const L>(lhs), doctest::detail::forward<const R>(rhs));                                                             \
+        if(m_at & assertType::is_false)                                                            \
+            res = !res;                                                                            \
+        if(!res || doctest::getContextOptions()->success)                                          \
+            return Result(res, stringifyBinaryExpr(lhs, op_str, rhs));                             \
+        return Result(res);                                                                        \
+    }                                                                                              \
+    template <typename R ,typename enable_if<!doctest::detail::is_rvalue_reference<R>::value, void >::type* = nullptr> \
+    DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(const R& rhs) {                              \
+    bool res = op_macro(doctest::detail::forward<const L>(lhs), rhs);                              \
+        if(m_at & assertType::is_false)                                                            \
+            res = !res;                                                                            \
+        if(!res || doctest::getContextOptions()->success)                                          \
+            return Result(res, stringifyBinaryExpr(lhs, op_str, rhs));                             \
+        return Result(res);                                                                        \
+    }
+
+    // more checks could be added - like in Catch:
+    // https://github.com/catchorg/Catch2/pull/1480/files
+    // https://github.com/catchorg/Catch2/pull/1481/files
+#define DOCTEST_FORBIT_EXPRESSION(rt, op)                                                          \
+    template <typename R>                                                                          \
+    rt& operator op(const R&) {                                                                    \
+        static_assert(deferred_false<R>::value,                                                    \
+                      "Expression Too Complex Please Rewrite As Binary Comparison!");              \
+        return *this;                                                                              \
+    }
+
+    struct DOCTEST_INTERFACE Result
+    {
+        bool   m_passed;
+        String m_decomp;
+
+        Result() = default;
+        Result(bool passed, const String& decomposition = String());
+
+        // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence
+        DOCTEST_FORBIT_EXPRESSION(Result, &)
+        DOCTEST_FORBIT_EXPRESSION(Result, ^)
+        DOCTEST_FORBIT_EXPRESSION(Result, |)
+        DOCTEST_FORBIT_EXPRESSION(Result, &&)
+        DOCTEST_FORBIT_EXPRESSION(Result, ||)
+        DOCTEST_FORBIT_EXPRESSION(Result, ==)
+        DOCTEST_FORBIT_EXPRESSION(Result, !=)
+        DOCTEST_FORBIT_EXPRESSION(Result, <)
+        DOCTEST_FORBIT_EXPRESSION(Result, >)
+        DOCTEST_FORBIT_EXPRESSION(Result, <=)
+        DOCTEST_FORBIT_EXPRESSION(Result, >=)
+        DOCTEST_FORBIT_EXPRESSION(Result, =)
+        DOCTEST_FORBIT_EXPRESSION(Result, +=)
+        DOCTEST_FORBIT_EXPRESSION(Result, -=)
+        DOCTEST_FORBIT_EXPRESSION(Result, *=)
+        DOCTEST_FORBIT_EXPRESSION(Result, /=)
+        DOCTEST_FORBIT_EXPRESSION(Result, %=)
+        DOCTEST_FORBIT_EXPRESSION(Result, <<=)
+        DOCTEST_FORBIT_EXPRESSION(Result, >>=)
+        DOCTEST_FORBIT_EXPRESSION(Result, &=)
+        DOCTEST_FORBIT_EXPRESSION(Result, ^=)
+        DOCTEST_FORBIT_EXPRESSION(Result, |=)
+    };
+
+#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+
+    DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion")
+    DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare")
+    //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion")
+    //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion")
+    //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal")
+
+    DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+    DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion")
+    DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare")
+    //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion")
+    //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion")
+    //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal")
+
+    DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+    // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389
+    DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch
+    DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch
+    DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch
+    //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation
+
+#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+
+    // clang-format off
+#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+#define DOCTEST_COMPARISON_RETURN_TYPE bool
+#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type
+    // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+    inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); }
+    inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); }
+    inline bool lt(const char* lhs, const char* rhs) { return String(lhs) <  String(rhs); }
+    inline bool gt(const char* lhs, const char* rhs) { return String(lhs) >  String(rhs); }
+    inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); }
+    inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); }
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+    // clang-format on
+
+#define DOCTEST_RELATIONAL_OP(name, op)                                                            \
+    template <typename L, typename R>                                                              \
+    DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs,                             \
+                                        const DOCTEST_REF_WRAP(R) rhs) {                           \
+        return lhs op rhs;                                                                         \
+    }
+
+    DOCTEST_RELATIONAL_OP(eq, ==)
+    DOCTEST_RELATIONAL_OP(ne, !=)
+    DOCTEST_RELATIONAL_OP(lt, <)
+    DOCTEST_RELATIONAL_OP(gt, >)
+    DOCTEST_RELATIONAL_OP(le, <=)
+    DOCTEST_RELATIONAL_OP(ge, >=)
+
+#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+#define DOCTEST_CMP_EQ(l, r) l == r
+#define DOCTEST_CMP_NE(l, r) l != r
+#define DOCTEST_CMP_GT(l, r) l > r
+#define DOCTEST_CMP_LT(l, r) l < r
+#define DOCTEST_CMP_GE(l, r) l >= r
+#define DOCTEST_CMP_LE(l, r) l <= r
+#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+#define DOCTEST_CMP_EQ(l, r) eq(l, r)
+#define DOCTEST_CMP_NE(l, r) ne(l, r)
+#define DOCTEST_CMP_GT(l, r) gt(l, r)
+#define DOCTEST_CMP_LT(l, r) lt(l, r)
+#define DOCTEST_CMP_GE(l, r) ge(l, r)
+#define DOCTEST_CMP_LE(l, r) le(l, r)
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+
+    template <typename L>
+    // cppcheck-suppress copyCtorAndEqOperator
+    struct Expression_lhs
+    {
+        L                lhs;
+        assertType::Enum m_at;
+
+        explicit Expression_lhs(L&& in, assertType::Enum at)
+                : lhs(doctest::detail::forward<L>(in))
+                , m_at(at) {}
+
+        DOCTEST_NOINLINE operator Result() {
+// this is needed only for MSVC 2015
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool
+            bool res = static_cast<bool>(lhs);
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+            if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
+                res = !res;
+
+            if(!res || getContextOptions()->success)
+                return Result(res, toString(lhs));
+            return Result(res);
+        }
+
+        /* This is required for user-defined conversions from Expression_lhs to L */
+        operator L() const { return lhs; }
+
+        // clang-format off
+        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional
+        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional
+        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>,  " >  ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional
+        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<,  " <  ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional
+        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional
+        DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional
+        // clang-format on
+
+        // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=)
+        // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the
+        // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression...
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<)
+        DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>)
+    };
+
+#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+
+    DOCTEST_CLANG_SUPPRESS_WARNING_POP
+    DOCTEST_MSVC_SUPPRESS_WARNING_POP
+    DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+
+#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0)
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+#endif
+
+    struct DOCTEST_INTERFACE ExpressionDecomposer
+    {
+        assertType::Enum m_at;
+
+        ExpressionDecomposer(assertType::Enum at);
+
+        // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table)
+        // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now...
+        // https://github.com/catchorg/Catch2/issues/870
+        // https://github.com/catchorg/Catch2/issues/565
+        template <typename L>
+        Expression_lhs<const L> operator<<(const L &&operand) {
+            return Expression_lhs<const L>(doctest::detail::forward<const L>(operand), m_at);
+        }
+
+        template <typename L,typename enable_if<!doctest::detail::is_rvalue_reference<L>::value,void >::type* = nullptr>
+        Expression_lhs<const L&> operator<<(const L &operand) {
+            return Expression_lhs<const L&>(operand, m_at);
+        }
+    };
+
+    struct DOCTEST_INTERFACE TestSuite
+    {
+        const char* m_test_suite = nullptr;
+        const char* m_description = nullptr;
+        bool        m_skip = false;
+        bool        m_no_breaks = false;
+        bool        m_no_output = false;
+        bool        m_may_fail = false;
+        bool        m_should_fail = false;
+        int         m_expected_failures = 0;
+        double      m_timeout = 0;
+
+        TestSuite& operator*(const char* in);
+
+        template <typename T>
+        TestSuite& operator*(const T& in) {
+            in.fill(*this);
+            return *this;
+        }
+    };
+
+    typedef void (*funcType)();
+
+    struct DOCTEST_INTERFACE TestCase : public TestCaseData
+    {
+        funcType m_test; // a function pointer to the test case
+
+        const char* m_type; // for templated test cases - gets appended to the real name
+        int m_template_id; // an ID used to distinguish between the different versions of a templated test case
+        String m_full_name; // contains the name (only for templated test cases!) + the template type
+
+        TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,
+                 const char* type = "", int template_id = -1);
+
+        TestCase(const TestCase& other);
+
+        DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function
+        TestCase& operator=(const TestCase& other);
+        DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+        TestCase& operator*(const char* in);
+
+        template <typename T>
+        TestCase& operator*(const T& in) {
+            in.fill(*this);
+            return *this;
+        }
+
+        bool operator<(const TestCase& other) const;
+    };
+
+    // forward declarations of functions used by the macros
+    DOCTEST_INTERFACE int  regTest(const TestCase& tc);
+    DOCTEST_INTERFACE int  setTestSuite(const TestSuite& ts);
+    DOCTEST_INTERFACE bool isDebuggerActive();
+
+    template<typename T>
+    int instantiationHelper(const T&) { return 0; }
+
+    namespace binaryAssertComparison {
+        enum Enum
+        {
+            eq = 0,
+            ne,
+            gt,
+            lt,
+            ge,
+            le
+        };
+    } // namespace binaryAssertComparison
+
+    // clang-format off
+    template <int, class L, class R> struct RelationalComparator     { bool operator()(const DOCTEST_REF_WRAP(L),     const DOCTEST_REF_WRAP(R)    ) const { return false;        } };
+
+#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \
+    template <class L, class R> struct RelationalComparator<n, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } };
+    // clang-format on
+
+    DOCTEST_BINARY_RELATIONAL_OP(0, doctest::detail::eq)
+    DOCTEST_BINARY_RELATIONAL_OP(1, doctest::detail::ne)
+    DOCTEST_BINARY_RELATIONAL_OP(2, doctest::detail::gt)
+    DOCTEST_BINARY_RELATIONAL_OP(3, doctest::detail::lt)
+    DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge)
+    DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le)
+
+    struct DOCTEST_INTERFACE ResultBuilder : public AssertData
+    {
+        ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
+                      const char* exception_type = "", const char* exception_string = "");
+
+        void setResult(const Result& res);
+
+        template <int comparison, typename L, typename R>
+        DOCTEST_NOINLINE bool binary_assert(const DOCTEST_REF_WRAP(L) lhs,
+                                            const DOCTEST_REF_WRAP(R) rhs) {
+            m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);
+            if(m_failed || getContextOptions()->success)
+                m_decomp = stringifyBinaryExpr(lhs, ", ", rhs);
+            return !m_failed;
+        }
+
+        template <typename L>
+        DOCTEST_NOINLINE bool unary_assert(const DOCTEST_REF_WRAP(L) val) {
+            m_failed = !val;
+
+            if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
+                m_failed = !m_failed;
+
+            if(m_failed || getContextOptions()->success)
+                m_decomp = toString(val);
+
+            return !m_failed;
+        }
+
+        void translateException();
+
+        bool log();
+        void react() const;
+    };
+
+    namespace assertAction {
+        enum Enum
+        {
+            nothing     = 0,
+            dbgbreak    = 1,
+            shouldthrow = 2
+        };
+    } // namespace assertAction
+
+    DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad);
+
+    DOCTEST_INTERFACE bool decomp_assert(assertType::Enum at, const char* file, int line,
+                                         const char* expr, Result result);
+
+#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp)                                                        \
+    do {                                                                                           \
+        if(!is_running_in_test) {                                                                  \
+            if(failed) {                                                                           \
+                ResultBuilder rb(at, file, line, expr);                                            \
+                rb.m_failed = failed;                                                              \
+                rb.m_decomp = decomp;                                                              \
+                failed_out_of_a_testing_context(rb);                                               \
+                if(isDebuggerActive() && !getContextOptions()->no_breaks)                          \
+                    DOCTEST_BREAK_INTO_DEBUGGER();                                                 \
+                if(checkIfShouldThrow(at))                                                         \
+                    throwException();                                                              \
+            }                                                                                      \
+            return !failed;                                                                        \
+        }                                                                                          \
+    } while(false)
+
+#define DOCTEST_ASSERT_IN_TESTS(decomp)                                                            \
+    ResultBuilder rb(at, file, line, expr);                                                        \
+    rb.m_failed = failed;                                                                          \
+    if(rb.m_failed || getContextOptions()->success)                                                \
+        rb.m_decomp = decomp;                                                                      \
+    if(rb.log())                                                                                   \
+        DOCTEST_BREAK_INTO_DEBUGGER();                                                             \
+    if(rb.m_failed && checkIfShouldThrow(at))                                                      \
+    throwException()
+
+    template <int comparison, typename L, typename R>
+    DOCTEST_NOINLINE bool binary_assert(assertType::Enum at, const char* file, int line,
+                                        const char* expr, const DOCTEST_REF_WRAP(L) lhs,
+                                        const DOCTEST_REF_WRAP(R) rhs) {
+        bool failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);
+
+        // ###################################################################################
+        // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT
+        // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED
+        // ###################################################################################
+        DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs));
+        DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs));
+        return !failed;
+    }
+
+    template <typename L>
+    DOCTEST_NOINLINE bool unary_assert(assertType::Enum at, const char* file, int line,
+                                       const char* expr, const DOCTEST_REF_WRAP(L) val) {
+        bool failed = !val;
+
+        if(at & assertType::is_false) //!OCLINT bitwise operator in conditional
+            failed = !failed;
+
+        // ###################################################################################
+        // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT
+        // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED
+        // ###################################################################################
+        DOCTEST_ASSERT_OUT_OF_TESTS(toString(val));
+        DOCTEST_ASSERT_IN_TESTS(toString(val));
+        return !failed;
+    }
+
+    struct DOCTEST_INTERFACE IExceptionTranslator
+    {
+        IExceptionTranslator();
+        virtual ~IExceptionTranslator();
+        virtual bool translate(String&) const = 0;
+    };
+
+    template <typename T>
+    class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class
+    {
+    public:
+        explicit ExceptionTranslator(String (*translateFunction)(T))
+                : m_translateFunction(translateFunction) {}
+
+        bool translate(String& res) const override {
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+            try {
+                throw; // lgtm [cpp/rethrow-no-exception]
+                // cppcheck-suppress catchExceptionByValue
+            } catch(T ex) {                    // NOLINT
+                res = m_translateFunction(ex); //!OCLINT parameter reassignment
+                return true;
+            } catch(...) {}         //!OCLINT -  empty catch statement
+#endif                              // DOCTEST_CONFIG_NO_EXCEPTIONS
+            static_cast<void>(res); // to silence -Wunused-parameter
+            return false;
+        }
+
+    private:
+        String (*m_translateFunction)(T);
+    };
+
+    DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et);
+
+    template <bool C>
+    struct StringStreamBase
+    {
+        template <typename T>
+        static void convert(std::ostream* s, const T& in) {
+            *s << toString(in);
+        }
+
+        // always treat char* as a string in this context - no matter
+        // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined
+        static void convert(std::ostream* s, const char* in) { *s << String(in); }
+    };
+
+    template <>
+    struct StringStreamBase<true>
+    {
+        template <typename T>
+        static void convert(std::ostream* s, const T& in) {
+            *s << in;
+        }
+    };
+
+    template <typename T>
+    struct StringStream : public StringStreamBase<has_insertion_operator<T>::value>
+    {};
+
+    template <typename T>
+    void toStream(std::ostream* s, const T& value) {
+        StringStream<T>::convert(s, value);
+    }
+
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+    DOCTEST_INTERFACE void toStream(std::ostream* s, char* in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in);
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+    DOCTEST_INTERFACE void toStream(std::ostream* s, bool in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, float in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, double in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, double long in);
+
+    DOCTEST_INTERFACE void toStream(std::ostream* s, char in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, int short in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, int in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, int long in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in);
+    DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in);
+
+    // ContextScope base class used to allow implementing methods of ContextScope 
+    // that don't depend on the template parameter in doctest.cpp.
+    class DOCTEST_INTERFACE ContextScopeBase : public IContextScope {
+    protected:
+        ContextScopeBase();
+        ContextScopeBase(ContextScopeBase&& other);
+
+        void destroy();
+        bool need_to_destroy{true};
+    };
+
+    template <typename L> class ContextScope : public ContextScopeBase
+    {
+        const L lambda_;
+
+    public:
+        explicit ContextScope(const L &lambda) : lambda_(lambda) {}
+
+        ContextScope(ContextScope &&other) : ContextScopeBase(static_cast<ContextScopeBase&&>(other)), lambda_(other.lambda_) {}
+
+        void stringify(std::ostream* s) const override { lambda_(s); }
+
+        ~ContextScope() override {
+            if (need_to_destroy) {
+                destroy();
+            }
+        }
+    };
+
+    struct DOCTEST_INTERFACE MessageBuilder : public MessageData
+    {
+        std::ostream* m_stream;
+        bool          logged = false;
+
+        MessageBuilder(const char* file, int line, assertType::Enum severity);
+        MessageBuilder() = delete;
+        ~MessageBuilder();
+
+        // the preferred way of chaining parameters for stringification
+        template <typename T>
+        MessageBuilder& operator,(const T& in) {
+            toStream(m_stream, in);
+            return *this;
+        }
+
+        // kept here just for backwards-compatibility - the comma operator should be preferred now
+        template <typename T>
+        MessageBuilder& operator<<(const T& in) { return this->operator,(in); }
+
+        // the `,` operator has the lowest operator precedence - if `<<` is used by the user then
+        // the `,` operator will be called last which is not what we want and thus the `*` operator
+        // is used first (has higher operator precedence compared to `<<`) so that we guarantee that
+        // an operator of the MessageBuilder class is called first before the rest of the parameters
+        template <typename T>
+        MessageBuilder& operator*(const T& in) { return this->operator,(in); }
+
+        bool log();
+        void react();
+    };
+    
+    template <typename L>
+    ContextScope<L> MakeContextScope(const L &lambda) {
+        return ContextScope<L>(lambda);
+    }
+} // namespace detail
+
+#define DOCTEST_DEFINE_DECORATOR(name, type, def)                                                  \
+    struct name                                                                                    \
+    {                                                                                              \
+        type data;                                                                                 \
+        name(type in = def)                                                                        \
+                : data(in) {}                                                                      \
+        void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; }           \
+        void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; }          \
+    }
+
+DOCTEST_DEFINE_DECORATOR(test_suite, const char*, "");
+DOCTEST_DEFINE_DECORATOR(description, const char*, "");
+DOCTEST_DEFINE_DECORATOR(skip, bool, true);
+DOCTEST_DEFINE_DECORATOR(no_breaks, bool, true);
+DOCTEST_DEFINE_DECORATOR(no_output, bool, true);
+DOCTEST_DEFINE_DECORATOR(timeout, double, 0);
+DOCTEST_DEFINE_DECORATOR(may_fail, bool, true);
+DOCTEST_DEFINE_DECORATOR(should_fail, bool, true);
+DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0);
+
+template <typename T>
+int registerExceptionTranslator(String (*translateFunction)(T)) {
+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors")
+    static detail::ExceptionTranslator<T> exceptionTranslator(translateFunction);
+    DOCTEST_CLANG_SUPPRESS_WARNING_POP
+    detail::registerExceptionTranslatorImpl(&exceptionTranslator);
+    return 0;
+}
+
+} // namespace doctest
+
+// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro
+// introduces an anonymous namespace in which getCurrentTestSuite gets overridden
+namespace doctest_detail_test_suite_ns {
+DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite();
+} // namespace doctest_detail_test_suite_ns
+
+namespace doctest {
+#else  // DOCTEST_CONFIG_DISABLE
+template <typename T>
+int registerExceptionTranslator(String (*)(T)) {
+    return 0;
+}
+#endif // DOCTEST_CONFIG_DISABLE
+
+namespace detail {
+    typedef void (*assert_handler)(const AssertData&);
+    struct ContextState;
+} // namespace detail
+
+class DOCTEST_INTERFACE Context
+{
+    detail::ContextState* p;
+
+    void parseArgs(int argc, const char* const* argv, bool withDefaults = false);
+
+public:
+    explicit Context(int argc = 0, const char* const* argv = nullptr);
+
+    ~Context();
+
+    void applyCommandLine(int argc, const char* const* argv);
+
+    void addFilter(const char* filter, const char* value);
+    void clearFilters();
+    void setOption(const char* option, bool value);
+    void setOption(const char* option, int value);
+    void setOption(const char* option, const char* value);
+
+    bool shouldExit();
+
+    void setAsDefaultForAssertsOutOfTestCases();
+
+    void setAssertHandler(detail::assert_handler ah);
+
+    void setCout(std::ostream* out);
+
+    int run();
+};
+
+namespace TestCaseFailureReason {
+    enum Enum
+    {
+        None                     = 0,
+        AssertFailure            = 1,   // an assertion has failed in the test case
+        Exception                = 2,   // test case threw an exception
+        Crash                    = 4,   // a crash...
+        TooManyFailedAsserts     = 8,   // the abort-after option
+        Timeout                  = 16,  // see the timeout decorator
+        ShouldHaveFailedButDidnt = 32,  // see the should_fail decorator
+        ShouldHaveFailedAndDid   = 64,  // see the should_fail decorator
+        DidntFailExactlyNumTimes = 128, // see the expected_failures decorator
+        FailedExactlyNumTimes    = 256, // see the expected_failures decorator
+        CouldHaveFailedAndDid    = 512  // see the may_fail decorator
+    };
+} // namespace TestCaseFailureReason
+
+struct DOCTEST_INTERFACE CurrentTestCaseStats
+{
+    int    numAssertsCurrentTest;
+    int    numAssertsFailedCurrentTest;
+    double seconds;
+    int    failure_flags; // use TestCaseFailureReason::Enum
+    bool   testCaseSuccess;
+};
+
+struct DOCTEST_INTERFACE TestCaseException
+{
+    String error_string;
+    bool   is_crash;
+};
+
+struct DOCTEST_INTERFACE TestRunStats
+{
+    unsigned numTestCases;
+    unsigned numTestCasesPassingFilters;
+    unsigned numTestSuitesPassingFilters;
+    unsigned numTestCasesFailed;
+    int      numAsserts;
+    int      numAssertsFailed;
+};
+
+struct QueryData
+{
+    const TestRunStats*  run_stats = nullptr;
+    const TestCaseData** data      = nullptr;
+    unsigned             num_data  = 0;
+};
+
+struct DOCTEST_INTERFACE IReporter
+{
+    // The constructor has to accept "const ContextOptions&" as a single argument
+    // which has most of the options for the run + a pointer to the stdout stream
+    // Reporter(const ContextOptions& in)
+
+    // called when a query should be reported (listing test cases, printing the version, etc.)
+    virtual void report_query(const QueryData&) = 0;
+
+    // called when the whole test run starts
+    virtual void test_run_start() = 0;
+    // called when the whole test run ends (caching a pointer to the input doesn't make sense here)
+    virtual void test_run_end(const TestRunStats&) = 0;
+
+    // called when a test case is started (safe to cache a pointer to the input)
+    virtual void test_case_start(const TestCaseData&) = 0;
+    // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input)
+    virtual void test_case_reenter(const TestCaseData&) = 0;
+    // called when a test case has ended
+    virtual void test_case_end(const CurrentTestCaseStats&) = 0;
+
+    // called when an exception is thrown from the test case (or it crashes)
+    virtual void test_case_exception(const TestCaseException&) = 0;
+
+    // called whenever a subcase is entered (don't cache pointers to the input)
+    virtual void subcase_start(const SubcaseSignature&) = 0;
+    // called whenever a subcase is exited (don't cache pointers to the input)
+    virtual void subcase_end() = 0;
+
+    // called for each assert (don't cache pointers to the input)
+    virtual void log_assert(const AssertData&) = 0;
+    // called for each message (don't cache pointers to the input)
+    virtual void log_message(const MessageData&) = 0;
+
+    // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator
+    // or isn't in the execution range (between first and last) (safe to cache a pointer to the input)
+    virtual void test_case_skipped(const TestCaseData&) = 0;
+
+    // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have
+    virtual ~IReporter();
+
+    // can obtain all currently active contexts and stringify them if one wishes to do so
+    static int                         get_num_active_contexts();
+    static const IContextScope* const* get_active_contexts();
+
+    // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown
+    static int           get_num_stringified_contexts();
+    static const String* get_stringified_contexts();
+};
+
+namespace detail {
+    typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&);
+
+    DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter);
+
+    template <typename Reporter>
+    IReporter* reporterCreator(const ContextOptions& o) {
+        return new Reporter(o);
+    }
+} // namespace detail
+
+template <typename Reporter>
+int registerReporter(const char* name, int priority, bool isReporter) {
+    detail::registerReporterImpl(name, priority, detail::reporterCreator<Reporter>, isReporter);
+    return 0;
+}
+} // namespace doctest
+
+// if registering is not disabled
+#if !defined(DOCTEST_CONFIG_DISABLE)
+
+// common code in asserts - for convenience
+#define DOCTEST_ASSERT_LOG_REACT_RETURN(b)                                                         \
+    if(b.log())                                                                                    \
+        DOCTEST_BREAK_INTO_DEBUGGER();                                                             \
+    b.react(); \
+    return !b.m_failed
+
+#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+#define DOCTEST_WRAP_IN_TRY(x) x;
+#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+#define DOCTEST_WRAP_IN_TRY(x)                                                                     \
+    try {                                                                                          \
+        x;                                                                                         \
+    } catch(...) { DOCTEST_RB.translateException(); }
+#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
+
+#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS
+#define DOCTEST_CAST_TO_VOID(...)                                                                  \
+    DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast")                                       \
+    static_cast<void>(__VA_ARGS__);                                                                \
+    DOCTEST_GCC_SUPPRESS_WARNING_POP
+#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS
+#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__;
+#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS
+
+// registers the test by initializing a dummy var with a function
+#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators)                                    \
+    global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_),                 \
+            doctest::detail::regTest(                                                              \
+                    doctest::detail::TestCase(                                                     \
+                            f, __FILE__, __LINE__,                                                 \
+                            doctest_detail_test_suite_ns::getCurrentTestSuite()) *                 \
+                    decorators))
+
+#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators)                                     \
+    namespace {                                                                                    \
+        struct der : public base                                                                   \
+        {                                                                                          \
+            void f();                                                                              \
+        };                                                                                         \
+        static void func() {                                                                       \
+            der v;                                                                                 \
+            v.f();                                                                                 \
+        }                                                                                          \
+        DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators)                                 \
+    }                                                                                              \
+    inline DOCTEST_NOINLINE void der::f()
+
+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators)                                        \
+    static void f();                                                                               \
+    DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators)                                        \
+    static void f()
+
+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators)                        \
+    static doctest::detail::funcType proxy() { return f; }                                         \
+    DOCTEST_REGISTER_FUNCTION(inline, proxy(), decorators)                                   \
+    static void f()
+
+// for registering tests
+#define DOCTEST_TEST_CASE(decorators)                                                              \
+    DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators)
+
+// for registering tests in classes - requires C++17 for inline variables!
+#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L)
+#define DOCTEST_TEST_CASE_CLASS(decorators)                                                        \
+    DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_),           \
+                                                  DOCTEST_ANONYMOUS(DOCTEST_ANON_PROXY_),          \
+                                                  decorators)
+#else // DOCTEST_TEST_CASE_CLASS
+#define DOCTEST_TEST_CASE_CLASS(...)                                                               \
+    TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER
+#endif // DOCTEST_TEST_CASE_CLASS
+
+// for registering tests with a fixture
+#define DOCTEST_TEST_CASE_FIXTURE(c, decorators)                                                   \
+    DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), c,                           \
+                              DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), decorators)
+
+// for converting types to strings without the <typeinfo> header and demangling
+#define DOCTEST_TYPE_TO_STRING_IMPL(...)                                                           \
+    template <>                                                                                    \
+    inline const char* type_to_string<__VA_ARGS__>() {                                             \
+        return "<" #__VA_ARGS__ ">";                                                               \
+    }
+#define DOCTEST_TYPE_TO_STRING(...)                                                                \
+    namespace doctest { namespace detail {                                                         \
+            DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__)                                               \
+        }                                                                                          \
+    }                                                                                              \
+    static_assert(true, "")
+
+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func)                                 \
+    template <typename T>                                                                          \
+    static void func();                                                                            \
+    namespace {                                                                                    \
+        template <typename Tuple>                                                                  \
+        struct iter;                                                                               \
+        template <typename Type, typename... Rest>                                                 \
+        struct iter<std::tuple<Type, Rest...>>                                                     \
+        {                                                                                          \
+            iter(const char* file, unsigned line, int index) {                                     \
+                doctest::detail::regTest(doctest::detail::TestCase(func<Type>, file, line,         \
+                                            doctest_detail_test_suite_ns::getCurrentTestSuite(),   \
+                                            doctest::detail::type_to_string<Type>(),               \
+                                            int(line) * 1000 + index)                              \
+                                         * dec);                                                   \
+                iter<std::tuple<Rest...>>(file, line, index + 1);                                  \
+            }                                                                                      \
+        };                                                                                         \
+        template <>                                                                                \
+        struct iter<std::tuple<>>                                                                  \
+        {                                                                                          \
+            iter(const char*, unsigned, int) {}                                                    \
+        };                                                                                         \
+    }                                                                                              \
+    template <typename T>                                                                          \
+    static void func()
+
+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id)                                              \
+    DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR),                      \
+                                           DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_))
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...)                                 \
+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY),                                           \
+        doctest::detail::instantiationHelper(                                                      \
+            DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0)))
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...)                                                 \
+    DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \
+    static_assert(true, "")
+
+#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...)                                                  \
+    DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__) \
+    static_assert(true, "")
+
+#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...)                                         \
+    DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon);             \
+    DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>)               \
+    template <typename T>                                                                          \
+    static void anon()
+
+#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...)                                                    \
+    DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_), __VA_ARGS__)
+
+// for subcases
+#define DOCTEST_SUBCASE(name)                                                                      \
+    if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED =  \
+               doctest::detail::Subcase(name, __FILE__, __LINE__))
+
+// for grouping tests in test suites by using code blocks
+#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name)                                               \
+    namespace ns_name { namespace doctest_detail_test_suite_ns {                                   \
+            static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() {            \
+                DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640)                                      \
+                DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors")                \
+                DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers")             \
+                static doctest::detail::TestSuite data{};                                          \
+                static bool                       inited = false;                                  \
+                DOCTEST_MSVC_SUPPRESS_WARNING_POP                                                  \
+                DOCTEST_CLANG_SUPPRESS_WARNING_POP                                                 \
+                DOCTEST_GCC_SUPPRESS_WARNING_POP                                                   \
+                if(!inited) {                                                                      \
+                    data* decorators;                                                              \
+                    inited = true;                                                                 \
+                }                                                                                  \
+                return data;                                                                       \
+            }                                                                                      \
+        }                                                                                          \
+    }                                                                                              \
+    namespace ns_name
+
+#define DOCTEST_TEST_SUITE(decorators)                                                             \
+    DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(DOCTEST_ANON_SUITE_))
+
+// for starting a testsuite block
+#define DOCTEST_TEST_SUITE_BEGIN(decorators)                                                       \
+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_),                               \
+            doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators))              \
+    static_assert(true, "")
+
+// for ending a testsuite block
+#define DOCTEST_TEST_SUITE_END                                                                     \
+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_VAR_),                               \
+            doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""))                      \
+    typedef int DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_)
+
+// for registering exception translators
+#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature)                      \
+    inline doctest::String translatorName(signature);                                              \
+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_),                        \
+            doctest::registerExceptionTranslator(translatorName))                                  \
+    doctest::String translatorName(signature)
+
+#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature)                                           \
+    DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_),        \
+                                               signature)
+
+// for registering reporters
+#define DOCTEST_REGISTER_REPORTER(name, priority, reporter)                                        \
+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_),                          \
+            doctest::registerReporter<reporter>(name, priority, true))                             \
+    static_assert(true, "")
+
+// for registering listeners
+#define DOCTEST_REGISTER_LISTENER(name, priority, reporter)                                        \
+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(DOCTEST_ANON_REPORTER_),                          \
+            doctest::registerReporter<reporter>(name, priority, false))                            \
+    static_assert(true, "")
+
+// clang-format off
+// for logging - disabling formatting because it's important to have these on 2 separate lines - see PR #557
+#define DOCTEST_INFO(...)                                                                          \
+    DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_),                                         \
+                      DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_OTHER_),                                   \
+                      __VA_ARGS__)
+// clang-format on
+
+#define DOCTEST_INFO_IMPL(mb_name, s_name, ...)                                       \
+    auto DOCTEST_ANONYMOUS(DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope(                  \
+        [&](std::ostream* s_name) {                                                                \
+        doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \
+        mb_name.m_stream = s_name;                                                                 \
+        mb_name * __VA_ARGS__;                                                                     \
+    })
+
+#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x)
+
+#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...)                                             \
+    [&] {                                                                                          \
+        doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type);                 \
+        mb * __VA_ARGS__;                                                                          \
+        if(mb.log())                                                                               \
+            DOCTEST_BREAK_INTO_DEBUGGER();                                                         \
+        mb.react();                                                                                \
+    }()
+
+// clang-format off
+#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__)
+#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__)
+#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(DOCTEST_MESSAGE_), __VA_ARGS__)
+// clang-format on
+
+#define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__)
+#define DOCTEST_FAIL_CHECK(...) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, __VA_ARGS__)
+#define DOCTEST_FAIL(...) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, __VA_ARGS__)
+
+#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility.
+
+#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...)                                               \
+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses")                  \
+    doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__,          \
+                                               __LINE__, #__VA_ARGS__);                            \
+    DOCTEST_WRAP_IN_TRY(DOCTEST_RB.setResult(                                                      \
+            doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type)                \
+            << __VA_ARGS__))                                                                       \
+    DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB)                                                    \
+    DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...)                                               \
+    [&] {                                                                                          \
+        DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__);                                      \
+    }()
+
+#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+// necessary for <ASSERT>_MESSAGE
+#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1
+
+#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...)                                               \
+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses")                  \
+    doctest::detail::decomp_assert(                                                                \
+            doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__,                    \
+            doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type)                \
+                    << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__)
+#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__)
+#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__)
+#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__)
+#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__)
+#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__)
+
+// clang-format off
+#define DOCTEST_WARN_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); }()
+#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); }()
+#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); }()
+#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); }()
+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); }()
+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); }()
+// clang-format on
+
+#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...)                                  \
+    [&] {                                                                                          \
+        if(!doctest::getContextOptions()->no_throw) {                                              \
+            doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__,  \
+                                                       __LINE__, #expr, #__VA_ARGS__, message);    \
+            try {                                                                                  \
+                DOCTEST_CAST_TO_VOID(expr)                                                         \
+            } catch(const typename doctest::detail::remove_const<                                  \
+                    typename doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) {       \
+                DOCTEST_RB.translateException();                                                   \
+                DOCTEST_RB.m_threw_as = true;                                                      \
+            } catch(...) { DOCTEST_RB.translateException(); }                                      \
+            DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB);                                           \
+        } else {                                                                                   \
+            return false;                                                                          \
+        }                                                                                          \
+    }()
+
+#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...)                               \
+    [&] {                                                                                          \
+        if(!doctest::getContextOptions()->no_throw) {                                              \
+            doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__,  \
+                                                       __LINE__, expr_str, "", __VA_ARGS__);       \
+            try {                                                                                  \
+                DOCTEST_CAST_TO_VOID(expr)                                                         \
+            } catch(...) { DOCTEST_RB.translateException(); }                                      \
+            DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB);                                           \
+        } else {                                                                                   \
+           return false;                                                                           \
+        }                                                                                          \
+    }()
+
+#define DOCTEST_ASSERT_NOTHROW(assert_type, ...)                                                   \
+    [&] {                                                                                          \
+        doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__,      \
+                                                   __LINE__, #__VA_ARGS__);                        \
+        try {                                                                                      \
+            DOCTEST_CAST_TO_VOID(__VA_ARGS__)                                                      \
+        } catch(...) { DOCTEST_RB.translateException(); }                                          \
+        DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB);                                               \
+    }()
+
+// clang-format off
+#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "")
+#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "")
+#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "")
+
+#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__)
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__)
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__)
+
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__)
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__)
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__)
+
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__)
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__)
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__)
+
+#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__)
+#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__)
+#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__)
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); }()
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); }()
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); }()
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); }()
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); }()
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); }()
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); }()
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); }()
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); }()
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); }()
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); }()
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); }()
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); }()
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); }()
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) [&] {DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); }()
+// clang-format on
+
+#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...)                                              \
+    [&] {                                                                                          \
+        doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__,      \
+                                                   __LINE__, #__VA_ARGS__);                        \
+        DOCTEST_WRAP_IN_TRY(                                                                       \
+                DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>(           \
+                        __VA_ARGS__))                                                              \
+        DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB);                                               \
+    }()
+
+#define DOCTEST_UNARY_ASSERT(assert_type, ...)                                                     \
+    [&] {                                                                                          \
+        doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__,      \
+                                                   __LINE__, #__VA_ARGS__);                        \
+        DOCTEST_WRAP_IN_TRY(DOCTEST_RB.unary_assert(__VA_ARGS__))                                  \
+        DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB);                                               \
+    }()
+
+#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...)                                        \
+    doctest::detail::binary_assert<doctest::detail::binaryAssertComparison::comparison>(           \
+            doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__)
+
+#define DOCTEST_UNARY_ASSERT(assert_type, ...)                                                     \
+    doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__,            \
+                                  #__VA_ARGS__, __VA_ARGS__)
+
+#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__)
+#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__)
+#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__)
+#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__)
+#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__)
+#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__)
+#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__)
+#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__)
+#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__)
+#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__)
+#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__)
+#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__)
+#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__)
+#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__)
+#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__)
+#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__)
+#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__)
+#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__)
+
+#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__)
+#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__)
+#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__)
+#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__)
+#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__)
+#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__)
+
+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS
+
+#undef DOCTEST_WARN_THROWS
+#undef DOCTEST_CHECK_THROWS
+#undef DOCTEST_REQUIRE_THROWS
+#undef DOCTEST_WARN_THROWS_AS
+#undef DOCTEST_CHECK_THROWS_AS
+#undef DOCTEST_REQUIRE_THROWS_AS
+#undef DOCTEST_WARN_THROWS_WITH
+#undef DOCTEST_CHECK_THROWS_WITH
+#undef DOCTEST_REQUIRE_THROWS_WITH
+#undef DOCTEST_WARN_THROWS_WITH_AS
+#undef DOCTEST_CHECK_THROWS_WITH_AS
+#undef DOCTEST_REQUIRE_THROWS_WITH_AS
+#undef DOCTEST_WARN_NOTHROW
+#undef DOCTEST_CHECK_NOTHROW
+#undef DOCTEST_REQUIRE_NOTHROW
+
+#undef DOCTEST_WARN_THROWS_MESSAGE
+#undef DOCTEST_CHECK_THROWS_MESSAGE
+#undef DOCTEST_REQUIRE_THROWS_MESSAGE
+#undef DOCTEST_WARN_THROWS_AS_MESSAGE
+#undef DOCTEST_CHECK_THROWS_AS_MESSAGE
+#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE
+#undef DOCTEST_WARN_THROWS_WITH_MESSAGE
+#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE
+#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE
+#undef DOCTEST_WARN_THROWS_WITH_AS_MESSAGE
+#undef DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE
+#undef DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE
+#undef DOCTEST_WARN_NOTHROW_MESSAGE
+#undef DOCTEST_CHECK_NOTHROW_MESSAGE
+#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE
+
+#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+
+#define DOCTEST_WARN_THROWS(...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS(...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS(...) ([] { return false; })
+#define DOCTEST_WARN_THROWS_AS(expr, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ([] { return false; })
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ([] { return false; })
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ([] { return false; })
+#define DOCTEST_WARN_NOTHROW(...) ([] { return false; })
+#define DOCTEST_CHECK_NOTHROW(...) ([] { return false; })
+#define DOCTEST_REQUIRE_NOTHROW(...) ([] { return false; })
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) ([] { return false; })
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; })
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; })
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; })
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) ([] { return false; })
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) ([] { return false; })
+
+#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+
+#undef DOCTEST_REQUIRE
+#undef DOCTEST_REQUIRE_FALSE
+#undef DOCTEST_REQUIRE_MESSAGE
+#undef DOCTEST_REQUIRE_FALSE_MESSAGE
+#undef DOCTEST_REQUIRE_EQ
+#undef DOCTEST_REQUIRE_NE
+#undef DOCTEST_REQUIRE_GT
+#undef DOCTEST_REQUIRE_LT
+#undef DOCTEST_REQUIRE_GE
+#undef DOCTEST_REQUIRE_LE
+#undef DOCTEST_REQUIRE_UNARY
+#undef DOCTEST_REQUIRE_UNARY_FALSE
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
+
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+// =================================================================================================
+// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING!                      ==
+// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY!                            ==
+// =================================================================================================
+#else // DOCTEST_CONFIG_DISABLE
+
+#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name)                                           \
+    namespace {                                                                                    \
+        template <typename DOCTEST_UNUSED_TEMPLATE_TYPE>                                           \
+        struct der : public base                                                                   \
+        { void f(); };                                                                             \
+    }                                                                                              \
+    template <typename DOCTEST_UNUSED_TEMPLATE_TYPE>                                               \
+    inline void der<DOCTEST_UNUSED_TEMPLATE_TYPE>::f()
+
+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name)                                              \
+    template <typename DOCTEST_UNUSED_TEMPLATE_TYPE>                                               \
+    static inline void f()
+
+// for registering tests
+#define DOCTEST_TEST_CASE(name)                                                                    \
+    DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name)
+
+// for registering tests in classes
+#define DOCTEST_TEST_CASE_CLASS(name)                                                              \
+    DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name)
+
+// for registering tests with a fixture
+#define DOCTEST_TEST_CASE_FIXTURE(x, name)                                                         \
+    DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(DOCTEST_ANON_CLASS_), x,                           \
+                              DOCTEST_ANONYMOUS(DOCTEST_ANON_FUNC_), name)
+
+// for converting types to strings without the <typeinfo> header and demangling
+#define DOCTEST_TYPE_TO_STRING(...) static_assert(true, "")
+#define DOCTEST_TYPE_TO_STRING_IMPL(...)
+
+// for typed tests
+#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...)                                                \
+    template <typename type>                                                                       \
+    inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)()
+
+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id)                                          \
+    template <typename type>                                                                       \
+    inline void DOCTEST_ANONYMOUS(DOCTEST_ANON_TMP_)()
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) static_assert(true, "")
+#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) static_assert(true, "")
+
+// for subcases
+#define DOCTEST_SUBCASE(name)
+
+// for a testsuite block
+#define DOCTEST_TEST_SUITE(name) namespace
+
+// for starting a testsuite block
+#define DOCTEST_TEST_SUITE_BEGIN(name) static_assert(true, "")
+
+// for ending a testsuite block
+#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(DOCTEST_ANON_FOR_SEMICOLON_)
+
+#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature)                                           \
+    template <typename DOCTEST_UNUSED_TEMPLATE_TYPE>                                               \
+    static inline doctest::String DOCTEST_ANONYMOUS(DOCTEST_ANON_TRANSLATOR_)(signature)
+
+#define DOCTEST_REGISTER_REPORTER(name, priority, reporter)
+#define DOCTEST_REGISTER_LISTENER(name, priority, reporter)
+
+#define DOCTEST_INFO(...) (static_cast<void>(0))
+#define DOCTEST_CAPTURE(x) (static_cast<void>(0))
+#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) (static_cast<void>(0))
+#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) (static_cast<void>(0))
+#define DOCTEST_ADD_FAIL_AT(file, line, ...) (static_cast<void>(0))
+#define DOCTEST_MESSAGE(...) (static_cast<void>(0))
+#define DOCTEST_FAIL_CHECK(...) (static_cast<void>(0))
+#define DOCTEST_FAIL(...) (static_cast<void>(0))
+
+#ifdef DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED
+
+#define DOCTEST_WARN(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_CHECK(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_REQUIRE(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_WARN_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_CHECK_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_FALSE(...) [&] { return !(__VA_ARGS__); }()
+
+#define DOCTEST_WARN_MESSAGE(cond, ...) [&] { return cond; }()
+#define DOCTEST_CHECK_MESSAGE(cond, ...) [&] { return cond; }()
+#define DOCTEST_REQUIRE_MESSAGE(cond, ...) [&] { return cond; }()
+#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }()
+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }()
+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) [&] { return !(cond); }()
+
+namespace doctest {
+namespace detail {
+#define DOCTEST_RELATIONAL_OP(name, op)                                                            \
+    template <typename L, typename R>                                                              \
+    bool name(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) { return lhs op rhs; }
+
+    DOCTEST_RELATIONAL_OP(eq, ==)
+    DOCTEST_RELATIONAL_OP(ne, !=)
+    DOCTEST_RELATIONAL_OP(lt, <)
+    DOCTEST_RELATIONAL_OP(gt, >)
+    DOCTEST_RELATIONAL_OP(le, <=)
+    DOCTEST_RELATIONAL_OP(ge, >=)
+} // namespace detail
+} // namespace doctest
+
+#define DOCTEST_WARN_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }()
+#define DOCTEST_CHECK_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_EQ(...) [&] { return doctest::detail::eq(__VA_ARGS__); }()
+#define DOCTEST_WARN_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }()
+#define DOCTEST_CHECK_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_NE(...) [&] { return doctest::detail::ne(__VA_ARGS__); }()
+#define DOCTEST_WARN_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }()
+#define DOCTEST_CHECK_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_LT(...) [&] { return doctest::detail::lt(__VA_ARGS__); }()
+#define DOCTEST_WARN_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }()
+#define DOCTEST_CHECK_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_GT(...) [&] { return doctest::detail::gt(__VA_ARGS__); }()
+#define DOCTEST_WARN_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }()
+#define DOCTEST_CHECK_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_LE(...) [&] { return doctest::detail::le(__VA_ARGS__); }()
+#define DOCTEST_WARN_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }()
+#define DOCTEST_CHECK_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_GE(...) [&] { return doctest::detail::ge(__VA_ARGS__); }()
+#define DOCTEST_WARN_UNARY(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_CHECK_UNARY(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_REQUIRE_UNARY(...) [&] { return __VA_ARGS__; }()
+#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
+
+#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED
+
+#define DOCTEST_WARN(...) ([] { return false; })
+#define DOCTEST_CHECK(...) ([] { return false; })
+#define DOCTEST_REQUIRE(...) ([] { return false; })
+#define DOCTEST_WARN_FALSE(...) ([] { return false; })
+#define DOCTEST_CHECK_FALSE(...) ([] { return false; })
+#define DOCTEST_REQUIRE_FALSE(...) ([] { return false; })
+
+#define DOCTEST_WARN_MESSAGE(cond, ...) ([] { return false; })
+#define DOCTEST_CHECK_MESSAGE(cond, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_MESSAGE(cond, ...) ([] { return false; })
+#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) ([] { return false; })
+#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) ([] { return false; })
+
+#define DOCTEST_WARN_EQ(...) ([] { return false; })
+#define DOCTEST_CHECK_EQ(...) ([] { return false; })
+#define DOCTEST_REQUIRE_EQ(...) ([] { return false; })
+#define DOCTEST_WARN_NE(...) ([] { return false; })
+#define DOCTEST_CHECK_NE(...) ([] { return false; })
+#define DOCTEST_REQUIRE_NE(...) ([] { return false; })
+#define DOCTEST_WARN_GT(...) ([] { return false; })
+#define DOCTEST_CHECK_GT(...) ([] { return false; })
+#define DOCTEST_REQUIRE_GT(...) ([] { return false; })
+#define DOCTEST_WARN_LT(...) ([] { return false; })
+#define DOCTEST_CHECK_LT(...) ([] { return false; })
+#define DOCTEST_REQUIRE_LT(...) ([] { return false; })
+#define DOCTEST_WARN_GE(...) ([] { return false; })
+#define DOCTEST_CHECK_GE(...) ([] { return false; })
+#define DOCTEST_REQUIRE_GE(...) ([] { return false; })
+#define DOCTEST_WARN_LE(...) ([] { return false; })
+#define DOCTEST_CHECK_LE(...) ([] { return false; })
+#define DOCTEST_REQUIRE_LE(...) ([] { return false; })
+
+#define DOCTEST_WARN_UNARY(...) ([] { return false; })
+#define DOCTEST_CHECK_UNARY(...) ([] { return false; })
+#define DOCTEST_REQUIRE_UNARY(...) ([] { return false; })
+#define DOCTEST_WARN_UNARY_FALSE(...) ([] { return false; })
+#define DOCTEST_CHECK_UNARY_FALSE(...) ([] { return false; })
+#define DOCTEST_REQUIRE_UNARY_FALSE(...) ([] { return false; })
+
+#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED
+
+// TODO: think about if these also need to work properly even when doctest is disabled
+#define DOCTEST_WARN_THROWS(...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS(...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS(...) ([] { return false; })
+#define DOCTEST_WARN_THROWS_AS(expr, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_AS(expr, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ([] { return false; })
+#define DOCTEST_WARN_THROWS_WITH(expr, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ([] { return false; })
+#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ([] { return false; })
+#define DOCTEST_WARN_NOTHROW(...) ([] { return false; })
+#define DOCTEST_CHECK_NOTHROW(...) ([] { return false; })
+#define DOCTEST_REQUIRE_NOTHROW(...) ([] { return false; })
+
+#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) ([] { return false; })
+#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) ([] { return false; })
+#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) ([] { return false; })
+#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; })
+#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) ([] { return false; })
+#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) ([] { return false; })
+#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) ([] { return false; })
+#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) ([] { return false; })
+
+#endif // DOCTEST_CONFIG_DISABLE
+
+// clang-format off
+// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS
+#define DOCTEST_FAST_WARN_EQ             DOCTEST_WARN_EQ
+#define DOCTEST_FAST_CHECK_EQ            DOCTEST_CHECK_EQ
+#define DOCTEST_FAST_REQUIRE_EQ          DOCTEST_REQUIRE_EQ
+#define DOCTEST_FAST_WARN_NE             DOCTEST_WARN_NE
+#define DOCTEST_FAST_CHECK_NE            DOCTEST_CHECK_NE
+#define DOCTEST_FAST_REQUIRE_NE          DOCTEST_REQUIRE_NE
+#define DOCTEST_FAST_WARN_GT             DOCTEST_WARN_GT
+#define DOCTEST_FAST_CHECK_GT            DOCTEST_CHECK_GT
+#define DOCTEST_FAST_REQUIRE_GT          DOCTEST_REQUIRE_GT
+#define DOCTEST_FAST_WARN_LT             DOCTEST_WARN_LT
+#define DOCTEST_FAST_CHECK_LT            DOCTEST_CHECK_LT
+#define DOCTEST_FAST_REQUIRE_LT          DOCTEST_REQUIRE_LT
+#define DOCTEST_FAST_WARN_GE             DOCTEST_WARN_GE
+#define DOCTEST_FAST_CHECK_GE            DOCTEST_CHECK_GE
+#define DOCTEST_FAST_REQUIRE_GE          DOCTEST_REQUIRE_GE
+#define DOCTEST_FAST_WARN_LE             DOCTEST_WARN_LE
+#define DOCTEST_FAST_CHECK_LE            DOCTEST_CHECK_LE
+#define DOCTEST_FAST_REQUIRE_LE          DOCTEST_REQUIRE_LE
+
+#define DOCTEST_FAST_WARN_UNARY          DOCTEST_WARN_UNARY
+#define DOCTEST_FAST_CHECK_UNARY         DOCTEST_CHECK_UNARY
+#define DOCTEST_FAST_REQUIRE_UNARY       DOCTEST_REQUIRE_UNARY
+#define DOCTEST_FAST_WARN_UNARY_FALSE    DOCTEST_WARN_UNARY_FALSE
+#define DOCTEST_FAST_CHECK_UNARY_FALSE   DOCTEST_CHECK_UNARY_FALSE
+#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id,__VA_ARGS__)
+// clang-format on
+
+// BDD style macros
+// clang-format off
+#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE("  Scenario: " name)
+#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS("  Scenario: " name)
+#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...)  DOCTEST_TEST_CASE_TEMPLATE("  Scenario: " name, T, __VA_ARGS__)
+#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE("  Scenario: " name, T, id)
+
+#define DOCTEST_GIVEN(name)     DOCTEST_SUBCASE("   Given: " name)
+#define DOCTEST_WHEN(name)      DOCTEST_SUBCASE("    When: " name)
+#define DOCTEST_AND_WHEN(name)  DOCTEST_SUBCASE("And when: " name)
+#define DOCTEST_THEN(name)      DOCTEST_SUBCASE("    Then: " name)
+#define DOCTEST_AND_THEN(name)  DOCTEST_SUBCASE("     And: " name)
+// clang-format on
+
+// == SHORT VERSIONS OF THE MACROS
+#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES)
+
+#define TEST_CASE(name) DOCTEST_TEST_CASE(name)
+#define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name)
+#define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name)
+#define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__)
+#define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__)
+#define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id)
+#define TEST_CASE_TEMPLATE_INVOKE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, __VA_ARGS__)
+#define TEST_CASE_TEMPLATE_APPLY(id, ...) DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, __VA_ARGS__)
+#define SUBCASE(name) DOCTEST_SUBCASE(name)
+#define TEST_SUITE(decorators) DOCTEST_TEST_SUITE(decorators)
+#define TEST_SUITE_BEGIN(name) DOCTEST_TEST_SUITE_BEGIN(name)
+#define TEST_SUITE_END DOCTEST_TEST_SUITE_END
+#define REGISTER_EXCEPTION_TRANSLATOR(signature) DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature)
+#define REGISTER_REPORTER(name, priority, reporter) DOCTEST_REGISTER_REPORTER(name, priority, reporter)
+#define REGISTER_LISTENER(name, priority, reporter) DOCTEST_REGISTER_LISTENER(name, priority, reporter)
+#define INFO(...) DOCTEST_INFO(__VA_ARGS__)
+#define CAPTURE(x) DOCTEST_CAPTURE(x)
+#define ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_MESSAGE_AT(file, line, __VA_ARGS__)
+#define ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_FAIL_CHECK_AT(file, line, __VA_ARGS__)
+#define ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_FAIL_AT(file, line, __VA_ARGS__)
+#define MESSAGE(...) DOCTEST_MESSAGE(__VA_ARGS__)
+#define FAIL_CHECK(...) DOCTEST_FAIL_CHECK(__VA_ARGS__)
+#define FAIL(...) DOCTEST_FAIL(__VA_ARGS__)
+#define TO_LVALUE(...) DOCTEST_TO_LVALUE(__VA_ARGS__)
+
+#define WARN(...) DOCTEST_WARN(__VA_ARGS__)
+#define WARN_FALSE(...) DOCTEST_WARN_FALSE(__VA_ARGS__)
+#define WARN_THROWS(...) DOCTEST_WARN_THROWS(__VA_ARGS__)
+#define WARN_THROWS_AS(expr, ...) DOCTEST_WARN_THROWS_AS(expr, __VA_ARGS__)
+#define WARN_THROWS_WITH(expr, ...) DOCTEST_WARN_THROWS_WITH(expr, __VA_ARGS__)
+#define WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_WARN_THROWS_WITH_AS(expr, with, __VA_ARGS__)
+#define WARN_NOTHROW(...) DOCTEST_WARN_NOTHROW(__VA_ARGS__)
+#define CHECK(...) DOCTEST_CHECK(__VA_ARGS__)
+#define CHECK_FALSE(...) DOCTEST_CHECK_FALSE(__VA_ARGS__)
+#define CHECK_THROWS(...) DOCTEST_CHECK_THROWS(__VA_ARGS__)
+#define CHECK_THROWS_AS(expr, ...) DOCTEST_CHECK_THROWS_AS(expr, __VA_ARGS__)
+#define CHECK_THROWS_WITH(expr, ...) DOCTEST_CHECK_THROWS_WITH(expr, __VA_ARGS__)
+#define CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_AS(expr, with, __VA_ARGS__)
+#define CHECK_NOTHROW(...) DOCTEST_CHECK_NOTHROW(__VA_ARGS__)
+#define REQUIRE(...) DOCTEST_REQUIRE(__VA_ARGS__)
+#define REQUIRE_FALSE(...) DOCTEST_REQUIRE_FALSE(__VA_ARGS__)
+#define REQUIRE_THROWS(...) DOCTEST_REQUIRE_THROWS(__VA_ARGS__)
+#define REQUIRE_THROWS_AS(expr, ...) DOCTEST_REQUIRE_THROWS_AS(expr, __VA_ARGS__)
+#define REQUIRE_THROWS_WITH(expr, ...) DOCTEST_REQUIRE_THROWS_WITH(expr, __VA_ARGS__)
+#define REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, __VA_ARGS__)
+#define REQUIRE_NOTHROW(...) DOCTEST_REQUIRE_NOTHROW(__VA_ARGS__)
+
+#define WARN_MESSAGE(cond, ...) DOCTEST_WARN_MESSAGE(cond, __VA_ARGS__)
+#define WARN_FALSE_MESSAGE(cond, ...) DOCTEST_WARN_FALSE_MESSAGE(cond, __VA_ARGS__)
+#define WARN_THROWS_MESSAGE(expr, ...) DOCTEST_WARN_THROWS_MESSAGE(expr, __VA_ARGS__)
+#define WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__)
+#define WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__)
+#define WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__)
+#define WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_WARN_NOTHROW_MESSAGE(expr, __VA_ARGS__)
+#define CHECK_MESSAGE(cond, ...) DOCTEST_CHECK_MESSAGE(cond, __VA_ARGS__)
+#define CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_CHECK_FALSE_MESSAGE(cond, __VA_ARGS__)
+#define CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_CHECK_THROWS_MESSAGE(expr, __VA_ARGS__)
+#define CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__)
+#define CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__)
+#define CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__)
+#define CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_CHECK_NOTHROW_MESSAGE(expr, __VA_ARGS__)
+#define REQUIRE_MESSAGE(cond, ...) DOCTEST_REQUIRE_MESSAGE(cond, __VA_ARGS__)
+#define REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_REQUIRE_FALSE_MESSAGE(cond, __VA_ARGS__)
+#define REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_REQUIRE_THROWS_MESSAGE(expr, __VA_ARGS__)
+#define REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__)
+#define REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__)
+#define REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__)
+#define REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, __VA_ARGS__)
+
+#define SCENARIO(name) DOCTEST_SCENARIO(name)
+#define SCENARIO_CLASS(name) DOCTEST_SCENARIO_CLASS(name)
+#define SCENARIO_TEMPLATE(name, T, ...) DOCTEST_SCENARIO_TEMPLATE(name, T, __VA_ARGS__)
+#define SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id)
+#define GIVEN(name) DOCTEST_GIVEN(name)
+#define WHEN(name) DOCTEST_WHEN(name)
+#define AND_WHEN(name) DOCTEST_AND_WHEN(name)
+#define THEN(name) DOCTEST_THEN(name)
+#define AND_THEN(name) DOCTEST_AND_THEN(name)
+
+#define WARN_EQ(...) DOCTEST_WARN_EQ(__VA_ARGS__)
+#define CHECK_EQ(...) DOCTEST_CHECK_EQ(__VA_ARGS__)
+#define REQUIRE_EQ(...) DOCTEST_REQUIRE_EQ(__VA_ARGS__)
+#define WARN_NE(...) DOCTEST_WARN_NE(__VA_ARGS__)
+#define CHECK_NE(...) DOCTEST_CHECK_NE(__VA_ARGS__)
+#define REQUIRE_NE(...) DOCTEST_REQUIRE_NE(__VA_ARGS__)
+#define WARN_GT(...) DOCTEST_WARN_GT(__VA_ARGS__)
+#define CHECK_GT(...) DOCTEST_CHECK_GT(__VA_ARGS__)
+#define REQUIRE_GT(...) DOCTEST_REQUIRE_GT(__VA_ARGS__)
+#define WARN_LT(...) DOCTEST_WARN_LT(__VA_ARGS__)
+#define CHECK_LT(...) DOCTEST_CHECK_LT(__VA_ARGS__)
+#define REQUIRE_LT(...) DOCTEST_REQUIRE_LT(__VA_ARGS__)
+#define WARN_GE(...) DOCTEST_WARN_GE(__VA_ARGS__)
+#define CHECK_GE(...) DOCTEST_CHECK_GE(__VA_ARGS__)
+#define REQUIRE_GE(...) DOCTEST_REQUIRE_GE(__VA_ARGS__)
+#define WARN_LE(...) DOCTEST_WARN_LE(__VA_ARGS__)
+#define CHECK_LE(...) DOCTEST_CHECK_LE(__VA_ARGS__)
+#define REQUIRE_LE(...) DOCTEST_REQUIRE_LE(__VA_ARGS__)
+#define WARN_UNARY(...) DOCTEST_WARN_UNARY(__VA_ARGS__)
+#define CHECK_UNARY(...) DOCTEST_CHECK_UNARY(__VA_ARGS__)
+#define REQUIRE_UNARY(...) DOCTEST_REQUIRE_UNARY(__VA_ARGS__)
+#define WARN_UNARY_FALSE(...) DOCTEST_WARN_UNARY_FALSE(__VA_ARGS__)
+#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__)
+#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__)
+
+// KEPT FOR BACKWARDS COMPATIBILITY
+#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__)
+#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__)
+#define FAST_REQUIRE_EQ(...) DOCTEST_FAST_REQUIRE_EQ(__VA_ARGS__)
+#define FAST_WARN_NE(...) DOCTEST_FAST_WARN_NE(__VA_ARGS__)
+#define FAST_CHECK_NE(...) DOCTEST_FAST_CHECK_NE(__VA_ARGS__)
+#define FAST_REQUIRE_NE(...) DOCTEST_FAST_REQUIRE_NE(__VA_ARGS__)
+#define FAST_WARN_GT(...) DOCTEST_FAST_WARN_GT(__VA_ARGS__)
+#define FAST_CHECK_GT(...) DOCTEST_FAST_CHECK_GT(__VA_ARGS__)
+#define FAST_REQUIRE_GT(...) DOCTEST_FAST_REQUIRE_GT(__VA_ARGS__)
+#define FAST_WARN_LT(...) DOCTEST_FAST_WARN_LT(__VA_ARGS__)
+#define FAST_CHECK_LT(...) DOCTEST_FAST_CHECK_LT(__VA_ARGS__)
+#define FAST_REQUIRE_LT(...) DOCTEST_FAST_REQUIRE_LT(__VA_ARGS__)
+#define FAST_WARN_GE(...) DOCTEST_FAST_WARN_GE(__VA_ARGS__)
+#define FAST_CHECK_GE(...) DOCTEST_FAST_CHECK_GE(__VA_ARGS__)
+#define FAST_REQUIRE_GE(...) DOCTEST_FAST_REQUIRE_GE(__VA_ARGS__)
+#define FAST_WARN_LE(...) DOCTEST_FAST_WARN_LE(__VA_ARGS__)
+#define FAST_CHECK_LE(...) DOCTEST_FAST_CHECK_LE(__VA_ARGS__)
+#define FAST_REQUIRE_LE(...) DOCTEST_FAST_REQUIRE_LE(__VA_ARGS__)
+
+#define FAST_WARN_UNARY(...) DOCTEST_FAST_WARN_UNARY(__VA_ARGS__)
+#define FAST_CHECK_UNARY(...) DOCTEST_FAST_CHECK_UNARY(__VA_ARGS__)
+#define FAST_REQUIRE_UNARY(...) DOCTEST_FAST_REQUIRE_UNARY(__VA_ARGS__)
+#define FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_WARN_UNARY_FALSE(__VA_ARGS__)
+#define FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_CHECK_UNARY_FALSE(__VA_ARGS__)
+#define FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_REQUIRE_UNARY_FALSE(__VA_ARGS__)
+
+#define TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, __VA_ARGS__)
+
+#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
+
+#if !defined(DOCTEST_CONFIG_DISABLE)
+
+// this is here to clear the 'current test suite' for the current translation unit - at the top
+DOCTEST_TEST_SUITE_END();
+
+// add stringification for primitive/fundamental types
+namespace doctest { namespace detail {
+    DOCTEST_TYPE_TO_STRING_IMPL(bool)
+    DOCTEST_TYPE_TO_STRING_IMPL(float)
+    DOCTEST_TYPE_TO_STRING_IMPL(double)
+    DOCTEST_TYPE_TO_STRING_IMPL(long double)
+    DOCTEST_TYPE_TO_STRING_IMPL(char)
+    DOCTEST_TYPE_TO_STRING_IMPL(signed char)
+    DOCTEST_TYPE_TO_STRING_IMPL(unsigned char)
+#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED)
+    DOCTEST_TYPE_TO_STRING_IMPL(wchar_t)
+#endif // not MSVC or wchar_t support enabled
+    DOCTEST_TYPE_TO_STRING_IMPL(short int)
+    DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int)
+    DOCTEST_TYPE_TO_STRING_IMPL(int)
+    DOCTEST_TYPE_TO_STRING_IMPL(unsigned int)
+    DOCTEST_TYPE_TO_STRING_IMPL(long int)
+    DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int)
+    DOCTEST_TYPE_TO_STRING_IMPL(long long int)
+    DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int)
+}} // namespace doctest::detail
+
+#endif // DOCTEST_CONFIG_DISABLE
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+DOCTEST_SUPPRESS_COMMON_WARNINGS_POP
+
+#endif // DOCTEST_LIBRARY_INCLUDED
+
+#ifndef DOCTEST_SINGLE_HEADER
+#define DOCTEST_SINGLE_HEADER
+#endif // DOCTEST_SINGLE_HEADER
+
+#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER)
+
+#ifndef DOCTEST_SINGLE_HEADER
+#include "doctest_fwd.h"
+#endif // DOCTEST_SINGLE_HEADER
+
+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros")
+
+#ifndef DOCTEST_LIBRARY_IMPLEMENTATION
+#define DOCTEST_LIBRARY_IMPLEMENTATION
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+DOCTEST_SUPPRESS_COMMON_WARNINGS_PUSH
+
+DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path")
+
+DOCTEST_GCC_SUPPRESS_WARNING_PUSH
+DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute")
+
+DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
+DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data
+DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled
+DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified
+DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal
+DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch
+DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C
+DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning)
+DOCTEST_MSVC_SUPPRESS_WARNING(5245) // unreferenced function with internal linkage has been removed
+
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
+
+// required includes - will go only in one translation unit!
+#include <ctime>
+#include <cmath>
+#include <climits>
+// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/doctest/doctest/pull/37
+#ifdef __BORLANDC__
+#include <math.h>
+#endif // __BORLANDC__
+#include <new>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+#include <utility>
+#include <fstream>
+#include <sstream>
+#include <iostream>
+#include <algorithm>
+#include <iomanip>
+#include <vector>
+#include <atomic>
+#include <mutex>
+#include <set>
+#include <map>
+#include <exception>
+#include <stdexcept>
+#include <csignal>
+#include <cfloat>
+#include <cctype>
+#include <cstdint>
+
+#ifdef DOCTEST_PLATFORM_MAC
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/sysctl.h>
+#endif // DOCTEST_PLATFORM_MAC
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+
+// defines for a leaner windows.h
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif // WIN32_LEAN_AND_MEAN
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif // NOMINMAX
+
+// not sure what AfxWin.h is for - here I do what Catch does
+#ifdef __AFXDLL
+#include <AfxWin.h>
+#else
+#include <windows.h>
+#endif
+#include <io.h>
+
+#else // DOCTEST_PLATFORM_WINDOWS
+
+#include <sys/time.h>
+#include <unistd.h>
+
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+// this is a fix for https://github.com/doctest/doctest/issues/348
+// https://mail.gnome.org/archives/xml/2012-January/msg00000.html
+#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO)
+#define STDOUT_FILENO fileno(stdout)
+#endif // HAVE_UNISTD_H
+
+DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END
+
+// counts the number of elements in a C array
+#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0]))
+
+#ifdef DOCTEST_CONFIG_DISABLE
+#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled
+#else // DOCTEST_CONFIG_DISABLE
+#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled
+#endif // DOCTEST_CONFIG_DISABLE
+
+#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX
+#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-"
+#endif
+
+#ifndef DOCTEST_THREAD_LOCAL
+#if DOCTEST_MSVC && (DOCTEST_MSVC < DOCTEST_COMPILER(19, 0, 0))
+#define DOCTEST_THREAD_LOCAL
+#else // DOCTEST_MSVC
+#define DOCTEST_THREAD_LOCAL thread_local
+#endif // DOCTEST_MSVC
+#endif // DOCTEST_THREAD_LOCAL
+
+#ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES
+#define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32
+#endif
+
+#ifndef DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE
+#define DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE 64
+#endif
+
+#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX
+#else
+#define DOCTEST_OPTIONS_PREFIX_DISPLAY ""
+#endif
+
+#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
+#define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
+#endif
+
+#ifndef DOCTEST_CDECL
+#define DOCTEST_CDECL __cdecl
+#endif
+
+namespace doctest {
+
+bool is_running_in_test = false;
+
+namespace {
+    using namespace detail;
+
+    template <typename Ex>
+    DOCTEST_NORETURN void throw_exception(Ex const& e) {
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+        throw e;
+#else  // DOCTEST_CONFIG_NO_EXCEPTIONS
+        std::cerr << "doctest will terminate because it needed to throw an exception.\n"
+                  << "The message was: " << e.what() << '\n';
+        std::terminate();
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+    }
+
+#ifndef DOCTEST_INTERNAL_ERROR
+#define DOCTEST_INTERNAL_ERROR(msg)                                                                \
+    throw_exception(std::logic_error(                                                              \
+            __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg))
+#endif // DOCTEST_INTERNAL_ERROR
+
+    // case insensitive strcmp
+    int stricmp(const char* a, const char* b) {
+        for(;; a++, b++) {
+            const int d = tolower(*a) - tolower(*b);
+            if(d != 0 || !*a)
+                return d;
+        }
+    }
+
+    template <typename T>
+    String fpToString(T value, int precision) {
+        std::ostringstream oss;
+        oss << std::setprecision(precision) << std::fixed << value;
+        std::string d = oss.str();
+        size_t      i = d.find_last_not_of('0');
+        if(i != std::string::npos && i != d.size() - 1) {
+            if(d[i] == '.')
+                i++;
+            d = d.substr(0, i + 1);
+        }
+        return d.c_str();
+    }
+
+    struct Endianness
+    {
+        enum Arch
+        {
+            Big,
+            Little
+        };
+
+        static Arch which() {
+            int x = 1;
+            // casting any data pointer to char* is allowed
+            auto ptr = reinterpret_cast<char*>(&x);
+            if(*ptr)
+                return Little;
+            return Big;
+        }
+    };
+} // namespace
+
+namespace detail {
+    String rawMemoryToString(const void* object, unsigned size) {
+        // Reverse order for little endian architectures
+        int i = 0, end = static_cast<int>(size), inc = 1;
+        if(Endianness::which() == Endianness::Little) {
+            i   = end - 1;
+            end = inc = -1;
+        }
+
+        unsigned const char* bytes = static_cast<unsigned const char*>(object);
+        std::ostream*        oss   = tlssPush();
+        *oss << "0x" << std::setfill('0') << std::hex;
+        for(; i != end; i += inc)
+            *oss << std::setw(2) << static_cast<unsigned>(bytes[i]);
+        return tlssPop();
+    }
+
+    DOCTEST_THREAD_LOCAL class
+    {
+        std::vector<std::streampos> stack;
+        std::stringstream           ss;
+
+    public:
+        std::ostream* push() {
+            stack.push_back(ss.tellp());
+            return &ss;
+        }
+
+        String pop() {
+            if (stack.empty())
+                DOCTEST_INTERNAL_ERROR("TLSS was empty when trying to pop!");
+
+            std::streampos pos = stack.back();
+            stack.pop_back();
+            unsigned sz = static_cast<unsigned>(ss.tellp() - pos);
+            ss.rdbuf()->pubseekpos(pos, std::ios::in | std::ios::out);
+            return String(ss, sz);
+        }
+    } g_oss;
+
+    std::ostream* tlssPush() {
+        return g_oss.push();
+    }
+
+    String tlssPop() {
+        return g_oss.pop();
+    }
+
+#ifndef DOCTEST_CONFIG_DISABLE
+
+namespace timer_large_integer
+{
+    
+#if defined(DOCTEST_PLATFORM_WINDOWS)
+    typedef ULONGLONG type;
+#else // DOCTEST_PLATFORM_WINDOWS
+    typedef std::uint64_t type;
+#endif // DOCTEST_PLATFORM_WINDOWS
+}
+
+typedef timer_large_integer::type ticks_t;
+
+#ifdef DOCTEST_CONFIG_GETCURRENTTICKS
+    ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); }
+#elif defined(DOCTEST_PLATFORM_WINDOWS)
+    ticks_t getCurrentTicks() {
+        static LARGE_INTEGER hz = {0}, hzo = {0};
+        if(!hz.QuadPart) {
+            QueryPerformanceFrequency(&hz);
+            QueryPerformanceCounter(&hzo);
+        }
+        LARGE_INTEGER t;
+        QueryPerformanceCounter(&t);
+        return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart;
+    }
+#else  // DOCTEST_PLATFORM_WINDOWS
+    ticks_t getCurrentTicks() {
+        timeval t;
+        gettimeofday(&t, nullptr);
+        return static_cast<ticks_t>(t.tv_sec) * 1000000 + static_cast<ticks_t>(t.tv_usec);
+    }
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+    struct Timer
+    {
+        void         start() { m_ticks = getCurrentTicks(); }
+        unsigned int getElapsedMicroseconds() const {
+            return static_cast<unsigned int>(getCurrentTicks() - m_ticks);
+        }
+        //unsigned int getElapsedMilliseconds() const {
+        //    return static_cast<unsigned int>(getElapsedMicroseconds() / 1000);
+        //}
+        double getElapsedSeconds() const { return static_cast<double>(getCurrentTicks() - m_ticks) / 1000000.0; }
+
+    private:
+        ticks_t m_ticks = 0;
+    };
+
+#ifdef DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
+    template <typename T>
+    using AtomicOrMultiLaneAtomic = std::atomic<T>;
+#else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
+    // Provides a multilane implementation of an atomic variable that supports add, sub, load,
+    // store. Instead of using a single atomic variable, this splits up into multiple ones,
+    // each sitting on a separate cache line. The goal is to provide a speedup when most
+    // operations are modifying. It achieves this with two properties:
+    //
+    // * Multiple atomics are used, so chance of congestion from the same atomic is reduced.
+    // * Each atomic sits on a separate cache line, so false sharing is reduced.
+    //
+    // The disadvantage is that there is a small overhead due to the use of TLS, and load/store
+    // is slower because all atomics have to be accessed.
+    template <typename T>
+    class MultiLaneAtomic
+    {
+        struct CacheLineAlignedAtomic
+        {
+            std::atomic<T> atomic{};
+            char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(std::atomic<T>)];
+        };
+        CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES];
+
+        static_assert(sizeof(CacheLineAlignedAtomic) == DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE,
+                      "guarantee one atomic takes exactly one cache line");
+
+    public:
+        T operator++() DOCTEST_NOEXCEPT { return fetch_add(1) + 1; }
+
+        T operator++(int) DOCTEST_NOEXCEPT { return fetch_add(1); }
+
+        T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT {
+            return myAtomic().fetch_add(arg, order);
+        }
+
+        T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT {
+            return myAtomic().fetch_sub(arg, order);
+        }
+
+        operator T() const DOCTEST_NOEXCEPT { return load(); }
+
+        T load(std::memory_order order = std::memory_order_seq_cst) const DOCTEST_NOEXCEPT {
+            auto result = T();
+            for(auto const& c : m_atomics) {
+                result += c.atomic.load(order);
+            }
+            return result;
+        }
+
+        T operator=(T desired) DOCTEST_NOEXCEPT { // lgtm [cpp/assignment-does-not-return-this]
+            store(desired);
+            return desired;
+        }
+
+        void store(T desired, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT {
+            // first value becomes desired", all others become 0.
+            for(auto& c : m_atomics) {
+                c.atomic.store(desired, order);
+                desired = {};
+            }
+        }
+
+    private:
+        // Each thread has a different atomic that it operates on. If more than NumLanes threads
+        // use this, some will use the same atomic. So performance will degrade a bit, but still
+        // everything will work.
+        //
+        // The logic here is a bit tricky. The call should be as fast as possible, so that there
+        // is minimal to no overhead in determining the correct atomic for the current thread.
+        //
+        // 1. A global static counter laneCounter counts continuously up.
+        // 2. Each successive thread will use modulo operation of that counter so it gets an atomic
+        //    assigned in a round-robin fashion.
+        // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with
+        //    little overhead.
+        std::atomic<T>& myAtomic() DOCTEST_NOEXCEPT {
+            static std::atomic<size_t> laneCounter;
+            DOCTEST_THREAD_LOCAL size_t tlsLaneIdx =
+                    laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES;
+
+            return m_atomics[tlsLaneIdx].atomic;
+        }
+    };
+
+    template <typename T>
+    using AtomicOrMultiLaneAtomic = MultiLaneAtomic<T>;
+#endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
+
+    // this holds both parameters from the command line and runtime data for tests
+    struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats
+    {
+        AtomicOrMultiLaneAtomic<int> numAssertsCurrentTest_atomic;
+        AtomicOrMultiLaneAtomic<int> numAssertsFailedCurrentTest_atomic;
+
+        std::vector<std::vector<String>> filters = decltype(filters)(9); // 9 different filters
+
+        std::vector<IReporter*> reporters_currently_used;
+
+        assert_handler ah = nullptr;
+
+        Timer timer;
+
+        std::vector<String> stringifiedContexts; // logging from INFO() due to an exception
+
+        // stuff for subcases
+        std::vector<SubcaseSignature>     subcasesStack;
+        std::set<decltype(subcasesStack)> subcasesPassed;
+        int                               subcasesCurrentMaxLevel;
+        bool                              should_reenter;
+        std::atomic<bool>                 shouldLogCurrentException;
+
+        void resetRunData() {
+            numTestCases                = 0;
+            numTestCasesPassingFilters  = 0;
+            numTestSuitesPassingFilters = 0;
+            numTestCasesFailed          = 0;
+            numAsserts                  = 0;
+            numAssertsFailed            = 0;
+            numAssertsCurrentTest       = 0;
+            numAssertsFailedCurrentTest = 0;
+        }
+
+        void finalizeTestCaseData() {
+            seconds = timer.getElapsedSeconds();
+
+            // update the non-atomic counters
+            numAsserts += numAssertsCurrentTest_atomic;
+            numAssertsFailed += numAssertsFailedCurrentTest_atomic;
+            numAssertsCurrentTest       = numAssertsCurrentTest_atomic;
+            numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic;
+
+            if(numAssertsFailedCurrentTest)
+                failure_flags |= TestCaseFailureReason::AssertFailure;
+
+            if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 &&
+               Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout)
+                failure_flags |= TestCaseFailureReason::Timeout;
+
+            if(currentTest->m_should_fail) {
+                if(failure_flags) {
+                    failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid;
+                } else {
+                    failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt;
+                }
+            } else if(failure_flags && currentTest->m_may_fail) {
+                failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid;
+            } else if(currentTest->m_expected_failures > 0) {
+                if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) {
+                    failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes;
+                } else {
+                    failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes;
+                }
+            }
+
+            bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) ||
+                              (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) ||
+                              (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags);
+
+            // if any subcase has failed - the whole test case has failed
+            testCaseSuccess = !(failure_flags && !ok_to_fail);
+            if(!testCaseSuccess)
+                numTestCasesFailed++;
+        }
+    };
+
+    ContextState* g_cs = nullptr;
+
+    // used to avoid locks for the debug output
+    // TODO: figure out if this is indeed necessary/correct - seems like either there still
+    // could be a race or that there wouldn't be a race even if using the context directly
+    DOCTEST_THREAD_LOCAL bool g_no_colors;
+
+#endif // DOCTEST_CONFIG_DISABLE
+} // namespace detail
+
+char* String::allocate(unsigned sz) {
+    if (sz <= last) {
+        buf[sz] = '\0';
+        setLast(last - sz);
+        return buf;
+    } else {
+        setOnHeap();
+        data.size = sz;
+        data.capacity = data.size + 1;
+        data.ptr = new char[data.capacity];
+        data.ptr[sz] = '\0';
+        return data.ptr;
+    }
+}
+
+void String::setOnHeap() { *reinterpret_cast<unsigned char*>(&buf[last]) = 128; }
+void String::setLast(unsigned in) { buf[last] = char(in); }
+
+void String::copy(const String& other) {
+    if(other.isOnStack()) {
+        memcpy(buf, other.buf, len);
+    } else {
+        memcpy(allocate(other.data.size), other.data.ptr, other.data.size);
+    }
+}
+
+String::String() {
+    buf[0] = '\0';
+    setLast();
+}
+
+String::~String() {
+    if(!isOnStack())
+        delete[] data.ptr;
+    // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+}
+
+String::String(const char* in)
+        : String(in, strlen(in)) {}
+
+String::String(const char* in, unsigned in_size) {
+    memcpy(allocate(in_size), in, in_size);
+}
+
+String::String(std::istream& in, unsigned in_size) {
+    in.read(allocate(in_size), in_size);
+}
+
+String::String(const String& other) { copy(other); }
+
+String& String::operator=(const String& other) {
+    if(this != &other) {
+        if(!isOnStack())
+            delete[] data.ptr;
+
+        copy(other);
+    }
+
+    return *this;
+}
+
+String& String::operator+=(const String& other) {
+    const unsigned my_old_size = size();
+    const unsigned other_size  = other.size();
+    const unsigned total_size  = my_old_size + other_size;
+    if(isOnStack()) {
+        if(total_size < len) {
+            // append to the current stack space
+            memcpy(buf + my_old_size, other.c_str(), other_size + 1);
+            // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+            setLast(last - total_size);
+        } else {
+            // alloc new chunk
+            char* temp = new char[total_size + 1];
+            // copy current data to new location before writing in the union
+            memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed
+            // update data in union
+            setOnHeap();
+            data.size     = total_size;
+            data.capacity = data.size + 1;
+            data.ptr      = temp;
+            // transfer the rest of the data
+            memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1);
+        }
+    } else {
+        if(data.capacity > total_size) {
+            // append to the current heap block
+            data.size = total_size;
+            memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1);
+        } else {
+            // resize
+            data.capacity *= 2;
+            if(data.capacity <= total_size)
+                data.capacity = total_size + 1;
+            // alloc new chunk
+            char* temp = new char[data.capacity];
+            // copy current data to new location before releasing it
+            memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed
+            // release old chunk
+            delete[] data.ptr;
+            // update the rest of the union members
+            data.size = total_size;
+            data.ptr  = temp;
+            // transfer the rest of the data
+            memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1);
+        }
+    }
+
+    return *this;
+}
+
+String::String(String&& other) {
+    memcpy(buf, other.buf, len);
+    other.buf[0] = '\0';
+    other.setLast();
+}
+
+String& String::operator=(String&& other) {
+    if(this != &other) {
+        if(!isOnStack())
+            delete[] data.ptr;
+        memcpy(buf, other.buf, len);
+        other.buf[0] = '\0';
+        other.setLast();
+    }
+    return *this;
+}
+
+char String::operator[](unsigned i) const {
+    return const_cast<String*>(this)->operator[](i); // NOLINT
+}
+
+char& String::operator[](unsigned i) {
+    if(isOnStack())
+        return reinterpret_cast<char*>(buf)[i];
+    return data.ptr[i];
+}
+
+DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized")
+unsigned String::size() const {
+    if(isOnStack())
+        return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32
+    return data.size;
+}
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+unsigned String::capacity() const {
+    if(isOnStack())
+        return len;
+    return data.capacity;
+}
+
+int String::compare(const char* other, bool no_case) const {
+    if(no_case)
+        return doctest::stricmp(c_str(), other);
+    return std::strcmp(c_str(), other);
+}
+
+int String::compare(const String& other, bool no_case) const {
+    return compare(other.c_str(), no_case);
+}
+
+// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+String operator+(const String& lhs, const String& rhs) { return  String(lhs) += rhs; }
+
+// clang-format off
+bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; }
+bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; }
+bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; }
+bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; }
+bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; }
+bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; }
+// clang-format on
+
+std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); }
+
+namespace {
+    void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;)
+} // namespace
+
+namespace Color {
+    std::ostream& operator<<(std::ostream& s, Color::Enum code) {
+        color_to_stream(s, code);
+        return s;
+    }
+} // namespace Color
+
+// clang-format off
+const char* assertString(assertType::Enum at) {
+    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled
+    switch(at) {  //!OCLINT missing default in switch statements
+        case assertType::DT_WARN                    : return "WARN";
+        case assertType::DT_CHECK                   : return "CHECK";
+        case assertType::DT_REQUIRE                 : return "REQUIRE";
+
+        case assertType::DT_WARN_FALSE              : return "WARN_FALSE";
+        case assertType::DT_CHECK_FALSE             : return "CHECK_FALSE";
+        case assertType::DT_REQUIRE_FALSE           : return "REQUIRE_FALSE";
+
+        case assertType::DT_WARN_THROWS             : return "WARN_THROWS";
+        case assertType::DT_CHECK_THROWS            : return "CHECK_THROWS";
+        case assertType::DT_REQUIRE_THROWS          : return "REQUIRE_THROWS";
+
+        case assertType::DT_WARN_THROWS_AS          : return "WARN_THROWS_AS";
+        case assertType::DT_CHECK_THROWS_AS         : return "CHECK_THROWS_AS";
+        case assertType::DT_REQUIRE_THROWS_AS       : return "REQUIRE_THROWS_AS";
+
+        case assertType::DT_WARN_THROWS_WITH        : return "WARN_THROWS_WITH";
+        case assertType::DT_CHECK_THROWS_WITH       : return "CHECK_THROWS_WITH";
+        case assertType::DT_REQUIRE_THROWS_WITH     : return "REQUIRE_THROWS_WITH";
+
+        case assertType::DT_WARN_THROWS_WITH_AS     : return "WARN_THROWS_WITH_AS";
+        case assertType::DT_CHECK_THROWS_WITH_AS    : return "CHECK_THROWS_WITH_AS";
+        case assertType::DT_REQUIRE_THROWS_WITH_AS  : return "REQUIRE_THROWS_WITH_AS";
+
+        case assertType::DT_WARN_NOTHROW            : return "WARN_NOTHROW";
+        case assertType::DT_CHECK_NOTHROW           : return "CHECK_NOTHROW";
+        case assertType::DT_REQUIRE_NOTHROW         : return "REQUIRE_NOTHROW";
+
+        case assertType::DT_WARN_EQ                 : return "WARN_EQ";
+        case assertType::DT_CHECK_EQ                : return "CHECK_EQ";
+        case assertType::DT_REQUIRE_EQ              : return "REQUIRE_EQ";
+        case assertType::DT_WARN_NE                 : return "WARN_NE";
+        case assertType::DT_CHECK_NE                : return "CHECK_NE";
+        case assertType::DT_REQUIRE_NE              : return "REQUIRE_NE";
+        case assertType::DT_WARN_GT                 : return "WARN_GT";
+        case assertType::DT_CHECK_GT                : return "CHECK_GT";
+        case assertType::DT_REQUIRE_GT              : return "REQUIRE_GT";
+        case assertType::DT_WARN_LT                 : return "WARN_LT";
+        case assertType::DT_CHECK_LT                : return "CHECK_LT";
+        case assertType::DT_REQUIRE_LT              : return "REQUIRE_LT";
+        case assertType::DT_WARN_GE                 : return "WARN_GE";
+        case assertType::DT_CHECK_GE                : return "CHECK_GE";
+        case assertType::DT_REQUIRE_GE              : return "REQUIRE_GE";
+        case assertType::DT_WARN_LE                 : return "WARN_LE";
+        case assertType::DT_CHECK_LE                : return "CHECK_LE";
+        case assertType::DT_REQUIRE_LE              : return "REQUIRE_LE";
+
+        case assertType::DT_WARN_UNARY              : return "WARN_UNARY";
+        case assertType::DT_CHECK_UNARY             : return "CHECK_UNARY";
+        case assertType::DT_REQUIRE_UNARY           : return "REQUIRE_UNARY";
+        case assertType::DT_WARN_UNARY_FALSE        : return "WARN_UNARY_FALSE";
+        case assertType::DT_CHECK_UNARY_FALSE       : return "CHECK_UNARY_FALSE";
+        case assertType::DT_REQUIRE_UNARY_FALSE     : return "REQUIRE_UNARY_FALSE";
+    }
+    DOCTEST_MSVC_SUPPRESS_WARNING_POP
+    return "";
+}
+// clang-format on
+
+const char* failureString(assertType::Enum at) {
+    if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional
+        return "WARNING";
+    if(at & assertType::is_check) //!OCLINT bitwise operator in conditional
+        return "ERROR";
+    if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
+        return "FATAL ERROR";
+    return "";
+}
+
+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference")
+DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference")
+// depending on the current options this will remove the path of filenames
+const char* skipPathFromFilename(const char* file) {
+#ifndef DOCTEST_CONFIG_DISABLE
+    if(getContextOptions()->no_path_in_filenames) {
+        auto back    = std::strrchr(file, '\\');
+        auto forward = std::strrchr(file, '/');
+        if(back || forward) {
+            if(back > forward)
+                forward = back;
+            return forward + 1;
+        }
+    }
+#endif // DOCTEST_CONFIG_DISABLE
+    return file;
+}
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+bool SubcaseSignature::operator<(const SubcaseSignature& other) const {
+    if(m_line != other.m_line)
+        return m_line < other.m_line;
+    if(std::strcmp(m_file, other.m_file) != 0)
+        return std::strcmp(m_file, other.m_file) < 0;
+    return m_name.compare(other.m_name) < 0;
+}
+
+IContextScope::IContextScope()  = default;
+IContextScope::~IContextScope() = default;
+
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+String toString(char* in) { return toString(static_cast<const char*>(in)); }
+// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; }
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+String toString(bool in) { return in ? "true" : "false"; }
+String toString(float in) { return fpToString(in, 5) + "f"; }
+String toString(double in) { return fpToString(in, 10); }
+String toString(double long in) { return fpToString(in, 15); }
+
+#define DOCTEST_TO_STRING_OVERLOAD(type, fmt)                                                      \
+    String toString(type in) {                                                                     \
+        char buf[64];                                                                              \
+        std::sprintf(buf, fmt, in);                                                                \
+        return buf;                                                                                \
+    }
+
+DOCTEST_TO_STRING_OVERLOAD(char, "%d")
+DOCTEST_TO_STRING_OVERLOAD(char signed, "%d")
+DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u")
+DOCTEST_TO_STRING_OVERLOAD(int short, "%d")
+DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u")
+DOCTEST_TO_STRING_OVERLOAD(int, "%d")
+DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u")
+DOCTEST_TO_STRING_OVERLOAD(int long, "%ld")
+DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu")
+DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld")
+DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu")
+
+String toString(std::nullptr_t) { return "NULL"; }
+
+#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0)
+// see this issue on why this is needed: https://github.com/doctest/doctest/issues/183
+String toString(const std::string& in) { return in.c_str(); }
+#endif // VS 2019
+
+Approx::Approx(double value)
+        : m_epsilon(static_cast<double>(std::numeric_limits<float>::epsilon()) * 100)
+        , m_scale(1.0)
+        , m_value(value) {}
+
+Approx Approx::operator()(double value) const {
+    Approx approx(value);
+    approx.epsilon(m_epsilon);
+    approx.scale(m_scale);
+    return approx;
+}
+
+Approx& Approx::epsilon(double newEpsilon) {
+    m_epsilon = newEpsilon;
+    return *this;
+}
+Approx& Approx::scale(double newScale) {
+    m_scale = newScale;
+    return *this;
+}
+
+bool operator==(double lhs, const Approx& rhs) {
+    // Thanks to Richard Harris for his help refining this formula
+    return std::fabs(lhs - rhs.m_value) <
+           rhs.m_epsilon * (rhs.m_scale + std::max<double>(std::fabs(lhs), std::fabs(rhs.m_value)));
+}
+bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); }
+bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); }
+bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); }
+bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; }
+bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; }
+bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; }
+bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; }
+bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; }
+bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; }
+bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; }
+bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; }
+
+String toString(const Approx& in) {
+    // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+    return "Approx( " + doctest::toString(in.m_value) + " )";
+}
+const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); }
+
+} // namespace doctest
+
+#ifdef DOCTEST_CONFIG_DISABLE
+namespace doctest {
+Context::Context(int, const char* const*) {}
+Context::~Context() = default;
+void Context::applyCommandLine(int, const char* const*) {}
+void Context::addFilter(const char*, const char*) {}
+void Context::clearFilters() {}
+void Context::setOption(const char*, bool) {}
+void Context::setOption(const char*, int) {}
+void Context::setOption(const char*, const char*) {}
+bool Context::shouldExit() { return false; }
+void Context::setAsDefaultForAssertsOutOfTestCases() {}
+void Context::setAssertHandler(detail::assert_handler) {}
+void Context::setCout(std::ostream* out) {}
+int  Context::run() { return 0; }
+
+IReporter::~IReporter() = default;
+
+int                         IReporter::get_num_active_contexts() { return 0; }
+const IContextScope* const* IReporter::get_active_contexts() { return nullptr; }
+int                         IReporter::get_num_stringified_contexts() { return 0; }
+const String*               IReporter::get_stringified_contexts() { return nullptr; }
+
+int registerReporter(const char*, int, IReporter*) { return 0; }
+
+} // namespace doctest
+#else // DOCTEST_CONFIG_DISABLE
+
+#if !defined(DOCTEST_CONFIG_COLORS_NONE)
+#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI)
+#ifdef DOCTEST_PLATFORM_WINDOWS
+#define DOCTEST_CONFIG_COLORS_WINDOWS
+#else // linux
+#define DOCTEST_CONFIG_COLORS_ANSI
+#endif // platform
+#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI
+#endif // DOCTEST_CONFIG_COLORS_NONE
+
+namespace doctest_detail_test_suite_ns {
+// holds the current test suite
+doctest::detail::TestSuite& getCurrentTestSuite() {
+    static doctest::detail::TestSuite data{};
+    return data;
+}
+} // namespace doctest_detail_test_suite_ns
+
+namespace doctest {
+namespace {
+    // the int (priority) is part of the key for automatic sorting - sadly one can register a
+    // reporter with a duplicate name and a different priority but hopefully that won't happen often :|
+    typedef std::map<std::pair<int, String>, reporterCreatorFunc> reporterMap;
+
+    reporterMap& getReporters() {
+        static reporterMap data;
+        return data;
+    }
+    reporterMap& getListeners() {
+        static reporterMap data;
+        return data;
+    }
+} // namespace
+namespace detail {
+#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...)                                           \
+    for(auto& curr_rep : g_cs->reporters_currently_used)                                           \
+    curr_rep->function(__VA_ARGS__)
+
+    bool checkIfShouldThrow(assertType::Enum at) {
+        if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
+            return true;
+
+        if((at & assertType::is_check) //!OCLINT bitwise operator in conditional
+           && getContextOptions()->abort_after > 0 &&
+           (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >=
+                   getContextOptions()->abort_after)
+            return true;
+
+        return false;
+    }
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+    DOCTEST_NORETURN void throwException() {
+        g_cs->shouldLogCurrentException = false;
+        throw TestFailureException();
+    } // NOLINT(cert-err60-cpp)
+#else // DOCTEST_CONFIG_NO_EXCEPTIONS
+    void throwException() {}
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+} // namespace detail
+
+namespace {
+    using namespace detail;
+    // matching of a string against a wildcard mask (case sensitivity configurable) taken from
+    // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing
+    int wildcmp(const char* str, const char* wild, bool caseSensitive) {
+        const char* cp = str;
+        const char* mp = wild;
+
+        while((*str) && (*wild != '*')) {
+            if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) &&
+               (*wild != '?')) {
+                return 0;
+            }
+            wild++;
+            str++;
+        }
+
+        while(*str) {
+            if(*wild == '*') {
+                if(!*++wild) {
+                    return 1;
+                }
+                mp = wild;
+                cp = str + 1;
+            } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) ||
+                      (*wild == '?')) {
+                wild++;
+                str++;
+            } else {
+                wild = mp;   //!OCLINT parameter reassignment
+                str  = cp++; //!OCLINT parameter reassignment
+            }
+        }
+
+        while(*wild == '*') {
+            wild++;
+        }
+        return !*wild;
+    }
+
+    //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html
+    //unsigned hashStr(unsigned const char* str) {
+    //    unsigned long hash = 5381;
+    //    char          c;
+    //    while((c = *str++))
+    //        hash = ((hash << 5) + hash) + c; // hash * 33 + c
+    //    return hash;
+    //}
+
+    // checks if the name matches any of the filters (and can be configured what to do when empty)
+    bool matchesAny(const char* name, const std::vector<String>& filters, bool matchEmpty,
+                    bool caseSensitive) {
+        if(filters.empty() && matchEmpty)
+            return true;
+        for(auto& curr : filters)
+            if(wildcmp(name, curr.c_str(), caseSensitive))
+                return true;
+        return false;
+    }
+} // namespace
+namespace detail {
+
+    Subcase::Subcase(const String& name, const char* file, int line)
+            : m_signature({name, file, line}) {
+        auto* s = g_cs;
+
+        // check subcase filters
+        if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) {
+            if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive))
+                return;
+            if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive))
+                return;
+        }
+        
+        // if a Subcase on the same level has already been entered
+        if(s->subcasesStack.size() < size_t(s->subcasesCurrentMaxLevel)) {
+            s->should_reenter = true;
+            return;
+        }
+
+        // push the current signature to the stack so we can check if the
+        // current stack + the current new subcase have been traversed
+        s->subcasesStack.push_back(m_signature);
+        if(s->subcasesPassed.count(s->subcasesStack) != 0) {
+            // pop - revert to previous stack since we've already passed this
+            s->subcasesStack.pop_back();
+            return;
+        }
+
+        s->subcasesCurrentMaxLevel = s->subcasesStack.size();
+        m_entered = true;
+
+        DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature);
+    }
+
+    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
+    DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+
+    Subcase::~Subcase() {
+        if(m_entered) {
+            // only mark the subcase stack as passed if no subcases have been skipped
+            if(g_cs->should_reenter == false)
+                g_cs->subcasesPassed.insert(g_cs->subcasesStack);
+            g_cs->subcasesStack.pop_back();
+
+#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)
+            if(std::uncaught_exceptions() > 0
+#else
+            if(std::uncaught_exception()
+#endif
+            && g_cs->shouldLogCurrentException) {
+                DOCTEST_ITERATE_THROUGH_REPORTERS(
+                        test_case_exception, {"exception thrown in subcase - will translate later "
+                                              "when the whole test case has been exited (cannot "
+                                              "translate while there is an active exception)",
+                                              false});
+                g_cs->shouldLogCurrentException = false;
+            }
+            DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY);
+        }
+    }
+
+    DOCTEST_CLANG_SUPPRESS_WARNING_POP
+    DOCTEST_GCC_SUPPRESS_WARNING_POP
+    DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+    Subcase::operator bool() const { return m_entered; }
+
+    Result::Result(bool passed, const String& decomposition)
+            : m_passed(passed)
+            , m_decomp(decomposition) {}
+
+    ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at)
+            : m_at(at) {}
+
+    TestSuite& TestSuite::operator*(const char* in) {
+        m_test_suite = in;
+        return *this;
+    }
+
+    TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,
+                       const char* type, int template_id) {
+        m_file              = file;
+        m_line              = line;
+        m_name              = nullptr; // will be later overridden in operator*
+        m_test_suite        = test_suite.m_test_suite;
+        m_description       = test_suite.m_description;
+        m_skip              = test_suite.m_skip;
+        m_no_breaks         = test_suite.m_no_breaks;
+        m_no_output         = test_suite.m_no_output;
+        m_may_fail          = test_suite.m_may_fail;
+        m_should_fail       = test_suite.m_should_fail;
+        m_expected_failures = test_suite.m_expected_failures;
+        m_timeout           = test_suite.m_timeout;
+
+        m_test        = test;
+        m_type        = type;
+        m_template_id = template_id;
+    }
+
+    TestCase::TestCase(const TestCase& other)
+            : TestCaseData() {
+        *this = other;
+    }
+
+    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function
+    DOCTEST_MSVC_SUPPRESS_WARNING(26437)           // Do not slice
+    TestCase& TestCase::operator=(const TestCase& other) {
+        static_cast<TestCaseData&>(*this) = static_cast<const TestCaseData&>(other);
+
+        m_test        = other.m_test;
+        m_type        = other.m_type;
+        m_template_id = other.m_template_id;
+        m_full_name   = other.m_full_name;
+
+        if(m_template_id != -1)
+            m_name = m_full_name.c_str();
+        return *this;
+    }
+    DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
+    TestCase& TestCase::operator*(const char* in) {
+        m_name = in;
+        // make a new name with an appended type for templated test case
+        if(m_template_id != -1) {
+            m_full_name = String(m_name) + m_type;
+            // redirect the name to point to the newly constructed full name
+            m_name = m_full_name.c_str();
+        }
+        return *this;
+    }
+
+    bool TestCase::operator<(const TestCase& other) const {
+        // this will be used only to differentiate between test cases - not relevant for sorting
+        if(m_line != other.m_line)
+            return m_line < other.m_line;
+        const int name_cmp = strcmp(m_name, other.m_name);
+        if(name_cmp != 0)
+            return name_cmp < 0;
+        const int file_cmp = m_file.compare(other.m_file);
+        if(file_cmp != 0)
+            return file_cmp < 0;
+        return m_template_id < other.m_template_id;
+    }
+
+    // all the registered tests
+    std::set<TestCase>& getRegisteredTests() {
+        static std::set<TestCase> data;
+        return data;
+    }
+} // namespace detail
+namespace {
+    using namespace detail;
+    // for sorting tests by file/line
+    bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) {
+        // this is needed because MSVC gives different case for drive letters
+        // for __FILE__ when evaluated in a header and a source file
+        const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC));
+        if(res != 0)
+            return res < 0;
+        if(lhs->m_line != rhs->m_line)
+            return lhs->m_line < rhs->m_line;
+        return lhs->m_template_id < rhs->m_template_id;
+    }
+
+    // for sorting tests by suite/file/line
+    bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) {
+        const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite);
+        if(res != 0)
+            return res < 0;
+        return fileOrderComparator(lhs, rhs);
+    }
+
+    // for sorting tests by name/suite/file/line
+    bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) {
+        const int res = std::strcmp(lhs->m_name, rhs->m_name);
+        if(res != 0)
+            return res < 0;
+        return suiteOrderComparator(lhs, rhs);
+    }
+
+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+    void color_to_stream(std::ostream& s, Color::Enum code) {
+        static_cast<void>(s);    // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS
+        static_cast<void>(code); // for DOCTEST_CONFIG_COLORS_NONE
+#ifdef DOCTEST_CONFIG_COLORS_ANSI
+        if(g_no_colors ||
+           (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false))
+            return;
+
+        auto col = "";
+        // clang-format off
+            switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement
+                case Color::Red:         col = "[0;31m"; break;
+                case Color::Green:       col = "[0;32m"; break;
+                case Color::Blue:        col = "[0;34m"; break;
+                case Color::Cyan:        col = "[0;36m"; break;
+                case Color::Yellow:      col = "[0;33m"; break;
+                case Color::Grey:        col = "[1;30m"; break;
+                case Color::LightGrey:   col = "[0;37m"; break;
+                case Color::BrightRed:   col = "[1;31m"; break;
+                case Color::BrightGreen: col = "[1;32m"; break;
+                case Color::BrightWhite: col = "[1;37m"; break;
+                case Color::Bright: // invalid
+                case Color::None:
+                case Color::White:
+                default:                 col = "[0m";
+            }
+        // clang-format on
+        s << "\033" << col;
+#endif // DOCTEST_CONFIG_COLORS_ANSI
+
+#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
+        if(g_no_colors ||
+           (_isatty(_fileno(stdout)) == false && getContextOptions()->force_colors == false))
+            return;
+
+        static struct ConsoleHelper {
+            HANDLE stdoutHandle;
+            WORD   origFgAttrs;
+            WORD   origBgAttrs;
+
+            ConsoleHelper() {
+                stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
+                CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+                GetConsoleScreenBufferInfo(stdoutHandle, &csbiInfo);
+                origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED |
+                    BACKGROUND_BLUE | BACKGROUND_INTENSITY);
+                origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED |
+                    FOREGROUND_BLUE | FOREGROUND_INTENSITY);
+            }
+        } ch;
+
+#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(ch.stdoutHandle, x | ch.origBgAttrs)
+
+        // clang-format off
+        switch (code) {
+            case Color::White:       DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
+            case Color::Red:         DOCTEST_SET_ATTR(FOREGROUND_RED);                                      break;
+            case Color::Green:       DOCTEST_SET_ATTR(FOREGROUND_GREEN);                                    break;
+            case Color::Blue:        DOCTEST_SET_ATTR(FOREGROUND_BLUE);                                     break;
+            case Color::Cyan:        DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN);                  break;
+            case Color::Yellow:      DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN);                   break;
+            case Color::Grey:        DOCTEST_SET_ATTR(0);                                                   break;
+            case Color::LightGrey:   DOCTEST_SET_ATTR(FOREGROUND_INTENSITY);                                break;
+            case Color::BrightRed:   DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED);               break;
+            case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN);             break;
+            case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
+            case Color::None:
+            case Color::Bright: // invalid
+            default:                 DOCTEST_SET_ATTR(ch.origFgAttrs);
+        }
+            // clang-format on
+#endif // DOCTEST_CONFIG_COLORS_WINDOWS
+    }
+    DOCTEST_CLANG_SUPPRESS_WARNING_POP
+
+    std::vector<const IExceptionTranslator*>& getExceptionTranslators() {
+        static std::vector<const IExceptionTranslator*> data;
+        return data;
+    }
+
+    String translateActiveException() {
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+        String res;
+        auto&  translators = getExceptionTranslators();
+        for(auto& curr : translators)
+            if(curr->translate(res))
+                return res;
+        // clang-format off
+        DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value")
+        try {
+            throw;
+        } catch(std::exception& ex) {
+            return ex.what();
+        } catch(std::string& msg) {
+            return msg.c_str();
+        } catch(const char* msg) {
+            return msg;
+        } catch(...) {
+            return "unknown exception";
+        }
+        DOCTEST_GCC_SUPPRESS_WARNING_POP
+// clang-format on
+#else  // DOCTEST_CONFIG_NO_EXCEPTIONS
+        return "";
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+    }
+} // namespace
+
+namespace detail {
+    // used by the macros for registering tests
+    int regTest(const TestCase& tc) {
+        getRegisteredTests().insert(tc);
+        return 0;
+    }
+
+    // sets the current test suite
+    int setTestSuite(const TestSuite& ts) {
+        doctest_detail_test_suite_ns::getCurrentTestSuite() = ts;
+        return 0;
+    }
+
+#ifdef DOCTEST_IS_DEBUGGER_ACTIVE
+    bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); }
+#else // DOCTEST_IS_DEBUGGER_ACTIVE
+#ifdef DOCTEST_PLATFORM_LINUX
+    class ErrnoGuard {
+    public:
+        ErrnoGuard() : m_oldErrno(errno) {}
+        ~ErrnoGuard() { errno = m_oldErrno; }
+    private:
+        int m_oldErrno;
+    };
+    // See the comments in Catch2 for the reasoning behind this implementation:
+    // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102
+    bool isDebuggerActive() {
+        ErrnoGuard guard;
+        std::ifstream in("/proc/self/status");
+        for(std::string line; std::getline(in, line);) {
+            static const int PREFIX_LEN = 11;
+            if(line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) {
+                return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0';
+            }
+        }
+        return false;
+    }
+#elif defined(DOCTEST_PLATFORM_MAC)
+    // The following function is taken directly from the following technical note:
+    // https://developer.apple.com/library/archive/qa/qa1361/_index.html
+    // Returns true if the current process is being debugged (either
+    // running under the debugger or has a debugger attached post facto).
+    bool isDebuggerActive() {
+        int        mib[4];
+        kinfo_proc info;
+        size_t     size;
+        // Initialize the flags so that, if sysctl fails for some bizarre
+        // reason, we get a predictable result.
+        info.kp_proc.p_flag = 0;
+        // Initialize mib, which tells sysctl the info we want, in this case
+        // we're looking for information about a specific process ID.
+        mib[0] = CTL_KERN;
+        mib[1] = KERN_PROC;
+        mib[2] = KERN_PROC_PID;
+        mib[3] = getpid();
+        // Call sysctl.
+        size = sizeof(info);
+        if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) {
+            std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n";
+            return false;
+        }
+        // We're being debugged if the P_TRACED flag is set.
+        return ((info.kp_proc.p_flag & P_TRACED) != 0);
+    }
+#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__)
+    bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; }
+#else
+    bool isDebuggerActive() { return false; }
+#endif // Platform
+#endif // DOCTEST_IS_DEBUGGER_ACTIVE
+
+    void registerExceptionTranslatorImpl(const IExceptionTranslator* et) {
+        if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) ==
+           getExceptionTranslators().end())
+            getExceptionTranslators().push_back(et);
+    }
+
+#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+    void toStream(std::ostream* s, char* in) { *s << in; }
+    void toStream(std::ostream* s, const char* in) { *s << in; }
+#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
+    void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; }
+    void toStream(std::ostream* s, float in) { *s << in; }
+    void toStream(std::ostream* s, double in) { *s << in; }
+    void toStream(std::ostream* s, double long in) { *s << in; }
+
+    void toStream(std::ostream* s, char in) { *s << in; }
+    void toStream(std::ostream* s, char signed in) { *s << in; }
+    void toStream(std::ostream* s, char unsigned in) { *s << in; }
+    void toStream(std::ostream* s, int short in) { *s << in; }
+    void toStream(std::ostream* s, int short unsigned in) { *s << in; }
+    void toStream(std::ostream* s, int in) { *s << in; }
+    void toStream(std::ostream* s, int unsigned in) { *s << in; }
+    void toStream(std::ostream* s, int long in) { *s << in; }
+    void toStream(std::ostream* s, int long unsigned in) { *s << in; }
+    void toStream(std::ostream* s, int long long in) { *s << in; }
+    void toStream(std::ostream* s, int long long unsigned in) { *s << in; }
+
+    DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO()
+
+    ContextScopeBase::ContextScopeBase() {
+        g_infoContexts.push_back(this);
+    }
+
+    ContextScopeBase::ContextScopeBase(ContextScopeBase&& other) {
+        if (other.need_to_destroy) {
+            other.destroy();
+        }
+        other.need_to_destroy = false;
+        g_infoContexts.push_back(this);
+    }
+
+    DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
+    DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+    DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
+
+    // destroy cannot be inlined into the destructor because that would mean calling stringify after
+    // ContextScope has been destroyed (base class destructors run after derived class destructors).
+    // Instead, ContextScope calls this method directly from its destructor.
+    void ContextScopeBase::destroy() {
+#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)
+        if(std::uncaught_exceptions() > 0) {
+#else
+        if(std::uncaught_exception()) {
+#endif
+            std::ostringstream s;
+            this->stringify(&s);
+            g_cs->stringifiedContexts.push_back(s.str().c_str());
+        }
+        g_infoContexts.pop_back();
+    }
+
+    DOCTEST_CLANG_SUPPRESS_WARNING_POP
+    DOCTEST_GCC_SUPPRESS_WARNING_POP
+    DOCTEST_MSVC_SUPPRESS_WARNING_POP
+} // namespace detail
+namespace {
+    using namespace detail;
+
+#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH)
+    struct FatalConditionHandler
+    {
+        static void reset() {}
+        static void allocateAltStackMem() {}
+        static void freeAltStackMem() {}
+    };
+#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
+
+    void reportFatal(const std::string&);
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+
+    struct SignalDefs
+    {
+        DWORD id;
+        const char* name;
+    };
+    // There is no 1-1 mapping between signals and windows exceptions.
+    // Windows can easily distinguish between SO and SigSegV,
+    // but SigInt, SigTerm, etc are handled differently.
+    SignalDefs signalDefs[] = {
+            {static_cast<DWORD>(EXCEPTION_ILLEGAL_INSTRUCTION),
+             "SIGILL - Illegal instruction signal"},
+            {static_cast<DWORD>(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow"},
+            {static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION),
+             "SIGSEGV - Segmentation violation signal"},
+            {static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error"},
+    };
+
+    struct FatalConditionHandler
+    {
+        static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) {
+            // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the
+            // console just once no matter how many threads have crashed.
+            static std::mutex mutex;
+            static bool execute = true;
+            {
+                std::lock_guard<std::mutex> lock(mutex);
+                if(execute) {
+                    bool reported = false;
+                    for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
+                        if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) {
+                            reportFatal(signalDefs[i].name);
+                            reported = true;
+                            break;
+                        }
+                    }
+                    if(reported == false)
+                        reportFatal("Unhandled SEH exception caught");
+                    if(isDebuggerActive() && !g_cs->no_breaks)
+                        DOCTEST_BREAK_INTO_DEBUGGER();
+                }
+                execute = false;
+            }
+            std::exit(EXIT_FAILURE);
+        }
+
+        static void allocateAltStackMem() {}
+        static void freeAltStackMem() {}
+
+        FatalConditionHandler() {
+            isSet = true;
+            // 32k seems enough for doctest to handle stack overflow,
+            // but the value was found experimentally, so there is no strong guarantee
+            guaranteeSize = 32 * 1024;
+            // Register an unhandled exception filter
+            previousTop = SetUnhandledExceptionFilter(handleException);
+            // Pass in guarantee size to be filled
+            SetThreadStackGuarantee(&guaranteeSize);
+
+            // On Windows uncaught exceptions from another thread, exceptions from
+            // destructors, or calls to std::terminate are not a SEH exception
+
+            // The terminal handler gets called when:
+            // - std::terminate is called FROM THE TEST RUNNER THREAD
+            // - an exception is thrown from a destructor FROM THE TEST RUNNER THREAD
+            original_terminate_handler = std::get_terminate();
+            std::set_terminate([]() DOCTEST_NOEXCEPT {
+                reportFatal("Terminate handler called");
+                if(isDebuggerActive() && !g_cs->no_breaks)
+                    DOCTEST_BREAK_INTO_DEBUGGER();
+                std::exit(EXIT_FAILURE); // explicitly exit - otherwise the SIGABRT handler may be called as well
+            });
+
+            // SIGABRT is raised when:
+            // - std::terminate is called FROM A DIFFERENT THREAD
+            // - an exception is thrown from a destructor FROM A DIFFERENT THREAD
+            // - an uncaught exception is thrown FROM A DIFFERENT THREAD
+            prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) DOCTEST_NOEXCEPT {
+                if(signal == SIGABRT) {
+                    reportFatal("SIGABRT - Abort (abnormal termination) signal");
+                    if(isDebuggerActive() && !g_cs->no_breaks)
+                        DOCTEST_BREAK_INTO_DEBUGGER();
+                    std::exit(EXIT_FAILURE);
+                }
+            });
+
+            // The following settings are taken from google test, and more
+            // specifically from UnitTest::Run() inside of gtest.cc
+
+            // the user does not want to see pop-up dialogs about crashes
+            prev_error_mode_1 = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT |
+                                             SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
+            // This forces the abort message to go to stderr in all circumstances.
+            prev_error_mode_2 = _set_error_mode(_OUT_TO_STDERR);
+            // In the debug version, Visual Studio pops up a separate dialog
+            // offering a choice to debug the aborted program - we want to disable that.
+            prev_abort_behavior = _set_abort_behavior(0x0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
+            // In debug mode, the Windows CRT can crash with an assertion over invalid
+            // input (e.g. passing an invalid file descriptor). The default handling
+            // for these assertions is to pop up a dialog and wait for user input.
+            // Instead ask the CRT to dump such assertions to stderr non-interactively.
+            prev_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
+            prev_report_file = _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
+        }
+
+        static void reset() {
+            if(isSet) {
+                // Unregister handler and restore the old guarantee
+                SetUnhandledExceptionFilter(previousTop);
+                SetThreadStackGuarantee(&guaranteeSize);
+                std::set_terminate(original_terminate_handler);
+                std::signal(SIGABRT, prev_sigabrt_handler);
+                SetErrorMode(prev_error_mode_1);
+                _set_error_mode(prev_error_mode_2);
+                _set_abort_behavior(prev_abort_behavior, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
+                static_cast<void>(_CrtSetReportMode(_CRT_ASSERT, prev_report_mode));
+                static_cast<void>(_CrtSetReportFile(_CRT_ASSERT, prev_report_file));
+                isSet = false;
+            }
+        }
+
+        ~FatalConditionHandler() { reset(); }
+
+    private:
+        static UINT         prev_error_mode_1;
+        static int          prev_error_mode_2;
+        static unsigned int prev_abort_behavior;
+        static int          prev_report_mode;
+        static _HFILE       prev_report_file;
+        static void (DOCTEST_CDECL *prev_sigabrt_handler)(int);
+        static std::terminate_handler original_terminate_handler;
+        static bool isSet;
+        static ULONG guaranteeSize;
+        static LPTOP_LEVEL_EXCEPTION_FILTER previousTop;
+    };
+
+    UINT         FatalConditionHandler::prev_error_mode_1;
+    int          FatalConditionHandler::prev_error_mode_2;
+    unsigned int FatalConditionHandler::prev_abort_behavior;
+    int          FatalConditionHandler::prev_report_mode;
+    _HFILE       FatalConditionHandler::prev_report_file;
+    void (DOCTEST_CDECL *FatalConditionHandler::prev_sigabrt_handler)(int);
+    std::terminate_handler FatalConditionHandler::original_terminate_handler;
+    bool FatalConditionHandler::isSet = false;
+    ULONG FatalConditionHandler::guaranteeSize = 0;
+    LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr;
+
+#else // DOCTEST_PLATFORM_WINDOWS
+
+    struct SignalDefs
+    {
+        int         id;
+        const char* name;
+    };
+    SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"},
+                               {SIGILL, "SIGILL - Illegal instruction signal"},
+                               {SIGFPE, "SIGFPE - Floating point error signal"},
+                               {SIGSEGV, "SIGSEGV - Segmentation violation signal"},
+                               {SIGTERM, "SIGTERM - Termination request signal"},
+                               {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}};
+
+    struct FatalConditionHandler
+    {
+        static bool             isSet;
+        static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)];
+        static stack_t          oldSigStack;
+        static size_t           altStackSize;
+        static char*            altStackMem;
+
+        static void handleSignal(int sig) {
+            const char* name = "<unknown signal>";
+            for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
+                SignalDefs& def = signalDefs[i];
+                if(sig == def.id) {
+                    name = def.name;
+                    break;
+                }
+            }
+            reset();
+            reportFatal(name);
+            raise(sig);
+        }
+
+        static void allocateAltStackMem() {
+            altStackMem = new char[altStackSize];
+        }
+
+        static void freeAltStackMem() {
+            delete[] altStackMem;
+        }
+
+        FatalConditionHandler() {
+            isSet = true;
+            stack_t sigStack;
+            sigStack.ss_sp    = altStackMem;
+            sigStack.ss_size  = altStackSize;
+            sigStack.ss_flags = 0;
+            sigaltstack(&sigStack, &oldSigStack);
+            struct sigaction sa = {};
+            sa.sa_handler       = handleSignal; // NOLINT
+            sa.sa_flags         = SA_ONSTACK;
+            for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
+                sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);
+            }
+        }
+
+        ~FatalConditionHandler() { reset(); }
+        static void reset() {
+            if(isSet) {
+                // Set signals back to previous values -- hopefully nobody overwrote them in the meantime
+                for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) {
+                    sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);
+                }
+                // Return the old stack
+                sigaltstack(&oldSigStack, nullptr);
+                isSet = false;
+            }
+        }
+    };
+
+    bool             FatalConditionHandler::isSet = false;
+    struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {};
+    stack_t          FatalConditionHandler::oldSigStack = {};
+    size_t           FatalConditionHandler::altStackSize = 4 * SIGSTKSZ;
+    char*            FatalConditionHandler::altStackMem = nullptr;
+
+#endif // DOCTEST_PLATFORM_WINDOWS
+#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
+
+} // namespace
+
+namespace {
+    using namespace detail;
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text)
+#else
+    // TODO: integration with XCode and other IDEs
+#define DOCTEST_OUTPUT_DEBUG_STRING(text) // NOLINT(clang-diagnostic-unused-macros)
+#endif // Platform
+
+    void addAssert(assertType::Enum at) {
+        if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional
+            g_cs->numAssertsCurrentTest_atomic++;
+    }
+
+    void addFailedAssert(assertType::Enum at) {
+        if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional
+            g_cs->numAssertsFailedCurrentTest_atomic++;
+    }
+
+#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH)
+    void reportFatal(const std::string& message) {
+        g_cs->failure_flags |= TestCaseFailureReason::Crash;
+
+        DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true});
+
+        while(g_cs->subcasesStack.size()) {
+            g_cs->subcasesStack.pop_back();
+            DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY);
+        }
+
+        g_cs->finalizeTestCaseData();
+
+        DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs);
+
+        DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs);
+    }
+#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
+} // namespace
+namespace detail {
+
+    ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
+                                 const char* exception_type, const char* exception_string) {
+        m_test_case        = g_cs->currentTest;
+        m_at               = at;
+        m_file             = file;
+        m_line             = line;
+        m_expr             = expr;
+        m_failed           = true;
+        m_threw            = false;
+        m_threw_as         = false;
+        m_exception_type   = exception_type;
+        m_exception_string = exception_string;
+#if DOCTEST_MSVC
+        if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC
+            ++m_expr;
+#endif // MSVC
+    }
+
+    void ResultBuilder::setResult(const Result& res) {
+        m_decomp = res.m_decomp;
+        m_failed = !res.m_passed;
+    }
+
+    void ResultBuilder::translateException() {
+        m_threw     = true;
+        m_exception = translateActiveException();
+    }
+
+    bool ResultBuilder::log() {
+        if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional
+            m_failed = !m_threw;
+        } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT
+            m_failed = !m_threw_as || (m_exception != m_exception_string);
+        } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional
+            m_failed = !m_threw_as;
+        } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional
+            m_failed = m_exception != m_exception_string;
+        } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional
+            m_failed = m_threw;
+        }
+
+        if(m_exception.size())
+            m_exception = "\"" + m_exception + "\"";
+
+        if(is_running_in_test) {
+            addAssert(m_at);
+            DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this);
+
+            if(m_failed)
+                addFailedAssert(m_at);
+        } else if(m_failed) {
+            failed_out_of_a_testing_context(*this);
+        }
+
+        return m_failed && isDebuggerActive() && !getContextOptions()->no_breaks &&
+            (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger
+    }
+
+    void ResultBuilder::react() const {
+        if(m_failed && checkIfShouldThrow(m_at))
+            throwException();
+    }
+
+    void failed_out_of_a_testing_context(const AssertData& ad) {
+        if(g_cs->ah)
+            g_cs->ah(ad);
+        else
+            std::abort();
+    }
+
+    bool decomp_assert(assertType::Enum at, const char* file, int line, const char* expr,
+                       Result result) {
+        bool failed = !result.m_passed;
+
+        // ###################################################################################
+        // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT
+        // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED
+        // ###################################################################################
+        DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp);
+        DOCTEST_ASSERT_IN_TESTS(result.m_decomp);
+        // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+        return !failed;
+    }
+
+    MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) {
+        m_stream   = tlssPush();
+        m_file     = file;
+        m_line     = line;
+        m_severity = severity;
+    }
+
+    MessageBuilder::~MessageBuilder() {
+        if (!logged)
+            tlssPop();
+    }
+
+    IExceptionTranslator::IExceptionTranslator()  = default;
+    IExceptionTranslator::~IExceptionTranslator() = default;
+
+    bool MessageBuilder::log() {
+        if (!logged) {
+            m_string = tlssPop();
+            logged = true;
+        }
+        
+        DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this);
+
+        const bool isWarn = m_severity & assertType::is_warn;
+
+        // warn is just a message in this context so we don't treat it as an assert
+        if(!isWarn) {
+            addAssert(m_severity);
+            addFailedAssert(m_severity);
+        }
+
+        return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn &&
+            (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger
+    }
+
+    void MessageBuilder::react() {
+        if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional
+            throwException();
+    }
+} // namespace detail
+namespace {
+    using namespace detail;
+
+    // clang-format off
+
+// =================================================================================================
+// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp
+// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched.
+// =================================================================================================
+
+    class XmlEncode {
+    public:
+        enum ForWhat { ForTextNodes, ForAttributes };
+
+        XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes );
+
+        void encodeTo( std::ostream& os ) const;
+
+        friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode );
+
+    private:
+        std::string m_str;
+        ForWhat m_forWhat;
+    };
+
+    class XmlWriter {
+    public:
+
+        class ScopedElement {
+        public:
+            ScopedElement( XmlWriter* writer );
+
+            ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT;
+            ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT;
+
+            ~ScopedElement();
+
+            ScopedElement& writeText( std::string const& text, bool indent = true );
+
+            template<typename T>
+            ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {
+                m_writer->writeAttribute( name, attribute );
+                return *this;
+            }
+
+        private:
+            mutable XmlWriter* m_writer = nullptr;
+        };
+
+        XmlWriter( std::ostream& os = std::cout );
+        ~XmlWriter();
+
+        XmlWriter( XmlWriter const& ) = delete;
+        XmlWriter& operator=( XmlWriter const& ) = delete;
+
+        XmlWriter& startElement( std::string const& name );
+
+        ScopedElement scopedElement( std::string const& name );
+
+        XmlWriter& endElement();
+
+        XmlWriter& writeAttribute( std::string const& name, std::string const& attribute );
+
+        XmlWriter& writeAttribute( std::string const& name, const char* attribute );
+
+        XmlWriter& writeAttribute( std::string const& name, bool attribute );
+
+        template<typename T>
+        XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {
+        std::stringstream rss;
+            rss << attribute;
+            return writeAttribute( name, rss.str() );
+        }
+
+        XmlWriter& writeText( std::string const& text, bool indent = true );
+
+        //XmlWriter& writeComment( std::string const& text );
+
+        //void writeStylesheetRef( std::string const& url );
+
+        //XmlWriter& writeBlankLine();
+
+        void ensureTagClosed();
+
+    private:
+
+        void writeDeclaration();
+
+        void newlineIfNecessary();
+
+        bool m_tagIsOpen = false;
+        bool m_needsNewline = false;
+        std::vector<std::string> m_tags;
+        std::string m_indent;
+        std::ostream& m_os;
+    };
+
+// =================================================================================================
+// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp
+// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched.
+// =================================================================================================
+
+using uchar = unsigned char;
+
+namespace {
+
+    size_t trailingBytes(unsigned char c) {
+        if ((c & 0xE0) == 0xC0) {
+            return 2;
+        }
+        if ((c & 0xF0) == 0xE0) {
+            return 3;
+        }
+        if ((c & 0xF8) == 0xF0) {
+            return 4;
+        }
+        DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+    }
+
+    uint32_t headerValue(unsigned char c) {
+        if ((c & 0xE0) == 0xC0) {
+            return c & 0x1F;
+        }
+        if ((c & 0xF0) == 0xE0) {
+            return c & 0x0F;
+        }
+        if ((c & 0xF8) == 0xF0) {
+            return c & 0x07;
+        }
+        DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+    }
+
+    void hexEscapeChar(std::ostream& os, unsigned char c) {
+        std::ios_base::fmtflags f(os.flags());
+        os << "\\x"
+            << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
+            << static_cast<int>(c);
+        os.flags(f);
+    }
+
+} // anonymous namespace
+
+    XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )
+    :   m_str( str ),
+        m_forWhat( forWhat )
+    {}
+
+    void XmlEncode::encodeTo( std::ostream& os ) const {
+        // Apostrophe escaping not necessary if we always use " to write attributes
+        // (see: https://www.w3.org/TR/xml/#syntax)
+
+        for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {
+            uchar c = m_str[idx];
+            switch (c) {
+            case '<':   os << "&lt;"; break;
+            case '&':   os << "&amp;"; break;
+
+            case '>':
+                // See: https://www.w3.org/TR/xml/#syntax
+                if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')
+                    os << "&gt;";
+                else
+                    os << c;
+                break;
+
+            case '\"':
+                if (m_forWhat == ForAttributes)
+                    os << "&quot;";
+                else
+                    os << c;
+                break;
+
+            default:
+                // Check for control characters and invalid utf-8
+
+                // Escape control characters in standard ascii
+                // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
+                if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+
+                // Plain ASCII: Write it to stream
+                if (c < 0x7F) {
+                    os << c;
+                    break;
+                }
+
+                // UTF-8 territory
+                // Check if the encoding is valid and if it is not, hex escape bytes.
+                // Important: We do not check the exact decoded values for validity, only the encoding format
+                // First check that this bytes is a valid lead byte:
+                // This means that it is not encoded as 1111 1XXX
+                // Or as 10XX XXXX
+                if (c <  0xC0 ||
+                    c >= 0xF8) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+
+                auto encBytes = trailingBytes(c);
+                // Are there enough bytes left to avoid accessing out-of-bounds memory?
+                if (idx + encBytes - 1 >= m_str.size()) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+                // The header is valid, check data
+                // The next encBytes bytes must together be a valid utf-8
+                // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)
+                bool valid = true;
+                uint32_t value = headerValue(c);
+                for (std::size_t n = 1; n < encBytes; ++n) {
+                    uchar nc = m_str[idx + n];
+                    valid &= ((nc & 0xC0) == 0x80);
+                    value = (value << 6) | (nc & 0x3F);
+                }
+
+                if (
+                    // Wrong bit pattern of following bytes
+                    (!valid) ||
+                    // Overlong encodings
+                    (value < 0x80) ||
+                    (                 value < 0x800   && encBytes > 2) || // removed "0x80 <= value &&" because redundant
+                    (0x800 < value && value < 0x10000 && encBytes > 3) ||
+                    // Encoded value out of range
+                    (value >= 0x110000)
+                    ) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+
+                // If we got here, this is in fact a valid(ish) utf-8 sequence
+                for (std::size_t n = 0; n < encBytes; ++n) {
+                    os << m_str[idx + n];
+                }
+                idx += encBytes - 1;
+                break;
+            }
+        }
+    }
+
+    std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
+        xmlEncode.encodeTo( os );
+        return os;
+    }
+
+    XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer )
+    :   m_writer( writer )
+    {}
+
+    XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT
+    :   m_writer( other.m_writer ){
+        other.m_writer = nullptr;
+    }
+    XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT {
+        if ( m_writer ) {
+            m_writer->endElement();
+        }
+        m_writer = other.m_writer;
+        other.m_writer = nullptr;
+        return *this;
+    }
+
+
+    XmlWriter::ScopedElement::~ScopedElement() {
+        if( m_writer )
+            m_writer->endElement();
+    }
+
+    XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) {
+        m_writer->writeText( text, indent );
+        return *this;
+    }
+
+    XmlWriter::XmlWriter( std::ostream& os ) : m_os( os )
+    {
+        writeDeclaration();
+    }
+
+    XmlWriter::~XmlWriter() {
+        while( !m_tags.empty() )
+            endElement();
+    }
+
+    XmlWriter& XmlWriter::startElement( std::string const& name ) {
+        ensureTagClosed();
+        newlineIfNecessary();
+        m_os << m_indent << '<' << name;
+        m_tags.push_back( name );
+        m_indent += "  ";
+        m_tagIsOpen = true;
+        return *this;
+    }
+
+    XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) {
+        ScopedElement scoped( this );
+        startElement( name );
+        return scoped;
+    }
+
+    XmlWriter& XmlWriter::endElement() {
+        newlineIfNecessary();
+        m_indent = m_indent.substr( 0, m_indent.size()-2 );
+        if( m_tagIsOpen ) {
+            m_os << "/>";
+            m_tagIsOpen = false;
+        }
+        else {
+            m_os << m_indent << "</" << m_tags.back() << ">";
+        }
+        m_os << std::endl;
+        m_tags.pop_back();
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) {
+        if( !name.empty() && !attribute.empty() )
+            m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) {
+        if( !name.empty() && attribute && attribute[0] != '\0' )
+            m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) {
+        m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"';
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) {
+        if( !text.empty() ){
+            bool tagWasOpen = m_tagIsOpen;
+            ensureTagClosed();
+            if( tagWasOpen && indent )
+                m_os << m_indent;
+            m_os << XmlEncode( text );
+            m_needsNewline = true;
+        }
+        return *this;
+    }
+
+    //XmlWriter& XmlWriter::writeComment( std::string const& text ) {
+    //    ensureTagClosed();
+    //    m_os << m_indent << "<!--" << text << "-->";
+    //    m_needsNewline = true;
+    //    return *this;
+    //}
+
+    //void XmlWriter::writeStylesheetRef( std::string const& url ) {
+    //    m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n";
+    //}
+
+    //XmlWriter& XmlWriter::writeBlankLine() {
+    //    ensureTagClosed();
+    //    m_os << '\n';
+    //    return *this;
+    //}
+
+    void XmlWriter::ensureTagClosed() {
+        if( m_tagIsOpen ) {
+            m_os << ">" << std::endl;
+            m_tagIsOpen = false;
+        }
+    }
+
+    void XmlWriter::writeDeclaration() {
+        m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+    }
+
+    void XmlWriter::newlineIfNecessary() {
+        if( m_needsNewline ) {
+            m_os << std::endl;
+            m_needsNewline = false;
+        }
+    }
+
+// =================================================================================================
+// End of copy-pasted code from Catch
+// =================================================================================================
+
+    // clang-format on
+
+    struct XmlReporter : public IReporter
+    {
+        XmlWriter  xml;
+        std::mutex mutex;
+
+        // caching pointers/references to objects of these types - safe to do
+        const ContextOptions& opt;
+        const TestCaseData*   tc = nullptr;
+
+        XmlReporter(const ContextOptions& co)
+                : xml(*co.cout)
+                , opt(co) {}
+
+        void log_contexts() {
+            int num_contexts = get_num_active_contexts();
+            if(num_contexts) {
+                auto              contexts = get_active_contexts();
+                std::stringstream ss;
+                for(int i = 0; i < num_contexts; ++i) {
+                    contexts[i]->stringify(&ss);
+                    xml.scopedElement("Info").writeText(ss.str());
+                    ss.str("");
+                }
+            }
+        }
+
+        unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; }
+
+        void test_case_start_impl(const TestCaseData& in) {
+            bool open_ts_tag = false;
+            if(tc != nullptr) { // we have already opened a test suite
+                if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) {
+                    xml.endElement();
+                    open_ts_tag = true;
+                }
+            }
+            else {
+                open_ts_tag = true; // first test case ==> first test suite
+            }
+
+            if(open_ts_tag) {
+                xml.startElement("TestSuite");
+                xml.writeAttribute("name", in.m_test_suite);
+            }
+
+            tc = &in;
+            xml.startElement("TestCase")
+                    .writeAttribute("name", in.m_name)
+                    .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str()))
+                    .writeAttribute("line", line(in.m_line))
+                    .writeAttribute("description", in.m_description);
+
+            if(Approx(in.m_timeout) != 0)
+                xml.writeAttribute("timeout", in.m_timeout);
+            if(in.m_may_fail)
+                xml.writeAttribute("may_fail", true);
+            if(in.m_should_fail)
+                xml.writeAttribute("should_fail", true);
+        }
+
+        // =========================================================================================
+        // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+        // =========================================================================================
+
+        void report_query(const QueryData& in) override {
+            test_run_start();
+            if(opt.list_reporters) {
+                for(auto& curr : getListeners())
+                    xml.scopedElement("Listener")
+                            .writeAttribute("priority", curr.first.first)
+                            .writeAttribute("name", curr.first.second);
+                for(auto& curr : getReporters())
+                    xml.scopedElement("Reporter")
+                            .writeAttribute("priority", curr.first.first)
+                            .writeAttribute("name", curr.first.second);
+            } else if(opt.count || opt.list_test_cases) {
+                for(unsigned i = 0; i < in.num_data; ++i) {
+                    xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name)
+                        .writeAttribute("testsuite", in.data[i]->m_test_suite)
+                        .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str()))
+                        .writeAttribute("line", line(in.data[i]->m_line))
+                        .writeAttribute("skipped", in.data[i]->m_skip);
+                }
+                xml.scopedElement("OverallResultsTestCases")
+                        .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters);
+            } else if(opt.list_test_suites) {
+                for(unsigned i = 0; i < in.num_data; ++i)
+                    xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite);
+                xml.scopedElement("OverallResultsTestCases")
+                        .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters);
+                xml.scopedElement("OverallResultsTestSuites")
+                        .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters);
+            }
+            xml.endElement();
+        }
+
+        void test_run_start() override {
+            // remove .exe extension - mainly to have the same output on UNIX and Windows
+            std::string binary_name = skipPathFromFilename(opt.binary_name.c_str());
+#ifdef DOCTEST_PLATFORM_WINDOWS
+            if(binary_name.rfind(".exe") != std::string::npos)
+                binary_name = binary_name.substr(0, binary_name.length() - 4);
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+            xml.startElement("doctest").writeAttribute("binary", binary_name);
+            if(opt.no_version == false)
+                xml.writeAttribute("version", DOCTEST_VERSION_STR);
+
+            // only the consequential ones (TODO: filters)
+            xml.scopedElement("Options")
+                    .writeAttribute("order_by", opt.order_by.c_str())
+                    .writeAttribute("rand_seed", opt.rand_seed)
+                    .writeAttribute("first", opt.first)
+                    .writeAttribute("last", opt.last)
+                    .writeAttribute("abort_after", opt.abort_after)
+                    .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels)
+                    .writeAttribute("case_sensitive", opt.case_sensitive)
+                    .writeAttribute("no_throw", opt.no_throw)
+                    .writeAttribute("no_skip", opt.no_skip);
+        }
+
+        void test_run_end(const TestRunStats& p) override {
+            if(tc) // the TestSuite tag - only if there has been at least 1 test case
+                xml.endElement();
+
+            xml.scopedElement("OverallResultsAsserts")
+                    .writeAttribute("successes", p.numAsserts - p.numAssertsFailed)
+                    .writeAttribute("failures", p.numAssertsFailed);
+
+            xml.startElement("OverallResultsTestCases")
+                    .writeAttribute("successes",
+                                    p.numTestCasesPassingFilters - p.numTestCasesFailed)
+                    .writeAttribute("failures", p.numTestCasesFailed);
+            if(opt.no_skipped_summary == false)
+                xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters);
+            xml.endElement();
+
+            xml.endElement();
+        }
+
+        void test_case_start(const TestCaseData& in) override {
+            test_case_start_impl(in);
+            xml.ensureTagClosed();
+        }
+        
+        void test_case_reenter(const TestCaseData&) override {}
+
+        void test_case_end(const CurrentTestCaseStats& st) override {
+            xml.startElement("OverallResultsAsserts")
+                    .writeAttribute("successes",
+                                    st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest)
+                    .writeAttribute("failures", st.numAssertsFailedCurrentTest)
+                    .writeAttribute("test_case_success", st.testCaseSuccess);
+            if(opt.duration)
+                xml.writeAttribute("duration", st.seconds);
+            if(tc->m_expected_failures)
+                xml.writeAttribute("expected_failures", tc->m_expected_failures);
+            xml.endElement();
+
+            xml.endElement();
+        }
+
+        void test_case_exception(const TestCaseException& e) override {
+            std::lock_guard<std::mutex> lock(mutex);
+
+            xml.scopedElement("Exception")
+                    .writeAttribute("crash", e.is_crash)
+                    .writeText(e.error_string.c_str());
+        }
+
+        void subcase_start(const SubcaseSignature& in) override {
+            xml.startElement("SubCase")
+                    .writeAttribute("name", in.m_name)
+                    .writeAttribute("filename", skipPathFromFilename(in.m_file))
+                    .writeAttribute("line", line(in.m_line));
+            xml.ensureTagClosed();
+        }
+
+        void subcase_end() override { xml.endElement(); }
+
+        void log_assert(const AssertData& rb) override {
+            if(!rb.m_failed && !opt.success)
+                return;
+
+            std::lock_guard<std::mutex> lock(mutex);
+
+            xml.startElement("Expression")
+                    .writeAttribute("success", !rb.m_failed)
+                    .writeAttribute("type", assertString(rb.m_at))
+                    .writeAttribute("filename", skipPathFromFilename(rb.m_file))
+                    .writeAttribute("line", line(rb.m_line));
+
+            xml.scopedElement("Original").writeText(rb.m_expr);
+
+            if(rb.m_threw)
+                xml.scopedElement("Exception").writeText(rb.m_exception.c_str());
+
+            if(rb.m_at & assertType::is_throws_as)
+                xml.scopedElement("ExpectedException").writeText(rb.m_exception_type);
+            if(rb.m_at & assertType::is_throws_with)
+                xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string);
+            if((rb.m_at & assertType::is_normal) && !rb.m_threw)
+                xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str());
+
+            log_contexts();
+
+            xml.endElement();
+        }
+
+        void log_message(const MessageData& mb) override {
+            std::lock_guard<std::mutex> lock(mutex);
+
+            xml.startElement("Message")
+                    .writeAttribute("type", failureString(mb.m_severity))
+                    .writeAttribute("filename", skipPathFromFilename(mb.m_file))
+                    .writeAttribute("line", line(mb.m_line));
+
+            xml.scopedElement("Text").writeText(mb.m_string.c_str());
+
+            log_contexts();
+
+            xml.endElement();
+        }
+
+        void test_case_skipped(const TestCaseData& in) override {
+            if(opt.no_skipped_summary == false) {
+                test_case_start_impl(in);
+                xml.writeAttribute("skipped", "true");
+                xml.endElement();
+            }
+        }
+    };
+
+    DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter);
+
+    void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) {
+        if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) ==
+            0) //!OCLINT bitwise operator in conditional
+            s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) "
+                << Color::None;
+
+        if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional
+            s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n";
+        } else if((rb.m_at & assertType::is_throws_as) &&
+                    (rb.m_at & assertType::is_throws_with)) { //!OCLINT
+            s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \""
+                << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None;
+            if(rb.m_threw) {
+                if(!rb.m_failed) {
+                    s << "threw as expected!\n";
+                } else {
+                    s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n";
+                }
+            } else {
+                s << "did NOT throw at all!\n";
+            }
+        } else if(rb.m_at &
+                    assertType::is_throws_as) { //!OCLINT bitwise operator in conditional
+            s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", "
+                << rb.m_exception_type << " ) " << Color::None
+                << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" :
+                                                "threw a DIFFERENT exception: ") :
+                                "did NOT throw at all!")
+                << Color::Cyan << rb.m_exception << "\n";
+        } else if(rb.m_at &
+                    assertType::is_throws_with) { //!OCLINT bitwise operator in conditional
+            s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \""
+                << rb.m_exception_string << "\" ) " << Color::None
+                << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" :
+                                                "threw a DIFFERENT exception: ") :
+                                "did NOT throw at all!")
+                << Color::Cyan << rb.m_exception << "\n";
+        } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional
+            s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan
+                << rb.m_exception << "\n";
+        } else {
+            s << (rb.m_threw ? "THREW exception: " :
+                                (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n"));
+            if(rb.m_threw)
+                s << rb.m_exception << "\n";
+            else
+                s << "  values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n";
+        }
+    }
+
+    // TODO:
+    // - log_message()
+    // - respond to queries
+    // - honor remaining options
+    // - more attributes in tags
+    struct JUnitReporter : public IReporter
+    {
+        XmlWriter  xml;
+        std::mutex mutex;
+        Timer timer;
+        std::vector<String> deepestSubcaseStackNames;
+
+        struct JUnitTestCaseData
+        {
+            static std::string getCurrentTimestamp() {
+                // Beware, this is not reentrant because of backward compatibility issues
+                // Also, UTC only, again because of backward compatibility (%z is C++11)
+                time_t rawtime;
+                std::time(&rawtime);
+                auto const timeStampSize = sizeof("2017-01-16T17:06:45Z");
+
+                std::tm timeInfo;
+#ifdef DOCTEST_PLATFORM_WINDOWS
+                gmtime_s(&timeInfo, &rawtime);
+#else // DOCTEST_PLATFORM_WINDOWS
+                gmtime_r(&rawtime, &timeInfo);
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+                char timeStamp[timeStampSize];
+                const char* const fmt = "%Y-%m-%dT%H:%M:%SZ";
+
+                std::strftime(timeStamp, timeStampSize, fmt, &timeInfo);
+                return std::string(timeStamp);
+            }
+
+            struct JUnitTestMessage
+            {
+                JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details)
+                    : message(_message), type(_type), details(_details) {}
+
+                JUnitTestMessage(const std::string& _message, const std::string& _details)
+                    : message(_message), type(), details(_details) {}
+
+                std::string message, type, details;
+            };
+
+            struct JUnitTestCase
+            {
+                JUnitTestCase(const std::string& _classname, const std::string& _name)
+                    : classname(_classname), name(_name), time(0), failures() {}
+
+                std::string classname, name;
+                double time;
+                std::vector<JUnitTestMessage> failures, errors;
+            };
+
+            void add(const std::string& classname, const std::string& name) {
+                testcases.emplace_back(classname, name);
+            }
+
+            void appendSubcaseNamesToLastTestcase(std::vector<String> nameStack) {
+                for(auto& curr: nameStack)
+                    if(curr.size())
+                        testcases.back().name += std::string("/") + curr.c_str();
+            }
+
+            void addTime(double time) {
+                if(time < 1e-4)
+                    time = 0;
+                testcases.back().time = time;
+                totalSeconds += time;
+            }
+
+            void addFailure(const std::string& message, const std::string& type, const std::string& details) {
+                testcases.back().failures.emplace_back(message, type, details);
+                ++totalFailures;
+            }
+
+            void addError(const std::string& message, const std::string& details) {
+                testcases.back().errors.emplace_back(message, details);
+                ++totalErrors;
+            }
+
+            std::vector<JUnitTestCase> testcases;
+            double totalSeconds = 0;
+            int totalErrors = 0, totalFailures = 0;
+        };
+
+        JUnitTestCaseData testCaseData;
+
+        // caching pointers/references to objects of these types - safe to do
+        const ContextOptions& opt;
+        const TestCaseData*   tc = nullptr;
+
+        JUnitReporter(const ContextOptions& co)
+                : xml(*co.cout)
+                , opt(co) {}
+
+        unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; }
+
+        // =========================================================================================
+        // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+        // =========================================================================================
+
+        void report_query(const QueryData&) override {}
+
+        void test_run_start() override {}
+
+        void test_run_end(const TestRunStats& p) override {
+            // remove .exe extension - mainly to have the same output on UNIX and Windows
+            std::string binary_name = skipPathFromFilename(opt.binary_name.c_str());
+#ifdef DOCTEST_PLATFORM_WINDOWS
+            if(binary_name.rfind(".exe") != std::string::npos)
+                binary_name = binary_name.substr(0, binary_name.length() - 4);
+#endif // DOCTEST_PLATFORM_WINDOWS
+            xml.startElement("testsuites");
+            xml.startElement("testsuite").writeAttribute("name", binary_name)
+                    .writeAttribute("errors", testCaseData.totalErrors)
+                    .writeAttribute("failures", testCaseData.totalFailures)
+                    .writeAttribute("tests", p.numAsserts);
+            if(opt.no_time_in_output == false) {
+                xml.writeAttribute("time", testCaseData.totalSeconds);
+                xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp());
+            }
+            if(opt.no_version == false)
+                xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR);
+
+            for(const auto& testCase : testCaseData.testcases) {
+                xml.startElement("testcase")
+                    .writeAttribute("classname", testCase.classname)
+                    .writeAttribute("name", testCase.name);
+                if(opt.no_time_in_output == false)
+                    xml.writeAttribute("time", testCase.time);
+                // This is not ideal, but it should be enough to mimic gtest's junit output.
+                xml.writeAttribute("status", "run");
+
+                for(const auto& failure : testCase.failures) {
+                    xml.scopedElement("failure")
+                        .writeAttribute("message", failure.message)
+                        .writeAttribute("type", failure.type)
+                        .writeText(failure.details, false);
+                }
+
+                for(const auto& error : testCase.errors) {
+                    xml.scopedElement("error")
+                        .writeAttribute("message", error.message)
+                        .writeText(error.details);
+                }
+
+                xml.endElement();
+            }
+            xml.endElement();
+            xml.endElement();
+        }
+
+        void test_case_start(const TestCaseData& in) override {
+            testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name);
+            timer.start();
+        }
+
+        void test_case_reenter(const TestCaseData& in) override {
+            testCaseData.addTime(timer.getElapsedSeconds());
+            testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames);
+            deepestSubcaseStackNames.clear();
+
+            timer.start();
+            testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name);
+        }
+
+        void test_case_end(const CurrentTestCaseStats&) override {
+            testCaseData.addTime(timer.getElapsedSeconds());
+            testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames);
+            deepestSubcaseStackNames.clear();
+        }
+
+        void test_case_exception(const TestCaseException& e) override {
+            std::lock_guard<std::mutex> lock(mutex);
+            testCaseData.addError("exception", e.error_string.c_str());
+        }
+
+        void subcase_start(const SubcaseSignature& in) override {
+            deepestSubcaseStackNames.push_back(in.m_name);
+        }
+
+        void subcase_end() override {}
+
+        void log_assert(const AssertData& rb) override {
+            if(!rb.m_failed) // report only failures & ignore the `success` option
+                return;
+
+            std::lock_guard<std::mutex> lock(mutex);
+
+            std::ostringstream os;
+            os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(")
+              << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl;
+
+            fulltext_log_assert_to_stream(os, rb);
+            log_contexts(os);
+            testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str());
+        }
+
+        void log_message(const MessageData&) override {}
+
+        void test_case_skipped(const TestCaseData&) override {}
+
+        void log_contexts(std::ostringstream& s) {
+            int num_contexts = get_num_active_contexts();
+            if(num_contexts) {
+                auto contexts = get_active_contexts();
+
+                s << "  logged: ";
+                for(int i = 0; i < num_contexts; ++i) {
+                    s << (i == 0 ? "" : "          ");
+                    contexts[i]->stringify(&s);
+                    s << std::endl;
+                }
+            }
+        }
+    };
+
+    DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter);
+
+    struct Whitespace
+    {
+        int nrSpaces;
+        explicit Whitespace(int nr)
+                : nrSpaces(nr) {}
+    };
+
+    std::ostream& operator<<(std::ostream& out, const Whitespace& ws) {
+        if(ws.nrSpaces != 0)
+            out << std::setw(ws.nrSpaces) << ' ';
+        return out;
+    }
+
+    struct ConsoleReporter : public IReporter
+    {
+        std::ostream&                 s;
+        bool                          hasLoggedCurrentTestStart;
+        std::vector<SubcaseSignature> subcasesStack;
+        size_t                        currentSubcaseLevel;
+        std::mutex                    mutex;
+
+        // caching pointers/references to objects of these types - safe to do
+        const ContextOptions& opt;
+        const TestCaseData*   tc;
+
+        ConsoleReporter(const ContextOptions& co)
+                : s(*co.cout)
+                , opt(co) {}
+
+        ConsoleReporter(const ContextOptions& co, std::ostream& ostr)
+                : s(ostr)
+                , opt(co) {}
+
+        // =========================================================================================
+        // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE
+        // =========================================================================================
+
+        void separator_to_stream() {
+            s << Color::Yellow
+              << "==============================================================================="
+                 "\n";
+        }
+
+        const char* getSuccessOrFailString(bool success, assertType::Enum at,
+                                           const char* success_str) {
+            if(success)
+                return success_str;
+            return failureString(at);
+        }
+
+        Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) {
+            return success ? Color::BrightGreen :
+                             (at & assertType::is_warn) ? Color::Yellow : Color::Red;
+        }
+
+        void successOrFailColoredStringToStream(bool success, assertType::Enum at,
+                                                const char* success_str = "SUCCESS") {
+            s << getSuccessOrFailColor(success, at)
+              << getSuccessOrFailString(success, at, success_str) << ": ";
+        }
+
+        void log_contexts() {
+            int num_contexts = get_num_active_contexts();
+            if(num_contexts) {
+                auto contexts = get_active_contexts();
+
+                s << Color::None << "  logged: ";
+                for(int i = 0; i < num_contexts; ++i) {
+                    s << (i == 0 ? "" : "          ");
+                    contexts[i]->stringify(&s);
+                    s << "\n";
+                }
+            }
+
+            s << "\n";
+        }
+
+        // this was requested to be made virtual so users could override it
+        virtual void file_line_to_stream(const char* file, int line,
+                                        const char* tail = "") {
+            s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(")
+            << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option
+            << (opt.gnu_file_line ? ":" : "):") << tail;
+        }
+
+        void logTestStart() {
+            if(hasLoggedCurrentTestStart)
+                return;
+
+            separator_to_stream();
+            file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n");
+            if(tc->m_description)
+                s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n";
+            if(tc->m_test_suite && tc->m_test_suite[0] != '\0')
+                s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n";
+            if(strncmp(tc->m_name, "  Scenario:", 11) != 0)
+                s << Color::Yellow << "TEST CASE:  ";
+            s << Color::None << tc->m_name << "\n";
+
+            for(size_t i = 0; i < currentSubcaseLevel; ++i) {
+                if(subcasesStack[i].m_name[0] != '\0')
+                    s << "  " << subcasesStack[i].m_name << "\n";
+            }
+
+            if(currentSubcaseLevel != subcasesStack.size()) {
+                s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None;
+                for(size_t i = 0; i < subcasesStack.size(); ++i) {
+                    if(subcasesStack[i].m_name[0] != '\0')
+                        s << "  " << subcasesStack[i].m_name << "\n";
+                }
+            }
+
+            s << "\n";
+
+            hasLoggedCurrentTestStart = true;
+        }
+
+        void printVersion() {
+            if(opt.no_version == false)
+                s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \""
+                  << DOCTEST_VERSION_STR << "\"\n";
+        }
+
+        void printIntro() {
+            if(opt.no_intro == false) {
+                printVersion();
+                s << Color::Cyan << "[doctest] " << Color::None
+                  << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n";
+            }
+        }
+
+        void printHelp() {
+            int sizePrefixDisplay = static_cast<int>(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY));
+            printVersion();
+            // clang-format off
+            s << Color::Cyan << "[doctest]\n" << Color::None;
+            s << Color::Cyan << "[doctest] " << Color::None;
+            s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n";
+            s << Color::Cyan << "[doctest] " << Color::None;
+            s << "filter  values: \"str1,str2,str3\" (comma separated strings)\n";
+            s << Color::Cyan << "[doctest]\n" << Color::None;
+            s << Color::Cyan << "[doctest] " << Color::None;
+            s << "filters use wildcards for matching strings\n";
+            s << Color::Cyan << "[doctest] " << Color::None;
+            s << "something passes a filter if any of the strings in a filter matches\n";
+#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+            s << Color::Cyan << "[doctest]\n" << Color::None;
+            s << Color::Cyan << "[doctest] " << Color::None;
+            s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n";
+#endif
+            s << Color::Cyan << "[doctest]\n" << Color::None;
+            s << Color::Cyan << "[doctest] " << Color::None;
+            s << "Query flags - the program quits after them. Available:\n\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h                      "
+              << Whitespace(sizePrefixDisplay*0) <<  "prints this message\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version                       "
+              << Whitespace(sizePrefixDisplay*1) << "prints the version\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count                         "
+              << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases               "
+              << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites              "
+              << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters                "
+              << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n";
+            // ================================================================================== << 79
+            s << Color::Cyan << "[doctest] " << Color::None;
+            s << "The available <int>/<string> options/filters are:\n\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case=<filters>           "
+              << Whitespace(sizePrefixDisplay*1) << "filters     tests by their name\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude=<filters>   "
+              << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file=<filters>         "
+              << Whitespace(sizePrefixDisplay*1) << "filters     tests by their file\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude=<filters> "
+              << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite=<filters>          "
+              << Whitespace(sizePrefixDisplay*1) << "filters     tests by their test suite\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude=<filters>  "
+              << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase=<filters>             "
+              << Whitespace(sizePrefixDisplay*1) << "filters     subcases by their name\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude=<filters>     "
+              << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters=<filters>           "
+              << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out=<string>                  "
+              << Whitespace(sizePrefixDisplay*1) << "output filename\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by=<string>             "
+              << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n";
+            s << Whitespace(sizePrefixDisplay*3) << "                                       <string> - [file/suite/name/rand/none]\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed=<int>               "
+              << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first=<int>                   "
+              << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n";
+            s << Whitespace(sizePrefixDisplay*3) << "                                       execute - for range-based execution\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last=<int>                    "
+              << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n";
+            s << Whitespace(sizePrefixDisplay*3) << "                                       execute - for range-based execution\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after=<int>             "
+              << Whitespace(sizePrefixDisplay*1) << "stop after <int> failed assertions\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels=<int>   "
+              << Whitespace(sizePrefixDisplay*1) << "apply filters for the first <int> levels\n";
+            s << Color::Cyan << "\n[doctest] " << Color::None;
+            s << "Bool options - can be used like flags and true is assumed. Available:\n\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success=<bool>                "
+              << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive=<bool>         "
+              << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit=<bool>                   "
+              << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration=<bool>               "
+              << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal=<bool>                "
+              << Whitespace(sizePrefixDisplay*1) << "minimal console output (only failures)\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet=<bool>                  "
+              << Whitespace(sizePrefixDisplay*1) << "no console output\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw=<bool>               "
+              << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode=<bool>            "
+              << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run=<bool>                 "
+              << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro=<bool>               "
+              << Whitespace(sizePrefixDisplay*1) << "omit the framework intro in the output\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version=<bool>             "
+              << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors=<bool>              "
+              << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors=<bool>           "
+              << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks=<bool>              "
+              << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip=<bool>                "
+              << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line=<bool>          "
+              << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames=<bool>      "
+              << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers=<bool>        "
+              << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n";
+            // ================================================================================== << 79
+            // clang-format on
+
+            s << Color::Cyan << "\n[doctest] " << Color::None;
+            s << "for more information visit the project documentation\n\n";
+        }
+
+        void printRegisteredReporters() {
+            printVersion();
+            auto printReporters = [this] (const reporterMap& reporters, const char* type) {
+                if(reporters.size()) {
+                    s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n";
+                    for(auto& curr : reporters)
+                        s << "priority: " << std::setw(5) << curr.first.first
+                          << " name: " << curr.first.second << "\n";
+                }
+            };
+            printReporters(getListeners(), "listeners");
+            printReporters(getReporters(), "reporters");
+        }
+
+        // =========================================================================================
+        // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+        // =========================================================================================
+
+        void report_query(const QueryData& in) override {
+            if(opt.version) {
+                printVersion();
+            } else if(opt.help) {
+                printHelp();
+            } else if(opt.list_reporters) {
+                printRegisteredReporters();
+            } else if(opt.count || opt.list_test_cases) {
+                if(opt.list_test_cases) {
+                    s << Color::Cyan << "[doctest] " << Color::None
+                      << "listing all test case names\n";
+                    separator_to_stream();
+                }
+
+                for(unsigned i = 0; i < in.num_data; ++i)
+                    s << Color::None << in.data[i]->m_name << "\n";
+
+                separator_to_stream();
+
+                s << Color::Cyan << "[doctest] " << Color::None
+                  << "unskipped test cases passing the current filters: "
+                  << g_cs->numTestCasesPassingFilters << "\n";
+
+            } else if(opt.list_test_suites) {
+                s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n";
+                separator_to_stream();
+
+                for(unsigned i = 0; i < in.num_data; ++i)
+                    s << Color::None << in.data[i]->m_test_suite << "\n";
+
+                separator_to_stream();
+
+                s << Color::Cyan << "[doctest] " << Color::None
+                  << "unskipped test cases passing the current filters: "
+                  << g_cs->numTestCasesPassingFilters << "\n";
+                s << Color::Cyan << "[doctest] " << Color::None
+                  << "test suites with unskipped test cases passing the current filters: "
+                  << g_cs->numTestSuitesPassingFilters << "\n";
+            }
+        }
+
+        void test_run_start() override {
+            if(!opt.minimal)
+                printIntro();
+        }
+
+        void test_run_end(const TestRunStats& p) override {
+            if(opt.minimal && p.numTestCasesFailed == 0)
+                return;
+
+            separator_to_stream();
+            s << std::dec;
+
+            auto totwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters, static_cast<unsigned>(p.numAsserts))) + 1)));
+            auto passwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast<unsigned>(p.numAsserts - p.numAssertsFailed))) + 1)));
+            auto failwidth = int(std::ceil(log10((std::max(p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) + 1)));
+            const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;
+            s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth)
+              << p.numTestCasesPassingFilters << " | "
+              << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None :
+                                                                          Color::Green)
+              << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed"
+              << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None)
+              << std::setw(failwidth) << p.numTestCasesFailed << " failed" << Color::None << " |";
+            if(opt.no_skipped_summary == false) {
+                const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters;
+                s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped
+                  << " skipped" << Color::None;
+            }
+            s << "\n";
+            s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth)
+              << p.numAsserts << " | "
+              << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green)
+              << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None
+              << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth)
+              << p.numAssertsFailed << " failed" << Color::None << " |\n";
+            s << Color::Cyan << "[doctest] " << Color::None
+              << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green)
+              << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl;
+        }
+
+        void test_case_start(const TestCaseData& in) override {
+            hasLoggedCurrentTestStart = false;
+            tc                        = &in;
+            subcasesStack.clear();
+            currentSubcaseLevel = 0;
+        }
+        
+        void test_case_reenter(const TestCaseData&) override {
+            subcasesStack.clear();
+        }
+
+        void test_case_end(const CurrentTestCaseStats& st) override {
+            if(tc->m_no_output)
+                return;
+
+            // log the preamble of the test case only if there is something
+            // else to print - something other than that an assert has failed
+            if(opt.duration ||
+               (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure))
+                logTestStart();
+
+            if(opt.duration)
+                s << Color::None << std::setprecision(6) << std::fixed << st.seconds
+                  << " s: " << tc->m_name << "\n";
+
+            if(st.failure_flags & TestCaseFailureReason::Timeout)
+                s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6)
+                  << std::fixed << tc->m_timeout << "!\n";
+
+            if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) {
+                s << Color::Red << "Should have failed but didn't! Marking it as failed!\n";
+            } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) {
+                s << Color::Yellow << "Failed as expected so marking it as not failed\n";
+            } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) {
+                s << Color::Yellow << "Allowed to fail so marking it as not failed\n";
+            } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) {
+                s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures
+                  << " times so marking it as failed!\n";
+            } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) {
+                s << Color::Yellow << "Failed exactly " << tc->m_expected_failures
+                  << " times as expected so marking it as not failed!\n";
+            }
+            if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) {
+                s << Color::Red << "Aborting - too many failed asserts!\n";
+            }
+            s << Color::None; // lgtm [cpp/useless-expression]
+        }
+
+        void test_case_exception(const TestCaseException& e) override {
+            std::lock_guard<std::mutex> lock(mutex);
+            if(tc->m_no_output)
+                return;
+
+            logTestStart();
+
+            file_line_to_stream(tc->m_file.c_str(), tc->m_line, " ");
+            successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require :
+                                                                   assertType::is_check);
+            s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ")
+              << Color::Cyan << e.error_string << "\n";
+
+            int num_stringified_contexts = get_num_stringified_contexts();
+            if(num_stringified_contexts) {
+                auto stringified_contexts = get_stringified_contexts();
+                s << Color::None << "  logged: ";
+                for(int i = num_stringified_contexts; i > 0; --i) {
+                    s << (i == num_stringified_contexts ? "" : "          ")
+                      << stringified_contexts[i - 1] << "\n";
+                }
+            }
+            s << "\n" << Color::None;
+        }
+
+        void subcase_start(const SubcaseSignature& subc) override {
+            subcasesStack.push_back(subc);
+            ++currentSubcaseLevel;
+            hasLoggedCurrentTestStart = false;
+        }
+
+        void subcase_end() override {
+            --currentSubcaseLevel;
+            hasLoggedCurrentTestStart = false;
+        }
+
+        void log_assert(const AssertData& rb) override {
+            if((!rb.m_failed && !opt.success) || tc->m_no_output)
+                return;
+
+            std::lock_guard<std::mutex> lock(mutex);
+
+            logTestStart();
+
+            file_line_to_stream(rb.m_file, rb.m_line, " ");
+            successOrFailColoredStringToStream(!rb.m_failed, rb.m_at);
+
+            fulltext_log_assert_to_stream(s, rb);
+
+            log_contexts();
+        }
+
+        void log_message(const MessageData& mb) override {
+            if(tc->m_no_output)
+                return;
+
+            std::lock_guard<std::mutex> lock(mutex);
+
+            logTestStart();
+
+            file_line_to_stream(mb.m_file, mb.m_line, " ");
+            s << getSuccessOrFailColor(false, mb.m_severity)
+              << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity,
+                                        "MESSAGE") << ": ";
+            s << Color::None << mb.m_string << "\n";
+            log_contexts();
+        }
+
+        void test_case_skipped(const TestCaseData&) override {}
+    };
+
+    DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter);
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+    struct DebugOutputWindowReporter : public ConsoleReporter
+    {
+        DOCTEST_THREAD_LOCAL static std::ostringstream oss;
+
+        DebugOutputWindowReporter(const ContextOptions& co)
+                : ConsoleReporter(co, oss) {}
+
+#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg)                                    \
+    void func(type arg) override {                                                                 \
+        bool with_col = g_no_colors;                                                               \
+        g_no_colors   = false;                                                                     \
+        ConsoleReporter::func(arg);                                                                \
+        if(oss.tellp() != std::streampos{}) {                                                      \
+            DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str());                                        \
+            oss.str("");                                                                           \
+        }                                                                                          \
+        g_no_colors = with_col;                                                                    \
+    }
+
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in)
+    };
+
+    DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss;
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+    // the implementation of parseOption()
+    bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) {
+        // going from the end to the beginning and stopping on the first occurrence from the end
+        for(int i = argc; i > 0; --i) {
+            auto index = i - 1;
+            auto temp = std::strstr(argv[index], pattern);
+            if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue
+                // eliminate matches in which the chars before the option are not '-'
+                bool noBadCharsFound = true;
+                auto curr            = argv[index];
+                while(curr != temp) {
+                    if(*curr++ != '-') {
+                        noBadCharsFound = false;
+                        break;
+                    }
+                }
+                if(noBadCharsFound && argv[index][0] == '-') {
+                    if(value) {
+                        // parsing the value of an option
+                        temp += strlen(pattern);
+                        const unsigned len = strlen(temp);
+                        if(len) {
+                            *value = temp;
+                            return true;
+                        }
+                    } else {
+                        // just a flag - no value
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    // parses an option and returns the string after the '=' character
+    bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr,
+                     const String& defaultVal = String()) {
+        if(value)
+            *value = defaultVal;
+#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+        // offset (normally 3 for "dt-") to skip prefix
+        if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value))
+            return true;
+#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+        return parseOptionImpl(argc, argv, pattern, value);
+    }
+
+    // locates a flag on the command line
+    bool parseFlag(int argc, const char* const* argv, const char* pattern) {
+        return parseOption(argc, argv, pattern);
+    }
+
+    // parses a comma separated list of words after a pattern in one of the arguments in argv
+    bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern,
+                           std::vector<String>& res) {
+        String filtersString;
+        if(parseOption(argc, argv, pattern, &filtersString)) {
+            // tokenize with "," as a separator, unless escaped with backslash
+            std::ostringstream s;
+            auto flush = [&s, &res]() {
+                auto string = s.str();
+                if(string.size() > 0) {
+                    res.push_back(string.c_str());
+                }
+                s.str("");
+            };
+
+            bool seenBackslash = false;
+            const char* current = filtersString.c_str();
+            const char* end = current + strlen(current);
+            while(current != end) {
+                char character = *current++;
+                if(seenBackslash) {
+                    seenBackslash = false;
+                    if(character == ',') {
+                        s.put(',');
+                        continue;
+                    }
+                    s.put('\\');
+                }
+                if(character == '\\') {
+                    seenBackslash = true;
+                } else if(character == ',') {
+                    flush();
+                } else {
+                    s.put(character);
+                }
+            }
+
+            if(seenBackslash) {
+                s.put('\\');
+            }
+            flush();
+            return true;
+        }
+        return false;
+    }
+
+    enum optionType
+    {
+        option_bool,
+        option_int
+    };
+
+    // parses an int/bool option from the command line
+    bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type,
+                        int& res) {
+        String parsedValue;
+        if(!parseOption(argc, argv, pattern, &parsedValue))
+            return false;
+
+        if(type == 0) {
+            // boolean
+            const char positive[][5] = {"1", "true", "on", "yes"};  // 5 - strlen("true") + 1
+            const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1
+
+            // if the value matches any of the positive/negative possibilities
+            for(unsigned i = 0; i < 4; i++) {
+                if(parsedValue.compare(positive[i], true) == 0) {
+                    res = 1; //!OCLINT parameter reassignment
+                    return true;
+                }
+                if(parsedValue.compare(negative[i], true) == 0) {
+                    res = 0; //!OCLINT parameter reassignment
+                    return true;
+                }
+            }
+        } else {
+            // integer
+            // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse...
+            int theInt = std::atoi(parsedValue.c_str()); // NOLINT
+            if(theInt != 0) {
+                res = theInt; //!OCLINT parameter reassignment
+                return true;
+            }
+        }
+        return false;
+    }
+} // namespace
+
+Context::Context(int argc, const char* const* argv)
+        : p(new detail::ContextState) {
+    parseArgs(argc, argv, true);
+    if(argc)
+        p->binary_name = argv[0];
+}
+
+Context::~Context() {
+    if(g_cs == p)
+        g_cs = nullptr;
+    delete p;
+}
+
+void Context::applyCommandLine(int argc, const char* const* argv) {
+    parseArgs(argc, argv);
+    if(argc)
+        p->binary_name = argv[0];
+}
+
+// parses args
+void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) {
+    using namespace detail;
+
+    // clang-format off
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=",        p->filters[0]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=",                 p->filters[0]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=",                p->filters[1]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=",         p->filters[2]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=",                 p->filters[2]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=",                p->filters[3]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=",          p->filters[4]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=",                 p->filters[4]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=",  p->filters[5]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=",                p->filters[5]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=",            p->filters[6]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=",                 p->filters[6]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=",    p->filters[7]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=",                p->filters[7]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=",          p->filters[8]);
+    parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=",                  p->filters[8]);
+    // clang-format on
+
+    int    intRes = 0;
+    String strRes;
+
+#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default)                                   \
+    if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) ||  \
+       parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes))   \
+        p->var = static_cast<bool>(intRes);                                                        \
+    else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) ||                           \
+            parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname))                            \
+        p->var = true;                                                                             \
+    else if(withDefaults)                                                                          \
+    p->var = default
+
+#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default)                                        \
+    if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) ||   \
+       parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes))    \
+        p->var = intRes;                                                                           \
+    else if(withDefaults)                                                                          \
+    p->var = default
+
+#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default)                                        \
+    if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) ||        \
+       parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) ||       \
+       withDefaults)                                                                               \
+    p->var = strRes
+
+    // clang-format off
+    DOCTEST_PARSE_STR_OPTION("out", "o", out, "");
+    DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file");
+    DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0);
+
+    DOCTEST_PARSE_INT_OPTION("first", "f", first, 0);
+    DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX);
+
+    DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0);
+    DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX);
+
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("minimal", "m", minimal, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("quiet", "q", quiet, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-intro", "ni", no_intro, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC));
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-debug-output", "ndo", no_debug_output, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false);
+    DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false);
+    // clang-format on
+
+    if(withDefaults) {
+        p->help             = false;
+        p->version          = false;
+        p->count            = false;
+        p->list_test_cases  = false;
+        p->list_test_suites = false;
+        p->list_reporters   = false;
+    }
+    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") ||
+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") ||
+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) {
+        p->help = true;
+        p->exit = true;
+    }
+    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") ||
+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) {
+        p->version = true;
+        p->exit    = true;
+    }
+    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") ||
+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) {
+        p->count = true;
+        p->exit  = true;
+    }
+    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") ||
+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) {
+        p->list_test_cases = true;
+        p->exit            = true;
+    }
+    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") ||
+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) {
+        p->list_test_suites = true;
+        p->exit             = true;
+    }
+    if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") ||
+       parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) {
+        p->list_reporters = true;
+        p->exit           = true;
+    }
+}
+
+// allows the user to add procedurally to the filters from the command line
+void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); }
+
+// allows the user to clear all filters from the command line
+void Context::clearFilters() {
+    for(auto& curr : p->filters)
+        curr.clear();
+}
+
+// allows the user to override procedurally the bool options from the command line
+void Context::setOption(const char* option, bool value) {
+    setOption(option, value ? "true" : "false");
+}
+
+// allows the user to override procedurally the int options from the command line
+void Context::setOption(const char* option, int value) {
+    setOption(option, toString(value).c_str());
+    // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+}
+
+// allows the user to override procedurally the string options from the command line
+void Context::setOption(const char* option, const char* value) {
+    auto argv   = String("-") + option + "=" + value;
+    auto lvalue = argv.c_str();
+    parseArgs(1, &lvalue);
+}
+
+// users should query this in their main() and exit the program if true
+bool Context::shouldExit() { return p->exit; }
+
+void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; }
+
+void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; }
+
+void Context::setCout(std::ostream* out) { p->cout = out; }
+
+static class DiscardOStream : public std::ostream
+{
+private:
+    class : public std::streambuf
+    {
+    private:
+        // allowing some buffering decreases the amount of calls to overflow
+        char buf[1024];
+
+    protected:
+        std::streamsize xsputn(const char_type*, std::streamsize count) override { return count; }
+
+        int_type overflow(int_type ch) override {
+            setp(std::begin(buf), std::end(buf));
+            return traits_type::not_eof(ch);
+        }
+    } discardBuf;
+
+public:
+    DiscardOStream()
+            : std::ostream(&discardBuf) {}
+} discardOut;
+
+// the main function that does all the filtering and test running
+int Context::run() {
+    using namespace detail;
+
+    // save the old context state in case such was setup - for using asserts out of a testing context
+    auto old_cs = g_cs;
+    // this is the current contest
+    g_cs               = p;
+    is_running_in_test = true;
+
+    g_no_colors = p->no_colors;
+    p->resetRunData();
+
+    std::fstream fstr;
+    if(p->cout == nullptr) {
+        if(p->quiet) {
+            p->cout = &discardOut;
+        } else if(p->out.size()) {
+            // to a file if specified
+            fstr.open(p->out.c_str(), std::fstream::out);
+            p->cout = &fstr;
+        } else {
+            // stdout by default
+            p->cout = &std::cout;
+        }
+    }
+
+    FatalConditionHandler::allocateAltStackMem();
+
+    auto cleanup_and_return = [&]() {
+        FatalConditionHandler::freeAltStackMem();
+
+        if(fstr.is_open())
+            fstr.close();
+
+        // restore context
+        g_cs               = old_cs;
+        is_running_in_test = false;
+
+        // we have to free the reporters which were allocated when the run started
+        for(auto& curr : p->reporters_currently_used)
+            delete curr;
+        p->reporters_currently_used.clear();
+
+        if(p->numTestCasesFailed && !p->no_exitcode)
+            return EXIT_FAILURE;
+        return EXIT_SUCCESS;
+    };
+
+    // setup default reporter if none is given through the command line
+    if(p->filters[8].empty())
+        p->filters[8].push_back("console");
+
+    // check to see if any of the registered reporters has been selected
+    for(auto& curr : getReporters()) {
+        if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive))
+            p->reporters_currently_used.push_back(curr.second(*g_cs));
+    }
+
+    // TODO: check if there is nothing in reporters_currently_used
+
+    // prepend all listeners
+    for(auto& curr : getListeners())
+        p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs));
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+    if(isDebuggerActive() && p->no_debug_output == false)
+        p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs));
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+    // handle version, help and no_run
+    if(p->no_run || p->version || p->help || p->list_reporters) {
+        DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData());
+
+        return cleanup_and_return();
+    }
+
+    std::vector<const TestCase*> testArray;
+    for(auto& curr : getRegisteredTests())
+        testArray.push_back(&curr);
+    p->numTestCases = testArray.size();
+
+    // sort the collected records
+    if(!testArray.empty()) {
+        if(p->order_by.compare("file", true) == 0) {
+            std::sort(testArray.begin(), testArray.end(), fileOrderComparator);
+        } else if(p->order_by.compare("suite", true) == 0) {
+            std::sort(testArray.begin(), testArray.end(), suiteOrderComparator);
+        } else if(p->order_by.compare("name", true) == 0) {
+            std::sort(testArray.begin(), testArray.end(), nameOrderComparator);
+        } else if(p->order_by.compare("rand", true) == 0) {
+            std::srand(p->rand_seed);
+
+            // random_shuffle implementation
+            const auto first = &testArray[0];
+            for(size_t i = testArray.size() - 1; i > 0; --i) {
+                int idxToSwap = std::rand() % (i + 1); // NOLINT
+
+                const auto temp = first[i];
+
+                first[i]         = first[idxToSwap];
+                first[idxToSwap] = temp;
+            }
+        } else if(p->order_by.compare("none", true) == 0) {
+            // means no sorting - beneficial for death tests which call into the executable
+            // with a specific test case in mind - we don't want to slow down the startup times
+        }
+    }
+
+    std::set<String> testSuitesPassingFilt;
+
+    bool                             query_mode = p->count || p->list_test_cases || p->list_test_suites;
+    std::vector<const TestCaseData*> queryResults;
+
+    if(!query_mode)
+        DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY);
+
+    // invoke the registered functions if they match the filter criteria (or just count them)
+    for(auto& curr : testArray) {
+        const auto& tc = *curr;
+
+        bool skip_me = false;
+        if(tc.m_skip && !p->no_skip)
+            skip_me = true;
+
+        if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive))
+            skip_me = true;
+        if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive))
+            skip_me = true;
+        if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive))
+            skip_me = true;
+        if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive))
+            skip_me = true;
+        if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive))
+            skip_me = true;
+        if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive))
+            skip_me = true;
+
+        if(!skip_me)
+            p->numTestCasesPassingFilters++;
+
+        // skip the test if it is not in the execution range
+        if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) ||
+           (p->first > p->numTestCasesPassingFilters))
+            skip_me = true;
+
+        if(skip_me) {
+            if(!query_mode)
+                DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc);
+            continue;
+        }
+
+        // do not execute the test if we are to only count the number of filter passing tests
+        if(p->count)
+            continue;
+
+        // print the name of the test and don't execute it
+        if(p->list_test_cases) {
+            queryResults.push_back(&tc);
+            continue;
+        }
+
+        // print the name of the test suite if not done already and don't execute it
+        if(p->list_test_suites) {
+            if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') {
+                queryResults.push_back(&tc);
+                testSuitesPassingFilt.insert(tc.m_test_suite);
+                p->numTestSuitesPassingFilters++;
+            }
+            continue;
+        }
+
+        // execute the test if it passes all the filtering
+        {
+            p->currentTest = &tc;
+
+            p->failure_flags = TestCaseFailureReason::None;
+            p->seconds       = 0;
+
+            // reset atomic counters
+            p->numAssertsFailedCurrentTest_atomic = 0;
+            p->numAssertsCurrentTest_atomic       = 0;
+
+            p->subcasesPassed.clear();
+
+            DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc);
+
+            p->timer.start();
+            
+            bool run_test = true;
+
+            do {
+                // reset some of the fields for subcases (except for the set of fully passed ones)
+                p->should_reenter          = false;
+                p->subcasesCurrentMaxLevel = 0;
+                p->subcasesStack.clear();
+
+                p->shouldLogCurrentException = true;
+
+                // reset stuff for logging with INFO()
+                p->stringifiedContexts.clear();
+
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+                try {
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+// MSVC 2015 diagnoses fatalConditionHandler as unused (because reset() is a static method)
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4101) // unreferenced local variable
+                    FatalConditionHandler fatalConditionHandler; // Handle signals
+                    // execute the test
+                    tc.m_test();
+                    fatalConditionHandler.reset();
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+                } catch(const TestFailureException&) {
+                    p->failure_flags |= TestCaseFailureReason::AssertFailure;
+                } catch(...) {
+                    DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception,
+                                                      {translateActiveException(), false});
+                    p->failure_flags |= TestCaseFailureReason::Exception;
+                }
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+
+                // exit this loop if enough assertions have failed - even if there are more subcases
+                if(p->abort_after > 0 &&
+                   p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) {
+                    run_test = false;
+                    p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts;
+                }
+                
+                if(p->should_reenter && run_test)
+                    DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc);
+                if(!p->should_reenter)
+                    run_test = false;
+            } while(run_test);
+
+            p->finalizeTestCaseData();
+
+            DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs);
+
+            p->currentTest = nullptr;
+
+            // stop executing tests if enough assertions have failed
+            if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after)
+                break;
+        }
+    }
+
+    if(!query_mode) {
+        DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs);
+    } else {
+        QueryData qdata;
+        qdata.run_stats = g_cs;
+        qdata.data      = queryResults.data();
+        qdata.num_data  = unsigned(queryResults.size());
+        DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata);
+    }
+
+    return cleanup_and_return();
+}
+
+IReporter::~IReporter() = default;
+
+int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); }
+const IContextScope* const* IReporter::get_active_contexts() {
+    return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr;
+}
+
+int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); }
+const String* IReporter::get_stringified_contexts() {
+    return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr;
+}
+
+namespace detail {
+    void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) {
+        if(isReporter)
+            getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
+        else
+            getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
+    }
+} // namespace detail
+
+} // namespace doctest
+
+#endif // DOCTEST_CONFIG_DISABLE
+
+#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182
+int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); }
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+DOCTEST_GCC_SUPPRESS_WARNING_POP
+
+DOCTEST_SUPPRESS_COMMON_WARNINGS_POP
+
+#endif // DOCTEST_LIBRARY_IMPLEMENTATION
+#endif // DOCTEST_CONFIG_IMPLEMENT
diff --git a/thirdparty/argparse/test/main.cpp b/thirdparty/argparse/test/main.cpp
new file mode 100644
index 0000000000..53c0a370c7
--- /dev/null
+++ b/thirdparty/argparse/test/main.cpp
@@ -0,0 +1 @@
+#include <doctest.hpp>
diff --git a/thirdparty/argparse/test/test_actions.cpp b/thirdparty/argparse/test/test_actions.cpp
new file mode 100644
index 0000000000..07f59c993f
--- /dev/null
+++ b/thirdparty/argparse/test/test_actions.cpp
@@ -0,0 +1,173 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Users can use default value inside actions" *
+          test_suite("actions")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("input").default_value("bar").action(
+      [=](const std::string &value) {
+        static const std::vector<std::string> choices = {"foo", "bar", "baz"};
+        if (std::find(choices.begin(), choices.end(), value) != choices.end()) {
+          return value;
+        }
+        return std::string{"bar"};
+      });
+
+  program.parse_args({"test", "fez"});
+  REQUIRE(program.get("input") == "bar");
+}
+
+TEST_CASE("Users can add actions that return nothing" * test_suite("actions")) {
+  argparse::ArgumentParser program("test");
+  bool pressed = false;
+  auto &arg = program.add_argument("button").action(
+      [&](const std::string &) mutable { pressed = true; });
+
+  REQUIRE_FALSE(pressed);
+
+  SUBCASE("action performed") {
+    program.parse_args({"test", "ignored"});
+    REQUIRE(pressed);
+  }
+
+  SUBCASE("action performed and nothing overrides the default value") {
+    arg.default_value(42);
+
+    program.parse_args({"test", "ignored"});
+    REQUIRE(pressed);
+    REQUIRE(program.get<int>("button") == 42);
+  }
+}
+
+class Image {
+public:
+  int w = 0, h = 0;
+
+  void resize(std::string_view geometry) {
+    std::stringstream s;
+    s << geometry;
+    s >> w;
+    s.ignore();
+    s >> h;
+  }
+
+  static auto create(int w, int h, std::string_view format) -> Image {
+    auto factor = [=] {
+      if (format == "720p")
+        return std::min(1280. / w, 720. / h);
+      else if (format == "1080p")
+        return std::min(1920. / w, 1080. / h);
+      else
+        return 1.;
+    }();
+
+    return {static_cast<int>(w * factor), static_cast<int>(h * factor)};
+  }
+};
+
+TEST_CASE("Users can bind arguments to actions" * test_suite("actions")) {
+  argparse::ArgumentParser program("test");
+
+  GIVEN("an default initialized object bounded by reference") {
+    Image img;
+    program.add_argument("--size").action(&Image::resize, std::ref(img));
+
+    WHEN("provided no command-line arguments") {
+      program.parse_args({"test"});
+
+      THEN("the object is not updated") {
+        REQUIRE(img.w == 0);
+        REQUIRE(img.h == 0);
+      }
+    }
+
+    WHEN("provided command-line arguments") {
+      program.parse_args({"test", "--size", "320x98"});
+
+      THEN("the object is updated accordingly") {
+        REQUIRE(img.w == 320);
+        REQUIRE(img.h == 98);
+      }
+    }
+  }
+
+  GIVEN("a command-line option waiting for the last argument in its action") {
+    program.add_argument("format").action(Image::create, 400, 300);
+
+    WHEN("provided such an argument") {
+      program.parse_args({"test", "720p"});
+
+      THEN("the option object is created as if providing all arguments") {
+        auto img = program.get<Image>("format");
+
+        REQUIRE(img.w == 960);
+        REQUIRE(img.h == 720);
+      }
+    }
+
+    WHEN("provided a different argument") {
+      program.parse_args({"test", "1080p"});
+
+      THEN("a different option object is created") {
+        auto img = program.get<Image>("format");
+
+        REQUIRE(img.w == 1440);
+        REQUIRE(img.h == 1080);
+      }
+    }
+  }
+}
+
+TEST_CASE("Users can use actions on nargs=ANY arguments" *
+          test_suite("actions")) {
+  argparse::ArgumentParser program("sum");
+
+  int result = 0;
+  program.add_argument("all")
+      .nargs(argparse::nargs_pattern::any)
+      .action(
+          [](int &sum, std::string const &value) { sum += std::stoi(value); },
+          std::ref(result));
+
+  program.parse_args({"sum", "42", "100", "-3", "-20"});
+  REQUIRE(result == 119);
+}
+
+TEST_CASE("Users can use actions on remaining arguments" *
+          test_suite("actions")) {
+  argparse::ArgumentParser program("concat");
+
+  std::string result = "";
+  program.add_argument("all").remaining().action(
+      [](std::string &sum, const std::string &value) { sum += value; },
+      std::ref(result));
+
+  program.parse_args({"concat", "a", "-b", "-c", "--d"});
+  REQUIRE(result == "a-b-c--d");
+}
+
+TEST_CASE("Users can run actions on parameterless optional arguments" *
+          test_suite("actions")) {
+  argparse::ArgumentParser program("test");
+
+  GIVEN("a flag argument with a counting action") {
+    int count = 0;
+    program.add_argument("-V", "--verbose")
+        .action([&](const auto &) { ++count; })
+        .append()
+        .default_value(false)
+        .implicit_value(true)
+        .nargs(0);
+
+    WHEN("the flag is repeated") {
+      program.parse_args({"test", "-VVVV"});
+
+      THEN("the count increments once per use") {
+        REQUIRE(program.get<bool>("-V"));
+        REQUIRE(count == 4);
+      }
+    }
+  }
+}
diff --git a/thirdparty/argparse/test/test_append.cpp b/thirdparty/argparse/test/test_append.cpp
new file mode 100644
index 0000000000..49345146ef
--- /dev/null
+++ b/thirdparty/argparse/test/test_append.cpp
@@ -0,0 +1,40 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Simplest .append" * test_suite("append")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--dir").append();
+  program.parse_args({"test", "--dir", "./Docs"});
+  std::string result{program.get("--dir")};
+  REQUIRE(result == "./Docs");
+}
+
+TEST_CASE("Two parameter .append" * test_suite("append")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--dir").append();
+  program.parse_args({"test", "--dir", "./Docs", "--dir", "./Src"});
+  auto result{program.get<std::vector<std::string>>("--dir")};
+  REQUIRE(result.at(0) == "./Docs");
+  REQUIRE(result.at(1) == "./Src");
+}
+
+TEST_CASE("Two int .append" * test_suite("append")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--factor").append().scan<'i', int>();
+  program.parse_args({"test", "--factor", "2", "--factor", "5"});
+  auto result{program.get<std::vector<int>>("--factor")};
+  REQUIRE(result.at(0) == 2);
+  REQUIRE(result.at(1) == 5);
+}
+
+TEST_CASE("Default value with .append" * test_suite("append")) {
+  std::vector<std::string> expected{"./Src", "./Imgs"};
+
+  argparse::ArgumentParser program("test");
+  program.add_argument("--dir").default_value(expected).append();
+  program.parse_args({"test"});
+  auto result{program.get<std::vector<std::string>>("--dir")};
+  REQUIRE(result == expected);
+}
diff --git a/thirdparty/argparse/test/test_compound_arguments.cpp b/thirdparty/argparse/test/test_compound_arguments.cpp
new file mode 100644
index 0000000000..3bc1601e14
--- /dev/null
+++ b/thirdparty/argparse/test/test_compound_arguments.cpp
@@ -0,0 +1,110 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+#include <test_utility.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Parse compound toggle arguments with implicit values" *
+          test_suite("compound_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-a").default_value(false).implicit_value(true);
+
+  program.add_argument("-u").default_value(false).implicit_value(true);
+
+  program.add_argument("-x").default_value(false).implicit_value(true);
+
+  program.parse_args({"./test.exe", "-aux"});
+  REQUIRE(program.get<bool>("-a") == true);
+  REQUIRE(program.get<bool>("-u") == true);
+  REQUIRE(program.get<bool>("-x") == true);
+}
+
+TEST_CASE("Parse compound toggle arguments with implicit values and nargs" *
+          test_suite("compound_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-a").default_value(false).implicit_value(true);
+
+  program.add_argument("-b").default_value(false).implicit_value(true);
+
+  program.add_argument("-c").nargs(2).scan<'g', float>();
+
+  program.add_argument("--input_files").nargs(3);
+
+  program.parse_args({"./test.exe", "-abc", "3.14", "2.718", "--input_files",
+                      "a.txt", "b.txt", "c.txt"});
+  REQUIRE(program.get<bool>("-a") == true);
+  REQUIRE(program.get<bool>("-b") == true);
+  auto c = program.get<std::vector<float>>("-c");
+  REQUIRE(c.size() == 2);
+  REQUIRE(c[0] == 3.14f);
+  REQUIRE(c[1] == 2.718f);
+  auto input_files = program.get<std::vector<std::string>>("--input_files");
+  REQUIRE(input_files.size() == 3);
+  REQUIRE(input_files[0] == "a.txt");
+  REQUIRE(input_files[1] == "b.txt");
+  REQUIRE(input_files[2] == "c.txt");
+}
+
+TEST_CASE("Parse compound toggle arguments with implicit values and nargs and "
+          "other positional arguments" *
+          test_suite("compound_arguments")) {
+  argparse::ArgumentParser program("test");
+
+  program.add_argument("numbers").nargs(3).scan<'i', int>();
+
+  program.add_argument("-a").default_value(false).implicit_value(true);
+
+  program.add_argument("-b").default_value(false).implicit_value(true);
+
+  program.add_argument("-c").nargs(2).scan<'g', float>();
+
+  program.add_argument("--input_files").nargs(3);
+
+  REQUIRE_THROWS(
+      program.parse_args({"./test.exe", "1", "-abc", "3.14", "2.718", "2",
+                          "--input_files", "a.txt", "b.txt", "c.txt", "3"}));
+}
+
+TEST_CASE("Parse out-of-order compound arguments" *
+          test_suite("compound_arguments")) {
+  argparse::ArgumentParser program("test");
+
+  program.add_argument("-a").default_value(false).implicit_value(true);
+
+  program.add_argument("-b").default_value(false).implicit_value(true);
+
+  program.add_argument("-c").nargs(2).scan<'g', float>();
+
+  program.parse_args({"./main", "-cab", "3.14", "2.718"});
+
+  auto a = program.get<bool>("-a");               // true
+  auto b = program.get<bool>("-b");               // true
+  auto c = program.get<std::vector<float>>("-c"); // {3.14f, 2.718f}
+  REQUIRE(a == true);
+  REQUIRE(b == true);
+  REQUIRE(program["-c"] == std::vector<float>{3.14f, 2.718f});
+}
+
+TEST_CASE("Parse out-of-order compound arguments. Second variation" *
+          test_suite("compound_arguments")) {
+  argparse::ArgumentParser program("test");
+
+  program.add_argument("-a").default_value(false).implicit_value(true);
+
+  program.add_argument("-b").default_value(false).implicit_value(true);
+
+  program.add_argument("-c")
+      .nargs(2)
+      .default_value(std::vector<float>{0.0f, 0.0f})
+      .scan<'g', float>();
+
+  program.parse_args({"./main", "-cb"});
+
+  auto a = program.get<bool>("-a");
+  auto b = program.get<bool>("-b");
+  auto c = program.get<std::vector<float>>("-c");
+
+  REQUIRE(a == false);
+  REQUIRE(b == true);
+  REQUIRE(program["-c"] == std::vector<float>{0.0f, 0.0f});
+}
diff --git a/thirdparty/argparse/test/test_const_correct.cpp b/thirdparty/argparse/test/test_const_correct.cpp
new file mode 100644
index 0000000000..1acfdd40b6
--- /dev/null
+++ b/thirdparty/argparse/test/test_const_correct.cpp
@@ -0,0 +1,27 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("ArgumentParser is const-correct after construction and parsing" *
+          test_suite("value_semantics")) {
+  GIVEN("a parser") {
+    argparse::ArgumentParser parser("test");
+    parser.add_argument("--foo", "-f").help("I am foo");
+    parser.add_description("A description");
+    parser.add_epilog("An epilog");
+
+    WHEN("becomes const-qualified") {
+      parser.parse_args({"./main", "--foo", "baz"});
+      const auto const_parser = std::move(parser);
+
+      THEN("only const methods are accessible") {
+        REQUIRE(const_parser.help().str().size() > 0);
+        REQUIRE(const_parser.present<std::string>("--foo"));
+        REQUIRE(const_parser.is_used("-f"));
+        REQUIRE(const_parser.get("-f") == "baz");
+        REQUIRE(const_parser["-f"] == std::string("baz"));
+      }
+    }
+  }
+}
diff --git a/thirdparty/argparse/test/test_container_arguments.cpp b/thirdparty/argparse/test/test_container_arguments.cpp
new file mode 100644
index 0000000000..5eab026e5a
--- /dev/null
+++ b/thirdparty/argparse/test/test_container_arguments.cpp
@@ -0,0 +1,80 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+#include <test_utility.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Parse vector of arguments" * test_suite("vector")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("input").nargs(2);
+
+  program.parse_args({"test", "rocket.mesh", "thrust_profile.csv"});
+
+  auto inputs = program.get<std::vector<std::string>>("input");
+  REQUIRE(inputs.size() == 2);
+  REQUIRE(inputs[0] == "rocket.mesh");
+  REQUIRE(inputs[1] == "thrust_profile.csv");
+}
+
+TEST_CASE("Parse list of arguments" * test_suite("vector")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("input").nargs(2);
+
+  program.parse_args({"test", "rocket.mesh", "thrust_profile.csv"});
+
+  auto inputs = program.get<std::list<std::string>>("input");
+  REQUIRE(inputs.size() == 2);
+  REQUIRE(testutility::get_from_list(inputs, 0) == "rocket.mesh");
+  REQUIRE(testutility::get_from_list(inputs, 1) == "thrust_profile.csv");
+}
+
+TEST_CASE("Parse list of arguments with default values" *
+          test_suite("vector")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--input")
+      .default_value(std::list<int>{1, 2, 3, 4, 5})
+      .nargs(5);
+
+  program.parse_args({"test"});
+
+  auto inputs = program.get<std::list<int>>("--input");
+  REQUIRE(inputs.size() == 5);
+  REQUIRE(testutility::get_from_list(inputs, 0) == 1);
+  REQUIRE(testutility::get_from_list(inputs, 1) == 2);
+  REQUIRE(testutility::get_from_list(inputs, 2) == 3);
+  REQUIRE(testutility::get_from_list(inputs, 3) == 4);
+  REQUIRE(testutility::get_from_list(inputs, 4) == 5);
+  REQUIRE(program["--input"] == std::list<int>{1, 2, 3, 4, 5});
+}
+
+TEST_CASE("Parse list of arguments and save in an object" *
+          test_suite("vector")) {
+
+  struct ConfigManager {
+    std::vector<std::string> files;
+    void add_file(const std::string &file) { files.push_back(file); }
+  };
+
+  ConfigManager config_manager;
+
+  argparse::ArgumentParser program("test");
+  program.add_argument("--input_files")
+      .nargs(2)
+      .action([&](const std::string &value) {
+        config_manager.add_file(value);
+        return value;
+      });
+
+  program.parse_args({"test", "--input_files", "config.xml", "system.json"});
+
+  auto file_args = program.get<std::vector<std::string>>("--input_files");
+  REQUIRE(file_args.size() == 2);
+  REQUIRE(file_args[0] == "config.xml");
+  REQUIRE(file_args[1] == "system.json");
+
+  REQUIRE(config_manager.files.size() == 2);
+  REQUIRE(config_manager.files[0] == "config.xml");
+  REQUIRE(config_manager.files[1] == "system.json");
+  REQUIRE(program["--input_files"] ==
+          std::vector<std::string>{"config.xml", "system.json"});
+}
diff --git a/thirdparty/argparse/test/test_default_args.cpp b/thirdparty/argparse/test/test_default_args.cpp
new file mode 100644
index 0000000000..7b5e581f29
--- /dev/null
+++ b/thirdparty/argparse/test/test_default_args.cpp
@@ -0,0 +1,19 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Include all default arguments" * test_suite("default_args")) {
+  argparse::ArgumentParser parser("test");
+  auto help_msg{parser.help().str()};
+  REQUIRE(help_msg.find("shows help message") != std::string::npos);
+  REQUIRE(help_msg.find("prints version information") != std::string::npos);
+}
+
+TEST_CASE("Do not include default arguments" * test_suite("default_args")) {
+  argparse::ArgumentParser parser("test", "1.0",
+                                  argparse::default_arguments::none);
+  parser.parse_args({"test"});
+  REQUIRE_THROWS_AS(parser.get("--help"), std::logic_error);
+  REQUIRE_THROWS_AS(parser.get("--version"), std::logic_error);
+}
diff --git a/thirdparty/argparse/test/test_equals_form.cpp b/thirdparty/argparse/test/test_equals_form.cpp
new file mode 100644
index 0000000000..81c31f8374
--- /dev/null
+++ b/thirdparty/argparse/test/test_equals_form.cpp
@@ -0,0 +1,39 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+#include <iostream>
+using doctest::test_suite;
+
+TEST_CASE("Basic --value=value" * test_suite("equals_form")) {
+  argparse::ArgumentParser parser("test");
+  parser.add_argument("--long");
+  parser.parse_args({"test", "--long=value"});
+  std::string result{parser.get("--long")};
+  REQUIRE(result == "value");
+}
+
+TEST_CASE("Fallback = in regular option name" * test_suite("equals_form")) {
+  argparse::ArgumentParser parser("test");
+  parser.add_argument("--long=mislead");
+  parser.parse_args({"test", "--long=mislead", "value"});
+  std::string result{parser.get("--long=mislead")};
+  REQUIRE(result == "value");
+}
+
+TEST_CASE("Duplicate =-named and standard" * test_suite("equals_form")) {
+  argparse::ArgumentParser parser("test");
+  parser.add_argument("--long=mislead");
+  parser.add_argument("--long").default_value(std::string{"NO_VALUE"});
+  parser.parse_args({"test", "--long=mislead", "value"});
+  std::string result{parser.get("--long=mislead")};
+  REQUIRE(result == "value");
+  std::string result2{parser.get("--long")};
+  REQUIRE(result2 == "NO_VALUE");
+}
+
+TEST_CASE("Basic --value=value with nargs(2)" * test_suite("equals_form")) {
+  argparse::ArgumentParser parser("test");
+  parser.add_argument("--long").nargs(2);
+  parser.parse_args({"test", "--long=value1", "value2"});
+  REQUIRE((parser.get<std::vector<std::string>>("--long") ==
+           std::vector<std::string>{"value1", "value2"}));
+}
\ No newline at end of file
diff --git a/thirdparty/argparse/test/test_get.cpp b/thirdparty/argparse/test/test_get.cpp
new file mode 100644
index 0000000000..9cc046c8d1
--- /dev/null
+++ b/thirdparty/argparse/test/test_get.cpp
@@ -0,0 +1,35 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Getting a simple argument" * test_suite("ArgumentParser::get")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-s", "--stuff");
+  REQUIRE_NOTHROW(program.parse_args({"test", "-s", "./src"}));
+  REQUIRE(program.get("--stuff") == "./src");
+}
+
+TEST_CASE("Skipped call to parse_args" * test_suite("ArgumentParser::get")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("stuff");
+  REQUIRE_THROWS_WITH_AS(program.get("stuff"),
+                         "Nothing parsed, no arguments are available.",
+                         std::logic_error);
+}
+
+TEST_CASE("Missing argument" * test_suite("ArgumentParser::get")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-s", "--stuff");
+  REQUIRE_NOTHROW(program.parse_args({"test"}));
+  REQUIRE_THROWS_WITH_AS(program.get("--stuff"),
+                         "No value provided for '--stuff'.", std::logic_error);
+}
+
+TEST_CASE("Implicit argument" * test_suite("ArgumentParser::get")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-s", "--stuff").nargs(1);
+  REQUIRE_NOTHROW(program.parse_args({"test"}));
+  REQUIRE_THROWS_WITH_AS(program.get("--stuff"),
+                         "No value provided for '--stuff'.", std::logic_error);
+}
diff --git a/thirdparty/argparse/test/test_help.cpp b/thirdparty/argparse/test/test_help.cpp
new file mode 100644
index 0000000000..a81af42284
--- /dev/null
+++ b/thirdparty/argparse/test/test_help.cpp
@@ -0,0 +1,75 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+#include <sstream>
+
+using doctest::test_suite;
+
+TEST_CASE("Users can format help message" * test_suite("help")) {
+  argparse::ArgumentParser program("test");
+
+  SUBCASE("Simple arguments") {
+    program.add_argument("input").help("positional input");
+    program.add_argument("-c").help("optional input");
+  }
+  SUBCASE("Default values") {
+    program.add_argument("-a").default_value(42);
+    program.add_argument("-b").default_value(4.4e-7);
+    program.add_argument("-c")
+        .default_value(std::vector<int>{1, 2, 3, 4, 5})
+        .nargs(5);
+    program.add_argument("-d").default_value("I am a string");
+    program.add_argument("-e").default_value(std::optional<float>{});
+    program.add_argument("-f").default_value(false);
+  }
+  std::ostringstream s;
+  s << program;
+  REQUIRE_FALSE(s.str().empty());
+
+  auto msg = program.help().str();
+  REQUIRE(msg == s.str());
+}
+
+TEST_CASE("Users can override the help options" * test_suite("help")) {
+  GIVEN("a program that meant to take -h as a normal option") {
+    argparse::ArgumentParser program("test");
+    program.add_argument("input");
+    program.add_argument("-h").implicit_value('h').default_value('x');
+
+    WHEN("provided -h without fulfilling other requirements") {
+      THEN("validation fails") {
+        REQUIRE_THROWS_AS(program.parse_args({"test", "-h"}),
+                          std::runtime_error);
+      }
+    }
+
+    WHEN("provided arguments to all parameters") {
+      program.parse_args({"test", "-h", "some input"});
+
+      THEN("these parameters get their values") {
+        REQUIRE(program["-h"] == 'h');
+        REQUIRE(program.get("input") == "some input");
+      }
+    }
+  }
+}
+
+TEST_CASE("Users can disable default -h/--help" * test_suite("help")) {
+  argparse::ArgumentParser program("test", "1.0",
+                                   argparse::default_arguments::version);
+  REQUIRE_THROWS_AS(program.parse_args({"test", "-h"}), std::runtime_error);
+}
+
+TEST_CASE("Users can replace default -h/--help" * test_suite("help")) {
+  argparse::ArgumentParser program("test", "1.0",
+                                   argparse::default_arguments::version);
+  std::stringstream buffer;
+  program.add_argument("-h", "--help")
+      .action([&](const auto &) { buffer << program; })
+      .default_value(false)
+      .implicit_value(true)
+      .nargs(0);
+
+  REQUIRE(buffer.str().empty());
+  program.parse_args({"test", "--help"});
+  REQUIRE_FALSE(buffer.str().empty());
+}
diff --git a/thirdparty/argparse/test/test_invalid_arguments.cpp b/thirdparty/argparse/test/test_invalid_arguments.cpp
new file mode 100644
index 0000000000..e8db3f269f
--- /dev/null
+++ b/thirdparty/argparse/test/test_invalid_arguments.cpp
@@ -0,0 +1,40 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Parse unknown optional argument" *
+          test_suite("compound_arguments")) {
+
+  argparse::ArgumentParser bfm("bfm");
+
+  bfm.add_argument("-l", "--load").help("load a VMM into the kernel");
+
+  bfm.add_argument("-x", "--start")
+      .default_value(false)
+      .implicit_value(true)
+      .help("start a previously loaded VMM");
+
+  bfm.add_argument("-d", "--dump")
+      .default_value(false)
+      .implicit_value(true)
+      .help("output the contents of the VMM's debug buffer");
+
+  bfm.add_argument("-s", "--stop")
+      .default_value(false)
+      .implicit_value(true)
+      .help("stop a previously started VMM");
+
+  bfm.add_argument("-u", "--unload")
+      .default_value(false)
+      .implicit_value(true)
+      .help("unload a previously loaded VMM");
+
+  bfm.add_argument("-m", "--mem")
+      .default_value(64ULL)
+      .scan<'u', unsigned long long>()
+      .help("memory in MB to give the VMM when loading");
+
+  REQUIRE_THROWS_WITH_AS(bfm.parse_args({"./test.exe", "-om"}),
+                         "Unknown argument: -om", std::runtime_error);
+}
diff --git a/thirdparty/argparse/test/test_is_used.cpp b/thirdparty/argparse/test/test_is_used.cpp
new file mode 100644
index 0000000000..8d67b8512c
--- /dev/null
+++ b/thirdparty/argparse/test/test_is_used.cpp
@@ -0,0 +1,20 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("User-supplied argument" * test_suite("is_used")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--dir").default_value(std::string("/"));
+  program.parse_args({"test", "--dir", "/home/user"});
+  REQUIRE(program.get("--dir") == "/home/user");
+  REQUIRE(program.is_used("--dir") == true);
+}
+
+TEST_CASE("Not user-supplied argument" * test_suite("is_used")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--dir").default_value(std::string("/"));
+  program.parse_args({"test"});
+  REQUIRE(program.get("--dir") == "/");
+  REQUIRE(program.is_used("--dir") == false);
+}
diff --git a/thirdparty/argparse/test/test_issue_37.cpp b/thirdparty/argparse/test/test_issue_37.cpp
new file mode 100644
index 0000000000..ab9c9840cf
--- /dev/null
+++ b/thirdparty/argparse/test/test_issue_37.cpp
@@ -0,0 +1,43 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Issues with implicit values #37" * test_suite("implicit_values")) {
+  argparse::ArgumentParser m_bfm("test");
+  m_bfm.add_argument("-l", "--load").help("load a VMM into the kernel");
+
+  m_bfm.add_argument("-u", "--unload")
+      .default_value(false)
+      .implicit_value(true)
+      .help("unload a previously loaded VMM");
+
+  m_bfm.add_argument("-x", "--start")
+      .default_value(false)
+      .implicit_value(true)
+      .help("start a previously loaded VMM");
+
+  m_bfm.add_argument("-s", "--stop")
+      .default_value(false)
+      .implicit_value(true)
+      .help("stop a previously started VMM");
+
+  m_bfm.add_argument("-d", "--dump")
+      .default_value(false)
+      .implicit_value(true)
+      .help("output the contents of the VMM's debug buffer");
+
+  m_bfm.add_argument("-m", "--mem")
+      .default_value(100)
+      .required()
+      .scan<'u', unsigned long long>()
+      .help("memory in MB to give the VMM when loading");
+  m_bfm.parse_args({"test", "-l", "blah", "-d", "-u"});
+
+  REQUIRE(m_bfm.get("--load") == "blah");
+  REQUIRE(m_bfm.get("-l") == "blah");
+  REQUIRE(m_bfm.get<bool>("-u") == true);
+  REQUIRE(m_bfm.get<bool>("-d") == true);
+  REQUIRE(m_bfm.get<bool>("-s") == false);
+  REQUIRE(m_bfm.get<bool>("--unload") == true);
+}
diff --git a/thirdparty/argparse/test/test_negative_numbers.cpp b/thirdparty/argparse/test/test_negative_numbers.cpp
new file mode 100644
index 0000000000..fd9b1f9a44
--- /dev/null
+++ b/thirdparty/argparse/test/test_negative_numbers.cpp
@@ -0,0 +1,255 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Parse negative integer" * test_suite("positional_arguments")) {
+  argparse::ArgumentParser program;
+  program.add_argument("--verbose", "-v")
+      .help("enable verbose logging")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.add_argument("number").help("Input number").scan<'i', int>();
+
+  program.parse_args({"./main", "-1"});
+  REQUIRE(program.get<int>("number") == -1);
+}
+
+TEST_CASE("Parse negative integers into a vector" *
+          test_suite("positional_arguments")) {
+  argparse::ArgumentParser program;
+  program.add_argument("--verbose", "-v")
+      .help("enable verbose logging")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.add_argument("number").help("Input number").nargs(3).scan<'i', int>();
+
+  program.parse_args({"./main", "-1", "-2", "3"});
+  REQUIRE(program["number"] == std::vector<int>{-1, -2, 3});
+}
+
+TEST_CASE("Parse negative float" * test_suite("positional_arguments")) {
+  argparse::ArgumentParser program;
+  program.add_argument("--verbose", "-v")
+      .help("enable verbose logging")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.add_argument("number").help("Input number").scan<'g', float>();
+
+  program.parse_args({"./main", "-1.0"});
+  REQUIRE(program.get<float>("number") == -1.0);
+}
+
+TEST_CASE("Parse negative floats into a vector" *
+          test_suite("positional_arguments")) {
+  argparse::ArgumentParser program;
+  program.add_argument("--verbose", "-v")
+      .help("enable verbose logging")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.add_argument("number")
+      .help("Input number")
+      .nargs(3)
+      .scan<'g', double>();
+
+  program.parse_args({"./main", "-1.001", "-2.002", "3.003"});
+  REQUIRE(program["number"] == std::vector<double>{-1.001, -2.002, 3.003});
+}
+
+TEST_CASE("Parse numbers in E notation" * test_suite("positional_arguments")) {
+  argparse::ArgumentParser program;
+  program.add_argument("--verbose", "-v")
+      .help("enable verbose logging")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.add_argument("number").help("Input number").scan<'g', double>();
+
+  program.parse_args({"./main", "-1.2e3"});
+  REQUIRE(program.get<double>("number") == -1200.0);
+}
+
+TEST_CASE("Parse numbers in E notation (capital E)" *
+          test_suite("positional_arguments")) {
+  argparse::ArgumentParser program;
+  program.add_argument("--verbose", "-v")
+      .help("enable verbose logging")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.add_argument("number").help("Input number").scan<'g', double>();
+
+  program.parse_args({"./main", "-1.32E4"});
+  REQUIRE(program.get<double>("number") == -13200.0);
+}
+
+TEST_CASE("Recognize negative decimal numbers" *
+          test_suite("positional_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("positional");
+
+  SUBCASE("zero") { REQUIRE_NOTHROW(program.parse_args({"test", "-0"})); }
+  SUBCASE("not a decimal") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-00"}), std::runtime_error);
+  }
+  SUBCASE("looks like an octal") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-003"}), std::runtime_error);
+  }
+  SUBCASE("nonzero-digit") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-9"}));
+  }
+  SUBCASE("nonzero-digit digit-sequence") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-92180"}));
+  }
+  SUBCASE("zero dot") { REQUIRE_NOTHROW(program.parse_args({"test", "-0."})); }
+  SUBCASE("nonzero-digit dot") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-8."}));
+  }
+  SUBCASE("nonzero-digit digit-sequence dot") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-200."}));
+  }
+  SUBCASE("integer-part dot") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-003."}));
+  }
+  SUBCASE("dot digit-sequence") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-.0927"}));
+  }
+  SUBCASE("not a single dot") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-."}), std::runtime_error);
+  }
+  SUBCASE("not a single e") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-e"}), std::runtime_error);
+  }
+  SUBCASE("not dot e") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-.e"}), std::runtime_error);
+  }
+  SUBCASE("integer-part exponent-part without sign") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-1e32"}));
+  }
+  SUBCASE("integer-part exponent-part with positive sign") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-1e+32"}));
+  }
+  SUBCASE("integer-part exponent-part with negative sign") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-00e-0"}));
+  }
+  SUBCASE("missing mantissa") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-e32"}), std::runtime_error);
+  }
+  SUBCASE("missing mantissa but with positive sign") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-e+7"}), std::runtime_error);
+  }
+  SUBCASE("missing mantissa but with negative sign") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-e-1"}), std::runtime_error);
+  }
+  SUBCASE("nothing after e followed by zero") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-0e"}), std::runtime_error);
+  }
+  SUBCASE("nothing after e followed by integer-part") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-13e"}), std::runtime_error);
+  }
+  SUBCASE("integer-part dot exponent-part without sign") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-18.e0"}));
+  }
+  SUBCASE("integer-part dot exponent-part with positive sign") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-18.e+92"}));
+  }
+  SUBCASE("integer-part dot exponent-part with negative sign") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-0.e-92"}));
+  }
+  SUBCASE("nothing after e followed by integer-part dot") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-13.e"}),
+                      std::runtime_error);
+  }
+  SUBCASE("dot digit-sequence exponent-part without sign") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-.023e0"}));
+  }
+  SUBCASE("dot digit-sequence exponent-part with positive sign") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-.2e+92"}));
+  }
+  SUBCASE("dot digit-sequence exponent-part with negative sign") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-.71564e-92"}));
+  }
+  SUBCASE("nothing after e in fractional-part") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-.283e"}),
+                      std::runtime_error);
+  }
+  SUBCASE("exponent-part followed by only a dot") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-.e3"}), std::runtime_error);
+  }
+  SUBCASE("exponent-part followed by only a dot but with positive sign") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-.e+3"}),
+                      std::runtime_error);
+  }
+  SUBCASE("exponent-part followed by only a dot but with negative sign") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-.e-3"}),
+                      std::runtime_error);
+  }
+  SUBCASE("integer-part dot digit-sequence exponent-part without sign") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-02.023e4000"}));
+  }
+  SUBCASE("integer-part dot digit-sequence exponent-part with positive sign") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-3.239e+76"}));
+  }
+  SUBCASE("integer-part dot digit-sequence exponent-part with negative sign") {
+    REQUIRE_NOTHROW(program.parse_args({"test", "-238237.0e-2"}));
+  }
+  SUBCASE("nothing after e") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-3.14e"}),
+                      std::runtime_error);
+  }
+  SUBCASE("nothing after e and positive sign") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-2.17e+"}),
+                      std::runtime_error);
+  }
+  SUBCASE("nothing after e and negative sign") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-13.6e-"}),
+                      std::runtime_error);
+  }
+  SUBCASE("more than one sign present in exponent-part") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-13.6e+-23"}),
+                      std::runtime_error);
+  }
+  SUBCASE("sign at wrong position") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-3.6e23+"}),
+                      std::runtime_error);
+  }
+  SUBCASE("more than one exponent-part") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-3.6e2e9"}),
+                      std::runtime_error);
+  }
+  SUBCASE("more than one fractional-part") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-3.6.3"}),
+                      std::runtime_error);
+  }
+  SUBCASE("number has its own sign") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-+42"}), std::runtime_error);
+  }
+  SUBCASE("looks like hexadecimal integer") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-0x0"}), std::runtime_error);
+  }
+  SUBCASE("looks like hexadecimal floating-point") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-0x27.8p1"}),
+                      std::runtime_error);
+  }
+  SUBCASE("looks like hexadecimal floating-point without prefix") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-3.8p1"}),
+                      std::runtime_error);
+  }
+  SUBCASE("Richard's pp-number") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-0x1e+2"}),
+                      std::runtime_error);
+  }
+  SUBCASE("Infinity") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-inf"}), std::runtime_error);
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-INFINITY"}),
+                      std::runtime_error);
+  }
+  SUBCASE("NaN") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-nan"}), std::runtime_error);
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-NAN"}), std::runtime_error);
+  }
+}
diff --git a/thirdparty/argparse/test/test_optional_arguments.cpp b/thirdparty/argparse/test/test_optional_arguments.cpp
new file mode 100644
index 0000000000..dd9cec6935
--- /dev/null
+++ b/thirdparty/argparse/test/test_optional_arguments.cpp
@@ -0,0 +1,200 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Parse toggle arguments with default value" *
+          test_suite("optional_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--verbose", "-v")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.parse_args({"./test.exe"});
+  REQUIRE(program.get<bool>("--verbose") == false);
+  REQUIRE(program["--verbose"] == false);
+}
+
+TEST_CASE("Argument '-' is not an optional argument" *
+          test_suite("optional_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("input");
+  program.parse_args({"./test.exe", "-"});
+  REQUIRE(program.get<std::string>("input") == "-");
+}
+
+TEST_CASE("Argument '-' is not an optional argument but '-l' is" *
+          test_suite("optional_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-l").default_value(false).implicit_value(true);
+  program.add_argument("input");
+  program.parse_args({"./test.exe", "-l", "-"});
+  REQUIRE(program.get<bool>("-l") == true);
+  REQUIRE(program.get<std::string>("input") == "-");
+}
+
+TEST_CASE("Argument '-l' is an optional argument but '-' is not" *
+          test_suite("optional_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-l").default_value(false).implicit_value(true);
+  program.add_argument("input");
+  program.parse_args({"./test.exe", "-", "-l"});
+  REQUIRE(program.get<bool>("-l") == true);
+  REQUIRE(program.get<std::string>("input") == "-");
+}
+
+TEST_CASE("Parse toggle arguments with implicit value" *
+          test_suite("optional_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--verbose").default_value(false).implicit_value(true);
+
+  program.parse_args({"./test.exe", "--verbose"});
+  REQUIRE(program.get<bool>("--verbose") == true);
+  REQUIRE(program["--verbose"] == true);
+  REQUIRE(program["--verbose"] != false);
+}
+
+TEST_CASE("Parse multiple toggle arguments with implicit values" *
+          test_suite("optional_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-a").default_value(false).implicit_value(true);
+
+  program.add_argument("-u").default_value(false).implicit_value(true);
+
+  program.add_argument("-x").default_value(false).implicit_value(true);
+
+  program.parse_args({"./test.exe", "-a", "-x"});
+  REQUIRE(program.get<bool>("-a") == true);
+  REQUIRE(program.get<bool>("-u") == false);
+  REQUIRE(program.get<bool>("-x") == true);
+}
+
+TEST_CASE("Parse optional arguments of many values" *
+          test_suite("optional_arguments")) {
+  GIVEN("a program that accepts an optional argument of many values") {
+    argparse::ArgumentParser program("test");
+    program.add_argument("-i").remaining().scan<'i', int>();
+
+    WHEN("provided no argument") {
+      THEN("the program accepts it but gets nothing") {
+        REQUIRE_NOTHROW(program.parse_args({"test"}));
+        REQUIRE_THROWS_AS(program.get<std::vector<int>>("-i"),
+                          std::logic_error);
+      }
+    }
+
+    WHEN("provided remaining arguments follow the option") {
+      program.parse_args({"test", "-i", "-42", "8", "100", "300"});
+
+      THEN("the optional parameter consumes all of them") {
+        auto inputs = program.get<std::vector<int>>("-i");
+        REQUIRE(inputs.size() == 4);
+        REQUIRE(inputs[0] == -42);
+        REQUIRE(inputs[1] == 8);
+        REQUIRE(inputs[2] == 100);
+        REQUIRE(inputs[3] == 300);
+      }
+    }
+  }
+}
+
+TEST_CASE("Parse 2 optional arguments of many values" *
+          test_suite("optional_arguments")) {
+  GIVEN("a program that accepts 2 optional arguments of many values") {
+    argparse::ArgumentParser program("test");
+    program.add_argument("-i")
+        .nargs(argparse::nargs_pattern::any)
+        .scan<'i', int>();
+    program.add_argument("-s").nargs(argparse::nargs_pattern::any);
+
+    WHEN("provided no argument") {
+      THEN("the program accepts it and gets empty container") {
+        REQUIRE_NOTHROW(program.parse_args({"test"}));
+        auto i = program.get<std::vector<int>>("-i");
+        REQUIRE(i.size() == 0);
+
+        auto s = program.get<std::vector<std::string>>("-s");
+        REQUIRE(s.size() == 0);
+      }
+    }
+
+    WHEN("provided 2 options with many arguments") {
+      program.parse_args({"test", "-i", "-42", "8", "100", "300", "-s", "ok",
+                          "this", "works"});
+
+      THEN("the optional parameter consumes each arguments") {
+        auto i = program.get<std::vector<int>>("-i");
+        REQUIRE(i.size() == 4);
+        REQUIRE(i[0] == -42);
+        REQUIRE(i[1] == 8);
+        REQUIRE(i[2] == 100);
+        REQUIRE(i[3] == 300);
+
+        auto s = program.get<std::vector<std::string>>("-s");
+        REQUIRE(s.size() == 3);
+        REQUIRE(s[0] == "ok");
+        REQUIRE(s[1] == "this");
+        REQUIRE(s[2] == "works");
+      }
+    }
+  }
+}
+
+TEST_CASE("Parse an optional argument of many values"
+          " and a positional argument of many values" *
+          test_suite("optional_arguments")) {
+  GIVEN("a program that accepts an optional argument of many values"
+        " and a positional argument of many values") {
+    argparse::ArgumentParser program("test");
+    program.add_argument("-s").nargs(argparse::nargs_pattern::any);
+    program.add_argument("input").nargs(argparse::nargs_pattern::any);
+
+    WHEN("provided no argument") {
+      program.parse_args({"test"});
+      THEN("the program accepts it and gets empty containers") {
+        auto s = program.get<std::vector<std::string>>("-s");
+        REQUIRE(s.size() == 0);
+
+        auto input = program.get<std::vector<std::string>>("input");
+        REQUIRE(input.size() == 0);
+      }
+    }
+
+    WHEN("provided many arguments followed by an option with many arguments") {
+      program.parse_args({"test", "foo", "bar", "-s", "ok", "this", "works"});
+
+      THEN("the parameters consume each arguments") {
+        auto s = program.get<std::vector<std::string>>("-s");
+        REQUIRE(s.size() == 3);
+        REQUIRE(s[0] == "ok");
+        REQUIRE(s[1] == "this");
+        REQUIRE(s[2] == "works");
+
+        auto input = program.get<std::vector<std::string>>("input");
+        REQUIRE(input.size() == 2);
+        REQUIRE(input[0] == "foo");
+        REQUIRE(input[1] == "bar");
+      }
+    }
+  }
+}
+
+TEST_CASE("Parse arguments of different types" *
+          test_suite("optional_arguments")) {
+  using namespace std::literals;
+
+  argparse::ArgumentParser program("test");
+  program.add_argument("--this-argument-is-longer-than-any-sso-buffer-that-"
+                       "makes-sense-unless-your-cache-line-is-this-long"s);
+
+  REQUIRE_NOTHROW(program.parse_args({"test"}));
+
+  program.add_argument("-string"s, "-string-view"sv, "-builtin")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.parse_args({"test", "-string-view"});
+  REQUIRE(program["-string"sv] == true);
+  REQUIRE(program["-string-view"] == true);
+  REQUIRE(program["-builtin"s] == true);
+}
diff --git a/thirdparty/argparse/test/test_parent_parsers.cpp b/thirdparty/argparse/test/test_parent_parsers.cpp
new file mode 100644
index 0000000000..ffb9e005f8
--- /dev/null
+++ b/thirdparty/argparse/test/test_parent_parsers.cpp
@@ -0,0 +1,36 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Add parent parsers" * test_suite("parent_parsers")) {
+  argparse::ArgumentParser parent_parser("main");
+  parent_parser.add_argument("--verbose")
+      .default_value(false)
+      .implicit_value(true);
+
+  argparse::ArgumentParser child_parser("foo");
+  child_parser.add_parents(parent_parser);
+  child_parser.parse_args({"./main", "--verbose"});
+  REQUIRE(child_parser["--verbose"] == true);
+  REQUIRE(parent_parser["--verbose"] == false);
+}
+
+TEST_CASE("Add parent to multiple parent parsers" *
+          test_suite("parent_parsers")) {
+  argparse::ArgumentParser parent_parser("main");
+  parent_parser.add_argument("--parent").default_value(0).scan<'i', int>();
+
+  argparse::ArgumentParser foo_parser("foo");
+  foo_parser.add_argument("foo");
+  foo_parser.add_parents(parent_parser);
+  foo_parser.parse_args({"./main", "--parent", "2", "XXX"});
+  REQUIRE(foo_parser["--parent"] == 2);
+  REQUIRE(foo_parser["foo"] == std::string("XXX"));
+  REQUIRE(parent_parser["--parent"] == 0);
+
+  argparse::ArgumentParser bar_parser("bar");
+  bar_parser.add_argument("--bar");
+  bar_parser.parse_args({"./main", "--bar", "YYY"});
+  REQUIRE(bar_parser["--bar"] == std::string("YYY"));
+}
diff --git a/thirdparty/argparse/test/test_parse_args.cpp b/thirdparty/argparse/test/test_parse_args.cpp
new file mode 100644
index 0000000000..7a38abf22d
--- /dev/null
+++ b/thirdparty/argparse/test/test_parse_args.cpp
@@ -0,0 +1,219 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Missing argument" * test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--config").nargs(1);
+  REQUIRE_THROWS_WITH_AS(program.parse_args({"test", "--config"}),
+                         "Too few arguments for '--config'.",
+                         std::runtime_error);
+}
+
+TEST_CASE("Parse a string argument with value" * test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--config");
+  program.parse_args({"test", "--config", "config.yml"});
+  REQUIRE(program.get("--config") == "config.yml");
+}
+
+TEST_CASE("Parse a string argument with default value" *
+          test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--config").default_value(std::string("foo.yml"));
+  program.parse_args({"test", "--config"});
+  REQUIRE(program.get("--config") == "foo.yml");
+}
+
+TEST_CASE("Parse a string argument without default value" *
+          test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--config");
+
+  WHEN("no value provided") {
+    program.parse_args({"test"});
+
+    THEN("the option is nullopt") {
+      auto opt = program.present("--config");
+      REQUIRE_FALSE(opt);
+      REQUIRE(opt == std::nullopt);
+    }
+  }
+
+  WHEN("a value is provided") {
+    program.parse_args({"test", "--config", ""});
+
+    THEN("the option has a value") {
+      auto opt = program.present("--config");
+      REQUIRE(opt);
+      REQUIRE(opt->empty());
+    }
+  }
+}
+
+TEST_CASE("Parse an int argument with value" * test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--count").scan<'i', int>();
+  program.parse_args({"test", "--count", "5"});
+  REQUIRE(program.get<int>("--count") == 5);
+}
+
+TEST_CASE("Parse an int argument with default value" *
+          test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--count").default_value(2).scan<'i', int>();
+  program.parse_args({"test", "--count"});
+  REQUIRE(program.get<int>("--count") == 2);
+}
+
+TEST_CASE("Parse a float argument with value" * test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--ratio").scan<'g', float>();
+  program.parse_args({"test", "--ratio", "5.6645"});
+  REQUIRE(program.get<float>("--ratio") == 5.6645f);
+}
+
+TEST_CASE("Parse a float argument with default value" *
+          test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--ratio").default_value(3.14f).scan<'g', float>();
+  program.parse_args({"test", "--ratio"});
+  REQUIRE(program.get<float>("--ratio") == 3.14f);
+}
+
+TEST_CASE("Parse a double argument with value" * test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--ratio").scan<'g', double>();
+  program.parse_args({"test", "--ratio", "5.6645"});
+  REQUIRE(program.get<double>("--ratio") == 5.6645);
+}
+
+TEST_CASE("Parse a double argument with default value" *
+          test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--ratio").default_value(3.14).scan<'g', double>();
+  program.parse_args({"test", "--ratio"});
+  REQUIRE(program.get<double>("--ratio") == 3.14);
+}
+
+TEST_CASE("Parse a vector of integer arguments" * test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--vector").nargs(5).scan<'i', int>();
+  program.parse_args({"test", "--vector", "1", "2", "3", "4", "5"});
+  auto vector = program.get<std::vector<int>>("--vector");
+  REQUIRE(vector.size() == 5);
+  REQUIRE(vector[0] == 1);
+  REQUIRE(vector[1] == 2);
+  REQUIRE(vector[2] == 3);
+  REQUIRE(vector[3] == 4);
+  REQUIRE(vector[4] == 5);
+}
+
+TEST_CASE("Parse a vector of float arguments" * test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--vector").nargs(5).scan<'g', float>();
+  program.parse_args({"test", "--vector", "1.1", "2.2", "3.3", "4.4", "5.5"});
+  auto vector = program.get<std::vector<float>>("--vector");
+  REQUIRE(vector.size() == 5);
+  REQUIRE(vector[0] == 1.1f);
+  REQUIRE(vector[1] == 2.2f);
+  REQUIRE(vector[2] == 3.3f);
+  REQUIRE(vector[3] == 4.4f);
+  REQUIRE(vector[4] == 5.5f);
+}
+
+TEST_CASE("Parse a vector of float without default value" *
+          test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--vector").scan<'g', float>().nargs(3);
+
+  WHEN("no value is provided") {
+    program.parse_args({"test"});
+
+    THEN("the option is nullopt") {
+      auto opt = program.present<std::vector<float>>("--vector");
+      REQUIRE_FALSE(opt.has_value());
+      REQUIRE(opt == std::nullopt);
+    }
+  }
+
+  WHEN("a value is provided") {
+    program.parse_args({"test", "--vector", ".3", "1.3", "6"});
+
+    THEN("the option has a value") {
+      auto opt = program.present<std::vector<float>>("--vector");
+      REQUIRE(opt.has_value());
+
+      auto &&vec = opt.value();
+      REQUIRE(vec.size() == 3);
+      REQUIRE(vec[0] == .3f);
+      REQUIRE(vec[1] == 1.3f);
+      REQUIRE(vec[2] == 6.f);
+    }
+  }
+}
+
+TEST_CASE("Parse a vector of double arguments" * test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--vector").nargs(5).scan<'g', double>();
+  program.parse_args({"test", "--vector", "1.1", "2.2", "3.3", "4.4", "5.5"});
+  auto vector = program.get<std::vector<double>>("--vector");
+  REQUIRE(vector.size() == 5);
+  REQUIRE(vector[0] == 1.1);
+  REQUIRE(vector[1] == 2.2);
+  REQUIRE(vector[2] == 3.3);
+  REQUIRE(vector[3] == 4.4);
+  REQUIRE(vector[4] == 5.5);
+}
+
+TEST_CASE("Parse a vector of string arguments" * test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--vector").nargs(5);
+  program.parse_args({"test", "--vector", "abc", "def", "ghi", "jkl", "mno"});
+  auto vector = program.get<std::vector<std::string>>("--vector");
+  REQUIRE(vector.size() == 5);
+  REQUIRE(vector[0] == "abc");
+  REQUIRE(vector[1] == "def");
+  REQUIRE(vector[2] == "ghi");
+  REQUIRE(vector[3] == "jkl");
+  REQUIRE(vector[4] == "mno");
+}
+
+TEST_CASE("Parse a vector of character arguments" * test_suite("parse_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--vector")
+      .nargs(5)
+      .action([](const std::string &value) { return value[0]; });
+  program.parse_args({"test", "--vector", "a", "b", "c", "d", "e"});
+  auto vector = program.get<std::vector<char>>("--vector");
+  REQUIRE(vector.size() == 5);
+  REQUIRE(vector[0] == 'a');
+  REQUIRE(vector[1] == 'b');
+  REQUIRE(vector[2] == 'c');
+  REQUIRE(vector[3] == 'd');
+  REQUIRE(vector[4] == 'e');
+}
+
+TEST_CASE("Parse a vector of string arguments and construct objects" *
+          test_suite("parse_args")) {
+
+  class Foo {
+  public:
+    Foo(const std::string &value) : m_value(value) {}
+    std::string m_value;
+  };
+
+  argparse::ArgumentParser program("test");
+  program.add_argument("--vector")
+      .nargs(5)
+      .action([](const std::string &value) { return Foo(value); });
+  program.parse_args({"test", "--vector", "abc", "def", "ghi", "jkl", "mno"});
+  auto vector = program.get<std::vector<Foo>>("--vector");
+  REQUIRE(vector.size() == 5);
+  REQUIRE(vector[0].m_value == Foo("abc").m_value);
+  REQUIRE(vector[1].m_value == Foo("def").m_value);
+  REQUIRE(vector[2].m_value == Foo("ghi").m_value);
+  REQUIRE(vector[3].m_value == Foo("jkl").m_value);
+  REQUIRE(vector[4].m_value == Foo("mno").m_value);
+}
diff --git a/thirdparty/argparse/test/test_parse_known_args.cpp b/thirdparty/argparse/test/test_parse_known_args.cpp
new file mode 100644
index 0000000000..2a9da84550
--- /dev/null
+++ b/thirdparty/argparse/test/test_parse_known_args.cpp
@@ -0,0 +1,82 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Parse unknown optional and positional arguments without exceptions" *
+          test_suite("parse_known_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--foo").implicit_value(true).default_value(false);
+  program.add_argument("bar");
+
+  SUBCASE("Parse unknown optional and positional arguments") {
+    auto unknown_args =
+        program.parse_known_args({"test", "--foo", "--badger", "BAR", "spam"});
+    REQUIRE((unknown_args == std::vector<std::string>{"--badger", "spam"}));
+    REQUIRE(program.get<bool>("--foo") == true);
+    REQUIRE(program.get<std::string>("bar") == std::string{"BAR"});
+  }
+
+  SUBCASE("Parse unknown compound arguments") {
+    auto unknown_args = program.parse_known_args({"test", "-jc", "BAR"});
+    REQUIRE((unknown_args == std::vector<std::string>{"-jc"}));
+    REQUIRE(program.get<bool>("--foo") == false);
+    REQUIRE(program.get<std::string>("bar") == std::string{"BAR"});
+  }
+}
+
+TEST_CASE("Parse unknown optional and positional arguments in subparsers "
+          "without exceptions" *
+          test_suite("parse_known_args")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--output");
+
+  argparse::ArgumentParser command_1("add");
+  command_1.add_argument("file").nargs(2);
+
+  argparse::ArgumentParser command_2("clean");
+  command_2.add_argument("--fullclean")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.add_subparser(command_1);
+  program.add_subparser(command_2);
+
+  SUBCASE("Parse unknown optional argument") {
+    auto unknown_args =
+        program.parse_known_args({"test", "add", "--badger", "BAR", "spam"});
+    REQUIRE(program.is_subcommand_used("add") == true);
+    REQUIRE((command_1.get<std::vector<std::string>>("file") ==
+             std::vector<std::string>{"BAR", "spam"}));
+    REQUIRE((unknown_args == std::vector<std::string>{"--badger"}));
+  }
+
+  SUBCASE("Parse unknown positional argument") {
+    auto unknown_args =
+        program.parse_known_args({"test", "add", "FOO", "BAR", "spam"});
+    REQUIRE(program.is_subcommand_used("add") == true);
+    REQUIRE((command_1.get<std::vector<std::string>>("file") ==
+             std::vector<std::string>{"FOO", "BAR"}));
+    REQUIRE((unknown_args == std::vector<std::string>{"spam"}));
+  }
+
+  SUBCASE("Parse unknown positional and optional arguments") {
+    auto unknown_args = program.parse_known_args(
+        {"test", "add", "--verbose", "FOO", "5", "BAR", "-jn", "spam"});
+    REQUIRE(program.is_subcommand_used("add") == true);
+    REQUIRE((command_1.get<std::vector<std::string>>("file") ==
+             std::vector<std::string>{"FOO", "5"}));
+    REQUIRE((unknown_args ==
+             std::vector<std::string>{"--verbose", "BAR", "-jn", "spam"}));
+  }
+
+  SUBCASE("Parse unknown positional and optional arguments 2") {
+    auto unknown_args =
+        program.parse_known_args({"test", "clean", "--verbose", "FOO", "5",
+                                  "BAR", "--fullclean", "-jn", "spam"});
+    REQUIRE(program.is_subcommand_used("clean") == true);
+    REQUIRE(command_2.get<bool>("--fullclean") == true);
+    REQUIRE((unknown_args == std::vector<std::string>{"--verbose", "FOO", "5",
+                                                      "BAR", "-jn", "spam"}));
+  }
+}
\ No newline at end of file
diff --git a/thirdparty/argparse/test/test_positional_arguments.cpp b/thirdparty/argparse/test/test_positional_arguments.cpp
new file mode 100644
index 0000000000..0875669737
--- /dev/null
+++ b/thirdparty/argparse/test/test_positional_arguments.cpp
@@ -0,0 +1,262 @@
+#include <argparse/argparse.hpp>
+#include <cmath>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Parse positional arguments" * test_suite("positional_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("input");
+  program.add_argument("output");
+  program.parse_args({"test", "rocket.mesh", "thrust_profile.csv"});
+  REQUIRE(program.get("input") == "rocket.mesh");
+  REQUIRE(program.get("output") == "thrust_profile.csv");
+}
+
+TEST_CASE("Missing expected positional argument" *
+          test_suite("positional_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("input");
+  REQUIRE_THROWS_WITH_AS(program.parse_args({"test"}),
+                         "1 argument(s) expected. 0 provided.",
+                         std::runtime_error);
+}
+
+TEST_CASE("Parse positional arguments with fixed nargs" *
+          test_suite("positional_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("input");
+  program.add_argument("output").nargs(2);
+  program.parse_args(
+      {"test", "rocket.mesh", "thrust_profile.csv", "output.mesh"});
+  REQUIRE(program.get("input") == "rocket.mesh");
+  auto outputs = program.get<std::vector<std::string>>("output");
+  REQUIRE(outputs.size() == 2);
+  REQUIRE(outputs[0] == "thrust_profile.csv");
+  REQUIRE(outputs[1] == "output.mesh");
+}
+
+TEST_CASE("Parse positional arguments with optional arguments" *
+          test_suite("positional_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("input");
+  program.add_argument("output").nargs(2);
+  program.add_argument("--num_iterations").scan<'i', int>();
+  program.parse_args({"test", "rocket.mesh", "--num_iterations", "15",
+                      "thrust_profile.csv", "output.mesh"});
+  REQUIRE(program.get<int>("--num_iterations") == 15);
+  REQUIRE(program.get("input") == "rocket.mesh");
+  auto outputs = program.get<std::vector<std::string>>("output");
+  REQUIRE(outputs.size() == 2);
+  REQUIRE(outputs[0] == "thrust_profile.csv");
+  REQUIRE(outputs[1] == "output.mesh");
+}
+
+TEST_CASE("Parse positional arguments with optional arguments in the middle" *
+          test_suite("positional_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("input");
+  program.add_argument("output").nargs(2);
+  program.add_argument("--num_iterations").scan<'i', int>();
+  REQUIRE_THROWS(
+      program.parse_args({"test", "rocket.mesh", "thrust_profile.csv",
+                          "--num_iterations", "15", "output.mesh"}));
+}
+
+TEST_CASE("Parse positional nargs=1..2 arguments" *
+          test_suite("positional_arguments")) {
+  GIVEN("a program that accepts an optional argument and nargs=1..2 positional "
+        "arguments") {
+    argparse::ArgumentParser program("test");
+    program.add_argument("-o");
+    program.add_argument("input").nargs(1, 2);
+
+    WHEN("provided no argument") {
+      THEN("the program does not accept it") {
+        REQUIRE_THROWS(program.parse_args({"test"}));
+      }
+    }
+
+    WHEN("provided 1 argument") {
+      THEN("the program accepts it") {
+        REQUIRE_NOTHROW(program.parse_args({"test", "a.c"}));
+
+        auto inputs = program.get<std::vector<std::string>>("input");
+        REQUIRE(inputs.size() == 1);
+        REQUIRE(inputs[0] == "a.c");
+      }
+    }
+
+    WHEN("provided 2 arguments") {
+      THEN("the program accepts it") {
+        REQUIRE_NOTHROW(program.parse_args({"test", "a.c", "b.c"}));
+
+        auto inputs = program.get<std::vector<std::string>>("input");
+        REQUIRE(inputs.size() == 2);
+        REQUIRE(inputs[0] == "a.c");
+        REQUIRE(inputs[1] == "b.c");
+      }
+    }
+
+    WHEN("provided 3 arguments") {
+      THEN("the program does not accept it") {
+        REQUIRE_THROWS(program.parse_args({"test", "a.c", "b.c", "main.c"}));
+      }
+    }
+
+    WHEN("provided an optional followed by positional arguments") {
+      program.parse_args({"test", "-o", "a.out", "a.c", "b.c"});
+
+      THEN("the optional parameter consumes an argument") {
+        using namespace std::literals;
+        REQUIRE(program["-o"] == "a.out"s);
+
+        auto inputs = program.get<std::vector<std::string>>("input");
+        REQUIRE(inputs.size() == 2);
+        REQUIRE(inputs[0] == "a.c");
+        REQUIRE(inputs[1] == "b.c");
+      }
+    }
+
+    WHEN("provided an optional preceded by positional arguments") {
+      program.parse_args({"test", "a.c", "b.c", "-o", "a.out"});
+
+      THEN("the optional parameter consumes an argument") {
+        using namespace std::literals;
+        REQUIRE(program["-o"] == "a.out"s);
+
+        auto inputs = program.get<std::vector<std::string>>("input");
+        REQUIRE(inputs.size() == 2);
+        REQUIRE(inputs[0] == "a.c");
+        REQUIRE(inputs[1] == "b.c");
+      }
+    }
+
+    WHEN("provided an optional in between positional arguments") {
+      THEN("the program does not accept it") {
+        REQUIRE_THROWS(
+            program.parse_args({"test", "a.c", "-o", "a.out", "b.c"}));
+      }
+    }
+  }
+}
+
+TEST_CASE("Parse positional nargs=ANY arguments" *
+          test_suite("positional_arguments")) {
+  GIVEN("a program that accepts an optional argument and nargs=ANY positional "
+        "arguments") {
+    argparse::ArgumentParser program("test");
+    program.add_argument("-o");
+    program.add_argument("input").nargs(argparse::nargs_pattern::any);
+
+    WHEN("provided no argument") {
+      THEN("the program accepts it and gets empty container") {
+        REQUIRE_NOTHROW(program.parse_args({"test"}));
+
+        auto inputs = program.get<std::vector<std::string>>("input");
+        REQUIRE(inputs.size() == 0);
+      }
+    }
+
+    WHEN("provided an optional followed by positional arguments") {
+      program.parse_args({"test", "-o", "a.out", "a.c", "b.c", "main.c"});
+
+      THEN("the optional parameter consumes an argument") {
+        using namespace std::literals;
+        REQUIRE(program["-o"] == "a.out"s);
+
+        auto inputs = program.get<std::vector<std::string>>("input");
+        REQUIRE(inputs.size() == 3);
+        REQUIRE(inputs[0] == "a.c");
+        REQUIRE(inputs[1] == "b.c");
+        REQUIRE(inputs[2] == "main.c");
+      }
+    }
+
+    WHEN("provided an optional preceded by positional arguments") {
+      program.parse_args({"test", "a.c", "b.c", "main.c", "-o", "a.out"});
+
+      THEN("the optional parameter consumes an argument") {
+        using namespace std::literals;
+        REQUIRE(program["-o"] == "a.out"s);
+
+        auto inputs = program.get<std::vector<std::string>>("input");
+        REQUIRE(inputs.size() == 3);
+        REQUIRE(inputs[0] == "a.c");
+        REQUIRE(inputs[1] == "b.c");
+        REQUIRE(inputs[2] == "main.c");
+      }
+    }
+  }
+}
+
+TEST_CASE("Parse remaining arguments deemed positional" *
+          test_suite("positional_arguments")) {
+  GIVEN("a program that accepts an optional argument and remaining arguments") {
+    argparse::ArgumentParser program("test");
+    program.add_argument("-o");
+    program.add_argument("input").remaining();
+
+    WHEN("provided no argument") {
+      THEN("the program accepts it but gets nothing") {
+        REQUIRE_NOTHROW(program.parse_args({"test"}));
+        REQUIRE_THROWS_AS(program.get<std::vector<std::string>>("input"),
+                          std::logic_error);
+      }
+    }
+
+    WHEN("provided an optional followed by remaining arguments") {
+      program.parse_args({"test", "-o", "a.out", "a.c", "b.c", "main.c"});
+
+      THEN("the optional parameter consumes an argument") {
+        using namespace std::literals;
+        REQUIRE(program["-o"] == "a.out"s);
+
+        auto inputs = program.get<std::vector<std::string>>("input");
+        REQUIRE(inputs.size() == 3);
+        REQUIRE(inputs[0] == "a.c");
+        REQUIRE(inputs[1] == "b.c");
+        REQUIRE(inputs[2] == "main.c");
+      }
+    }
+
+    WHEN("provided remaining arguments including optional arguments") {
+      program.parse_args({"test", "a.c", "b.c", "main.c", "-o", "a.out"});
+
+      THEN("the optional argument is deemed remaining") {
+        REQUIRE_THROWS_AS(program.get("-o"), std::logic_error);
+
+        auto inputs = program.get<std::vector<std::string>>("input");
+        REQUIRE(inputs.size() == 5);
+        REQUIRE(inputs[0] == "a.c");
+        REQUIRE(inputs[1] == "b.c");
+        REQUIRE(inputs[2] == "main.c");
+        REQUIRE(inputs[3] == "-o");
+        REQUIRE(inputs[4] == "a.out");
+      }
+    }
+  }
+}
+
+TEST_CASE("Reversed order nargs is not allowed" *
+          test_suite("positional_arguments")) {
+  argparse::ArgumentParser program("test");
+  REQUIRE_THROWS_AS(program.add_argument("output").nargs(2, 1),
+                    std::logic_error);
+}
+
+TEST_CASE("Square a number" * test_suite("positional_arguments")) {
+  argparse::ArgumentParser program;
+  program.add_argument("--verbose", "-v")
+      .help("enable verbose logging")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.add_argument("square")
+      .help("display a square of a given number")
+      .action(
+          [](const std::string &value) { return pow(std::stoi(value), 2); });
+
+  program.parse_args({"./main", "15"});
+  REQUIRE(program.get<double>("square") == 225);
+}
diff --git a/thirdparty/argparse/test/test_prefix_chars.cpp b/thirdparty/argparse/test/test_prefix_chars.cpp
new file mode 100644
index 0000000000..e62b1d9ba9
--- /dev/null
+++ b/thirdparty/argparse/test/test_prefix_chars.cpp
@@ -0,0 +1,41 @@
+#include <argparse/argparse.hpp>
+#include <cmath>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Parse with custom prefix chars" * test_suite("prefix_chars")) {
+  argparse::ArgumentParser program("test");
+  program.set_prefix_chars("-+");
+  program.add_argument("+f");
+  program.add_argument("++bar");
+  program.parse_args({"test", "+f", "X", "++bar", "Y"});
+  REQUIRE(program.get("+f") == "X");
+  REQUIRE(program.get("++bar") == "Y");
+}
+
+TEST_CASE("Parse with custom Windows-style prefix chars" *
+          test_suite("prefix_chars")) {
+  argparse::ArgumentParser program("dir");
+  program.set_prefix_chars("/");
+  program.add_argument("/A").nargs(1);
+  program.add_argument("/B").default_value(false).implicit_value(true);
+  program.add_argument("/C").default_value(false).implicit_value(true);
+  program.parse_args({"dir", "/A", "D", "/B", "/C"});
+  REQUIRE(program.get("/A") == "D");
+  REQUIRE(program.get<bool>("/B") == true);
+}
+
+TEST_CASE("Parse with custom Windows-style prefix chars and assign chars" *
+          test_suite("prefix_chars")) {
+  argparse::ArgumentParser program("dir");
+  program.set_prefix_chars("/");
+  program.set_assign_chars(":=");
+  program.add_argument("/A").nargs(1);
+  program.add_argument("/B").nargs(1);
+  program.add_argument("/C").default_value(false).implicit_value(true);
+  program.parse_args({"dir", "/A:D", "/B=Boo", "/C"});
+  REQUIRE(program.get("/A") == "D");
+  REQUIRE(program.get("/B") == "Boo");
+  REQUIRE(program.get<bool>("/C") == true);
+}
\ No newline at end of file
diff --git a/thirdparty/argparse/test/test_repr.cpp b/thirdparty/argparse/test/test_repr.cpp
new file mode 100644
index 0000000000..c09c3ac569
--- /dev/null
+++ b/thirdparty/argparse/test/test_repr.cpp
@@ -0,0 +1,56 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+#include <set>
+
+using doctest::test_suite;
+
+TEST_CASE("Test bool representation" * test_suite("repr")) {
+  REQUIRE(argparse::details::repr(true) == "true");
+  REQUIRE(argparse::details::repr(false) == "false");
+}
+
+TEST_CASE_TEMPLATE("Test built-in int types representation" *
+                       test_suite("repr"),
+                   T, char, short, int, long long, unsigned char, unsigned,
+                   unsigned long long) {
+  std::stringstream ss;
+  T v = 42;
+  ss << v;
+  REQUIRE(argparse::details::repr(v) == ss.str());
+}
+
+TEST_CASE_TEMPLATE("Test built-in float types representation" *
+                       test_suite("repr"),
+                   T, float, double, long double) {
+  std::stringstream ss;
+  T v = static_cast<T>(0.3333333333);
+  ss << v;
+  REQUIRE(argparse::details::repr(v) == ss.str());
+}
+
+TEST_CASE_TEMPLATE("Test container representation" * test_suite("repr"), T,
+                   std::vector<int>, std::list<int>, std::set<int>) {
+  T empty;
+  T one = {42};
+  T small = {1, 2, 3};
+  T big = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+  REQUIRE(argparse::details::repr(empty) == "{}");
+  REQUIRE(argparse::details::repr(one) == "{42}");
+  REQUIRE(argparse::details::repr(small) == "{1 2 3}");
+  REQUIRE(argparse::details::repr(big) == "{1 2 3 4...15}");
+}
+
+TEST_CASE_TEMPLATE("Test string representation" * test_suite("repr"), T,
+                   char const *, std::string, std::string_view) {
+  T empty = "";
+  T str = "A A A#";
+
+  REQUIRE(argparse::details::repr(empty) == "\"\"");
+  REQUIRE(argparse::details::repr(str) == "\"A A A#\"");
+}
+
+TEST_CASE("Test unknown representation" * test_suite("repr")) {
+  struct TestClass {};
+  REQUIRE(argparse::details::repr(TestClass{}) == "<not representable>");
+}
diff --git a/thirdparty/argparse/test/test_required_arguments.cpp b/thirdparty/argparse/test/test_required_arguments.cpp
new file mode 100644
index 0000000000..299808aae6
--- /dev/null
+++ b/thirdparty/argparse/test/test_required_arguments.cpp
@@ -0,0 +1,63 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE(
+    "Parse required arguments which are not set and don't have default value" *
+    test_suite("required_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--output", "-o").required();
+  REQUIRE_THROWS(program.parse_args({"./main"}));
+}
+
+TEST_CASE("Parse required arguments which are set as empty value and don't "
+          "have default value" *
+          test_suite("required_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--output", "-o").required();
+  REQUIRE_THROWS(program.parse_args({"./main", "-o"}));
+}
+
+TEST_CASE("Parse required arguments which are set as some value and don't have "
+          "default value" *
+          test_suite("required_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--output", "-o").required();
+  program.parse_args({"./main", "-o", "filename"});
+  REQUIRE(program.get("--output") == "filename");
+  REQUIRE(program.get("-o") == "filename");
+}
+
+TEST_CASE("Parse required arguments which are not set and have default value" *
+          test_suite("required_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--output", "-o")
+      .required()
+      .default_value(std::string("filename"));
+  program.parse_args({"./main"});
+  REQUIRE(program.get("--output") == "filename");
+  REQUIRE(program.get("-o") == "filename");
+}
+
+TEST_CASE(
+    "Parse required arguments which are set as empty and have default value" *
+    test_suite("required_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--output", "-o")
+      .required()
+      .default_value(std::string("filename"));
+  REQUIRE_THROWS(program.parse_args({"./main", "-o"}));
+}
+
+TEST_CASE("Parse required arguments which are set as some value and have "
+          "default value" *
+          test_suite("required_arguments")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--output", "-o")
+      .required()
+      .default_value(std::string("filename"));
+  program.parse_args({"./main", "-o", "anotherfile"});
+  REQUIRE(program.get("--output") == "anotherfile");
+  REQUIRE(program.get("-o") == "anotherfile");
+}
diff --git a/thirdparty/argparse/test/test_scan.cpp b/thirdparty/argparse/test/test_scan.cpp
new file mode 100644
index 0000000000..6cc53e5c39
--- /dev/null
+++ b/thirdparty/argparse/test/test_scan.cpp
@@ -0,0 +1,348 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+#include <stdint.h>
+
+using doctest::test_suite;
+
+TEST_CASE_TEMPLATE("Parse a decimal integer argument" * test_suite("scan"), T,
+                   int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t,
+                   uint32_t, uint64_t) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-n").scan<'d', T>();
+
+  SUBCASE("zero") {
+    program.parse_args({"test", "-n", "0"});
+    REQUIRE(program.get<T>("-n") == 0);
+  }
+
+  SUBCASE("non-negative") {
+    program.parse_args({"test", "-n", "5"});
+    REQUIRE(program.get<T>("-n") == 5);
+  }
+
+  SUBCASE("negative") {
+    if constexpr (std::is_signed_v<T>) {
+      program.parse_args({"test", "-n", "-128"});
+      REQUIRE(program.get<T>("-n") == -128);
+    } else {
+      REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-135"}),
+                        std::invalid_argument);
+    }
+  }
+
+  SUBCASE("left-padding is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", " 32"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("right-padding is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "12 "}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("plus sign is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+12"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("does not fit") {
+    REQUIRE_THROWS_AS(
+        program.parse_args({"test", "-n", "987654321987654321987654321"}),
+        std::range_error);
+  }
+}
+
+TEST_CASE_TEMPLATE("Parse an octal integer argument" * test_suite("scan"), T,
+                   uint8_t, uint16_t, uint32_t, uint64_t) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-n").scan<'o', T>();
+
+  SUBCASE("zero") {
+    program.parse_args({"test", "-n", "0"});
+    REQUIRE(program.get<T>("-n") == 0);
+  }
+
+  SUBCASE("with octal base") {
+    program.parse_args({"test", "-n", "066"});
+    REQUIRE(program.get<T>("-n") == 066);
+  }
+
+  SUBCASE("minus sign produces an optional argument") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-003"}),
+                      std::runtime_error);
+  }
+
+  SUBCASE("plus sign is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+012"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("does not fit") {
+    REQUIRE_THROWS_AS(
+        program.parse_args({"test", "-n", "02000000000000000000001"}),
+        std::range_error);
+  }
+}
+
+TEST_CASE_TEMPLATE("Parse a hexadecimal integer argument" * test_suite("scan"),
+                   T, uint8_t, uint16_t, uint32_t, uint64_t) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-n").scan<'X', T>();
+
+  SUBCASE("with hex digit") {
+    program.parse_args({"test", "-n", "0x1a"});
+    REQUIRE(program.get<T>("-n") == 0x1a);
+  }
+
+  SUBCASE("minus sign produces an optional argument") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0x1"}),
+                      std::runtime_error);
+  }
+
+  SUBCASE("plus sign is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+0x1a"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("does not fit") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0XFFFFFFFFFFFFFFFF1"}),
+                      std::range_error);
+  }
+}
+
+TEST_CASE_TEMPLATE("Parse integer argument of any format" * test_suite("scan"),
+                   T, int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t,
+                   uint32_t, uint64_t) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-n").scan<'i', T>();
+
+  SUBCASE("zero") {
+    program.parse_args({"test", "-n", "0"});
+    REQUIRE(program.get<T>("-n") == 0);
+  }
+
+  SUBCASE("octal") {
+    program.parse_args({"test", "-n", "077"});
+    REQUIRE(program.get<T>("-n") == 077);
+  }
+
+  SUBCASE("no negative octal") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0777"}),
+                      std::runtime_error);
+  }
+
+  SUBCASE("hex") {
+    program.parse_args({"test", "-n", "0X2c"});
+    REQUIRE(program.get<T>("-n") == 0X2c);
+  }
+
+  SUBCASE("no negative hex") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0X2A"}),
+                      std::runtime_error);
+  }
+
+  SUBCASE("decimal") {
+    program.parse_args({"test", "-n", "98"});
+    REQUIRE(program.get<T>("-n") == 98);
+  }
+
+  SUBCASE("negative decimal") {
+    if constexpr (std::is_signed_v<T>) {
+      program.parse_args({"test", "-n", "-39"});
+      REQUIRE(program.get<T>("-n") == -39);
+    } else {
+      REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-39"}),
+                        std::invalid_argument);
+    }
+  }
+
+  SUBCASE("left-padding is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "\t32"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("right-padding is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "32\n"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("plus sign is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+670"}),
+                      std::invalid_argument);
+  }
+}
+
+#define FLOAT_G(t, literal)                                                    \
+  ([] {                                                                        \
+    if constexpr (std::is_same_v<t, float>)                                    \
+      return literal##f;                                                       \
+    else if constexpr (std::is_same_v<t, double>)                              \
+      return literal;                                                          \
+    else if constexpr (std::is_same_v<t, long double>)                         \
+      return literal##l;                                                       \
+  }())
+
+TEST_CASE_TEMPLATE("Parse floating-point argument of general format" *
+                       test_suite("scan"),
+                   T, float, double, long double) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-n").scan<'g', T>();
+
+  SUBCASE("zero") {
+    program.parse_args({"test", "-n", "0"});
+    REQUIRE(program.get<T>("-n") == 0.);
+  }
+
+  SUBCASE("non-negative") {
+    program.parse_args({"test", "-n", "3.14"});
+    REQUIRE(program.get<T>("-n") == FLOAT_G(T, 3.14));
+  }
+
+  SUBCASE("negative") {
+    program.parse_args({"test", "-n", "-0.12"});
+    REQUIRE(program.get<T>("-n") == FLOAT_G(T, -0.12));
+  }
+
+  SUBCASE("left-padding is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "\t.32"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("right-padding is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", ".32\n"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("plus sign is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+.12"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("plus sign after padding is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "   +.12"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("hexfloat is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0x1a.3p+1"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("does not fit") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "1.3e+5000"}),
+                      std::range_error);
+  }
+}
+
+TEST_CASE_TEMPLATE("Parse hexadecimal floating-point argument" *
+                       test_suite("scan"),
+                   T, float, double, long double) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-n").scan<'a', T>();
+
+  SUBCASE("zero") {
+    // binary-exponent-part is not optional in C++ grammar
+    program.parse_args({"test", "-n", "0x0"});
+    REQUIRE(program.get<T>("-n") == 0x0.p0);
+  }
+
+  SUBCASE("non-negative") {
+    program.parse_args({"test", "-n", "0x1a.3p+1"});
+    REQUIRE(program.get<T>("-n") == 0x1a.3p+1);
+  }
+
+  SUBCASE("minus sign produces an optional argument") {
+    // XXX may worth a fix
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0x0.12p1"}),
+                      std::runtime_error);
+  }
+
+  SUBCASE("plus sign is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+0x1p0"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("general format is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "3.14"}),
+                      std::invalid_argument);
+  }
+}
+
+TEST_CASE_TEMPLATE("Parse floating-point argument of scientific format" *
+                       test_suite("scan"),
+                   T, float, double, long double) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-n").scan<'e', T>();
+
+  SUBCASE("zero") {
+    program.parse_args({"test", "-n", "0e0"});
+    REQUIRE(program.get<T>("-n") == 0e0);
+  }
+
+  SUBCASE("non-negative") {
+    program.parse_args({"test", "-n", "3.14e-1"});
+    REQUIRE(program.get<T>("-n") == FLOAT_G(T, 3.14e-1));
+  }
+
+  SUBCASE("negative") {
+    program.parse_args({"test", "-n", "-0.12e+1"});
+    REQUIRE(program.get<T>("-n") == FLOAT_G(T, -0.12e+1));
+  }
+
+  SUBCASE("plus sign is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+.12e+1"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("fixed format is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "3.14"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("hexfloat is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0x1.33p+0"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("does not fit") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "1.3e+5000"}),
+                      std::range_error);
+  }
+}
+
+TEST_CASE_TEMPLATE("Parse floating-point argument of fixed format" *
+                       test_suite("scan"),
+                   T, float, double, long double) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("-n").scan<'f', T>();
+
+  SUBCASE("zero") {
+    program.parse_args({"test", "-n", ".0"});
+    REQUIRE(program.get<T>("-n") == .0);
+  }
+
+  SUBCASE("non-negative") {
+    program.parse_args({"test", "-n", "3.14"});
+    REQUIRE(program.get<T>("-n") == FLOAT_G(T, 3.14));
+  }
+
+  SUBCASE("negative") {
+    program.parse_args({"test", "-n", "-0.12"});
+    REQUIRE(program.get<T>("-n") == FLOAT_G(T, -0.12));
+  }
+
+  SUBCASE("plus sign is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+.12"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("scientific format is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "3.14e+0"}),
+                      std::invalid_argument);
+  }
+
+  SUBCASE("hexfloat is not allowed") {
+    REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0x1.33p+0"}),
+                      std::invalid_argument);
+  }
+}
diff --git a/thirdparty/argparse/test/test_subparsers.cpp b/thirdparty/argparse/test/test_subparsers.cpp
new file mode 100644
index 0000000000..e4a42746cd
--- /dev/null
+++ b/thirdparty/argparse/test/test_subparsers.cpp
@@ -0,0 +1,202 @@
+#include <argparse/argparse.hpp>
+#include <cmath>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Add subparsers" * test_suite("subparsers")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--output");
+
+  argparse::ArgumentParser command_1("add");
+  command_1.add_argument("file").nargs(2);
+
+  argparse::ArgumentParser command_2("clean");
+
+  program.add_subparser(command_1);
+  program.add_subparser(command_2);
+
+  program.parse_args({"test", "--output", "thrust_profile.csv"});
+  REQUIRE(program.is_subcommand_used("add") == false);
+  REQUIRE(program.get("--output") == "thrust_profile.csv");
+}
+
+TEST_CASE("Parse subparser command" * test_suite("subparsers")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--output");
+
+  argparse::ArgumentParser command_1("add");
+  command_1.add_argument("file").nargs(2);
+
+  argparse::ArgumentParser command_2("clean");
+  command_2.add_argument("--fullclean")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.add_subparser(command_1);
+  program.add_subparser(command_2);
+
+  SUBCASE("command 1") {
+    program.parse_args({"test", "add", "file1.txt", "file2.txt"});
+    REQUIRE(program.is_subcommand_used("add") == true);
+    REQUIRE(command_1.is_used("file"));
+    REQUIRE((command_1.get<std::vector<std::string>>("file") ==
+             std::vector<std::string>{"file1.txt", "file2.txt"}));
+  }
+
+  SUBCASE("command 2") {
+    program.parse_args({"test", "clean", "--fullclean"});
+    REQUIRE(program.is_subcommand_used("clean") == true);
+    REQUIRE(command_2.get<bool>("--fullclean") == true);
+  }
+}
+
+TEST_CASE("Parse subparser command with optional argument" *
+          test_suite("subparsers")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--verbose").default_value(false).implicit_value(true);
+
+  argparse::ArgumentParser command_1("add");
+  command_1.add_argument("file");
+
+  argparse::ArgumentParser command_2("clean");
+  command_2.add_argument("--fullclean")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.add_subparser(command_1);
+  program.add_subparser(command_2);
+
+  SUBCASE("Optional argument BEFORE subcommand") {
+    program.parse_args({"test", "--verbose", "clean", "--fullclean"});
+    REQUIRE(program.is_subcommand_used("clean") == true);
+    REQUIRE(program.get<bool>("--verbose") == true);
+    REQUIRE(command_2.get<bool>("--fullclean") == true);
+  }
+
+  SUBCASE("Optional argument AFTER subcommand") {
+    REQUIRE_THROWS_WITH_AS(
+        program.parse_args({"test", "clean", "--fullclean", "--verbose"}),
+        "Unknown argument: --verbose", std::runtime_error);
+  }
+}
+
+TEST_CASE("Parse subparser command with parent parser" *
+          test_suite("subparsers")) {
+  argparse::ArgumentParser program("test");
+
+  argparse::ArgumentParser parent("parent");
+  parent.add_argument("--verbose").default_value(false).implicit_value(true);
+  program.add_parents(parent);
+
+  argparse::ArgumentParser command_1("add");
+  command_1.add_argument("file");
+
+  argparse::ArgumentParser command_2("clean");
+  command_2.add_argument("--fullclean")
+      .default_value(false)
+      .implicit_value(true);
+
+  program.add_subparser(command_1);
+  program.add_subparser(command_2);
+
+  SUBCASE("Optional argument BEFORE subcommand") {
+    program.parse_args({"test", "--verbose", "clean", "--fullclean"});
+    REQUIRE(program.is_subcommand_used("clean") == true);
+    REQUIRE(program.get<bool>("--verbose") == true);
+    REQUIRE(command_2.get<bool>("--fullclean") == true);
+  }
+
+  SUBCASE("Optional argument AFTER subcommand") {
+    REQUIRE_THROWS_WITH_AS(
+        program.parse_args({"test", "clean", "--fullclean", "--verbose"}),
+        "Unknown argument: --verbose", std::runtime_error);
+  }
+}
+
+TEST_CASE("Parse git commands" * test_suite("subparsers")) {
+  argparse::ArgumentParser program("git");
+
+  argparse::ArgumentParser add_command("add");
+  add_command.add_argument("files").remaining();
+
+  argparse::ArgumentParser commit_command("commit");
+  commit_command.add_argument("-a").default_value(false).implicit_value(true);
+
+  commit_command.add_argument("-m");
+
+  argparse::ArgumentParser catfile_command("cat-file");
+  catfile_command.add_argument("-t");
+  catfile_command.add_argument("-p");
+
+  argparse::ArgumentParser submodule_command("submodule");
+  argparse::ArgumentParser submodule_update_command("update");
+  submodule_update_command.add_argument("--init")
+      .default_value(false)
+      .implicit_value(true);
+  submodule_update_command.add_argument("--recursive")
+      .default_value(false)
+      .implicit_value(true);
+  submodule_command.add_subparser(submodule_update_command);
+
+  program.add_subparser(add_command);
+  program.add_subparser(commit_command);
+  program.add_subparser(catfile_command);
+  program.add_subparser(submodule_command);
+
+  SUBCASE("git add") {
+    program.parse_args({"git", "add", "main.cpp", "foo.hpp", "foo.cpp"});
+    REQUIRE(program.is_subcommand_used("add") == true);
+    REQUIRE((add_command.get<std::vector<std::string>>("files") ==
+             std::vector<std::string>{"main.cpp", "foo.hpp", "foo.cpp"}));
+  }
+
+  SUBCASE("git commit") {
+    program.parse_args({"git", "commit", "-am", "Initial commit"});
+    REQUIRE(program.is_subcommand_used("commit") == true);
+    REQUIRE(commit_command.get<bool>("-a") == true);
+    REQUIRE(commit_command.get<std::string>("-m") ==
+            std::string{"Initial commit"});
+  }
+
+  SUBCASE("git cat-file -t") {
+    program.parse_args({"git", "cat-file", "-t", "3739f5"});
+    REQUIRE(program.is_subcommand_used("cat-file") == true);
+    REQUIRE(catfile_command.get<std::string>("-t") == std::string{"3739f5"});
+  }
+
+  SUBCASE("git cat-file -p") {
+    program.parse_args({"git", "cat-file", "-p", "3739f5"});
+    REQUIRE(program.is_subcommand_used("cat-file") == true);
+    REQUIRE(catfile_command.get<std::string>("-p") == std::string{"3739f5"});
+  }
+
+  SUBCASE("git submodule update") {
+    program.parse_args({"git", "submodule", "update"});
+    REQUIRE(program.is_subcommand_used("submodule") == true);
+    REQUIRE(submodule_command.is_subcommand_used("update") == true);
+  }
+
+  SUBCASE("git submodule update --init") {
+    program.parse_args({"git", "submodule", "update", "--init"});
+    REQUIRE(program.is_subcommand_used("submodule") == true);
+    REQUIRE(submodule_command.is_subcommand_used("update") == true);
+    REQUIRE(submodule_update_command.get<bool>("--init") == true);
+    REQUIRE(submodule_update_command.get<bool>("--recursive") == false);
+  }
+
+  SUBCASE("git submodule update --recursive") {
+    program.parse_args({"git", "submodule", "update", "--recursive"});
+    REQUIRE(program.is_subcommand_used("submodule") == true);
+    REQUIRE(submodule_command.is_subcommand_used("update") == true);
+    REQUIRE(submodule_update_command.get<bool>("--recursive") == true);
+  }
+
+  SUBCASE("git submodule update --init --recursive") {
+    program.parse_args({"git", "submodule", "update", "--init", "--recursive"});
+    REQUIRE(program.is_subcommand_used("submodule") == true);
+    REQUIRE(submodule_command.is_subcommand_used("update") == true);
+    REQUIRE(submodule_update_command.get<bool>("--init") == true);
+    REQUIRE(submodule_update_command.get<bool>("--recursive") == true);
+  }
+}
\ No newline at end of file
diff --git a/thirdparty/argparse/test/test_utility.hpp b/thirdparty/argparse/test/test_utility.hpp
new file mode 100644
index 0000000000..fd061de7a5
--- /dev/null
+++ b/thirdparty/argparse/test/test_utility.hpp
@@ -0,0 +1,17 @@
+#ifndef ARGPARSE_TEST_UTILITY_HPP
+#define ARGPARSE_TEST_UTILITY_HPP
+
+namespace testutility {
+// Get value at index from std::list
+template <typename T>
+T get_from_list(const std::list<T>& aList, size_t aIndex) {
+  if (aList.size() > aIndex) {
+    auto tIterator = aList.begin();
+    std::advance(tIterator, aIndex);
+    return *tIterator;
+  }
+  return T();
+}
+}
+
+#endif //ARGPARSE_TEST_UTILITY_HPP
diff --git a/thirdparty/argparse/test/test_value_semantics.cpp b/thirdparty/argparse/test/test_value_semantics.cpp
new file mode 100644
index 0000000000..2cc0f88777
--- /dev/null
+++ b/thirdparty/argparse/test/test_value_semantics.cpp
@@ -0,0 +1,96 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("ArgumentParser is MoveConstructible and MoveAssignable" *
+          test_suite("value_semantics")) {
+  GIVEN("a parser that has two arguments") {
+    argparse::ArgumentParser parser("test");
+    parser.add_argument("foo");
+    parser.add_argument("-f");
+
+    WHEN("move construct a new parser from it") {
+      auto new_parser = std::move(parser);
+
+      THEN("the old parser replaces the new parser") {
+        new_parser.parse_args({"test", "bar", "-f", "nul"});
+
+        REQUIRE(new_parser.get("foo") == "bar");
+        REQUIRE(new_parser.get("-f") == "nul");
+      }
+    }
+
+    WHEN("move assign a parser prvalue to it") {
+      parser = argparse::ArgumentParser("test");
+
+      THEN("the old parser is replaced") {
+        REQUIRE_THROWS_AS(parser.parse_args({"test", "bar"}),
+                          std::runtime_error);
+        REQUIRE_THROWS_AS(parser.parse_args({"test", "-f", "nul"}),
+                          std::runtime_error);
+        REQUIRE_NOTHROW(parser.parse_args({"test"}));
+      }
+    }
+  }
+}
+
+TEST_CASE("ArgumentParser is CopyConstructible and CopyAssignable" *
+          test_suite("value_semantics")) {
+  GIVEN("a parser that has two arguments") {
+    argparse::ArgumentParser parser("test");
+    parser.add_argument("foo");
+    parser.add_argument("-f");
+
+    WHEN("copy construct a new parser from it") {
+      auto new_parser = parser;
+
+      THEN("the new parser has the old parser's capability") {
+        new_parser.parse_args({"test", "bar", "-f", "nul"});
+
+        REQUIRE(new_parser.get("foo") == "bar");
+        REQUIRE(new_parser.get("-f") == "nul");
+
+        AND_THEN("but does not share states with the old parser") {
+          REQUIRE_THROWS_AS(parser.get("foo"), std::logic_error);
+          REQUIRE_THROWS_AS(parser.get("-f"), std::logic_error);
+        }
+      }
+
+      AND_THEN("the old parser works as a distinct copy") {
+        new_parser.parse_args({"test", "toe", "-f", "/"});
+
+        REQUIRE(new_parser.get("foo") == "toe");
+        REQUIRE(new_parser.get("-f") == "/");
+      }
+    }
+
+    WHEN("copy assign a parser lvalue to it") {
+      argparse::ArgumentParser optional_parser("test");
+      optional_parser.add_argument("-g");
+      parser = optional_parser;
+
+      THEN("the old parser is replaced") {
+        REQUIRE_THROWS_AS(parser.parse_args({"test", "bar"}),
+                          std::runtime_error);
+        REQUIRE_THROWS_AS(parser.parse_args({"test", "-f", "nul"}),
+                          std::runtime_error);
+        REQUIRE_NOTHROW(parser.parse_args({"test"}));
+        REQUIRE_NOTHROW(parser.parse_args({"test", "-g", "nul"}));
+
+        AND_THEN("but does not share states with the other parser") {
+          REQUIRE(parser.get("-g") == "nul");
+          REQUIRE_THROWS_AS(optional_parser.get("-g"), std::logic_error);
+        }
+      }
+
+      AND_THEN("the other parser works as a distinct copy") {
+        REQUIRE_NOTHROW(optional_parser.parse_args({"test"}));
+        REQUIRE_NOTHROW(optional_parser.parse_args({"test", "-g", "nul"}));
+        REQUIRE_THROWS_AS(
+            optional_parser.parse_args({"test", "bar", "-g", "nul"}),
+            std::runtime_error);
+      }
+    }
+  }
+}
diff --git a/thirdparty/argparse/test/test_version.cpp b/thirdparty/argparse/test/test_version.cpp
new file mode 100644
index 0000000000..377126e3d6
--- /dev/null
+++ b/thirdparty/argparse/test/test_version.cpp
@@ -0,0 +1,36 @@
+#include <argparse/argparse.hpp>
+#include <doctest.hpp>
+#include <sstream>
+
+using doctest::test_suite;
+
+TEST_CASE("Users can print version and exit" * test_suite("version") *
+          doctest::skip()) {
+  argparse::ArgumentParser program("cli-test", "1.9.0");
+  program.add_argument("-d", "--dir").required();
+  program.parse_args({"test", "--version"});
+  REQUIRE(program.get("--version") == "1.9.0");
+}
+
+TEST_CASE("Users can disable default -v/--version" * test_suite("version")) {
+  argparse::ArgumentParser program("test", "1.0",
+                                   argparse::default_arguments::help);
+  REQUIRE_THROWS_WITH_AS(program.parse_args({"test", "--version"}),
+                         "Unknown argument: --version", std::runtime_error);
+}
+
+TEST_CASE("Users can replace default -v/--version" * test_suite("version")) {
+  std::string version{"3.1415"};
+  argparse::ArgumentParser program("test", version,
+                                   argparse::default_arguments::help);
+  std::stringstream buffer;
+  program.add_argument("-v", "--version")
+      .action([&](const auto &) { buffer << version; })
+      .default_value(true)
+      .implicit_value(false)
+      .nargs(0);
+
+  REQUIRE(buffer.str().empty());
+  program.parse_args({"test", "--version"});
+  REQUIRE_FALSE(buffer.str().empty());
+}
diff --git a/thirdparty/argparse/tools/build.bat b/thirdparty/argparse/tools/build.bat
new file mode 100644
index 0000000000..47c24c2012
--- /dev/null
+++ b/thirdparty/argparse/tools/build.bat
@@ -0,0 +1,4 @@
+call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat"
+
+cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DARGPARSE_BUILD_TESTS=ON
+ninja -C build
diff --git a/thirdparty/argparse/tools/build.sh b/thirdparty/argparse/tools/build.sh
new file mode 100644
index 0000000000..289d97c882
--- /dev/null
+++ b/thirdparty/argparse/tools/build.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DARGPARSE_BUILD_TESTS=ON
+ninja -C build
diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt
index 2e46fda968..ac8962c804 100644
--- a/utils/CMakeLists.txt
+++ b/utils/CMakeLists.txt
@@ -5,4 +5,3 @@ if( COMPILER_SUPPORTS_WARNINGS )
 endif()
 
 add_subdirectory( idftools )
-add_subdirectory( kicad2step )