[med-svn] [Git][med-team/libmcfp][upstream] 2 commits: New upstream version 1.2.4

Maarten L. Hekkelman (@mhekkel-guest) gitlab at salsa.debian.org
Tue Jan 21 08:53:35 GMT 2025



Maarten L. Hekkelman pushed to branch upstream at Debian Med / libmcfp


Commits:
eea302aa by Maarten L. Hekkelman at 2023-08-15T09:08:11+02:00
New upstream version 1.2.4
- - - - -
199ee613 by Maarten L. Hekkelman at 2025-01-21T09:04:10+01:00
New upstream version 1.3.4
- - - - -


28 changed files:

- + .github/workflows/build-documentation.yml
- + .github/workflows/cmake-multi-platform.yml
- .gitignore
- + .readthedocs.yaml
- CMakeLists.txt
- README.md
- changelog
- + cmake/FindSphinx.cmake
- cmake/libmcfpConfig.cmake.in
- + cmake/mcfpConfig.cmake.in
- + docs/CMakeLists.txt
- + docs/Doxyfile.in
- + docs/_static/.gitignore
- + docs/conf.py.in
- + docs/genindex.rst
- + docs/index.rst
- + docs/requirements.in
- + docs/requirements.txt
- examples/CMakeLists.txt
- + include/mcfp/detail/charconv.hpp
- + include/mcfp/detail/options.hpp
- + include/mcfp/error.hpp
- include/mcfp/mcfp.hpp
- include/mcfp/text.hpp
- include/mcfp/utilities.hpp
- + test/CMakeLists.txt
- test/unit-test.conf
- test/unit-test.cpp


Changes:

=====================================
.github/workflows/build-documentation.yml
=====================================
@@ -0,0 +1,65 @@
+# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform.
+# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml
+name: publish docs
+
+on:
+  push:
+    branches: [ "trunk" ]
+
+permissions:
+  contents: read
+  pages: write
+  id-token: write
+
+concurrency:
+  group: "pages"
+  cancel-in-progress: false
+
+jobs:
+  docs:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout at v1
+
+    - name: Set reusable strings
+      # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file.
+      id: strings
+      shell: bash
+      run: |
+        echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
+
+    - name: Install dependencies Ubuntu
+      run: sudo apt-get update && sudo apt-get install cmake doxygen
+
+    - uses: actions/setup-python at v4
+      with:
+        python-version: '3.9'
+        cache: 'pip' # caching pip dependencies
+    - run: pip install -r docs/requirements.txt
+
+    - name: Configure CMake
+      run: cmake -S . -B build -DBUILD_DOCUMENTATION=ON -DBUILD_TESTING=OFF
+
+    - name: Run Sphinx
+      run: |
+        cmake --build build --target Sphinx-mcfp
+        ls -l ${{ steps.strings.outputs.build-output-dir }}
+        ls -l ${{ steps.strings.outputs.build-output-dir }}/docs/sphinx
+
+    - name: Upload artifact
+      uses: actions/upload-pages-artifact at v2
+      with:
+        path: ${{ steps.strings.outputs.build-output-dir }}/docs/sphinx
+
+  deploy:
+    environment:
+      name: github-pages
+      url: ${{ steps.deployment.outputs.page_url }}
+
+    runs-on: ubuntu-latest
+    needs: docs
+
+    steps:
+      - name: Deploy to GitHub Pages
+        id: deployment
+        uses: actions/deploy-pages at v2


=====================================
.github/workflows/cmake-multi-platform.yml
=====================================
@@ -0,0 +1,57 @@
+name: multi platform test
+
+on:
+  push:
+    branches: [ "trunk", "develop" ]
+  pull_request:
+    branches: [ "trunk" ]
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      fail-fast: false
+
+      matrix:
+        os: [ubuntu-latest, windows-latest, macos-latest]
+        include:
+          - os: windows-latest
+            cpp_compiler: cl
+          - os: ubuntu-latest
+            cpp_compiler: g++
+          - os: macos-latest
+            cpp_compiler: clang++
+
+    steps:
+    - uses: actions/checkout at v3
+
+    - name: Set reusable strings
+      id: strings
+      shell: bash
+      run: echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
+
+    - name: Install Catch2 Ubuntu
+      if: matrix.os == 'ubuntu-latest'
+      run: >
+        sudo apt-get update && sudo apt-get install catch2
+
+    - name: Install Catch2 macOS
+      if: matrix.os == 'macos-latest'
+      run: >
+        brew install catch2
+
+    - name: Configure CMake
+      run: >
+        cmake -B ${{ steps.strings.outputs.build-output-dir }}
+        -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}
+        -DCMAKE_BUILD_TYPE=Release
+        -S ${{ github.workspace }}
+        
+    - name: Build
+      run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config Release
+
+    - name: Test
+      working-directory: ${{ steps.strings.outputs.build-output-dir }}
+      run: ctest --build-config Release --output-on-failure
+


=====================================
.gitignore
=====================================
@@ -1,3 +1,4 @@
 build
 .vscode
-cmake/libmcfpConfig.cmake
+docs/api
+docs/conf.py


=====================================
.readthedocs.yaml
=====================================
@@ -0,0 +1,22 @@
+version: 2
+
+build:
+  os: "ubuntu-22.04"
+  tools:
+    python: "3.11"
+  apt_packages:
+    - "doxygen"
+    - "cmake"
+  jobs:
+    pre_build:
+      - "cmake -S . -B build -DBUILD_DOCUMENTATION=ON -DBUILD_TESTING=OFF"
+      - "cmake --build build --target Doxygen-mcfp"
+
+# Build from the docs/ directory with Sphinx
+sphinx:
+  configuration: docs/conf.py
+
+# Explicitly set the version of Python and its requirements
+python:
+  install:
+    - requirements: docs/requirements.txt


=====================================
CMakeLists.txt
=====================================
@@ -22,22 +22,28 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-cmake_minimum_required(VERSION 3.16)
+cmake_minimum_required(VERSION 3.23)
 
 # set the project name
-project(libmcfp VERSION 1.2.3 LANGUAGES CXX)
+project(mcfp VERSION 1.3.4 LANGUAGES CXX)
+
+list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
 
 include(GNUInstallDirs)
 include(CMakePackageConfigHelpers)
-include(Dart)
+include(CTest)
 
 set(CXX_EXTENSIONS OFF)
 set(CMAKE_CXX_STANDARD 17 CACHE STRING "The minimum version of C++ required for this library")
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
-option(ENABLE_TESTING "Build the unit test applications" OFF)
+option(BUILD_DOCUMENTATION "Build the documentation" OFF)
 
 if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+	if("${CMAKE_CXX_COMPILER_VERSION}" LESS 9.4)
+		message(FATAL_ERROR "Your GNU g++ is too old, need at least 9.4")
+	endif()
+
 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers")
 elseif(MSVC)
 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
@@ -64,78 +70,76 @@ if(MSVC)
 	add_definitions(-DNOMINMAX)
 endif()
 
-add_library(libmcfp INTERFACE)
-add_library(libmcfp::libmcfp ALIAS libmcfp)
+add_library(mcfp INTERFACE)
+add_library(mcfp::mcfp ALIAS mcfp)
 
-target_include_directories(libmcfp INTERFACE
-	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
-	$<INSTALL_INTERFACE:include>
-)
+target_compile_features(mcfp INTERFACE cxx_std_20)
+
+if(MSVC)
+	target_compile_definitions(mcfp INTERFACE NOMINMAX=1)
+endif()
 
 # adding header sources just helps IDEs
-target_sources(libmcfp INTERFACE
-	$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>$<INSTALL_INTERFACE:include>/mcfp/mcfp.hpp
+target_sources(mcfp PUBLIC
+	FILE_SET mcfp_headers TYPE HEADERS
+	BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include
+	FILES
+	include/mcfp/detail/charconv.hpp
+	include/mcfp/detail/options.hpp
+	include/mcfp/error.hpp
+	include/mcfp/mcfp.hpp
+	include/mcfp/text.hpp
+	include/mcfp/utilities.hpp
 )
 
-set_target_properties(libmcfp PROPERTIES PUBLIC_HEADER include/mcfp/mcfp.hpp)
-
 # installation
-set(version_config "${CMAKE_CURRENT_BINARY_DIR}/libmcfpConfigVersion.cmake")
 
-set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
+# Clean up old config files (with old names)
+file(GLOB OLD_CONFIG_FILES LIST_DIRECTORIES TRUE
+	${CMAKE_INSTALL_FULL_LIBDIR}/cmake/mcfp/mcfp-*.cmake)
 
-write_basic_package_version_file("${version_config}"
-	VERSION ${PROJECT_VERSION}
-	COMPATIBILITY SameMajorVersion)
+if(OLD_CONFIG_FILES)
+	message(
+		WARNING "Installation will remove old config files: ${OLD_CONFIG_FILES}")
+	install(CODE "file(REMOVE_RECURSE ${OLD_CONFIG_FILES})")
+endif()
 
-install(TARGETS libmcfp
-	EXPORT libmcfpConfig
-	PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+install(TARGETS mcfp
+	EXPORT mcfp
+	FILE_SET mcfp_headers
+	DESTINATION include/)
 
-install(
-	DIRECTORY include/mcfp
-	DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
-	COMPONENT Devel
-)
+install(EXPORT mcfp
+	NAMESPACE mcfp::
+	DESTINATION lib/cmake/mcfp
+	FILE "mcfpTargets.cmake")
 
-export(TARGETS libmcfp NAMESPACE libmcfp:: FILE libmcfpTargets.cmake)
+configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/mcfpConfig.cmake.in
+	${CMAKE_CURRENT_BINARY_DIR}/mcfpConfig.cmake
+	INSTALL_DESTINATION lib/cmake/mcfp)
 
-if(WIN32 AND NOT CYGWIN)
-	set(CONFIG_LOC CMake)
-else()
-	set(CONFIG_LOC "${CMAKE_INSTALL_LIBDIR}/cmake/libmcfp")
-endif()
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/mcfpConfig.cmake"
+	DESTINATION lib/cmake/mcfp)
 
-configure_package_config_file(
-	${PROJECT_SOURCE_DIR}/cmake/libmcfpConfig.cmake.in
-	${CMAKE_CURRENT_BINARY_DIR}/libmcfpConfig.cmake
-	INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libmcfp
-	PATH_VARS INCLUDE_INSTALL_DIR
-)
+# Install old config file, for use in older code
 
-install(EXPORT libmcfpConfig
-	FILE libmcfpTargets.cmake
+install(EXPORT mcfp
 	NAMESPACE libmcfp::
-	DESTINATION ${CONFIG_LOC})
-
-install(
-	FILES ${CMAKE_CURRENT_BINARY_DIR}/libmcfpConfig.cmake "${version_config}"
-	DESTINATION ${CONFIG_LOC})
-
-if(ENABLE_TESTING)
-	enable_testing()
+	DESTINATION lib/cmake/libmcfp
+	FILE "libmcfpTargets.cmake")
 
-	find_package(Boost REQUIRED)
-
-	add_executable(libmcfp-unit-test ${PROJECT_SOURCE_DIR}/test/unit-test.cpp)
+configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/libmcfpConfig.cmake.in
+	${CMAKE_CURRENT_BINARY_DIR}/libmcfpConfig.cmake
+	INSTALL_DESTINATION lib/cmake/libmcfp)
 
-	target_link_libraries(libmcfp-unit-test libmcfp::libmcfp Boost::boost)
+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libmcfpConfig.cmake"
+	DESTINATION lib/cmake/libmcfp)
 
-	if(MSVC)
-		# Specify unwind semantics so that MSVC knowns how to handle exceptions
-		target_compile_options(libmcfp-unit-test PRIVATE /EHsc)
-	endif()
+if(BUILD_TESTING)
+	add_subdirectory(test)
+endif()
 
