libmcfp-1.3.5/0000755000175000017500000000000014746144354013010 5ustar maartenmaartenlibmcfp-1.3.5/.github/0000755000175000017500000000000014746144354014350 5ustar maartenmaartenlibmcfp-1.3.5/.github/workflows/0000755000175000017500000000000014746144354016405 5ustar maartenmaartenlibmcfp-1.3.5/.github/workflows/build-documentation.yml0000644000175000017500000000347414746144354023106 0ustar maartenmaarten# 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@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@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@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@v2 libmcfp-1.3.5/.github/workflows/cmake-multi-platform.yml0000644000175000017500000000262514746144354023167 0ustar maartenmaartenname: 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@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 libmcfp-1.3.5/.gitignore0000644000175000017500000000004414746144354014776 0ustar maartenmaartenbuild .vscode docs/api docs/conf.py libmcfp-1.3.5/.readthedocs.yaml0000644000175000017500000000072614746144354016244 0ustar maartenmaartenversion: 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 libmcfp-1.3.5/CMakeLists.txt0000644000175000017500000001172114746144354015552 0ustar maartenmaarten# 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. cmake_minimum_required(VERSION 3.23) # set the project name project(mcfp VERSION 1.3.5 LANGUAGES CXX) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) include(GNUInstallDirs) include(CMakePackageConfigHelpers) 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(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") endif() if(MSVC) # make msvc standards compliant... add_compile_options(/permissive-) if(${CMAKE_SYSTEM_VERSION} GREATER_EQUAL 10) # Windows 10 add_definitions(-D _WIN32_WINNT=0x0A00) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.3) # Windows 8.1 add_definitions(-D _WIN32_WINNT=0x0603) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.2) # Windows 8 add_definitions(-D _WIN32_WINNT=0x0602) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.1) # Windows 7 add_definitions(-D _WIN32_WINNT=0x0601) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.0) # Windows Vista add_definitions(-D _WIN32_WINNT=0x0600) else() # Windows XP (5.1) add_definitions(-D _WIN32_WINNT=0x0501) endif() add_definitions(-DNOMINMAX) endif() add_library(mcfp INTERFACE) add_library(mcfp::mcfp ALIAS mcfp) 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(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 ) # installation # Clean up old config files (with old names) file(GLOB OLD_CONFIG_FILES LIST_DIRECTORIES TRUE ${CMAKE_INSTALL_FULL_LIBDIR}/cmake/mcfp/mcfp-*.cmake) 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 mcfp EXPORT mcfp FILE_SET mcfp_headers DESTINATION include/) install(EXPORT mcfp NAMESPACE mcfp:: DESTINATION lib/cmake/mcfp FILE "mcfpTargets.cmake") configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/mcfpConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/mcfpConfig.cmake INSTALL_DESTINATION lib/cmake/mcfp) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/mcfpConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/mcfpConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/mcfpConfigVersion.cmake" DESTINATION lib/cmake/mcfp) # Install old config file, for use in older code install(EXPORT mcfp NAMESPACE libmcfp:: DESTINATION lib/cmake/libmcfp FILE "libmcfpTargets.cmake") configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/libmcfpConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/libmcfpConfig.cmake INSTALL_DESTINATION lib/cmake/libmcfp) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/libmcfpConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libmcfpConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/libmcfpConfigVersion.cmake" DESTINATION lib/cmake/libmcfp) if(BUILD_TESTING) add_subdirectory(test) endif() if(BUILD_DOCUMENTATION) add_subdirectory(docs) endif() libmcfp-1.3.5/LICENSE0000644000175000017500000000245614746144354014024 0ustar maartenmaartenBSD-2-Clause License Copyright (c) 2022 Maarten L. Hekkelman All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 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. libmcfp-1.3.5/README.md0000644000175000017500000001035114746144354014267 0ustar maartenmaarten[![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++ // Example of using libmcfp #include #include #include int main(int argc, char * const argv[]) { // config is a singleton auto &config = mcfp::config::instance(); // Initialise the config object. This can be done more than once, // e.g. when you have different sets of options depending on the // first operand. config.init( // The first parameter is the 'usage' line, used when printing out the options "usage: example [options] file", // Flag options (not taking a parameter) mcfp::make_option("help,h", "Print this help text"), mcfp::make_option("verbose,v", "Verbose level, can be specified more than once to increase level"), // A couple of options with parameter mcfp::make_option("config", "Config file to use"), mcfp::make_option("text", "The text string to echo"), // And options with a default parameter mcfp::make_option("a", 1, "first parameter for multiplication"), mcfp::make_option("b", 2.0f, "second parameter for multiplication"), // You can also allow multiple values mcfp::make_option>("c", "Option c, can be specified more than once"), // This option is not shown when printing out the options mcfp::make_hidden_option("d", "Debug mode") ); // There are two flavors of calls, ones that take an error_code // and return the error in that code in case something is wrong. // The alternative is calling without an error_code, in which // case an exception is thrown when appropriate // Parse the command line arguments here std::error_code ec; config.parse(argc, argv, ec); if (ec) { std::cerr << "Error parsing arguments: " << ec.message() << std::endl; exit(1); } // First check, to see if we need to stop early on if (config.has("help") or config.operands().size() != 1) { // This will print out the 'usage' message with all the visible options std::cerr << config << std::endl; exit(config.has("help") ? 0 : 1); } // Configuration files, read it if it exists. If the users // specifies an alternative config file, it is an error if that // file cannot be found. config.parse_config_file("config", "example.conf", { "." }, ec); if (ec) { std::cerr << "Error parsing config file: " << ec.message() << std::endl; exit(1); } // If options are specified more than once, you can get the count int VERBOSE = config.count("verbose"); // Operands are arguments that are not options, e.g. files to act upon std::cout << "The first operand is " << config.operands().front() << std::endl; // Getting the value of a string option auto text = config.get("text", ec); if (ec) { std::cerr << "Error getting option text: " << ec.message() << std::endl; exit(1); } std::cout << "Text option is " << text << std::endl; // Likewise for numeric options int a = config.get("a"); float b = config.get("b"); std::cout << "a (" << a << ") * b (" << b << ") = " << a * b << std::endl; // And multiple strings for (std::string s : config.get>("c")) std::cout << "c: " << s << std::endl; return 0; } ``` ## Installation Use [cmake](https://cmake.org/) to install _libmcfp_. ```bash git clone https://github.com/mhekkel/libmcfp.git cd libmcfp mkdir build cd build cmake .. cmake --build . cmake --install . ``` libmcfp-1.3.5/changelog0000644000175000017500000000211214746144354014656 0ustar maartenmaartenVersion 1.3.5 - Install a cmake version file - Avoid using catch2 version 2 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 Version 1.2.2 - Added a non-throwing get - Added extended example code Version 1.2.1 - Renamed to libmcfp and namespace mcfp Version 1.2.0 - Fix in word wrapping code Version 1.1.1 - Better support for older GCC compilers Version 1.2.0 - New, tuples base implementation - Add 'usage' parameter to init Version 1.1.0 - Parse config files Version 1.0.2 - Fix get_terminal_width. Should be inline of course. Version 1.0.1 - printing options, word-wrapped Version 1.0.0 - initial release libmcfp-1.3.5/cmake/0000755000175000017500000000000014746144354014070 5ustar maartenmaartenlibmcfp-1.3.5/cmake/FindSphinx.cmake0000644000175000017500000000066314746144354017151 0ustar maartenmaarten#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)libmcfp-1.3.5/cmake/libmcfpConfig.cmake.in0000644000175000017500000000043214746144354020240 0ustar maartenmaarten@PACKAGE_INIT@ 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) libmcfp-1.3.5/cmake/mcfpConfig.cmake.in0000644000175000017500000000015014746144354017546 0ustar maartenmaarten@PACKAGE_INIT@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/mcfpTargets.cmake") check_required_components(mcfp) libmcfp-1.3.5/docs/0000755000175000017500000000000014746144354013740 5ustar maartenmaartenlibmcfp-1.3.5/docs/CMakeLists.txt0000644000175000017500000000326214746144354016503 0ustar maartenmaartenfind_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) libmcfp-1.3.5/docs/Doxyfile.in0000644000175000017500000000044014746144354016051 0ustar maartenmaartenEXCLUDE_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@ libmcfp-1.3.5/docs/_static/0000755000175000017500000000000014746144354015366 5ustar maartenmaartenlibmcfp-1.3.5/docs/_static/.gitignore0000644000175000017500000000010714746144354017354 0ustar maartenmaarten# Ignore everything in this directory * # Except this file !.gitignore libmcfp-1.3.5/docs/conf.py.in0000644000175000017500000000337214746144354015651 0ustar maartenmaartenproject = '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::' ] libmcfp-1.3.5/docs/genindex.rst0000644000175000017500000000001414746144354016266 0ustar maartenmaartenIndex ===== libmcfp-1.3.5/docs/index.rst0000644000175000017500000000424114746144354015602 0ustar maartenmaartenIntroduction ============ This library attempts to implement the `POSIX.1-2017 `_ 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 `_ 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 libmcfp-1.3.5/docs/requirements.in0000644000175000017500000000010314746144354017005 0ustar maartenmaartensphinx<5 exhale==0.3.6 myst-parser breathe sphinx_rtd_theme==1.3.0 libmcfp-1.3.5/docs/requirements.txt0000644000175000017500000000346714746144354017236 0ustar maartenmaarten# # 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 libmcfp-1.3.5/examples/0000755000175000017500000000000014746144354014626 5ustar maartenmaartenlibmcfp-1.3.5/examples/CMakeLists.txt0000644000175000017500000000052014746144354017363 0ustar maartenmaartencmake_minimum_required(VERSION 3.16) project(mcfp-usage LANGUAGES CXX) 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(mcfp REQUIRED) add_executable(example example.cpp) target_link_libraries(example mcfp::mcfp) libmcfp-1.3.5/examples/example.conf0000644000175000017500000000001114746144354017120 0ustar maartenmaartentext=foo libmcfp-1.3.5/examples/example.cpp0000644000175000017500000000611114746144354016764 0ustar maartenmaarten// Example of using libmcfp #include #include #include int main(int argc, char * const argv[]) { auto &config = mcfp::config::instance(); // Initialise the config object. This can be done more than once, // e.g. when you have different sets of options depending on the // first operand. config.init( // The first parameter is the 'usage' line, used when printing out the options "usage: example [options] file", // Flag options (not taking a parameter) mcfp::make_option("help,h", "Print this help text"), mcfp::make_option("verbose,v", "Verbose level, can be specified more than once to increase level"), // A couple of options with parameter mcfp::make_option("config", "Config file to use"), mcfp::make_option("text", "The text string to echo"), // And options with a default parameter mcfp::make_option("a", 1, "first parameter for multiplication"), mcfp::make_option("b", 2.0f, "second parameter for multiplication"), // You can also allow multiple values mcfp::make_option>("c", "Option c, can be specified more than once"), // This option is not shown when printing out the options mcfp::make_hidden_option("d", "Debug mode") ); // There are two flavors of calls, ones that take an error_code // and return the error in that code in case something is wrong. // The alternative is calling without an error_code, in which // case an exception is thrown when appropriate // Parse the command line arguments here std::error_code ec; config.parse(argc, argv, ec); if (ec) { std::cerr << "Error parsing arguments: " << ec.message() << std::endl; exit(1); } // First check, to see if we need to stop early on if (config.has("help") or config.operands().size() != 1) { // This will print out the 'usage' message with all the visible options std::cerr << config << std::endl; exit(config.has("help") ? 0 : 1); } // Configuration files, read it if it exists. If the users // specifies an alternative config file, it is an error if that // file cannot be found. config.parse_config_file("config", "example.conf", { "." }, ec); if (ec) { std::cerr << "Error parsing config file: " << ec.message() << std::endl; exit(1); } // If options are specified more than once, you can get the count int VERBOSE = config.count("verbose"); // Operands are arguments that are not options, e.g. files to act upon std::cout << "The first operand is " << config.operands().front() << std::endl; // Getting the value of a string option auto text = config.get("text", ec); if (ec) { std::cerr << "Error getting option text: " << ec.message() << std::endl; exit(1); } std::cout << "Text option is " << text << std::endl; // Likewise for numeric options int a = config.get("a"); float b = config.get("b"); std::cout << "a (" << a << ") * b (" << b << ") = " << a * b << std::endl; // And multiple strings for (std::string s : config.get>("c")) std::cout << "c: " << s << std::endl; return 0; }libmcfp-1.3.5/include/0000755000175000017500000000000014746144354014433 5ustar maartenmaartenlibmcfp-1.3.5/include/mcfp/0000755000175000017500000000000014746144354015360 5ustar maartenmaartenlibmcfp-1.3.5/include/mcfp/detail/0000755000175000017500000000000014746144354016622 5ustar maartenmaartenlibmcfp-1.3.5/include/mcfp/detail/charconv.hpp0000644000175000017500000001574714746144354021154 0ustar maartenmaarten/*- * 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 #include #include #include #include #if __has_include() #include #else #include #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 Op, class... Args> struct detector { using value_t = std::false_type; using type = Default; }; template class Op, class... Args> struct detector>, Op, Args...> { // Note that std::void_t is a c++17 feature using value_t = std::true_type; using type = Op; }; } // namespace detail struct nonesuch { nonesuch() = delete; ~nonesuch() = delete; nonesuch(nonesuch const&) = delete; void operator=(nonesuch const&) = delete; }; template class Op, class... Args> using is_detected = typename detail::detector::value_t; template class Op, class... Args> constexpr inline bool is_detected_v = is_detected::value; template class Op, class... Args> using detected_t = typename detail::detector::type; template class Op, class... Args> using detected_or = detail::detector; template class Op, class... Args> using is_detected_exact = std::is_same>; template class Op, class... Args> constexpr inline bool is_detected_exact_v = is_detected_exact::value; #else template class Op, class... Args> constexpr inline bool is_detected_v = std::experimental::is_detected::value; #endif template 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::max()) result.ec = std::errc::result_out_of_range; value = static_cast(v); } return result; } template , 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) 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 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 static std::to_chars_result to_chars(Iterator a, Iterator b, const T &value) { return std::to_chars(a, b, value); } }; template using from_chars_function = decltype(std::from_chars(std::declval(), std::declval(), std::declval())); template using charconv = typename std::conditional_t, std_charconv, my_charconv>; }libmcfp-1.3.5/include/mcfp/detail/options.hpp0000644000175000017500000002076614746144354021041 0ustar maartenmaarten/*- * 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 #include #include #include namespace mcfp::detail { // -------------------------------------------------------------------- // Some template wizardry to detect containers, needed to have special // handling of options that can be repeated. template using iterator_t = typename T::iterator; template using value_type_t = typename T::value_type; template using std_string_npos_t = decltype(T::npos); template struct is_container_type : std::false_type { }; /** * @brief Template to detect whether a type is a container */ template struct is_container_type and is_detected_v and not is_detected_v>> : std::true_type { }; template inline constexpr bool is_container_type_v = is_container_type::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 struct option_traits; template struct option_traits>> { using value_type = T; static value_type set_value(std::string_view argument, std::error_code &ec) { value_type value{}; auto r = charconv::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::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 { 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 struct option_traits and std::is_assignable_v>> { 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 struct option : public option_base { using traits_type = option_traits; using value_type = typename option_traits::value_type; std::optional 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) return *m_value; else return traits_type::to_string(*m_value); } }; template struct multiple_option : public option_base { using value_type = typename T::value_type; using traits_type = option_traits; std::vector 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 : 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::detaillibmcfp-1.3.5/include/mcfp/error.hpp0000644000175000017500000001114614746144354017225 0ustar maartenmaarten/*- * 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 #include #include 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(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(e), config_category()); } inline std::error_condition make_error_condition(config_error e) { return std::error_condition(static_cast(e), config_category()); } } // namespace mcfplibmcfp-1.3.5/include/mcfp/mcfp.hpp0000644000175000017500000005447214746144354017032 0ustar maartenmaarten/*- * 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 /// This header-only library contains code to parse argc/argv and store the /// values provided into a singleton object. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mcfp { // -------------------------------------------------------------------- /** * @brief A singleton class. Use @ref mcfp::config::instance to create and/or * retrieve the single instance * */ class config { 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 * * @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 void init(std::string_view usage, Options... options) { m_usage = usage; m_ignore_unknown = false; m_impl.reset(new config_impl(std::forward(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 s_instance; if (not s_instance) s_instance.reset(new 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 auto get(std::string_view name) const { using return_type = std::remove_cv_t; std::error_code ec; return_type result = get(name, ec); if (ec) throw std::system_error(ec, std::string{ name }); 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 auto get(std::string_view name, std::error_code &ec) const { using return_type = std::remove_cv_t; return_type result{}; auto opt = m_impl->get_option(name); if (opt == nullptr) ec = make_error_code(config_error::unknown_option); else { std::any value = opt->get_value(); if (not value.has_value()) ec = make_error_code(config_error::option_not_specified); else { try { result = std::any_cast(value); } catch (const std::bad_cast &) { ec = make_error_code(config_error::wrong_type_cast); } } } 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(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(name, ec); } /** * @brief Return the list of operands. * * @return const std::vector& The operand as a vector of strings */ const std::vector &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(); if (not conf.m_usage.empty()) os << conf.m_usage << std::endl; size_t options_width = conf.m_impl->get_option_width(); if (options_width > terminal_width / 2) options_width = terminal_width / 2; conf.m_impl->write(os, options_width); return os; } // -------------------------------------------------------------------- /** * @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; parse(argc, argv, ec); if (ec) 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 search_dirs) { std::error_code ec; parse_config_file(config_option, config_file_name, search_dirs, ec); if (ec) 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 search_dirs, std::error_code &ec) { std::string file_name{ config_file_name }; bool parsed_config_file = false; if (has(config_option)) file_name = get(config_option); for (std::filesystem::path dir : search_dirs) { std::ifstream file(dir / file_name); if (not file.is_open()) continue; parse_config_file(file, ec); parsed_config_file = true; break; } if (not parsed_config_file and has(config_option)) 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); if (is.is_open()) parse_config_file(is, ec); } private: static bool is_name_char(int ch) { return std::isalnum(ch) or ch == '_' or ch == '-'; } static constexpr bool is_eoln(int ch) { return ch == '\n' or ch == '\r' or ch == std::char_traits::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(); enum class State { NAME_START, COMMENT, NAME, ASSIGN, VALUE_START, VALUE } state = State::NAME_START; std::string name, value; for (;;) { auto ch = buffer.sbumpc(); switch (state) { case State::NAME_START: if (is_name_char(ch)) { name = { static_cast(ch) }; value.clear(); state = State::NAME; } 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); break; case State::COMMENT: if (is_eoln(ch)) state = State::NAME_START; break; case State::NAME: if (is_name_char(ch)) name.insert(name.end(), static_cast(ch)); else if (is_eoln(ch)) { auto opt = m_impl->get_option(name); if (opt == nullptr) { if (not m_ignore_unknown) ec = make_error_code(config_error::unknown_option); } else if (not opt->m_is_flag) ec = make_error_code(config_error::missing_argument_for_option); else ++opt->m_seen; state = State::NAME_START; } else { buffer.sungetc(); state = State::ASSIGN; } break; case State::ASSIGN: if (ch == '=') state = State::VALUE_START; else if (is_eoln(ch)) { auto opt = m_impl->get_option(name); if (opt == nullptr) { if (not m_ignore_unknown) ec = make_error_code(config_error::unknown_option); } else if (not opt->m_is_flag) ec = make_error_code(config_error::missing_argument_for_option); else ++opt->m_seen; state = State::NAME_START; } else if (ch != ' ' and ch != '\t') ec = make_error_code(config_error::invalid_config_file); break; case State::VALUE_START: case State::VALUE: if (is_eoln(ch)) { auto opt = m_impl->get_option(name); if (opt == nullptr) { if (not m_ignore_unknown) ec = make_error_code(config_error::unknown_option); } else if (opt->m_is_flag) ec = make_error_code(config_error::option_does_not_accept_argument); else if (not value.empty() and (opt->m_seen == 0 or opt->m_multi)) { opt->set_value(value, ec); ++opt->m_seen; } state = State::NAME_START; } else if (state == State::VALUE) value.insert(value.end(), static_cast(ch)); else if (ch != ' ' and ch != '\t') { value = { static_cast(ch) }; state = State::VALUE; } break; } if (ec or ch == std::char_traits::eof()) break; } } /** * @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; enum class State { options, operands } state = State::options; for (int i = 1; i < argc and not ec; ++i) { const char *arg = argv[i]; if (arg == nullptr) // should not happen break; if (state == State::options) { if (*arg != '-') // according to POSIX this is the end of options, start operands // state = State::operands; { // however, people nowadays expect to be able to mix operands and options m_impl->m_operands.emplace_back(arg); continue; } else if (arg[1] == '-' and arg[2] == 0) { state = State::operands; continue; } } if (state == State::operands) { m_impl->m_operands.emplace_back(arg); continue; } option_base *opt = nullptr; std::string_view opt_arg; assert(*arg == '-'); ++arg; if (*arg == '-') // double --, start of new argument { ++arg; assert(*arg != 0); // this should not happen, as it was checked for before std::string_view s_arg(arg); std::string_view::size_type p = s_arg.find('='); if (p != std::string_view::npos) { opt_arg = s_arg.substr(p + 1); s_arg = s_arg.substr(0, p); } opt = m_impl->get_option(s_arg); if (opt == nullptr) { if (not m_ignore_unknown) ec = make_error_code(config_error::unknown_option); continue; } if (opt->m_is_flag) { if (not opt_arg.empty()) ec = make_error_code(config_error::option_does_not_accept_argument); ++opt->m_seen; continue; } ++opt->m_seen; } else // single character options { bool expect_option_argument = false; while (*arg != 0 and not ec) { opt = m_impl->get_option(*arg++); if (opt == nullptr) { if (not m_ignore_unknown) ec = make_error_code(config_error::unknown_option); continue; } ++opt->m_seen; if (opt->m_is_flag) continue; opt_arg = arg; expect_option_argument = true; break; } if (not expect_option_argument) continue; } if (opt_arg.empty() and i + 1 < argc) // So, the = character was not present, the next arg must be the option argument { ++i; opt_arg = argv[i]; } if (opt_arg.empty()) ec = make_error_code(config_error::missing_argument_for_option); else opt->set_value(opt_arg, ec); } } private: config() = default; config(const config &) = delete; config &operator=(const config &) = delete; /// @cond struct config_impl_base { virtual ~config_impl_base() = default; virtual option_base *get_option(std::string_view name) = 0; virtual option_base *get_option(char short_name) = 0; virtual size_t get_option_width() const = 0; virtual void write(std::ostream &os, size_t width) const = 0; std::vector m_operands; }; template struct config_impl : public config_impl_base { static constexpr size_t N = sizeof...(Options); config_impl(Options... options) : m_options(std::forward(options)...) { } option_base *get_option(std::string_view name) override { return get_option<0>(name); } template option_base *get_option([[maybe_unused]] std::string_view name) { if constexpr (Ix == N) return nullptr; else { option_base &opt = std::get(m_options); return (opt.m_name == name) ? &opt : get_option(name); } } option_base *get_option(char short_name) override { return get_option<0>(short_name); } template option_base *get_option([[maybe_unused]] char short_name) { if constexpr (Ix == N) return nullptr; else { option_base &opt = std::get(m_options); return (opt.m_short_name == short_name) ? &opt : get_option(short_name); } } virtual size_t get_option_width() const override { return std::apply([](Options const& ...opts) { size_t width = 0; ((width = std::max(width, opts.width())), ...); return width; }, m_options); } virtual void write(std::ostream &os, size_t width) const override { std::apply([&os,width](Options const& ...opts) { (opts.write(os, width), ...); }, m_options); } std::tuple m_options; }; std::unique_ptr m_impl; bool m_ignore_unknown = false; std::string m_usage; /// @endcond }; // -------------------------------------------------------------------- /** * @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 , int> = 0> auto make_option(std::string_view name, std::string_view description) { return detail::option(name, description, false); } template , int> = 0> auto make_option(std::string_view name, std::string_view description) { return detail::multiple_option(name, description, false); } /** * @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 , int> = 0> auto make_option(std::string_view name, const T &v, std::string_view description) { return detail::option(name, v, description, false); } /** * @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 , int> = 0> auto make_hidden_option(std::string_view name, std::string_view description) { return detail::option(name, description, true); } template , int> = 0> auto make_hidden_option(std::string_view name, std::string_view description) { return detail::multiple_option(name, description, true); } /** * @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 , int> = 0> auto make_hidden_option(std::string_view name, const T &v, std::string_view description) { return detail::option(name, v, description, true); } } // namespace mcfp namespace std { template <> struct is_error_condition_enum : public true_type { }; } // namespace std libmcfp-1.3.5/include/mcfp/text.hpp0000644000175000017500000001706014746144354017061 0ustar maartenmaarten/*- * 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 text.hpp * This file contains an implementation of charconv and of work wrapping code */ #include namespace mcfp { /** 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 using charconv = typename detail::charconv; /** Use the private implementation of is_detected_v which maybe * resolved to the std::is_detected_v template when available */ template class Op, class... Args> constexpr inline bool is_detected_v = detail::is_detected_v; // -------------------------------------------------------------------- /// Simplified line breaking code taken from a decent text editor. /// In this case, simplified means it only supports ASCII. /// The algorithm uses dynamic programming to find the optimal /// separation in lines. class word_wrapper : public std::vector { public: word_wrapper(std::string_view text, size_t width) : m_width(width) { std::string_view::size_type line_start = 0, line_end = text.find('\n'); for (;;) { auto line = text.substr(line_start, line_end - line_start); if (line.empty()) this->push_back(line); else { auto lines = wrap_line(line); this->insert(this->end(), lines.begin(), lines.end()); } if (line_end == std::string_view::npos) break; line_start = line_end + 1; line_end = text.find('\n', line_start); } } private: std::vector wrap_line(std::string_view line) { std::vector result; std::vector offsets = { 0 }; auto b = line.begin(); while (b != line.end()) { auto e = next_line_break(b, line.end()); offsets.push_back(e - line.begin()); b = e; } size_t count = offsets.size() - 1; std::vector minima(count + 1, std::numeric_limits::max()); minima[0] = 0; std::vector breaks(count + 1, 0); for (size_t i = 0; i < count; ++i) { size_t j = i + 1; while (j <= count) { size_t w = offsets[j] - offsets[i]; if (w > m_width) break; while (w > 0 and std::isspace(line[offsets[i] + w - 1])) --w; size_t cost = minima[i]; if (j < count) // last line may be shorter cost += (m_width - w) * (m_width - w); if (cost < minima[j]) { minima[j] = cost; breaks[j] = i; } ++j; } } size_t j = count; while (j > 0) { size_t i = breaks[j]; result.push_back(line.substr(offsets[i], offsets[j] - offsets[i])); j = i; } reverse(result.begin(), result.end()); return result; } std::string_view::const_iterator next_line_break(std::string_view::const_iterator text, std::string_view::const_iterator end) { if (text == end) return text; enum LineBreakClass { OP, // OpenPunctuation, CL, // ClosePunctuation, CP, // CloseParenthesis, QU, // Quotation, EX, // Exlamation, SY, // SymbolAllowingBreakAfter, IS, // InfixNumericSeparator, PR, // PrefixNumeric, PO, // PostfixNumeric, NU, // Numeric, AL, // Alphabetic, HY, // Hyphen, BA, // BreakAfter, CM, // CombiningMark, WJ, // WordJoiner, MB, // MandatoryBreak, SP, // Space, }; static const LineBreakClass kASCII_LineBreakTable[128] = { CM, CM, CM, CM, CM, CM, CM, CM, CM, BA, MB, MB, MB, SP, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, SP, EX, QU, AL, PR, PO, AL, QU, OP, CP, AL, PR, IS, HY, IS, SY, NU, NU, NU, NU, NU, NU, NU, NU, NU, NU, IS, IS, AL, AL, AL, EX, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, OP, PR, CP, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, OP, BA, CL, AL, CM }; enum BreakAction { DBK = 0, // direct break (blank in table) IBK, // indirect break (% in table) PBK, // prohibited break (^ in table) CIB, // combining indirect break CPB // combining prohibited break }; static const BreakAction brkTable[15][15] = { // OP CL CP QU EX SY IS PR PO NU AL HY BA CM WJ /* OP */ { PBK, PBK, PBK, PBK, PBK, PBK, PBK, PBK, PBK, PBK, PBK, PBK, PBK, CPB, PBK }, /* CL */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, IBK, IBK, DBK, DBK, IBK, IBK, CIB, PBK }, /* CP */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, IBK, IBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* QU */ { PBK, PBK, PBK, IBK, PBK, PBK, PBK, IBK, IBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* EX */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, DBK, DBK, IBK, IBK, CIB, PBK }, /* SY */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, DBK, IBK, IBK, CIB, PBK }, /* IS */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* PR */ { IBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* PO */ { IBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* NU */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, IBK, IBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* AL */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* HY */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, DBK, IBK, IBK, CIB, PBK }, /* BA */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, DBK, DBK, IBK, IBK, CIB, PBK }, /* CM */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* WJ */ { IBK, PBK, PBK, IBK, PBK, PBK, PBK, IBK, IBK, IBK, IBK, IBK, IBK, CIB, PBK }, }; uint8_t ch = *text; LineBreakClass cls; if (ch < 128) cls = kASCII_LineBreakTable[ch]; else cls = AL; if (cls == SP) cls = WJ; LineBreakClass ncls = cls; while (++text != end and cls != MB) { ch = *text; LineBreakClass lcls = ncls; if (ch < 128) ncls = kASCII_LineBreakTable[ch]; else ncls = AL; if (ncls == MB) { ++text; break; } if (ncls == SP) continue; BreakAction brk = brkTable[cls][ncls]; if (brk == DBK or (brk == IBK and lcls == SP)) break; cls = ncls; } return text; } size_t m_width; }; } // namespace mcfp libmcfp-1.3.5/include/mcfp/utilities.hpp0000644000175000017500000000460514746144354020111 0ustar maartenmaarten/*- * 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 #include #if __has_include() #include #include #include #elif defined(_WIN32) #include #endif namespace mcfp { #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() { CONSOLE_SCREEN_BUFFER_INFO csbi; ::GetConsoleScreenBufferInfo(::GetStdHandle(STD_OUTPUT_HANDLE), &csbi); return csbi.srWindow.Right - csbi.srWindow.Left + 1; } #elif __has_include() /// @brief Get the width in columns of the current terminal /// @return number of columns of the terminal inline uint32_t get_terminal_width() { uint32_t result = 80; if (::isatty(STDOUT_FILENO)) { struct winsize w; ::ioctl(0, TIOCGWINSZ, &w); result = w.ws_col; } return result; } #else #warning "Could not find the terminal width, falling back to default" inline uint32_t get_terminal_width() { return 80; } #endif } // namespace mcfp libmcfp-1.3.5/test/0000755000175000017500000000000014746144354013767 5ustar maartenmaartenlibmcfp-1.3.5/test/CMakeLists.txt0000644000175000017500000000124414746144354016530 0ustar maartenmaarten if(NOT(Catch2_FOUND OR TARGET Catch2)) find_package(Catch2 3 QUIET) if(NOT Catch2_FOUND) include(FetchContent) FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git GIT_TAG v3.8.0) FetchContent_MakeAvailable(Catch2) 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(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 $ --data-dir ${CMAKE_CURRENT_SOURCE_DIR}) libmcfp-1.3.5/test/unit-test.conf0000644000175000017500000000010214746144354016563 0ustar maartenmaarten# A simple test config file aap = 2 noot = 3 ; With some comments libmcfp-1.3.5/test/unit-test.cpp0000644000175000017500000003707014746144354016436 0ustar maartenmaarten/*- * 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. */ #define CATCH_CONFIG_RUNNER #include #include #include namespace fs = std::filesystem; std::filesystem::path gTestDir = std::filesystem::current_path(); int main(int argc, char *argv[]) { Catch::Session session; // There must be exactly one instance // Build a new parser on top of Catch2's using namespace Catch::Clara; 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(); } // -------------------------------------------------------------------- TEST_CASE("t_1, * utf::tolerance(0.001)") { int argc = 3; const char *const argv[] = { "test", "--flag", nullptr }; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("flag", ""), mcfp::make_option("param_int", ""), mcfp::make_option("param_int_2", 1, ""), mcfp::make_option("param_float", ""), mcfp::make_option("param_float_2", 3.14f, "")); config.parse(argc, argv); CHECK(config.has("flag")); CHECK(not config.has("flag2")); CHECK(config.get("param_int_2") == 1); CHECK_THROWS_AS(config.get("param_int_2"), std::system_error); CHECK_THROWS_AS(config.get("param_int"), std::system_error); CHECK(std::to_string(config.get("param_float_2")) == std::to_string(3.14)); CHECK_THROWS_AS(config.get("param_float_2"), std::system_error); CHECK_THROWS_AS(config.get("param_float"), std::system_error); } TEST_CASE("t_2") { int argc = 3; const char *const argv[] = { "test", "-vvvv", "--verbose", nullptr }; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("verbose,v", "")); config.parse(argc, argv); CHECK(config.count("verbose") == 5); } TEST_CASE("t_3") { int argc = 2; const char *const argv[] = { "test", "--param_int=42", nullptr }; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("param_int", "")); config.parse(argc, argv); CHECK(config.has("param_int")); CHECK(config.get("param_int") == 42); } TEST_CASE("t_4") { int argc = 3; const char *const argv[] = { "test", "--param_int", "42", nullptr }; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("param_int", "")); config.parse(argc, argv); CHECK(config.has("param_int")); CHECK(config.get("param_int") == 42); } TEST_CASE("t_5") { const char *const argv[] = { "test", "-i", "42", "-j43", nullptr }; int argc = sizeof(argv) / sizeof(char*); auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("nr1,i", ""), mcfp::make_option("nr2,j", "")); config.parse(argc, argv); CHECK(config.has("nr1")); CHECK(config.has("nr2")); CHECK(config.get("nr1") == 42); CHECK(config.get("nr2") == 43); } TEST_CASE("t_6") { const char *const argv[] = { "test", "-i", "42", "-j43", "foo", "bar", nullptr }; int argc = sizeof(argv) / sizeof(char*); auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("nr1,i", ""), mcfp::make_option("nr2,j", "")); config.parse(argc, argv); CHECK(config.has("nr1")); CHECK(config.has("nr2")); CHECK(config.get("nr1") == 42); CHECK(config.get("nr2") == 43); CHECK(config.operands().size() == 2); CHECK(config.operands().front() == "foo"); CHECK(config.operands().back() == "bar"); } TEST_CASE("t_7") { const char *const argv[] = { "test", "--", "-i", "42", "-j43", "foo", "bar", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("nr1,i", ""), mcfp::make_option("nr2,j", "")); config.parse(argc, argv); CHECK(not config.has("nr1")); CHECK(not config.has("nr2")); CHECK(config.operands().size() == 5); auto compare = std::vector{ argv[2], argv[3], argv[4], argv[5], argv[6] }; CHECK(config.operands() == compare); } TEST_CASE("t_8") { const char *const argv[] = { "test", "-i", "foo", "-jbar", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("i", ""), mcfp::make_option("j", ""), mcfp::make_option("k", "baz", "")); config.parse(argc, argv); CHECK(config.has("i")); CHECK(config.get("i") == "foo"); CHECK(config.has("j")); CHECK(config.get("j") == "bar"); CHECK(config.has("k")); CHECK(config.get("k") == "baz"); } TEST_CASE("t_9") { auto &config = mcfp::config::instance(); config.set_usage("usage: test [options]"); config.init( "test [options]", mcfp::make_option("i", "First option"), mcfp::make_option("j", "This is the second option"), mcfp::make_option("a-very-long-option-name,k", "baz", "And, you guessed it, this must be option three.")); // std::stringstream ss; // int fd = open("/dev/null", O_RDWR); // dup2(fd, STDOUT_FILENO); // ss << config << std::endl; // const char kExpected[] = R"(usage: test [options] // -i arg First option // -j arg This is the second option // -k [ --a-very-long-option-name ] arg (=baz) // And, you guessed it, this must be // option three. // )"; // std::cerr << '>' << kExpected << '<' << std::endl; // std::cerr << '>' << ss.str() << '<' << std::endl; // CHECK_EQUAL(ss.str(), kExpected); } TEST_CASE("t_10") { std::string s1 = R"(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. )"; mcfp::word_wrapper ww(s1, 80); std::ostringstream os; for (auto line : ww) os << line << std::endl; CHECK(os.str() == R"(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. )"); } TEST_CASE("t_11") { const char *const argv[] = { "test", "-faap", "-fnoot", "-fmies", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option>("file,f", "")); config.parse(argc, argv); CHECK(config.count("file") == 3); std::vector files = config.get>("file"); CHECK(files.size() == 3); CHECK(files[0] == "aap"); CHECK(files[1] == "noot"); CHECK(files[2] == "mies"); } TEST_CASE("t_12") { const char *const argv[] = { "test", "--aap", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option>("file,f", "")); std::error_code ec; config.parse(argc, argv, ec); CHECK(ec == mcfp::config_error::unknown_option); config.set_ignore_unknown(true); ec = {}; config.parse(argc, argv, 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("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("test", "")); CHECK_THROWS_AS(config.parse(argc, argv), std::system_error); } // -------------------------------------------------------------------- TEST_CASE("file_1, * utf::tolerance(0.001)") { const std::string_view config_file{ R"( # This is a test configuration aap=1 noot = 2 mies = pi = 3.14 s = hello, world! verbose )" }; struct membuf : public std::streambuf { membuf(char * text, size_t length) { this->setg(text, text, text + length); } } buffer(const_cast(config_file.data()), config_file.length()); std::istream is(&buffer); auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("aap", ""), mcfp::make_option("noot", ""), mcfp::make_option("mies", ""), mcfp::make_option("pi", ""), mcfp::make_option("s", ""), mcfp::make_option("verbose,v", "")); std::error_code ec; config.parse_config_file(is, ec); CHECK(not ec); CHECK(config.has("aap")); CHECK(config.get("aap") == "1"); CHECK(config.has("noot")); CHECK(config.get("noot") == 2); CHECK(config.has("pi")); CHECK(std::to_string(config.get("pi")) == std::to_string(3.14)); CHECK(config.has("s")); CHECK(config.get("s") == "hello, world!"); CHECK(config.has("verbose")); } TEST_CASE("file_2") { auto &config = mcfp::config::instance(); std::tuple tests[] = { { "aap !", "aap", make_error_code(mcfp::config_error::invalid_config_file) }, { "aap=aap", "aap", {} }, { "aap", "aap", make_error_code(mcfp::config_error::missing_argument_for_option) }, { "verbose=1", "verbose", make_error_code(mcfp::config_error::option_does_not_accept_argument) }, }; for (const auto &[config_file, option, err] : tests) { struct membuf : public std::streambuf { membuf(char * text, size_t length) { this->setg(text, text, text + length); } } buffer(const_cast(config_file.data()), config_file.length()); std::istream is(&buffer); std::error_code ec; config.init( "test [options]", mcfp::make_option("aap", ""), mcfp::make_option("noot", ""), mcfp::make_option("pi", ""), mcfp::make_option("s", ""), mcfp::make_option("verbose,v", "")); config.parse_config_file(is, ec); CHECK(ec == err); if (ec == std::errc()) CHECK(config.has(option)); } } TEST_CASE("file_3") { auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("aap", ""), mcfp::make_option("noot", ""), mcfp::make_option("config", "")); std::error_code ec; const char *const argv[] = { "test", "--aap=aap", "--noot=42", "--config=unit-test.conf", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; config.parse(argc, argv); config.parse_config_file("config", "bla-bla.conf", { gTestDir.string() }, ec); CHECK(not ec); CHECK(config.has("aap")); CHECK(config.get("aap") == "aap"); CHECK(config.has("noot")); CHECK(config.get("noot") == 42); } TEST_CASE("file_4") { auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("aap", ""), mcfp::make_option("noot", ""), mcfp::make_option("config", "")); std::error_code ec; const char *const argv[] = { "test", "--aap=aap", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; config.parse(argc, argv); config.parse_config_file("config", "unit-test.conf", { gTestDir.string() }, ec); CHECK(not ec); CHECK(config.has("aap")); CHECK(config.get("aap") == "aap"); CHECK(config.has("noot")); CHECK(config.get("noot") == 3); }