-	add_test(NAME libmcfp-unit-test
-		COMMAND $<TARGET_FILE:libmcfp-unit-test> -- ${PROJECT_SOURCE_DIR}/test)
+if(BUILD_DOCUMENTATION)
+	add_subdirectory(docs)
 endif()
+


=====================================
README.md
=====================================
@@ -1,9 +1,14 @@
+[![github CI](https://github.com/mhekkel/libmcfp/actions/workflows/cmake-multi-platform.yml/badge.svg)](https://github.com/mhekkel/libmcfp/actions)
+[![github CI](https://github.com/mhekkel/libmcfp/actions/workflows/build-documentation.yml/badge.svg)](https://github.com/mhekkel/libmcfp/actions)
+
 # libmcfp
 
 A library for parsing command line arguments and configuration files and making them available throughout a program.
 
 There's a config file parser as well.
 
+> **_NOTE:_** The naming of libmcfp has changed again in version 1.3.4 reverting the rename of 1.3.3. To use libmcfp you should use find_package(mcfp) and link to mcfp::mcfp
+
 ## Synopsis
 
 ```c++
@@ -129,4 +134,3 @@ cmake ..
 cmake --build .
 cmake --install .
 ```
-


=====================================
changelog
=====================================
@@ -1,3 +1,26 @@
+Version 1.3.4
+- Renaming the mcfp to libmcfp was not very consistent
+  with the naming of all the other libraries I write.
+
+Version 1.3.3
+- Yet another config fix
+
+Version 1.3.2
+- Fix config.cmake file
+
+Version 1.3.1
+- revert the name change
+
+Version 1.3.0
+- Config file syntax improvements (better conforming to ini file syntax)
+- Better cmake file
+
+Version 1.2.5
+- Replace test for _MSC_VER with more generic _WIN32
+
+Version 1.2.4
+- Simpler get (added a version without template arguments)
+
 Version 1.2.3
 - MSVC compatibility
 
@@ -28,4 +51,4 @@ Version 1.0.1
 - printing options, word-wrapped
 
 Version 1.0.0
-- initial release
\ No newline at end of file
+- initial release


=====================================
cmake/FindSphinx.cmake
=====================================
@@ -0,0 +1,11 @@
+#Look for an executable called sphinx-build
+find_program(SPHINX_EXECUTABLE
+             NAMES sphinx-build
+             DOC "Path to sphinx-build executable")
+
+include(FindPackageHandleStandardArgs)
+
+#Handle standard arguments to find_package like REQUIRED and QUIET
+find_package_handle_standard_args(Sphinx
+                                  "Failed to find sphinx-build executable"
+                                  SPHINX_EXECUTABLE)
\ No newline at end of file


=====================================
cmake/libmcfpConfig.cmake.in
=====================================
@@ -3,3 +3,9 @@
 INCLUDE("${CMAKE_CURRENT_LIST_DIR}/libmcfpTargets.cmake")
 
 check_required_components(libmcfp)
+
+message(WARNING "Using find_package(libmcfp) is deprecated, please use find_package(mcfp) instead")
+
+# and add an alias
+add_library(libmcfp::libmcfp ALIAS libmcfp::mcfp)
+


=====================================
cmake/mcfpConfig.cmake.in
=====================================
@@ -0,0 +1,5 @@
+ at PACKAGE_INIT@
+
+INCLUDE("${CMAKE_CURRENT_LIST_DIR}/mcfpTargets.cmake")
+
+check_required_components(mcfp)


=====================================
docs/CMakeLists.txt
=====================================
@@ -0,0 +1,43 @@
+find_package(Doxygen REQUIRED)
+find_package(Sphinx REQUIRED)
+
+# Find all the public headers
+set(MCFP_PUBLIC_HEADER_DIR ${PROJECT_SOURCE_DIR}/include/mcfp)
+file(GLOB_RECURSE MCFP_PUBLIC_HEADERS ${MCFP_PUBLIC_HEADER_DIR}/*.hpp)
+
+set(DOXYGEN_INPUT_DIR ${MCFP_PUBLIC_HEADER_DIR})
+set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/xml)
+set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/index.xml)
+set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)
+set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
+
+# Replace variables inside @@ with the current values
+configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY)
+
+add_custom_command(
+	OUTPUT ${DOXYGEN_OUTPUT_DIR}
+	COMMAND ${CMAKE_COMMAND} -E make_directory ${DOXYGEN_OUTPUT_DIR})
+
+add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE}
+	DEPENDS ${MCFP_PUBLIC_HEADERS} ${DOXYGEN_OUTPUT_DIR} ${DOXYFILE_OUT}
+	COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT}
+	MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN}
+	COMMENT "Generating docs")
+
+add_custom_target("Doxygen-${PROJECT_NAME}" ALL DEPENDS ${DOXYGEN_INDEX_FILE})
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in ${CMAKE_CURRENT_SOURCE_DIR}/conf.py @ONLY)
+
+set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR})
+set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/sphinx)
+
+add_custom_target("Sphinx-${PROJECT_NAME}" ALL
+	COMMAND ${SPHINX_EXECUTABLE} -b html
+	-Dbreathe_projects.${PROJECT_NAME}=${DOXYGEN_OUTPUT_DIR}
+	${SPHINX_SOURCE} ${SPHINX_BUILD}
+	DEPENDS ${DOXYGEN_INDEX_FILE}
+	BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/api
+	WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+	COMMENT "Generating documentation with Sphinx")
+
+install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/sphinx/ DESTINATION ${CMAKE_INSTALL_DOCDIR} PATTERN .doctrees EXCLUDE)


=====================================
docs/Doxyfile.in
=====================================
@@ -0,0 +1,6 @@
+EXCLUDE_SYMBOLS        = mcfp::detail::*, mcfp::config_*, mcfp::config::ig_*, mcfp::config_category*, mcfp::make_error_*, std*
+FILE_PATTERNS          = *.hpp
+GENERATE_XML           = YES
+GENERATE_HTML          = NO
+GENERATE_TODOLIST      = NO
+INPUT                  = @DOXYGEN_INPUT_DIR@


=====================================
docs/_static/.gitignore
=====================================
@@ -0,0 +1,4 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore


=====================================
docs/conf.py.in
=====================================
@@ -0,0 +1,65 @@
+project = 'mcfp'
+copyright = '2023, Maarten L. Hekkelman'
+author = 'Maarten L. Hekkelman'
+release = '@PROJECT_VERSION@'
+
+# -- General configuration ---------------------------------------------------
+
+extensions = [
+    "breathe",
+    "exhale",
+    "myst_parser"
+]
+
+breathe_projects = {
+	"mcfp": "../build/docs/xml"
+}
+
+myst_enable_extensions = [ "colon_fence" ]
+breathe_default_project = "mcfp"
+
+# Setup the exhale extension
+exhale_args = {
+    # These arguments are required
+    "containmentFolder":     "./api",
+    "rootFileName":          "library_root.rst",
+    "doxygenStripFromPath":  "../include/",
+    # Heavily encouraged optional argument (see docs)
+    "rootFileTitle":         "API Reference",
+    # Suggested optional arguments
+    # "createTreeView":        True,
+    # TIP: if using the sphinx-bootstrap-theme, you need
+    # "treeViewIsBootstrap": True,
+    "exhaleExecutesDoxygen": False,
+    "contentsDirectives" : False,
+    
+    "verboseBuild": False
+}
+
+# Tell sphinx what the primary language being documented is.
+primary_domain = 'cpp'
+
+# Tell sphinx what the pygments highlight language should be.
+highlight_language = 'cpp'
+
+templates_path = ['_templates']
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_rtd_theme'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+html_theme_options = {
+}
+
+cpp_index_common_prefix = [
+	'mcfp::'
+]


=====================================
docs/genindex.rst
=====================================
@@ -0,0 +1,2 @@
+Index
+=====


=====================================
docs/index.rst
=====================================
@@ -0,0 +1,57 @@
+Introduction
+============
+
+This library attempts to implement the `POSIX.1-2017 <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html>`_ standard for parsing arguments passed to an application. These arguments are delivered to the main function as the well known argc and argv parameters. This library allows you to parse the contents of these variables and then provides easy access to the information. The library also contains code to parse configuration files 
+
+Options and Operands
+--------------------
+
+The POSIX standard defines two kinds of arguments, the ones whose name start with a hyphen are called *options* whereas the rest is called *operands*. An example is:
+
+.. code-block:: console
+
+	my-tool [-v] [-o option_arg] [operand...]
+
+The option **-o** in the example above has an *option-argument*, the **-v** does not. Operands usually follow the options, but in the case of libmcfp options and operands can be mixed.
+
+configuration files
+-------------------
+
+The syntax for configuration files is the usual format of *name* followed by an equals character and then a *value* terminated by an end of line. E.g.:
+
+.. code-block::
+
+	name = value
+
+The function :cpp:func:`~mcfp::config::parse_config_file` can be used to parse these files. The first variant of this function is noteworthy, it takes an *option* name and uses its *option-argument* if specified as replacement for the second parameter which holds the default configuration file name. This file is then searched in the list of directories in the third parameter and when found, the file is parsed and the options in the file are appended to the config instance. Options provided on the command line take precedence.
+
+Installation
+------------
+
+Use `CMake <https://cmake.org/>`_ to install **libmcfp**.
+
+.. code-block:: console
+
+   git clone https://github.com/mhekkel/libmcfp.git
+   cd libmcfp
+   cmake -S . -B build
+   cmake --build build
+   cmake --install build
+
+Synopsis
+--------
+
+.. include:: ../README.md
+  :parser: myst_parser.sphinx_
+  :start-after: ## Synopsis
+  :end-before: Installation
+  :tab-width: 4
+
+.. toctree::
+   :maxdepth: 2
+   :caption: Contents
+
+   self
+   api/library_root.rst
+   genindex.rst
+


=====================================
docs/requirements.in
=====================================
@@ -0,0 +1,5 @@
+sphinx<5
+exhale==0.3.6
+myst-parser
+breathe
+sphinx_rtd_theme==1.3.0


=====================================
docs/requirements.txt
=====================================
@@ -0,0 +1,93 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+#    pip-compile --output-file=requirements.txt requirements.in
+#
+alabaster==0.7.13
+    # via sphinx
+babel==2.12.1
+    # via sphinx
+beautifulsoup4==4.12.2
+    # via exhale
+breathe==4.35.0
+    # via
+    #   -r requirements.in
+    #   exhale
+certifi==2023.7.22
+    # via requests
+charset-normalizer==3.2.0
+    # via requests
+docutils==0.17.1
+    # via
+    #   breathe
+    #   exhale
+    #   myst-parser
+    #   sphinx
+    #   sphinx-rtd-theme
+exhale==0.3.6
+    # via -r requirements.in
+idna==3.4
+    # via requests
+imagesize==1.4.1
+    # via sphinx
+jinja2==3.1.2
+    # via
+    #   myst-parser
+    #   sphinx
+lxml==4.9.3
+    # via exhale
+markdown-it-py==2.2.0
+    # via
+    #   mdit-py-plugins
+    #   myst-parser
+markupsafe==2.1.3
+    # via jinja2
+mdit-py-plugins==0.3.5
+    # via myst-parser
+mdurl==0.1.2
+    # via markdown-it-py
+myst-parser==0.18.1
+    # via -r requirements.in
+packaging==23.1
+    # via sphinx
+pygments==2.16.1
+    # via sphinx
+pyyaml==6.0.1
+    # via myst-parser
+requests==2.31.0
+    # via sphinx
+six==1.16.0
+    # via exhale
+snowballstemmer==2.2.0
+    # via sphinx
+soupsieve==2.4.1
+    # via beautifulsoup4
+sphinx==4.5.0
+    # via
+    #   -r requirements.in
+    #   breathe
+    #   exhale
+    #   myst-parser
+    #   sphinx-rtd-theme
+    #   sphinxcontrib-jquery
+sphinx-rtd-theme==1.3.0
+    # via -r requirements.in
+sphinxcontrib-applehelp==1.0.4
+    # via sphinx
+sphinxcontrib-devhelp==1.0.2
+    # via sphinx
+sphinxcontrib-htmlhelp==2.0.1
+    # via sphinx
+sphinxcontrib-jquery==4.1
+    # via sphinx-rtd-theme
+sphinxcontrib-jsmath==1.0.1
+    # via sphinx
+sphinxcontrib-qthelp==1.0.3
+    # via sphinx
+sphinxcontrib-serializinghtml==1.1.5
+    # via sphinx
+typing-extensions==4.7.1
+    # via myst-parser
+urllib3==2.0.4
+    # via requests


=====================================
examples/CMakeLists.txt
=====================================
@@ -6,7 +6,7 @@ set(CXX_EXTENSIONS OFF)
 set(CMAKE_CXX_STANDARD 17 CACHE STRING "The minimum version of C++ required for this library")
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
-find_package(libmcfp REQUIRED)
+find_package(mcfp REQUIRED)
 
 add_executable(example example.cpp)
-target_link_libraries(example libmcfp::libmcfp)
+target_link_libraries(example mcfp::mcfp)


=====================================
include/mcfp/detail/charconv.hpp
=====================================
@@ -0,0 +1,269 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Maarten L. hekkelman
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <charconv>
+#include <cmath>
+#include <string>
+#include <vector>
+
+#if __has_include(<experimental/type_traits>)
+#include <experimental/type_traits>
+#else
+#include <type_traits>
+#endif
+
+namespace mcfp::detail
+{
+
+#if (not defined(__cpp_lib_experimental_detect) or (__cpp_lib_experimental_detect < 201505)) and (not defined(_LIBCPP_VERSION) or _LIBCPP_VERSION < 5000)
+// This code is copied from:
+// https://ld2015.scusa.lsu.edu/cppreference/en/cpp/experimental/is_detected.html
+
+template< class... >
+using void_t = void;
+
+namespace detail
+{
+	template <class Default, class AlwaysVoid,
+			template<class...> class Op, class... Args>
+	struct detector
+	{
+		using value_t = std::false_type;
+		using type = Default;
+	};
+	
+	template <class Default, template<class...> class Op, class... Args>
+	struct detector<Default, void_t<Op<Args...>>, Op, Args...> {
+		// Note that std::void_t is a c++17 feature
+		using value_t = std::true_type;
+		using type = Op<Args...>;
+	};
+} // namespace detail
+
+struct nonesuch
+{
+	nonesuch() = delete;
+	~nonesuch() = delete;
+	nonesuch(nonesuch const&) = delete;
+	void operator=(nonesuch const&) = delete;
+};
+
+template <template<class...> class Op, class... Args>
+using is_detected = typename detail::detector<nonesuch, void, Op, Args...>::value_t;
+
+template <template<class...> class Op, class... Args>
+constexpr inline bool is_detected_v = is_detected<Op,Args...>::value;
+
+template <template<class...> class Op, class... Args>
+using detected_t = typename detail::detector<nonesuch, void, Op, Args...>::type;
+
+template <class Default, template<class...> class Op, class... Args>
+using detected_or = detail::detector<Default, void, Op, Args...>;
+
+template <class Expected, template <class...> class Op, class... Args>
+using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;
+
+template <class Expected, template<class...> class Op, class... Args>
+constexpr inline bool is_detected_exact_v = is_detected_exact<Expected, Op, Args...>::value;
+
+#else
+
+template <template<class...> class Op, class... Args>
+constexpr inline bool is_detected_v = std::experimental::is_detected<Op,Args...>::value;
+
+#endif
+
+template <typename T>
+struct my_charconv
+{
+	using value_type = T;
+
+	static std::from_chars_result from_chars(const char *first, const char *last, value_type &value)
+	{
+		std::from_chars_result result{ first, {} };
+
+		enum State
+		{
+			IntegerSign,
+			Integer,
+			Fraction,
+			ExponentSign,
+			Exponent
+		} state = IntegerSign;
+		int sign = 1;
+		unsigned long long vi = 0;
+		long double f = 1;
+		int exponent_sign = 1;
+		int exponent = 0;
+		bool done = false;
+
+		while (not done and result.ec == std::errc())
+		{
+			char ch = result.ptr != last ? *result.ptr : 0;
+			++result.ptr;
+
+			switch (state)
+			{
+				case IntegerSign:
+					if (ch == '-')
+					{
+						sign = -1;
+						state = Integer;
+					}
+					else if (ch == '+')
+						state = Integer;
+					else if (ch >= '0' and ch <= '9')
+					{
+						vi = ch - '0';
+						state = Integer;
+					}
+					else if (ch == '.')
+						state = Fraction;
+					else
+						result.ec = std::errc::invalid_argument;
+					break;
+
+				case Integer:
+					if (ch >= '0' and ch <= '9')
+						vi = 10 * vi + (ch - '0');
+					else if (ch == 'e' or ch == 'E')
+						state = ExponentSign;
+					else if (ch == '.')
+						state = Fraction;
+					else
+					{
+						done = true;
+						--result.ptr;
+					}
+					break;
+
+				case Fraction:
+					if (ch >= '0' and ch <= '9')
+					{
+						vi = 10 * vi + (ch - '0');
+						f /= 10;
+					}
+					else if (ch == 'e' or ch == 'E')
+						state = ExponentSign;
+					else
+					{
+						done = true;
+						--result.ptr;
+					}
+					break;
+
+				case ExponentSign:
+					if (ch == '-')
+					{
+						exponent_sign = -1;
+						state = Exponent;
+					}
+					else if (ch == '+')
+						state = Exponent;
+					else if (ch >= '0' and ch <= '9')
+					{
+						exponent = ch - '0';
+						state = Exponent;
+					}
+					else
+						result.ec = std::errc::invalid_argument;
+					break;
+
+				case Exponent:
+					if (ch >= '0' and ch <= '9')
+						exponent = 10 * exponent + (ch - '0');
+					else
+					{
+						done = true;
+						--result.ptr;
+					}
+					break;
+			}
+		}
+
+		if (result.ec == std::errc())
+		{
+			long double v = f * vi * sign;
+			if (exponent != 0)
+				v *= std::pow(10, exponent * exponent_sign);
+
+			if (std::isnan(v))
+				result.ec = std::errc::invalid_argument;
+			else if (std::abs(v) > std::numeric_limits<value_type>::max())
+				result.ec = std::errc::result_out_of_range;
+
+			value = static_cast<value_type>(v);
+		}
+
+		return result;
+	}
+
+	template <typename Iterator, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
+	static std::to_chars_result to_chars(Iterator first, Iterator last, const T &value)
+	{
+		int size = last - first;
+		int r;
+
+		if constexpr (std::is_same_v<T, long double>)
+			r = snprintf(first, last - first, "%lg", value);
+		else
+			r = snprintf(first, last - first, "%g", value);
+
+		std::to_chars_result result;
+		if (r < 0 or r >= size)
+			result = { first, std::errc::value_too_large };
+		else
+			result = { first + r, std::errc() };
+
+		return result;
+	}
+};
+
+template <typename T>
+struct std_charconv
+{
+	static std::from_chars_result from_chars(const char *a, const char *b, T &d)
+	{
+		return std::from_chars(a, b, d);
+	}
+
+	template<typename Iterator>
+	static std::to_chars_result to_chars(Iterator a, Iterator b, const T &value)
+	{
+		return std::to_chars(a, b, value);
+	}
+};
+
+template <typename T>
+using from_chars_function = decltype(std::from_chars(std::declval<const char *>(), std::declval<const char *>(), std::declval<T &>()));
+
+template <typename T>
+using charconv = typename std::conditional_t<is_detected_v<from_chars_function, T>, std_charconv<T>, my_charconv<T>>;
+
+}
\ No newline at end of file


=====================================
include/mcfp/detail/options.hpp
=====================================
@@ -0,0 +1,345 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Maarten L. hekkelman
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <filesystem>
+#include <string>
+#include <type_traits>
+
+namespace mcfp::detail
+{
+
+// --------------------------------------------------------------------
+// Some template wizardry to detect containers, needed to have special
+// handling of options that can be repeated.
+
+template <typename T>
+using iterator_t = typename T::iterator;
+
+template <typename T>
+using value_type_t = typename T::value_type;
+
+template <typename T>
+using std_string_npos_t = decltype(T::npos);
+
+template <typename T, typename = void>
+struct is_container_type : std::false_type
+{
+};
+
+
+
+
+
+/**
+ * @brief Template to detect whether a type is a container
+ */
+
+
+
+template <typename T>
+struct is_container_type<T,
+	std::enable_if_t<
+		is_detected_v<value_type_t, T> and
+		is_detected_v<iterator_t, T> and
+		not is_detected_v<std_string_npos_t, T>>> : std::true_type
+{
+};
+
+template <typename T>
+inline constexpr bool is_container_type_v = is_container_type<T>::value;
+
+
+// --------------------------------------------------------------------
+// The options classes
+
+// The option traits classes are used to convert from the string-based
+// command line argument to the type that should be stored.
+// In fact, here is where the command line arguments are checked for
+// proper formatting.
+template <typename T, typename = void>
+struct option_traits;
+
+template <typename T>
+struct option_traits<T, typename std::enable_if_t<std::is_arithmetic_v<T>>>
+{
+	using value_type = T;
+
+	static value_type set_value(std::string_view argument, std::error_code &ec)
+	{
+		value_type value{};
+		auto r = charconv<value_type>::from_chars(argument.data(), argument.data() + argument.length(), value);
+		if (r.ec != std::errc())
+			ec = std::make_error_code(r.ec);
+		return value;
+	}
+
+	static std::string to_string(const T &value)
+	{
+		char b[32];
+		auto r = charconv<value_type>::to_chars(b, b + sizeof(b), value);
+		if (r.ec != std::errc())
+			throw std::system_error(std::make_error_code(r.ec));
+		return { b, r.ptr };
+	}
+};
+
+template <>
+struct option_traits<std::filesystem::path>
+{
+	using value_type = std::filesystem::path;
+
+	static value_type set_value(std::string_view argument, std::error_code & /*ec*/)
+	{
+		return value_type{ argument };
+	}
+
+	static std::string to_string(const std::filesystem::path &value)
+	{
+		return value.string();
+	}
+};
+
+template <typename T>
+struct option_traits<T, typename std::enable_if_t<not std::is_arithmetic_v<T> and std::is_assignable_v<std::string, T>>>
+{
+	using value_type = std::string;
+
+	static value_type set_value(std::string_view argument, std::error_code & /*ec*/)
+	{
+		return value_type{ argument };
+	}
+
+	static std::string to_string(const T &value)
+	{
+		return { value };
+	}
+};
+
+// The Options. The reason to have this weird constructing of
+// polymorphic options based on templates is to have a very
+// simple interface. The disadvantage is that the options have
+// to be copied during the construction of the config object.
+
+struct option_base
+{
+	std::string m_name;        ///< The long argument name
+	std::string m_desc;        ///< The description of the argument
+	char m_short_name;         ///< The single character name of the argument, can be zero
+	bool m_is_flag = true,     ///< When true, this option does not allow arguments
+		m_has_default = false, ///< When true, this option has a default value.
+		m_multi = false,       ///< When true, this option allows mulitple values.
+		m_hidden;              ///< When true, this option is hidden from the help text
+	int m_seen = 0;            ///< How often the option was seen on the command line
+
+	option_base(const option_base &rhs) = default;
+
+	option_base(std::string_view name, std::string_view desc, bool hidden)
+		: m_name(name)
+		, m_desc(desc)
+		, m_short_name(0)
+		, m_hidden(hidden)
+	{
+		if (m_name.length() == 1)
+			m_short_name = m_name.front();
+		else if (m_name.length() > 2 and m_name[m_name.length() - 2] == ',')
+		{
+			m_short_name = m_name.back();
+			m_name.erase(m_name.end() - 2, m_name.end());
+		}
+	}
+
+	virtual ~option_base() = default;
+
+	virtual void set_value(std::string_view /*value*/, std::error_code & /*ec*/)
+	{
+		assert(false);
+	}
+
+	virtual std::any get_value() const
+	{
+		return {};
+	}
+
+	virtual std::string get_default_value() const
+	{
+		return {};
+	}
+
+	size_t width() const
+	{
+		size_t result = m_name.length();
+		if (result <= 1)
+			result = 2;
+		else if (m_short_name != 0)
+			result += 7;
+		if (not m_is_flag)
+		{
+			result += 4;
+			if (m_has_default)
+				result += 4 + get_default_value().length();
+		}
+		return result + 6;
+	}
+
+	void write(std::ostream &os, size_t width) const
+	{
+		if (m_hidden) // quick exit
+			return;
+
+		size_t w2 = 2;
+		os << "  ";
+		if (m_short_name)
+		{
+			os << '-' << m_short_name;
+			w2 += 2;
+			if (m_name.length() > 1)
+			{
+				os << " [ --" << m_name << " ]";
+				w2 += 7 + m_name.length();
+			}
+		}
+		else
+		{
+			os << "--" << m_name;
+			w2 += 2 + m_name.length();
+		}
+
+		if (not m_is_flag)
+		{
+			os << " arg";
+			w2 += 4;
+
+			if (m_has_default)
+			{
+				auto default_value = get_default_value();
+				os << " (=" << default_value << ')';
+				w2 += 4 + default_value.length();
+			}
+		}
+
+		auto leading_spaces = width;
+		if (w2 + 2 > width)
+			os << std::endl;
+		else
+			leading_spaces = width - w2;
+
+		word_wrapper ww(m_desc, get_terminal_width() - width);
+		for (auto line : ww)
+		{
+			os << std::string(leading_spaces, ' ') << line << std::endl;
+			leading_spaces = width;
+		}
+	}
+};
+
+template <typename T>
+struct option : public option_base
+{
+	using traits_type = option_traits<T>;
+	using value_type = typename option_traits<T>::value_type;
+
+	std::optional<value_type> m_value;
+
+	option(const option &rhs) = default;
+
+	option(std::string_view name, std::string_view desc, bool hidden)
+		: option_base(name, desc, hidden)
+	{
+		m_is_flag = false;
+	}
+
+	option(std::string_view name, const value_type &default_value, std::string_view desc, bool hidden)
+		: option(name, desc, hidden)
+	{
+		m_has_default = true;
+		m_value = default_value;
+	}
+
+	void set_value(std::string_view argument, std::error_code &ec) override
+	{
+		m_value = traits_type::set_value(argument, ec);
+	}
+
+	std::any get_value() const override
+	{
+		std::any result;
+		if (m_value)
+			result = *m_value;
+		return result;
+	}
+
+	std::string get_default_value() const override
+	{
+		if constexpr (std::is_same_v<value_type, std::string>)
+			return *m_value;
+		else
+			return traits_type::to_string(*m_value);
+	}
+};
+
+template <typename T>
+struct multiple_option : public option_base
+{
+	using value_type = typename T::value_type;
+	using traits_type = option_traits<value_type>;
+
+	std::vector<value_type> m_values;
+
+	multiple_option(const multiple_option &rhs) = default;
+
+	multiple_option(std::string_view name, std::string_view desc, bool hidden)
+		: option_base(name, desc, hidden)
+	{
+		m_is_flag = false;
+		m_multi = true;
+	}
+
+	void set_value(std::string_view argument, std::error_code &ec) override
+	{
+		m_values.emplace_back(traits_type::set_value(argument, ec));
+	}
+
+	std::any get_value() const override
+	{
+		return { m_values };
+	}
+};
+
+template <>
+struct option<void> : public option_base
+{
+	option(const option &rhs) = default;
+
+	option(std::string_view name, std::string_view desc, bool hidden)
+		: option_base(name, desc, hidden)
+	{
+	}
+};
+
+} // namespace mcfp::detail
\ No newline at end of file


=====================================
include/mcfp/error.hpp
=====================================
@@ -0,0 +1,141 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Maarten L. Hekkelman
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+/**
+ * @file error.hpp
+ *
+ * Header file containing the error codes used by libmcfp
+ *
+ */
+
+#include <cassert>
+#include <string>
+#include <system_error>
+
+namespace mcfp
+{
+
+// we use the new system_error stuff.
+
+/**
+ * @enum config_error error.hpp mcfp/error.hpp
+ * 
+ * @brief A stronly typed class containing the error codes reported by @ref mcfp::config
+ */
+enum class config_error
+{
+	unknown_option = 1,              /**< The option requested does not exist, was not part of @ref mcfp::config::init. This error is returned by @ref mcfp::config::get */
+	option_does_not_accept_argument, /**< When parsing the command line arguments a value (argument) was specified for an option that should not have one */
+	missing_argument_for_option,     /**< A option without a required argument was found while parsing the command line arguments */
+	option_not_specified,            /**< There was not option found on the command line and no default argument was specified for the option passed in @ref mcfp::config::get */
+	invalid_config_file,             /**< The config file is not of the expected format */
+	wrong_type_cast,                 /**< An attempt was made to ask for an option in another type than used when registering this option in @ref mcfp::config::init */
+	config_file_not_found            /**< The specified config file was not found */
+};
+/**
+ * @brief The implementation for @ref config_category error messages
+ *
+ */
+class config_category_impl : public std::error_category
+{
+  public:
+	/**
+	 * @brief User friendly name
+	 *
+	 * @return const char*
+	 */
+
+	const char *name() const noexcept override
+	{
+		return "configuration";
+	}
+
+	/**
+	 * @brief Provide the error message as a string for the error code @a ev
+	 *
+	 * @param ev The error code
+	 * @return std::string
+	 */
+
+	std::string message(int ev) const override
+	{
+		switch (static_cast<config_error>(ev))
+		{
+			case config_error::unknown_option:
+				return "unknown option";
+			case config_error::option_does_not_accept_argument:
+				return "option does not accept argument";
+			case config_error::missing_argument_for_option:
+				return "missing argument for option";
+			case config_error::option_not_specified:
+				return "option was not specified";
+			case config_error::invalid_config_file:
+				return "config file contains a syntax error";
+			case config_error::wrong_type_cast:
+				return "the implementation contains a type cast error";
+			case config_error::config_file_not_found:
+				return "the specified config file was not found";
+			default:
+				assert(false);
+				return "unknown error code";
+		}
+	}
+
+	/**
+	 * @brief Return whether two error codes are equivalent, always false in this case
+	 *
+	 */
+
+	bool equivalent(const std::error_code & /*code*/, int /*condition*/) const noexcept override
+	{
+		return false;
+	}
+};
+
+/**
+ * @brief Return the implementation for the config_category
+ *
+ * @return std::error_category&
+ */
+inline std::error_category &config_category()
+{
+	static config_category_impl instance;
+	return instance;
+}
+
+inline std::error_code make_error_code(config_error e)
+{
+	return std::error_code(static_cast<int>(e), config_category());
+}
+
+inline std::error_condition make_error_condition(config_error e)
+{
+	return std::error_condition(static_cast<int>(e), config_category());
+}
+
+} // namespace mcfp
\ No newline at end of file


=====================================
include/mcfp/mcfp.hpp
=====================================
@@ -26,13 +26,14 @@
 
 #pragma once
 
-/// \file mcfp.hpp
+/// \file
 /// This header-only library contains code to parse argc/argv and store the
-/// values contained therein into a singleton object.
+/// values provided into a singleton object.
 
 #include <cassert>
 #include <cstring>
 
+#include <algorithm>
 #include <any>
 #include <charconv>
 #include <deque>
@@ -43,406 +44,67 @@
 #include <type_traits>
 #include <vector>
 
+#include <mcfp/error.hpp>
 #include <mcfp/text.hpp>
 #include <mcfp/utilities.hpp>
+#include <mcfp/detail/options.hpp>
 
 namespace mcfp
 {
 
-// we use the new system_error stuff.
-
-enum class config_error
-{
-	unknown_option = 1,
-	option_does_not_accept_argument,
-	missing_argument_for_option,
-	option_not_specified,
-	invalid_config_file,
-	wrong_type_cast,
-	config_file_not_found
-};
-
-class config_category_impl : public std::error_category
-{
-  public:
-	const char *name() const noexcept override
-	{
-		return "configuration";
-	}
-
-	std::string message(int ev) const override
-	{
-		switch (static_cast<config_error>(ev))
-		{
-			case config_error::unknown_option:
-				return "unknown option";
-			case config_error::option_does_not_accept_argument:
-				return "option does not accept argument";
-			case config_error::missing_argument_for_option:
-				return "missing argument for option";
-			case config_error::option_not_specified:
-				return "option was not specified";
-			case config_error::invalid_config_file:
-				return "config file contains a syntax error";
-			case config_error::wrong_type_cast:
-				return "the implementation contains a type cast error";
-			case config_error::config_file_not_found:
-				return "the specified config file was not found";
-			default:
-				assert(false);
-				return "unknown error code";
-		}
-	}
-
-	bool equivalent(const std::error_code &/*code*/, int /*condition*/) const noexcept override
-	{
-		return false;
-	}
-};
-
-inline std::error_category &config_category()
-{
-	static config_category_impl instance;
-	return instance;
-}
-
-inline std::error_code make_error_code(config_error e)
-{
-	return std::error_code(static_cast<int>(e), config_category());
-}
-
-inline std::error_condition make_error_condition(config_error e)
-{
-	return std::error_condition(static_cast<int>(e), config_category());
-}
-
-// --------------------------------------------------------------------
-// Some template wizardry to detect containers, needed to have special
-// handling of options that can be repeated.
-
-template <typename T>
-using iterator_t = typename T::iterator;
-
-template <typename T>
-using value_type_t = typename T::value_type;
-
-template <typename T>
-using std_string_npos_t = decltype(T::npos);
-
-template <typename T, typename = void>
-struct is_container_type : std::false_type
-{
-};
-
-template <typename T>
-struct is_container_type<T,
-	std::enable_if_t<
-		is_detected_v<value_type_t, T> and
-		is_detected_v<iterator_t, T> and
-		not is_detected_v<std_string_npos_t, T>>> : std::true_type
-{
-};
-
-template <typename T>
-inline constexpr bool is_container_type_v = is_container_type<T>::value;
-
-// --------------------------------------------------------------------
-// The options classes
-
-namespace detail
-{
-	// The option traits classes are used to convert from the string-based
-	// command line argument to the type that should be stored.
-	// In fact, here is where the command line arguments are checked for
-	// proper formatting.
-	template <typename T, typename = void>
-	struct option_traits;
-
-	template <typename T>
-	struct option_traits<T, typename std::enable_if_t<std::is_arithmetic_v<T>>>
-	{
-		using value_type = T;
-
-		static value_type set_value(std::string_view argument, std::error_code &ec)
-		{
-			value_type value{};
-			auto r = charconv<value_type>::from_chars(argument.data(), argument.data() + argument.length(), value);
-			if (r.ec != std::errc())
-				ec = std::make_error_code(r.ec);
-			return value;
-		}
-
-		static std::string to_string(const T &value)
-		{
-			char b[32];
-			auto r = charconv<value_type>::to_chars(b, b + sizeof(b), value);
-			if (r.ec != std::errc())
-				throw std::system_error(std::make_error_code(r.ec));
-			return { b, r.ptr };
-		}
-	};
-
-	template <>
-	struct option_traits<std::filesystem::path>
-	{
-		using value_type = std::filesystem::path;
-
-		static value_type set_value(std::string_view argument, std::error_code &/*ec*/)
-		{
-			return value_type{ argument };
-		}
-
-		static std::string to_string(const std::filesystem::path &value)
-		{
-			return value.string();
-		}
-	};
-
-	template <typename T>
-	struct option_traits<T, typename std::enable_if_t<not std::is_arithmetic_v<T> and std::is_assignable_v<std::string, T>>>
-	{
-		using value_type = std::string;
-
-		static value_type set_value(std::string_view argument, std::error_code &/*ec*/)
-		{
-			return value_type{ argument };
-		}
-
-		static std::string to_string(const T &value)
-		{
-			return { value };
-		}
-	};
-
-	// The Options. The reason to have this weird constructing of
-	// polymorphic options based on templates is to have a very
-	// simple interface. The disadvantage is that the options have
-	// to be copied during the construction of the config object.
-
-	struct option_base
-	{
-		std::string m_name;        ///< The long argument name
-		std::string m_desc;        ///< The description of the argument
-		char m_short_name;         ///< The single character name of the argument, can be zero
-		bool m_is_flag = true,     ///< When true, this option does not allow arguments
-			m_has_default = false, ///< When true, this option has a default value.
-			m_multi = false,       ///< When true, this option allows mulitple values.
-			m_hidden;              ///< When true, this option is hidden from the help text
-		int m_seen = 0;            ///< How often the option was seen on the command line
-
-		option_base(const option_base &rhs) = default;
-
-		option_base(std::string_view name, std::string_view desc, bool hidden)
-			: m_name(name)
-			, m_desc(desc)
-			, m_short_name(0)
-			, m_hidden(hidden)
-		{
-			if (m_name.length() == 1)
-				m_short_name = m_name.front();
-			else if (m_name.length() > 2 and m_name[m_name.length() - 2] == ',')
-			{
-				m_short_name = m_name.back();
-				m_name.erase(m_name.end() - 2, m_name.end());
-			}
-		}
-
-		virtual ~option_base() = default;
-
-		virtual void set_value(std::string_view /*value*/, std::error_code &/*ec*/)
-		{
-			assert(false);
-		}
-
-		virtual std::any get_value() const
-		{
-			return {};
-		}
-
-		virtual std::string get_default_value() const
-		{
-			return {};
-		}
-
-		size_t width() const
-		{
-			size_t result = m_name.length();
-			if (result <= 1)
-				result = 2;
-			else if (m_short_name != 0)
-				result += 7;
-			if (not m_is_flag)
-			{
-				result += 4;
-				if (m_has_default)
-					result += 4 + get_default_value().length();
-			}
-			return result + 6;
-		}
-
-		void write(std::ostream &os, size_t width) const
-		{
-			if (m_hidden) // quick exit
-				return;
-
-			size_t w2 = 2;
-			os << "  ";
-			if (m_short_name)
-			{
-				os << '-' << m_short_name;
-				w2 += 2;
-				if (m_name.length() > 1)
-				{
-					os << " [ --" << m_name << " ]";
-					w2 += 7 + m_name.length();
-				}
-			}
-			else
-			{
-				os << "--" << m_name;
-				w2 += 2 + m_name.length();
-			}
-
-			if (not m_is_flag)
-			{
-				os << " arg";
-				w2 += 4;
-
-				if (m_has_default)
-				{
-					auto default_value = get_default_value();
-					os << " (=" << default_value << ')';
-					w2 += 4 + default_value.length();
-				}
-			}
-
-			auto leading_spaces = width;
-			if (w2 + 2 > width)
-				os << std::endl;
-			else
-				leading_spaces = width - w2;
-
-			word_wrapper ww(m_desc, get_terminal_width() - width);
-			for (auto line : ww)
-			{
-				os << std::string(leading_spaces, ' ') << line << std::endl;
-				leading_spaces = width;
-			}
-		}
-	};
-
-	template <typename T>
-	struct option : public option_base
-	{
-		using traits_type = option_traits<T>;
-		using value_type = typename option_traits<T>::value_type;
-
-		std::optional<value_type> m_value;
-
-		option(const option &rhs) = default;
-
-		option(std::string_view name, std::string_view desc, bool hidden)
-			: option_base(name, desc, hidden)
-		{
-			m_is_flag = false;
-		}
-
-		option(std::string_view name, const value_type &default_value, std::string_view desc, bool hidden)
-			: option(name, desc, hidden)
-		{
-			m_has_default = true;
-			m_value = default_value;
-		}
-
-		void set_value(std::string_view argument, std::error_code &ec) override
-		{
-			m_value = traits_type::set_value(argument, ec);
-		}
-
-		std::any get_value() const override
-		{
-			std::any result;
-			if (m_value)
-				result = *m_value;
-			return result;
-		}
-
-		std::string get_default_value() const override
-		{
-			if constexpr (std::is_same_v<value_type, std::string>)
-				return *m_value;
-			else
-				return traits_type::to_string(*m_value);
-		}
-	};
-
-	template <typename T>
-	struct multiple_option : public option_base
-	{
-		using value_type = typename T::value_type;
-		using traits_type = option_traits<value_type>;
-
-		std::vector<value_type> m_values;
-
-		multiple_option(const multiple_option &rhs) = default;
-
-		multiple_option(std::string_view name, std::string_view desc, bool hidden)
-			: option_base(name, desc, hidden)
-		{
-			m_is_flag = false;
-			m_multi = true;
-		}
-
-		void set_value(std::string_view argument, std::error_code &ec) override
-		{
-			m_values.emplace_back(traits_type::set_value(argument, ec));
-		}
-
-		std::any get_value() const override
-		{
-			return { m_values };
-		}
-	};
-
-	template <>
-	struct option<void> : public option_base
-	{
-		option(const option &rhs) = default;
-
-		option(std::string_view name, std::string_view desc, bool hidden)
-			: option_base(name, desc, hidden)
-		{
-		}
-	};
-
-} // namespace detail
-
 // --------------------------------------------------------------------
-/// \brief A singleton class. Use config::instance to create an instance
+/**
+ * @brief A singleton class. Use @ref mcfp::config::instance to create and/or
+ * retrieve the single instance
+ * 
+ */
 
 class config
 {
-  public:
 	using option_base = detail::option_base;
 
+  public:
+
+	/**
+	 * @brief Set the 'usage' string
+	 * 
+	 * @param usage The usage message
+	 */
 	void set_usage(std::string_view usage)
 	{
 		m_usage = usage;
 	}
 
-	/// \brief Initialise a config instance with a \a usage message and a set of \a options
+	/**
+	 * @brief Initialise a config instance with a \a usage message and a set of \a options
+	 * 
+	 * @param usage The usage message
+	 * @param options Variadic list of options recognised by this config object, use mcfp::make_option and variants to create these
+	 */
 	template <typename... Options>
 	void init(std::string_view usage, Options... options)
 	{
 		m_usage = usage;
+		m_ignore_unknown = false;
 		m_impl.reset(new config_impl(std::forward<Options>(options)...));
 	}
 
+	/**
+	 * @brief Set the ignore unknown flag
+	 * 
+	 * @param ignore_unknown When true, unknown options are simply ignored instead of
+	 * throwing an error
+	 */
 	void set_ignore_unknown(bool ignore_unknown)
 	{
 		m_ignore_unknown = ignore_unknown;
 	}
 
+	/**
+	 * @brief Use this to retrieve the single instance of this class
+	 * 
+	 * @return config& The singleton instance
+	 */
 	static config &instance()
 	{
 		static std::unique_ptr<config> s_instance;
@@ -451,18 +113,39 @@ class config
 		return *s_instance;
 	}
 
+	/**
+	 * @brief Simply return true if the option with \a name has a value assigned
+	 * 
+	 * @param name The name of the option
+	 * @return bool Returns true when the option has a value
+	 */
 	bool has(std::string_view name) const
 	{
 		auto opt = m_impl->get_option(name);
 		return opt != nullptr and (opt->m_seen > 0 or opt->m_has_default);
 	}
 
+	/**
+	 * @brief Return how often an option with the name \a name was seen.
+	 * Use e.g. to increase verbosity level
+	 * 
+	 * @param name The name of the option to check
+	 * @return int The count for the named option
+	 */
 	int count(std::string_view name) const
 	{
 		auto opt = m_impl->get_option(name);
 		return opt ? opt->m_seen : 0;
 	}
 
+	/**
+	 * @brief Returns the value for the option with name \a name. Throws
+	 * an exception if the option has not value assigned
+	 * 
+	 * @tparam T The type of the value requested.
+	 * @param name The name of the option requested
+	 * @return auto The value of the named option
+	 */
 	template <typename T>
 	auto get(std::string_view name) const
 	{
@@ -477,6 +160,16 @@ class config
 		return result;
 	}
 
+	/**
+	 * @brief Returns the value for the option with name \a name. If
+	 * the option has no value assigned or is of a wrong type,
+	 * ec is set to an appropriate error
+	 * 
+	 * @tparam T The type of the value requested.
+	 * @param name The name of the option requested
+	 * @param ec The error status is returned in this variable
+	 * @return auto The value of the named option
+	 */
 	template <typename T>
 	auto get(std::string_view name, std::error_code &ec) const
 	{
@@ -509,11 +202,53 @@ class config
 		return result;
 	}
 
+	/**
+	 * @brief Return the std::string value of the option with name \a name
+	 * If no value was assigned, or the type of the option cannot be casted
+	 * to a string, an exception is thrown.
+	 * 
+	 * @param name The name of the option value requested
+	 * @return std::string The value of the option
+	 */
+	std::string get(std::string_view name) const
+	{
+		return get<std::string>(name);
+	}
+
+	/**
+	 * @brief Return the std::string value of the option with name \a name
+	 * If no value was assigned, or the type of the option cannot be casted
+	 * to a string, an error is returned in \a ec.
+	 * 
+	 * @param name The name of the option value requested
+	 * @param ec The error status is returned in this variable
+	 * @return std::string The value of the option
+	 */
+	std::string get(std::string_view name, std::error_code &ec) const
+	{
+		return get<std::string>(name, ec);
+	}
+
+	/**
+	 * @brief Return the list of operands.
+	 * 
+	 * @return const std::vector<std::string>& The operand as a vector of strings
+	 */
 	const std::vector<std::string> &operands() const
 	{
 		return m_impl->m_operands;
 	}
 
+	/**
+	 * @brief Write the configuration to the std::ostream \a os
+	 * This will print the usage string and each of the configured
+	 * options along with their optional default value as well as
+	 * their help string
+	 * 
+	 * @param os The std::ostream to write to, usually std::cout or std::cerr
+	 * @param conf The config object to write out
+	 * @return std::ostream& Returns the parameter \a os
+	 */
 	friend std::ostream &operator<<(std::ostream &os, const config &conf)
 	{
 		size_t terminal_width = get_terminal_width();
@@ -533,6 +268,13 @@ class config
 
 	// --------------------------------------------------------------------
 
+	/**
+	 * @brief Parse the \a argv vector containing \a argc elements. Throws
+	 * an exception if any error was found
+	 * 
+	 * @param argc The number of elements in \a argv
+	 * @param argv The vector of command line arguments
+	 */
 	void parse(int argc, const char *const argv[])
 	{
 		std::error_code ec;
@@ -541,6 +283,17 @@ class config
 			throw std::system_error(ec);
 	}
 
+	/**
+	 * @brief Parse a configuration file called \a config_file_name optionally
+	 * specified on the command line with option \a config_option
+	 * The file is searched for in each of the directories specified in \a search_dirs
+	 * This function throws an exception if an error was found during processing
+	 * 
+	 * @param config_option The name of the option used to specify the config file
+	 * @param config_file_name The default name of the option file to use if the config
+	 * option was not specified on the command line
+	 * @param search_dirs The list of directories to search for the config file
+	 */
 	void parse_config_file(std::string_view config_option, std::string_view config_file_name,
 		std::initializer_list<std::string_view> search_dirs)
 	{
@@ -550,6 +303,18 @@ class config
 			throw std::system_error(ec);
 	}
 
+	/**
+	 * @brief Parse a configuration file called \a config_file_name optionally
+	 * specified on the command line with option \a config_option
+	 * The file is searched for in each of the directories specified in \a search_dirs
+	 * If an error is found it is returned in the variable \a ec
+	 * 
+	 * @param config_option The name of the option used to specify the config file
+	 * @param config_file_name The default name of the option file to use if the config
+	 * option was not specified on the command line
+	 * @param search_dirs The list of directories to search for the config file
+	 * @param ec The variable containing the error status
+	 */
 	void parse_config_file(std::string_view config_option, std::string_view config_file_name,
 		std::initializer_list<std::string_view> search_dirs, std::error_code &ec)
 	{
@@ -575,6 +340,13 @@ class config
 			ec = make_error_code(config_error::config_file_not_found);
 	}
 
+	/**
+	 * @brief Parse a configuration file specified by \a file
+	 * If an error is found it is returned in the variable \a ec
+	 * 
+	 * @param file The path to the config file
+	 * @param ec The variable containing the error status
+	 */
 	void parse_config_file(const std::filesystem::path &file, std::error_code &ec)
 	{
 		std::ifstream is(file);
@@ -582,6 +354,8 @@ class config
 			parse_config_file(is, ec);
 	}
 
+  private:
+
 	static bool is_name_char(int ch)
 	{
 		return std::isalnum(ch) or ch == '_' or ch == '-';
@@ -592,6 +366,15 @@ class config
 		return ch == '\n' or ch == '\r' or ch == std::char_traits<char>::eof();
 	}
 
+  public:
+
+	/**
+	 * @brief Parse the configuration file in \a is
+	 * If an error is found it is returned in the variable \a ec
+	 * 
+	 * @param is A std::istream for the contents of a config file
+	 * @param ec The variable containing the error status
+	 */
 	void parse_config_file(std::istream &is, std::error_code &ec)
 	{
 		auto &buffer = *is.rdbuf();
@@ -621,7 +404,7 @@ class config
 						value.clear();
 						state = State::NAME;
 					}
-					else if (ch == '#')
+					else if (ch == '#' or ch == ';')
 						state = State::COMMENT;
 					else if (ch != ' ' and ch != '\t' and not is_eoln(ch))
 						ec = make_error_code(config_error::invalid_config_file);
@@ -717,6 +500,14 @@ class config
 		}
 	}
 
+	/**
+	 * @brief Parse the \a argv vector containing \a argc elements.
+	 * In case of an error, the error is returned in \a ec
+	 * 
+	 * @param argc The number of elements in \a argv
+	 * @param argv The vector of command line arguments
+	 * @param ec The variable receiving the error status
+	 */
 	void parse(int argc, const char *const argv[], std::error_code &ec)
 	{
 		using namespace std::literals;
@@ -841,6 +632,8 @@ class config
 	config(const config &) = delete;
 	config &operator=(const config &) = delete;
 
+	/// @cond
+
 	struct config_impl_base
 	{
 		virtual ~config_impl_base() = default;
@@ -920,44 +713,113 @@ class config
 	std::unique_ptr<config_impl_base> m_impl;
 	bool m_ignore_unknown = false;
 	std::string m_usage;
+
+	/// @endcond
 };
 
 // --------------------------------------------------------------------
 
-template <typename T = void, std::enable_if_t<not is_container_type_v<T>, int> = 0>
+/**
+ * @brief Create an option with name \a name and without a default value.
+ * If \a T is void the option does not expect a value and is in fact a flag. 
+ * 
+ * If the type of \a T is a container (std::vector e.g.) the option can be
+ * specified multiple times on the command line. 
+ * 
+ * The name \a name may end with a comma and a single character. This last
+ * character will then be the short version whereas the leading characters
+ * make up the long version.
+ * 
+ * @tparam T The type of the option
+ * @param name The name of the option
+ * @param description The help text for this option
+ * @return auto The option object created
+ */
+template <typename T = void, std::enable_if_t<not detail::is_container_type_v<T>, int> = 0>
 auto make_option(std::string_view name, std::string_view description)
 {
 	return detail::option<T>(name, description, false);
 }
 
-template <typename T = void, std::enable_if_t<not is_container_type_v<T>, int> = 0>
-auto make_hidden_option(std::string_view name, std::string_view description)
+template <typename T, std::enable_if_t<detail::is_container_type_v<T>, int> = 0>
+auto make_option(std::string_view name, std::string_view description)
 {
-	return detail::option<T>(name, description, true);
+	return detail::multiple_option<T>(name, description, false);
 }
 
-template <typename T, std::enable_if_t<not is_container_type_v<T>, int> = 0>
+/**
+ * @brief Create an option with name \a name and with a default value \a v.
+ * 
+ * If the type of \a T is a container (std::vector e.g.) the option can be
+ * specified multiple times on the command line. 
+ * 
+ * The name \a name may end with a comma and a single character. This last
+ * character will then be the short version whereas the leading characters
+ * make up the long version.
+ * 
+ * @tparam T The type of the option
+ * @param name The name of the option
+ * @param v The default value to use
+ * @param description The help text for this option
+ * @return auto The option object created
+ */
+template <typename T, std::enable_if_t<not detail::is_container_type_v<T>, int> = 0>
 auto make_option(std::string_view name, const T &v, std::string_view description)
 {
 	return detail::option<T>(name, v, description, false);
 }
 
-template <typename T, std::enable_if_t<not is_container_type_v<T>, int> = 0>
-auto make_hidden_option(std::string_view name, const T &v, std::string_view description)
+/**
+ * @brief Create an option with name \a name and without a default value.
+ * If \a T is void the option does not expect a value and is in fact a flag. 
+ * This option will not be shown in the help / usage output.
+ * 
+ * If the type of \a T is a container (std::vector e.g.) the option can be
+ * specified multiple times on the command line. 
+ * 
+ * The name \a name may end with a comma and a single character. This last
+ * character will then be the short version whereas the leading characters
+ * make up the long version.
+ * 
+ * @tparam T The type of the option
+ * @param name The name of the option
+ * @param description The help text for this option
+ * @return auto The option object created
+ */
+template <typename T = void, std::enable_if_t<not detail::is_container_type_v<T>, int> = 0>
+auto make_hidden_option(std::string_view name, std::string_view description)
 {
-	return detail::option<T>(name, v, description, true);
+	return detail::option<T>(name, description, true);
 }
 
-template <typename T, std::enable_if_t<is_container_type_v<T>, int> = 0>
-auto make_option(std::string_view name, std::string_view description)
+template <typename T, std::enable_if_t<detail::is_container_type_v<T>, int> = 0>
+auto make_hidden_option(std::string_view name, std::string_view description)
 {
-	return detail::multiple_option<T>(name, description, false);
+	return detail::multiple_option<T>(name, description, true);
 }
 
-template <typename T, std::enable_if_t<is_container_type_v<T>, int> = 0>
-auto make_hidden_option(std::string_view name, std::string_view description)
+/**
+ * @brief Create an option with name \a name and with default value \a v.
+ * If \a T is void the option does not expect a value and is in fact a flag. 
+ * This option will not be shown in the help / usage output.
+ * 
+ * If the type of \a T is a container (std::vector e.g.) the option can be
+ * specified multiple times on the command line. 
+ * 
+ * The name \a name may end with a comma and a single character. This last
+ * character will then be the short version whereas the leading characters
+ * make up the long version.
+ * 
+ * @tparam T The type of the option
+ * @param name The name of the option
+ * @param v The default value to use
+ * @param description The help text for this option
+ * @return auto The option object created
+ */
+template <typename T, std::enable_if_t<not detail::is_container_type_v<T>, int> = 0>
+auto make_hidden_option(std::string_view name, const T &v, std::string_view description)
 {
-	return detail::option<T>(name, description, true);
+	return detail::option<T>(name, v, description, true);
 }
 
 } // namespace mcfp
@@ -971,4 +833,4 @@ struct is_error_condition_enum<mcfp::config_error>
 {
 };
 
-} // namespace std
\ No newline at end of file
+} // namespace std


=====================================
include/mcfp/text.hpp
=====================================
@@ -26,245 +26,30 @@
 
 #pragma once
 
-#include <algorithm>
-#include <charconv>
-#include <cmath>
-#include <string>
-#include <vector>
-
-#if __has_include(<experimental/type_traits>)
-#include <experimental/type_traits>
-#else
-#include <type_traits>
-#endif
-
-namespace mcfp
-{
-
-#if (not defined(__cpp_lib_experimental_detect) or (__cpp_lib_experimental_detect < 201505)) and (not defined(_LIBCPP_VERSION) or _LIBCPP_VERSION < 5000)
-// This code is copied from:
-// https://ld2015.scusa.lsu.edu/cppreference/en/cpp/experimental/is_detected.html
+/**
+ * @file text.hpp
+ * This file contains an implementation of charconv and of work wrapping code
+ */
 
-template< class... >
-using void_t = void;
+#include <mcfp/detail/charconv.hpp>
 
-namespace detail
-{
-	template <class Default, class AlwaysVoid,
-			template<class...> class Op, class... Args>
-	struct detector
-	{
-		using value_t = std::false_type;
-		using type = Default;
-	};
-	
-	template <class Default, template<class...> class Op, class... Args>
-	struct detector<Default, void_t<Op<Args...>>, Op, Args...> {
-		// Note that std::void_t is a c++17 feature
-		using value_t = std::true_type;
-		using type = Op<Args...>;
-	};
-} // namespace detail
-
-struct nonesuch
+namespace mcfp
 {
-	nonesuch() = delete;
-	~nonesuch() = delete;
-	nonesuch(nonesuch const&) = delete;
-	void operator=(nonesuch const&) = delete;
-};
-
-template <template<class...> class Op, class... Args>
-using is_detected = typename detail::detector<nonesuch, void, Op, Args...>::value_t;
-
-template <template<class...> class Op, class... Args>
-constexpr inline bool is_detected_v = is_detected<Op,Args...>::value;
-
-template <template<class...> class Op, class... Args>
-using detected_t = typename detail::detector<nonesuch, void, Op, Args...>::type;
-
-template <class Default, template<class...> class Op, class... Args>
-using detected_or = detail::detector<Default, void, Op, Args...>;
-
-template <class Expected, template <class...> class Op, class... Args>
-using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;
-
-template <class Expected, template<class...> class Op, class... Args>
-constexpr inline bool is_detected_exact_v = is_detected_exact<Expected, Op, Args...>::value;
 
-#else
-
-template <template<class...> class Op, class... Args>
-constexpr inline bool is_detected_v = std::experimental::is_detected<Op,Args...>::value;
-
-#endif
+/**	Import of the private implementation of charconv which maybe
+ * resolved to the std::charconv implementation or a private one
+ * defined in the detail namespace.
+*/
 
 template <typename T>
-struct my_charconv
-{
-	using value_type = T;
-
-	static std::from_chars_result from_chars(const char *first, const char *last, value_type &value)
-	{
-		std::from_chars_result result{ first, {} };
-
-		enum State
-		{
-			IntegerSign,
-			Integer,
-			Fraction,
-			ExponentSign,
-			Exponent
-		} state = IntegerSign;
-		int sign = 1;
-		unsigned long long vi = 0;
-		long double f = 1;
-		int exponent_sign = 1;
-		int exponent = 0;
-		bool done = false;
-
-		while (not done and result.ec == std::errc())
-		{
-			char ch = result.ptr != last ? *result.ptr : 0;
-			++result.ptr;
-
-			switch (state)
-			{
-				case IntegerSign:
-					if (ch == '-')
-					{
-						sign = -1;
-						state = Integer;
-					}
-					else if (ch == '+')
-						state = Integer;
-					else if (ch >= '0' and ch <= '9')
-					{
-						vi = ch - '0';
-						state = Integer;
-					}
-					else if (ch == '.')
-						state = Fraction;
-					else
-						result.ec = std::errc::invalid_argument;
-					break;
-
-				case Integer:
-					if (ch >= '0' and ch <= '9')
-						vi = 10 * vi + (ch - '0');
-					else if (ch == 'e' or ch == 'E')
-						state = ExponentSign;
-					else if (ch == '.')
-						state = Fraction;
-					else
-					{
-						done = true;
-						--result.ptr;
-					}
-					break;
-
-				case Fraction:
-					if (ch >= '0' and ch <= '9')
-					{
-						vi = 10 * vi + (ch - '0');
-						f /= 10;
-					}
-					else if (ch == 'e' or ch == 'E')
-						state = ExponentSign;
-					else
-					{
-						done = true;
-						--result.ptr;
-					}
-					break;
-
-				case ExponentSign:
-					if (ch == '-')
-					{
-						exponent_sign = -1;
-						state = Exponent;
-					}
-					else if (ch == '+')
-						state = Exponent;
-					else if (ch >= '0' and ch <= '9')
-					{
-						exponent = ch - '0';
-						state = Exponent;
-					}
-					else
-						result.ec = std::errc::invalid_argument;
-					break;
+using charconv = typename detail::charconv<T>;
 
-				case Exponent:
-					if (ch >= '0' and ch <= '9')
-						exponent = 10 * exponent + (ch - '0');
-					else
-					{
-						done = true;
-						--result.ptr;
-					}
-					break;
-			}
-		}
-
-		if (result.ec == std::errc())
-		{
-			long double v = f * vi * sign;
-			if (exponent != 0)
-				v *= std::pow(10, exponent * exponent_sign);
-
-			if (std::isnan(v))
-				result.ec = std::errc::invalid_argument;
-			else if (std::abs(v) > std::numeric_limits<value_type>::max())
-				result.ec = std::errc::result_out_of_range;
-
-			value = static_cast<value_type>(v);
-		}
-
-		return result;
-	}
-
-	template <typename Iterator, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
-	static std::to_chars_result to_chars(Iterator first, Iterator last, const T &value)
-	{
-		int size = last - first;
-		int r;
-
-		if constexpr (std::is_same_v<T, long double>)
-			r = snprintf(first, last - first, "%lg", value);
-		else
-			r = snprintf(first, last - first, "%g", value);
-
-		std::to_chars_result result;
-		if (r < 0 or r >= size)
-			result = { first, std::errc::value_too_large };
-		else
-			result = { first + r, std::errc() };
-
-		return result;
-	}
-};
-
-template <typename T>
-struct std_charconv
-{
-	static std::from_chars_result from_chars(const char *a, const char *b, T &d)
-	{
-		return std::from_chars(a, b, d);
-	}
-
-	template<typename Iterator>
-	static std::to_chars_result to_chars(Iterator a, Iterator b, const T &value)
-	{
-		return std::to_chars(a, b, value);
-	}
-};
-
-template <typename T>
-using from_chars_function = decltype(std::from_chars(std::declval<const char *>(), std::declval<const char *>(), std::declval<T &>()));
+/** Use the private implementation of is_detected_v which maybe
+ * resolved to the std::is_detected_v template when available
+ */
 
-template <typename T>
-using charconv = typename std::conditional_t<is_detected_v<from_chars_function, T>, std_charconv<T>, my_charconv<T>>;
+template <template<class...> class Op, class... Args>
+constexpr inline bool is_detected_v = detail::is_detected_v<Op,Args...>;
 
 // --------------------------------------------------------------------
 /// Simplified line breaking code taken from a decent text editor.


=====================================
include/mcfp/utilities.hpp
=====================================
@@ -33,14 +33,14 @@
 #include <sys/ioctl.h>
 #include <fcntl.h>
 #include <unistd.h>
-#elif defined(_MSC_VER)
+#elif defined(_WIN32)
 #include <windows.h>
 #endif
 
 namespace mcfp
 {
 
-#if defined(_MSC_VER)
+#if defined(_WIN32)
 /// @brief Get the width in columns of the current terminal
 /// @return number of columns of the terminal
 inline uint32_t get_terminal_width()
@@ -73,4 +73,4 @@ inline uint32_t get_terminal_width()
 }
 #endif
 
-} // namespace mcfp
\ No newline at end of file
+} // namespace mcfp


=====================================
test/CMakeLists.txt
=====================================
@@ -0,0 +1,35 @@
+
+if(NOT(Catch2_FOUND OR TARGET Catch2))
+	find_package(Catch2 QUIET)
+
+	if(NOT Catch2_FOUND)
+		include(FetchContent)
+
+		FetchContent_Declare(
+			Catch2
+			GIT_REPOSITORY https://github.com/catchorg/Catch2.git
+			GIT_TAG v2.13.9)
+
+		FetchContent_MakeAvailable(Catch2)
+
+		set(Catch2_VERSION "2.13.9")
+	endif()
+endif()
+
+add_executable(mcfp-unit-test ${CMAKE_CURRENT_SOURCE_DIR}/unit-test.cpp)
+
+target_link_libraries(mcfp-unit-test mcfp::mcfp Catch2::Catch2)
+
+if(${Catch2_VERSION} VERSION_GREATER_EQUAL 3.0.0)
+	target_compile_definitions(mcfp-unit-test PUBLIC CATCH22=0)
+else()
+	target_compile_definitions(mcfp-unit-test PUBLIC CATCH22=1)
+endif()
+
+if(MSVC)
+	# Specify unwind semantics so that MSVC knowns how to handle exceptions
+	target_compile_options(mcfp-unit-test PRIVATE /EHsc)
+endif()
+
+add_test(NAME mcfp-unit-test
+	COMMAND $<TARGET_FILE:mcfp-unit-test> --data-dir ${CMAKE_CURRENT_SOURCE_DIR})


=====================================
test/unit-test.conf
=====================================
@@ -1,3 +1,4 @@
 # A simple test config file
 aap = 2
-noot = 3
\ No newline at end of file
+noot = 3
+; With some comments


=====================================
test/unit-test.cpp
=====================================
@@ -24,33 +24,53 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#define BOOST_TEST_ALTERNATIVE_INIT_API
-#include <boost/test/included/unit_test.hpp>
+#define CATCH_CONFIG_RUNNER
+
+#if CATCH22
+# include <catch2/catch.hpp>
+#else
+# include <catch2/catch_all.hpp>
+#endif
 
 #include <filesystem>
 
 #include <mcfp/mcfp.hpp>
 
-namespace tt = boost::test_tools;
-namespace utf = boost::unit_test;
 namespace fs = std::filesystem;
 
-fs::path gTestDir = fs::current_path();
-
-// --------------------------------------------------------------------
+std::filesystem::path gTestDir = std::filesystem::current_path();
 
-bool init_unit_test()
+int main(int argc, char *argv[])
 {
-	// not a test, just initialize test dir
-	if (boost::unit_test::framework::master_test_suite().argc == 2)
-		gTestDir = boost::unit_test::framework::master_test_suite().argv[1];
-
-	return true;
+	Catch::Session session; // There must be exactly one instance
+
+	// Build a new parser on top of Catch2's
+#if CATCH22
+	using namespace Catch::clara;
+#else
+	// Build a new parser on top of Catch2's
+	using namespace Catch::Clara;
+#endif
+
+	auto cli = session.cli()                                // Get Catch2's command line parser
+	           | Opt(gTestDir, "data-dir")                  // bind variable to a new option, with a hint string
+	                 ["-D"]["--data-dir"]                   // the option names it will respond to
+	           ("The directory containing the data files"); // description string for the help output
+
+	// Now pass the new composite back to Catch2 so it uses that
+	session.cli(cli);
+
+	// Let Catch2 (using Clara) parse the command line
+	int returnCode = session.applyCommandLine(argc, argv);
+	if (returnCode != 0) // Indicates a command line error
+		return returnCode;
+
+	return session.run();
 }
 
 // --------------------------------------------------------------------
 
-BOOST_AUTO_TEST_CASE(t_1, * utf::tolerance(0.001))
+TEST_CASE("t_1, * utf::tolerance(0.001)")
 {
 	int argc = 3;
 	const char *const argv[] = {
@@ -69,19 +89,19 @@ BOOST_AUTO_TEST_CASE(t_1, * utf::tolerance(0.001))
 	
 	config.parse(argc, argv);
 
-	BOOST_CHECK(config.has("flag"));
-	BOOST_CHECK(not config.has("flag2"));
+	CHECK(config.has("flag"));
+	CHECK(not config.has("flag2"));
 
-	BOOST_CHECK_EQUAL(config.get<int>("param_int_2"), 1);
-	BOOST_CHECK_THROW(config.get<float>("param_int_2"), std::system_error);
-	BOOST_CHECK_THROW(config.get<int>("param_int"), std::system_error);
+	CHECK(config.get<int>("param_int_2") == 1);
+	CHECK_THROWS_AS(config.get<float>("param_int_2"), std::system_error);
+	CHECK_THROWS_AS(config.get<int>("param_int"), std::system_error);
 
-	BOOST_TEST(config.get<float>("param_float_2") == 3.14);
-	BOOST_CHECK_THROW(config.get<int>("param_float_2"), std::system_error);
-	BOOST_CHECK_THROW(config.get<float>("param_float"), std::system_error);
+	CHECK(std::to_string(config.get<float>("param_float_2")) == std::to_string(3.14));
+	CHECK_THROWS_AS(config.get<int>("param_float_2"), std::system_error);
+	CHECK_THROWS_AS(config.get<float>("param_float"), std::system_error);
 }
 
-BOOST_AUTO_TEST_CASE(t_2)
+TEST_CASE("t_2")
 {
 	int argc = 3;
 	const char *const argv[] = {
@@ -96,10 +116,10 @@ BOOST_AUTO_TEST_CASE(t_2)
 	
 	config.parse(argc, argv);
 
-	BOOST_CHECK_EQUAL(config.count("verbose"), 5);
+	CHECK(config.count("verbose") == 5);
 }
 
-BOOST_AUTO_TEST_CASE(t_3)
+TEST_CASE("t_3")
 {
 	int argc = 2;
 	const char *const argv[] = {
@@ -114,11 +134,10 @@ BOOST_AUTO_TEST_CASE(t_3)
 	
 	config.parse(argc, argv);
 
-	BOOST_CHECK(config.has("param_int"));
-	BOOST_CHECK_EQUAL(config.get<int>("param_int"), 42);
+	CHECK(config.has("param_int"));
+	CHECK(config.get<int>("param_int") == 42);
 }
-
-BOOST_AUTO_TEST_CASE(t_4)
+TEST_CASE("t_4")
 {
 	int argc = 3;
 	const char *const argv[] = {
@@ -133,11 +152,11 @@ BOOST_AUTO_TEST_CASE(t_4)
 	
 	config.parse(argc, argv);
 
-	BOOST_CHECK(config.has("param_int"));
-	BOOST_CHECK_EQUAL(config.get<int>("param_int"), 42);
+	CHECK(config.has("param_int"));
+	CHECK(config.get<int>("param_int") == 42);
 }
 
-BOOST_AUTO_TEST_CASE(t_5)
+TEST_CASE("t_5")
 {
 	const char *const argv[] = {
 		"test", "-i", "42", "-j43", nullptr
@@ -153,14 +172,14 @@ BOOST_AUTO_TEST_CASE(t_5)
 	
 	config.parse(argc, argv);
 
-	BOOST_CHECK(config.has("nr1"));
-	BOOST_CHECK(config.has("nr2"));
+	CHECK(config.has("nr1"));
+	CHECK(config.has("nr2"));
 
-	BOOST_CHECK_EQUAL(config.get<int>("nr1"), 42);
-	BOOST_CHECK_EQUAL(config.get<int>("nr2"), 43);
+	CHECK(config.get<int>("nr1") == 42);
+	CHECK(config.get<int>("nr2") == 43);
 }
 
-BOOST_AUTO_TEST_CASE(t_6)
+TEST_CASE("t_6")
 {
 	const char *const argv[] = {
 		"test", "-i", "42", "-j43", "foo", "bar", nullptr
@@ -176,18 +195,18 @@ BOOST_AUTO_TEST_CASE(t_6)
 	
 	config.parse(argc, argv);
 
-	BOOST_CHECK(config.has("nr1"));
-	BOOST_CHECK(config.has("nr2"));
+	CHECK(config.has("nr1"));
+	CHECK(config.has("nr2"));
 
-	BOOST_CHECK_EQUAL(config.get<int>("nr1"), 42);
-	BOOST_CHECK_EQUAL(config.get<int>("nr2"), 43);
+	CHECK(config.get<int>("nr1") == 42);
+	CHECK(config.get<int>("nr2") == 43);
 
-	BOOST_CHECK_EQUAL(config.operands().size(), 2);
-	BOOST_CHECK_EQUAL(config.operands().front(), "foo");
-	BOOST_CHECK_EQUAL(config.operands().back(), "bar");
+	CHECK(config.operands().size() == 2);
+	CHECK(config.operands().front() == "foo");
+	CHECK(config.operands().back() == "bar");
 }
 
-BOOST_AUTO_TEST_CASE(t_7)
+TEST_CASE("t_7")
 {
 	const char *const argv[] = {
 		"test", "--", "-i", "42", "-j43", "foo", "bar", nullptr
@@ -203,16 +222,16 @@ BOOST_AUTO_TEST_CASE(t_7)
 	
 	config.parse(argc, argv);
 
-	BOOST_CHECK(not config.has("nr1"));
-	BOOST_CHECK(not config.has("nr2"));
+	CHECK(not config.has("nr1"));
+	CHECK(not config.has("nr2"));
 
-	BOOST_CHECK_EQUAL(config.operands().size(), 5);
+	CHECK(config.operands().size() == 5);
 
 	auto compare = std::vector<std::string>{ argv[2], argv[3], argv[4], argv[5], argv[6] };
-	BOOST_CHECK(config.operands() == compare);
+	CHECK(config.operands() == compare);
 }
 
-BOOST_AUTO_TEST_CASE(t_8)
+TEST_CASE("t_8")
 {
 	const char *const argv[] = {
 		"test", "-i", "foo", "-jbar", nullptr
@@ -229,16 +248,16 @@ BOOST_AUTO_TEST_CASE(t_8)
 	
 	config.parse(argc, argv);
 
-	BOOST_CHECK(config.has("i"));
-	BOOST_CHECK_EQUAL(config.get<std::string>("i"), "foo");
-	BOOST_CHECK(config.has("j"));
-	BOOST_CHECK_EQUAL(config.get<std::string>("j"), "bar");
+	CHECK(config.has("i"));
+	CHECK(config.get<std::string>("i") == "foo");
+	CHECK(config.has("j"));
+	CHECK(config.get<std::string>("j") == "bar");
 
-	BOOST_CHECK(config.has("k"));
-	BOOST_CHECK_EQUAL(config.get<std::string>("k"), "baz");
+	CHECK(config.has("k"));
+	CHECK(config.get<std::string>("k") == "baz");
 }
 
-BOOST_AUTO_TEST_CASE(t_9)
+TEST_CASE("t_9")
 {
 	auto &config = mcfp::config::instance();
 
@@ -269,10 +288,10 @@ BOOST_AUTO_TEST_CASE(t_9)
 // 	std::cerr << '>' << kExpected << '<' << std::endl;
 // 	std::cerr << '>' << ss.str() << '<' << std::endl;
 
-// 	BOOST_CHECK_EQUAL(ss.str(), kExpected);
+// 	CHECK_EQUAL(ss.str(), kExpected);
 }
 
-BOOST_AUTO_TEST_CASE(t_10)
+TEST_CASE("t_10")
 {
 	std::string s1 = R"(SPDX-License-Identifier: BSD-2-Clause
 
@@ -293,7 +312,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 	for (auto line : ww)
 		os << line << std::endl;
 	
-	BOOST_CHECK_EQUAL(os.str(), R"(SPDX-License-Identifier: BSD-2-Clause
+	CHECK(os.str() == R"(SPDX-License-Identifier: BSD-2-Clause
 
 Copyright (c) 2022 Maarten L. Hekkelman
 
@@ -320,7 +339,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 )");
 }
 
-BOOST_AUTO_TEST_CASE(t_11)
+TEST_CASE("t_11")
 {
 	const char *const argv[] = {
 		"test", "-faap", "-fnoot", "-fmies", nullptr
@@ -335,16 +354,16 @@ BOOST_AUTO_TEST_CASE(t_11)
 	
 	config.parse(argc, argv);
 
-	BOOST_CHECK_EQUAL(config.count("file"), 3);
+	CHECK(config.count("file") == 3);
 	
 	std::vector<std::string> files = config.get<std::vector<std::string>>("file");
-	BOOST_CHECK_EQUAL(files.size(), 3);
-	BOOST_CHECK_EQUAL(files[0], "aap");
-	BOOST_CHECK_EQUAL(files[1], "noot");
-	BOOST_CHECK_EQUAL(files[2], "mies");
+	CHECK(files.size() == 3);
+	CHECK(files[0] == "aap");
+	CHECK(files[1] == "noot");
+	CHECK(files[2] == "mies");
 }
 
-BOOST_AUTO_TEST_CASE(t_12)
+TEST_CASE("t_12")
 {
 	const char *const argv[] = {
 		"test", "--aap", nullptr
@@ -359,18 +378,53 @@ BOOST_AUTO_TEST_CASE(t_12)
 	
 	std::error_code ec;
 	config.parse(argc, argv, ec);
-	BOOST_CHECK(ec == mcfp::config_error::unknown_option);
+	CHECK(ec == mcfp::config_error::unknown_option);
 
 	config.set_ignore_unknown(true);
 	ec = {};
 
 	config.parse(argc, argv, ec);
-	BOOST_CHECK(not ec);
+	CHECK(not ec);
+}
+
+TEST_CASE("t_13")
+{
+	const char *const argv[] = {
+		"test", "--test=bla", nullptr
+	};
+	int argc = sizeof(argv) / sizeof(char*) - 1;
+
+	auto &config = mcfp::config::instance();
+
+	config.init(
+		"test [options]",
+		mcfp::make_option<std::string>("test", ""));
+	
+	CHECK_NOTHROW(config.parse(argc, argv));
+
+	CHECK(config.has("test"));
+	CHECK(config.get("test") == "bla");
+}
+
+TEST_CASE("t_14")
+{
+	const char *const argv[] = {
+		"test", "-test=bla", nullptr
+	};
+	int argc = sizeof(argv) / sizeof(char*) - 1;
+
+	auto &config = mcfp::config::instance();
+
+	config.init(
+		"test [options]",
+		mcfp::make_option<std::string>("test", ""));
+	
+	CHECK_THROWS_AS(config.parse(argc, argv), std::system_error);
 }
 
 // --------------------------------------------------------------------
 
-BOOST_AUTO_TEST_CASE(file_1, * utf::tolerance(0.001))
+TEST_CASE("file_1, * utf::tolerance(0.001)")
 {
 	const std::string_view config_file{ R"(
 # This is a test configuration
@@ -407,24 +461,24 @@ verbose
 
 	config.parse_config_file(is, ec);
 
-	BOOST_CHECK(not ec);
+	CHECK(not ec);
 
-	BOOST_CHECK(config.has("aap"));
-	BOOST_CHECK_EQUAL(config.get<std::string>("aap"), "1");
+	CHECK(config.has("aap"));
+	CHECK(config.get<std::string>("aap") == "1");
 
-	BOOST_CHECK(config.has("noot"));
-	BOOST_CHECK_EQUAL(config.get<int>("noot"), 2);
+	CHECK(config.has("noot"));
+	CHECK(config.get<int>("noot") == 2);
 
-	BOOST_CHECK(config.has("pi"));
-	BOOST_TEST(config.get<float>("pi") == 3.14);
+	CHECK(config.has("pi"));
+	CHECK(std::to_string(config.get<float>("pi")) == std::to_string(3.14));
 
-	BOOST_CHECK(config.has("s"));
-	BOOST_CHECK_EQUAL(config.get<std::string>("s"), "hello, world!");
+	CHECK(config.has("s"));
+	CHECK(config.get<std::string>("s") == "hello, world!");
 
-	BOOST_CHECK(config.has("verbose"));
+	CHECK(config.has("verbose"));
 }
 
-BOOST_AUTO_TEST_CASE(file_2)
+TEST_CASE("file_2")
 {
 	auto &config = mcfp::config::instance();
 
@@ -459,14 +513,14 @@ BOOST_AUTO_TEST_CASE(file_2)
 		
 		config.parse_config_file(is, ec);
 
-		BOOST_CHECK(ec == err);
+		CHECK(ec == err);
 
 		if (ec == std::errc())
-			BOOST_CHECK(config.has(option));
+			CHECK(config.has(option));
 	}
 }
 
-BOOST_AUTO_TEST_CASE(file_3)
+TEST_CASE("file_3")
 {
 	auto &config = mcfp::config::instance();
 
@@ -487,16 +541,16 @@ BOOST_AUTO_TEST_CASE(file_3)
 
 	config.parse_config_file("config", "bla-bla.conf", { gTestDir.string() }, ec);
 
-	BOOST_CHECK(not ec);
+	CHECK(not ec);
 
-	BOOST_CHECK(config.has("aap"));
-	BOOST_CHECK_EQUAL(config.get<std::string>("aap"), "aap");
+	CHECK(config.has("aap"));
+	CHECK(config.get<std::string>("aap") == "aap");
 
-	BOOST_CHECK(config.has("noot"));
-	BOOST_CHECK_EQUAL(config.get<int>("noot"), 42);
+	CHECK(config.has("noot"));
+	CHECK(config.get<int>("noot") == 42);
 }
 
-BOOST_AUTO_TEST_CASE(file_4)
+	TEST_CASE("file_4")
 {
 	auto &config = mcfp::config::instance();
 
@@ -517,11 +571,11 @@ BOOST_AUTO_TEST_CASE(file_4)
 
 	config.parse_config_file("config", "unit-test.conf", { gTestDir.string() }, ec);
 
-	BOOST_CHECK(not ec);
+	CHECK(not ec);
 
-	BOOST_CHECK(config.has("aap"));
-	BOOST_CHECK_EQUAL(config.get<std::string>("aap"), "aap");
+	CHECK(config.has("aap"));
+	CHECK(config.get<std::string>("aap") == "aap");
 
-	BOOST_CHECK(config.has("noot"));
-	BOOST_CHECK_EQUAL(config.get<int>("noot"), 3);
-}
\ No newline at end of file
+	CHECK(config.has("noot"));
+	CHECK(config.get<int>("noot") == 3);
+}



View it on GitLab: https://salsa.debian.org/med-team/libmcfp/-/compare/2938e41ea7383899f6d1205821dc5e7bd5518f49...199ee613b07f1c106b5c4c094c361e8b73d79e61

-- 
View it on GitLab: https://salsa.debian.org/med-team/libmcfp/-/compare/2938e41ea7383899f6d1205821dc5e7bd5518f49...199ee613b07f1c106b5c4c094c361e8b73d79e61
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20250121/52ab0773/attachment-0001.htm>


More information about the debian-med-commit mailing list