From cab45b5afe2ae95cbad54f4d1c430b5744fedfbb Mon Sep 17 00:00:00 2001 From: Leonardo Ayala Date: Mon, 5 Aug 2024 13:35:39 +0200 Subject: [PATCH 01/24] Adds CPack to Cmake to package project as .deb file --- .gitlab-ci.yml | 2 ++ CHANGELOG.md | 2 +- CMakeLists.txt | 4 ++-- Dockerfile | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7fa8b27..3beba2d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,6 +13,8 @@ build: - mkdir cmake-build && cd cmake-build - cmake .. - make MCML -j + - make package -j + - dpkg -i MCML*.deb artifacts: paths: - cmake-build/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b395a..2ce45a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- +- Adds Cpack to CMake to package .deb file ### Changed diff --git a/CMakeLists.txt b/CMakeLists.txt index a24a7ef..972b99f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06402e446f23470070da0ac0ab0285e2b032a580cec036fe4526d8eb89500b1b -size 2393 +oid sha256:0dc37578b3dcf1f4f10b2d89f119c9d154ac20fbc4f6c61f3f24afd3b4e99db1 +size 2729 diff --git a/Dockerfile b/Dockerfile index f15d424..4a503f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,8 @@ WORKDIR /code/build RUN nvcc --version RUN cmake .. RUN make MCML -j +RUN make package -j +RUN dpkg -i MCML*.deb RUN chmod +x MCML ENTRYPOINT ["/code/build/MCML"] From 7f57fe567251b82eb18b73824b6d58f3b5292c5c Mon Sep 17 00:00:00 2001 From: Leonardo Ayala Date: Mon, 5 Aug 2024 14:02:17 +0200 Subject: [PATCH 02/24] Adds cuda version and SM compute arch to .deb file name file during packaging --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 972b99f..d84c6be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0dc37578b3dcf1f4f10b2d89f119c9d154ac20fbc4f6c61f3f24afd3b4e99db1 -size 2729 +oid sha256:cfb861e8937e1b9fc20d0fc76c2faf91797106d9c3211bab1297959e9c5bebd6 +size 2949 From 79bd9d97dec6cc7bb7cec8f80af9e14e23ec7686 Mon Sep 17 00:00:00 2001 From: Leonardo Ayala Date: Fri, 9 Aug 2024 12:01:17 +0200 Subject: [PATCH 03/24] Adds CITATION.cff --- CITATION.cff | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..ce46cb5 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,9 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: +- family-names: "Ayala" + given-names: "Leonardo" + orcid: "https://orcid.org/0000-0002-3574-2085" +title: "mcmlgpu" +version: 0.0.4 +url: "https://github.com/IMSY-DKFZ/mcmlgpu" From 121ee1af5f5f75e6d0baa781f991ae7e281488a5 Mon Sep 17 00:00:00 2001 From: Leonardo Ayala Date: Fri, 9 Aug 2024 12:01:47 +0200 Subject: [PATCH 04/24] Corrects LICENSE --- LICENSE | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 8587a08..a6e61b7 100644 --- a/LICENSE +++ b/LICENSE @@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - Copyright (C) 2024 Ayala Menjivar, Leonardo + Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -645,14 +645,13 @@ the "copyright" line and a pointer to where the full notice is found. GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Copyright (C) 2024 Ayala Menjivar, Leonardo + Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. From 6b6be753cfc6b963097b3d21b84d2486c3c1c80f Mon Sep 17 00:00:00 2001 From: Leonardo Ayala Date: Fri, 9 Aug 2024 12:02:53 +0200 Subject: [PATCH 05/24] Adds logo and updates README description --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++--- resources/icon.png | Bin 0 -> 72758 bytes 2 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 resources/icon.png diff --git a/README.md b/README.md index da81c73..95571d9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,26 @@ -# Monte Carlo Multi Layer +

+ Logo +

-This repository contains the base code for Monte Carlo simulations in GPU. Some custom implementations have been added -to the original code developed by Erik Alerstam, David Han, and William C. Y. Lo: https://code.google.com/archive/p/gpumcml/. +# Monte Carlo Multi Layer accelerated by GPU + +This repository contains the base code for Monte Carlo simulations in a GPU of light transport on turbid media in GPU. +Custom implementations have been added to the original code developed by +[Erik Alerstam, David Han, and William C. Y. Lo](https://code.google.com/archive/p/gpumcml/). + +This project adds the following features: + +1. Modern build and install rules with CMake. +2. Custom targeting of compute capabilities for modern GPUs through the flag `-DCUDA_ARCH`. +3. Reduces IO operations thus increasing speed. +4. New docker image. +5. Easy install and uninstall mechanisms. +6. Conan packaging enabled. +7. Adds computation of penetration depth for each simulation at runtime. +8. Modern code styling using pre-commit hooks. +9. Progress bar display for simulations. +10. Reduced terminal clutter. +11. Eliminates dependency to deprecated `cutil` library. # Setup development environment All you need to have is a CUDA capable computer, `cmake` and `git lfs`. You can set up these dependencies by running @@ -53,6 +72,35 @@ of the repository. conan create . issi/stable-cuda11.5-sm86 -o cuda_arch=86 ``` +# Running an example +After building `MCML`, you can run an example to be sure that it is working as intended: + +```bash +MCML -i resources/sample.mci -O batch.mco +``` + +This should create a file called `batch.mco` with the following content: + +```text +ID,Specular,Diffuse,Absorbed,Transmittance,Penetration +MCML_Bat_NA_0_Sim_0_3.00e-07.mco,0.02404,0.0277725,0.948185,0,0.998 +MCML_Bat_NA_0_Sim_0_3.02e-07.mco,0.02404,0.0299027,0.946057,0,0.998 +``` + +# Contributing a feature/bug fix +If you have doubts on how to finish your feature branch, you can always ask for help + +1. Create an issue on `GitHub `_, if a task does not exist yet. +2. Assign the task to you. +3. Create a fork of the repository. +4. Create a new branch. + The `branch name` has to match the following pattern: `-` +5. Implement your feature +6. Update :code:`feature` branch: :code:`git checkout && git merge develop`. +7. Create a merge request for your feature. + select `develop` as the destination branch. +8. The branch will be reviewed and automatically merged if there are no requested changes. + # Docker image We also have a docker image that you can use for your projects. The image can be built by running the following command in a terminal. Make sure to have _Docker_ or _Docker compose_ installed on your computer. You can append the flag diff --git a/resources/icon.png b/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..60b28c746ea7e8adf8ba79b012758d4e74c921eb GIT binary patch literal 72758 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 t^Nh$<hA literal 0 HcmV?d00001 From b9b13943c2f7cb43c28a40bb8a90767a2aad6161 Mon Sep 17 00:00:00 2001 From: Leonardo Ayala Date: Fri, 9 Aug 2024 12:04:28 +0200 Subject: [PATCH 06/24] Corrects link to repository in CMakeLists.txt --- CMakeLists.txt | 4 +-- src/gpumcml_main.cu | 64 +++++---------------------------------------- 2 files changed, 8 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d84c6be..033a595 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cfb861e8937e1b9fc20d0fc76c2faf91797106d9c3211bab1297959e9c5bebd6 -size 2949 +oid sha256:e484eab9794bc56ff1987ae192cb4a4efb1cf88a7aa62d49ba39b1eccd45ce8d +size 2948 diff --git a/src/gpumcml_main.cu b/src/gpumcml_main.cu index e2258d4..8c54158 100755 --- a/src/gpumcml_main.cu +++ b/src/gpumcml_main.cu @@ -1,61 +1,9 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// GPU-based Monte Carlo simulation of photon migration in multi-layered media (GPU-MCML) -// Copyright (C) 2009 -// -// || DEVELOPMENT TEAM: -// -------------------------------------------------------------------------------------------------- -// Erik Alerstam, David Han, and William C. Y. Lo -// -// This code is the result of the collaborative efforts between -// Lund University and the University of Toronto. -// -// || DOCUMENTATION AND USER MANUAL: -// -------------------------------------------------------------------------------------------------- -// Detailed "Wiki" style documentation is being developed for GPU-MCML -// and will be available on our webpage soon: -// http://code.google.com/p/gpumcml -// -// || NEW FEATURES: -// -------------------------------------------------------------------------------------------------- -// - Supports the Fermi GPU architecture -// - Multi-GPU execution -// - Automatic selection of optimization parameters -// - Backward compatible on pre-Fermi graphics cards -// - Supports linux and Windows environment (Visual Studio) -// -// || PREVIOUS WORK: -// -------------------------------------------------------------------------------------------------- -// This code is the fusion of our earlier, preliminary implementations and combines the best features -// from each implementation. -// -// W. C. Y. Lo, T. D. Han, J. Rose, and L. Lilge, "GPU-accelerated Monte Carlo simulation for photodynamic -// therapy treatment planning," in Proc. of SPIE-OSA Biomedical Optics, vol. 7373. -// -// and -// -// http://www.atomic.physics.lu.se/biophotonics/our_research/monte_carlo_simulations/gpu_monte_carlo/ -// E. Alerstam, T. Svensson and S. Andersson-Engels, "Parallel computing with graphics processing -// units for high-speed Monte Carlo simulations of photon migration", Journal of Biomedical Optics -// Letters, 13(6) 060504 (2008). -// -// || CITATION: -// -------------------------------------------------------------------------------------------------- -// We encourage the use, and modification of this code, and hope it will help -// users/programmers to utilize the power of GPGPU for their simulation needs. While we -// don't have a scientific publication describing this code yet, we would very much appreciate it -// if you cite our original papers above if you use this code or derivations -// thereof for your own scientific work -// -// To compile and run this code, please visit www.nvidia.com and download the necessary -// CUDA Toolkit, SDK, and Developer Drivers -// -// If you use Visual Studio, the express edition is available for free at -// http://www.microsoft.com/express/Downloads/). -// -// This code is distributed under the terms of the GNU General Public Licence (see below). -// -///////////////////////////////////////////////////////////////////////////////////////////////////////// +/***************************************************************************** +* +* Main control of MCMLGPU +* ========================================================================= +* +****************************************************************************/ /* * This file is part of GPUMCML. * From d2154d98fdf328b4c28ea16eee295ddf3fe662c1 Mon Sep 17 00:00:00 2001 From: Leonardo Ayala Date: Fri, 9 Aug 2024 13:11:51 +0200 Subject: [PATCH 07/24] Adds GitHub configuration: actions, templates, release drafter. --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 3 ++ .github/ISSUE_TEMPLATE/feature_request.md | 23 +++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 27 +++++++++++++++ .github/release-drafter.yml | 31 +++++++++++++++++ .github/stale.yml | 17 ++++++++++ .github/workflows/build.yml | 38 +++++++++++++++++++++ .github/workflows/greetings.yml | 16 +++++++++ .github/workflows/release-drafter.yml | 41 +++++++++++++++++++++++ CHANGELOG.md | 29 +++++++++------- 10 files changed, 250 insertions(+), 13 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/release-drafter.yml create mode 100644 .github/stale.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/greetings.yml create mode 100644 .github/workflows/release-drafter.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..bcdf194 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: 🐛 Bug report +about: If something isn't working 🔧 +title: '' +labels: bug +assignees: +--- + +## 🐛 Bug Report + + + +## 🔬 How To Reproduce + +Steps to reproduce the behavior: + +1. ... + +### Code sample + + + +### Environment + +* OS: [e.g. Linux / Windows / macOS] +* Python version: [e.g. 3.10] + +### Screenshots + + + +## 📈 Expected behavior + + + +## 📎 Additional context + + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..8f2da54 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,3 @@ +# Configuration: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository + +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..c387120 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: 🚀 Feature request +about: Suggest an idea for this project 🏖 +title: '' +labels: enhancement +assignees: +--- + +## 🚀 Feature Request + + + +## 🔈 Motivation + + + +## 🛰 Alternatives + + + +## 📎 Additional context + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6333e57 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,27 @@ +## Description + + + +## Related Issue + + + +## Type of Change + + + +- [ ] 📚 Examples / docs / tutorials / dependencies update +- [ ] 🔧 Bug fix (non-breaking change which fixes an issue) +- [ ] 🥂 Improvement (non-breaking change which improves an existing feature) +- [ ] 🚀 New feature (non-breaking change which adds functionality) +- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to change) +- [ ] 🔐 Security fix + +## Checklist + + + +- [ ] I've read the `CODE_OF_CONDUCT.md` document. +- [ ] I've updated the code style using `make codestyle`. +- [ ] I've written tests for all new methods and classes that I created. +- [ ] I've written the docstring for all the methods and classes that I used. diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..fcca4ea --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,31 @@ +name-template: 'v$RESOLVED_VERSION 🌈' +tag-template: 'v$RESOLVED_VERSION' +categories: + - title: '🚀 Features' + labels: + - 'feature' + - 'enhancement' + - title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bugfix' + - 'bug' + - title: '🧰 Maintenance' + label: 'chore' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: patch +template: | + ## Changes + + $CHANGES diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..dc90e5a --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,17 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security +# Label to use when marking an issue as stale +staleLabel: wontfix +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..28de7f2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,38 @@ +name: Build, Test & Install + +on: + push: + branches: + - main + - develop + pull_request: + types: [opened, reopened, synchronize] + +jobs: + build: + strategy: + matrix: + os: [ubuntu-22.04, ubuntu-24.04] + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout project + uses: actions/checkout@v4.1.7 + - name: Install apt dependencies + run: | + apt update && apt install cmake -y + - name: Build project + run: | + mkdir build + cd build + cmake -DCMAKE_INSTALL_PREFIX=/usr .. + make all -j + make package -j + dpkg -i MCML*.deb + - name: Build and install DEB package + run: | + make package -j + dpkg -i MCML*.deb + - name: Install application + run: | + make install diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml new file mode 100644 index 0000000..a1f6e89 --- /dev/null +++ b/.github/workflows/greetings.yml @@ -0,0 +1,16 @@ +name: Greetings + +on: [pull_request, issues] + +jobs: + greeting: + runs-on: ubuntu-latest + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + pr-message: 'Hello @${{ github.actor }}, thank you for submitting a PR! We will respond as soon as possible.' + issue-message: | + Hello @${{ github.actor }}, thank you for your interest in our work! + + If this is a bug report, please provide screenshots and **minimum viable code to reproduce your issue**, otherwise we can not help you. diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 0000000..9f63d74 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,41 @@ +name: Release Drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - main + # pull_request event is required only for autolabeler + pull_request: + # Only following types are handled by the action, but one can default to all as well + types: [opened, reopened, synchronize] + # pull_request_target event is required for autolabeler to support PRs from forks + # pull_request_target: + # types: [opened, reopened, synchronize] + +permissions: + contents: read + +jobs: + update_release_draft: + permissions: + # write permission is required to create a github release + contents: write + # write permission is required for autolabeler + # otherwise, read permission is required at least + pull-requests: write + runs-on: ubuntu-latest + steps: + # (Optional) GitHub Enterprise requires GHE_HOST variable set + #- name: Set GHE_HOST + # run: | + # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV + + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v6 + # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml + # with: + # config-name: my-config.yml + # disable-autolabeler: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ce45a9..642eab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Adds Cpack to CMake to package .deb file +- Adds citation file. +- Adds GitHub actions, templates, etc. +- Adds logo. ### Changed -- +- Improves information on how to build the application. ### Removed @@ -28,37 +31,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added new macro definition for CUDA_SAFE_CALL_INFO to print additional information when an error occurs. - (https://git.dkfz.de/imsy/issi/mcmlgpu/-/commit/b957acc240fd0b014c1136b13811d7ec260fccdd) + (b957acc240fd0b014c1136b13811d7ec260fccdd) - MCML now expects an MCO file instead of an MCO FOLDER as input. - (https://git.dkfz.de/imsy/issi/mcmlgpu/-/commit/9689ad1fab47407d5fe649677420f9e1fd35c561) + (9689ad1fab47407d5fe649677420f9e1fd35c561) - Added conan package - (https://git.dkfz.de/imsy/issi/mcmlgpu/-/commit/a5573e170f1795ca3dfb523386557649e509a545) + (a5573e170f1795ca3dfb523386557649e509a545) - Adds editor config and pre-commit hooks - Added `-V` flag to print software version and build information ### Changed - Modified path for safe_primes installation to the same path as MCML - (https://git.dkfz.de/imsy/issi/mcmlgpu/-/commit/c310827b19735bb81cd6c9ed08d3a82acbd5feff) + (c310827b19735bb81cd6c9ed08d3a82acbd5feff) - Defined path to safeprimes relative to the executable path - (https://git.dkfz.de/imsy/issi/mcmlgpu/-/commit/91759c9207f714db4274bf5a2499684b7dce8a36) + (91759c9207f714db4274bf5a2499684b7dce8a36) ### Removed - Removed elapsed time computation as it was not used anymore. - (https://git.dkfz.de/imsy/issi/mcmlgpu/-/commit/b957acc240fd0b014c1136b13811d7ec260fccdd) + (b957acc240fd0b014c1136b13811d7ec260fccdd) - Removed deprecated information from README.md - (https://git.dkfz.de/imsy/issi/mcmlgpu/-/commit/85ada768be4a3fc447726294912af4da619c9390) + (85ada768be4a3fc447726294912af4da619c9390) - Removed dependency from the definition of __CUDA_ARCH__ - (https://git.dkfz.de/imsy/issi/mcmlgpu/-/commit/078ef4a7e0a62585347baa2c3677177b234eb26b) + (078ef4a7e0a62585347baa2c3677177b234eb26b) - Removed unused include statements. - (https://git.dkfz.de/imsy/issi/mcmlgpu/-/commit/078ef4a7e0a62585347baa2c3677177b234eb26b) + (078ef4a7e0a62585347baa2c3677177b234eb26b) ### Fixed - Fixed an issue of illegal memory access by removing duplicated memory allocation for the number of photons left in the - host state struct (https://git.dkfz.de/imsy/issi/mcmlgpu/-/commit/1eb40b22e0828ddc43ac55a5b9eee01c22d4ea08) + host state struct (1eb40b22e0828ddc43ac55a5b9eee01c22d4ea08) - Fixed path to safeprimes in init_RNG - (https://git.dkfz.de/imsy/issi/mcmlgpu/-/commit/ad34a281cc74303134c07421024bb5e3a53995b2) + (ad34a281cc74303134c07421024bb5e3a53995b2) - Fixed penetration depth calculations (issue #13). - (https://git.dkfz.de/imsy/issi/mcmlgpu/-/commit/a0c99b3b8d2659a07324dbef73cafe0b4602e005) + (a0c99b3b8d2659a07324dbef73cafe0b4602e005) ## [v0.0.3] From 74397445a17aeab6cb6c866d55f1b0e7d552779f Mon Sep 17 00:00:00 2001 From: Leonardo Ayala Date: Fri, 9 Aug 2024 13:14:44 +0200 Subject: [PATCH 08/24] Adds .cast to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0bd9852..b8d77de 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ cutil-linux executable *build* +*.cast From 080df72b1657793997880516e5f12e487888695a Mon Sep 17 00:00:00 2001 From: Leonardo Ayala Date: Fri, 9 Aug 2024 15:35:18 +0200 Subject: [PATCH 09/24] Adds CLI11 as argument parser --- CHANGELOG.md | 1 + LICENSES/cli11_license.md | 25 + src/CLI11.h | 10998 ++++++++++++++++++++++++++++++++++++ src/gpumcml.h | 26 +- src/gpumcml_io.cpp | 101 +- src/gpumcml_main.cu | 26 +- 6 files changed, 11095 insertions(+), 82 deletions(-) create mode 100644 LICENSES/cli11_license.md create mode 100644 src/CLI11.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 642eab2..e8f11bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Improves information on how to build the application. +- Integrates CLI11 as a argument parser. ### Removed diff --git a/LICENSES/cli11_license.md b/LICENSES/cli11_license.md new file mode 100644 index 0000000..715be0b --- /dev/null +++ b/LICENSES/cli11_license.md @@ -0,0 +1,25 @@ +CLI11 2.2 Copyright (c) 2017-2024 University of Cincinnati, developed by Henry +Schreiner under NSF AWARD 1414736. All rights reserved. + +Redistribution and use in source and binary forms of CLI11, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/CLI11.h b/src/CLI11.h new file mode 100644 index 0000000..8a5b4c5 --- /dev/null +++ b/src/CLI11.h @@ -0,0 +1,10998 @@ +// CLI11: Version 2.4.2 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v2.4.2 +// +// CLI11 2.4.2 Copyright (c) 2017-2024 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +// Standard combined includes: +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define CLI11_VERSION_MAJOR 2 +#define CLI11_VERSION_MINOR 4 +#define CLI11_VERSION_PATCH 2 +#define CLI11_VERSION "2.4.2" + + + + +// The following version macro is very similar to the one in pybind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if _MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + +// GCC < 10 doesn't ignore this in unevaluated contexts +#if !defined(CLI11_CPP17) || \ + (defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4) +#define CLI11_NODISCARD +#else +#define CLI11_NODISCARD [[nodiscard]] +#endif + +/** detection of rtti */ +#ifndef CLI11_USE_STATIC_RTTI +#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) +#define CLI11_USE_STATIC_RTTI 1 +#elif defined(__cpp_rtti) +#if(defined(_CPPRTTI) && _CPPRTTI == 0) +#define CLI11_USE_STATIC_RTTI 1 +#else +#define CLI11_USE_STATIC_RTTI 0 +#endif +#elif(defined(__GCC_RTTI) && __GXX_RTTI) +#define CLI11_USE_STATIC_RTTI 0 +#else +#define CLI11_USE_STATIC_RTTI 1 +#endif +#endif + +/** availability */ +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include() +// Filesystem cannot be used if targeting macOS < 10.15 +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define CLI11_HAS_FILESYSTEM 0 +#elif defined(__wasi__) +// As of wasi-sdk-14, filesystem is not implemented +#define CLI11_HAS_FILESYSTEM 0 +#else +#include +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 +#define CLI11_HAS_FILESYSTEM 1 +#elif defined(__GLIBCXX__) +// if we are using gcc and Version <9 default to no filesystem +#define CLI11_HAS_FILESYSTEM 0 +#else +#define CLI11_HAS_FILESYSTEM 1 +#endif +#else +#define CLI11_HAS_FILESYSTEM 0 +#endif +#endif +#endif +#endif + +/** availability */ +#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 +#define CLI11_HAS_CODECVT 0 +#else +#define CLI11_HAS_CODECVT 1 +#include +#endif + +/** disable deprecations */ +#if defined(__GNUC__) // GCC or clang +#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") +#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + +#elif defined(_MSC_VER) +#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push)) +#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop)) + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) + +#else +#define CLI11_DIAGNOSTIC_PUSH +#define CLI11_DIAGNOSTIC_POP + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +#endif + +/** Inline macro **/ +#ifdef CLI11_COMPILE +#define CLI11_INLINE +#else +#define CLI11_INLINE inline +#endif + + + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include // NOLINT(build/include) +#else +#include +#include +#endif + + + + +#ifdef CLI11_CPP17 +#include +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include +#include // NOLINT(build/include) +#endif // CLI11_HAS_FILESYSTEM + + + +#if defined(_WIN32) +#if !(defined(_AMD64_) || defined(_X86_) || defined(_ARM_)) +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || \ + defined(_M_AMD64) +#define _AMD64_ +#elif defined(i386) || defined(__i386) || defined(__i386__) || defined(__i386__) || defined(_M_IX86) +#define _X86_ +#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARMT) +#define _ARM_ +#elif defined(__aarch64__) || defined(_M_ARM64) +#define _ARM64_ +#elif defined(_M_ARM64EC) +#define _ARM64EC_ +#endif +#endif + +// first +#ifndef NOMINMAX +// if NOMINMAX is already defined we don't want to mess with that either way +#define NOMINMAX +#include +#undef NOMINMAX +#else +#include +#endif + +// second +#include +// third +#include +#include +#endif + + +namespace CLI { + + +/// Convert a wide string to a narrow string. +CLI11_INLINE std::string narrow(const std::wstring &str); +CLI11_INLINE std::string narrow(const wchar_t *str); +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size); + +/// Convert a narrow string to a wide string. +CLI11_INLINE std::wstring widen(const std::string &str); +CLI11_INLINE std::wstring widen(const char *str); +CLI11_INLINE std::wstring widen(const char *str, std::size_t size); + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str); +CLI11_INLINE std::wstring widen(std::string_view str); +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +/// Convert a char-string to a native path correctly. +CLI11_INLINE std::filesystem::path to_path(std::string_view str); +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { + +#if !CLI11_HAS_CODECVT +/// Attempt to set one of the acceptable unicode locales for conversion +CLI11_INLINE void set_unicode_locale() { + static const std::array unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}}; + + for(const auto &locale_name : unicode_locales) { + if(std::setlocale(LC_ALL, locale_name) != nullptr) { + return; + } + } + throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8"); +} + +template struct scope_guard_t { + F closure; + + explicit scope_guard_t(F closure_) : closure(closure_) {} + ~scope_guard_t() { closure(); } +}; + +template CLI11_NODISCARD CLI11_INLINE scope_guard_t scope_guard(F &&closure) { + return scope_guard_t{std::forward(closure)}; +} + +#endif // !CLI11_HAS_CODECVT + +CLI11_DIAGNOSTIC_PUSH +CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().to_bytes(str, str + str_size); + +#else + return std::wstring_convert>().to_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const wchar_t *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " + + std::to_string(it - str)); + } + std::string result(new_size, '\0'); + std::wcsrtombs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert>().from_bytes(str, str + str_size); + +#else + return std::wstring_convert>().from_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const char *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state); + if(new_size == static_cast(-1)) { + throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " + + std::to_string(it - str)); + } + std::wstring result(new_size, L'\0'); + std::mbsrtowcs(const_cast(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_DIAGNOSTIC_POP + +} // namespace detail + +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); } +CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); } + +CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); } +CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); } + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); } +CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); } +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE std::filesystem::path to_path(std::string_view str) { + return std::filesystem::path{ +#ifdef _WIN32 + widen(str) +#else + str +#endif // _WIN32 + }; +} +#endif // CLI11_HAS_FILESYSTEM + + + + +namespace detail { +#ifdef _WIN32 +/// Decode and return UTF-8 argv from GetCommandLineW. +CLI11_INLINE std::vector compute_win32_argv(); +#endif +} // namespace detail + + + +namespace detail { + +#ifdef _WIN32 +CLI11_INLINE std::vector compute_win32_argv() { + std::vector result; + int argc = 0; + + auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; + // NOLINTBEGIN(*-avoid-c-arrays) + auto wargv = std::unique_ptr(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); + // NOLINTEND(*-avoid-c-arrays) + + if(wargv == nullptr) { + throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); + } + + result.reserve(static_cast(argc)); + for(size_t i = 0; i < static_cast(argc); ++i) { + result.push_back(narrow(wargv[i])); + } + + return result; +} +#endif + +} // namespace detail + + + + +/// Include the items in this namespace to get free conversion of enums to/from streams. +/// (This is available inside CLI as well, so CLI11 will use this without a using statement). +namespace enums { + +/// output streaming for enumerations +template ::value>::type> +std::ostream &operator<<(std::ostream &in, const T &item) { + // make sure this is out of the detail namespace otherwise it won't be found when needed + return in << static_cast::type>(item); +} + +} // namespace enums + +/// Export to CLI namespace +using enums::operator<<; + +namespace detail { +/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not +/// produce overflow for some expected uses +constexpr int expected_max_vector_size{1 << 29}; +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +CLI11_INLINE std::vector split(const std::string &s, char delim); + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + if(beg != end) + s << *beg++; + while(beg != end) { + s << delim << *beg++; + } + return s.str(); +} + +/// Simple function to join a string from processed elements +template ::value>::type> +std::string join(const T &v, Callable func, std::string delim = ",") { + std::ostringstream s; + auto beg = std::begin(v); + auto end = std::end(v); + auto loc = s.tellp(); + while(beg != end) { + auto nloc = s.tellp(); + if(nloc > loc) { + s << delim; + loc = nloc; + } + s << func(*beg++); + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(std::size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +CLI11_INLINE std::string <rim(std::string &str); + +/// Trim anything from left of string +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter); + +/// Trim whitespace from right of string +CLI11_INLINE std::string &rtrim(std::string &str); + +/// Trim anything from right of string +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter); + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// remove quotes at the front and back of a string either '"' or '\'' +CLI11_INLINE std::string &remove_quotes(std::string &str); + +/// remove quotes from all elements of a string vector and process escaped components +CLI11_INLINE void remove_quotes(std::vector &args); + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input); + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} +/// Print a two part "help" string +CLI11_INLINE std::ostream & +format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid); + +/// Print subcommand aliases +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid); + +/// Verify the first character of an option +/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with +template bool valid_first_char(T c) { + return ((c != '-') && (static_cast(c) > 33)); // space and '!' not allowed +} + +/// Verify following characters of an option +template bool valid_later_char(T c) { + // = and : are value separators, { has special meaning for option defaults, + // and control codes other than tab would just be annoying to deal with in many places allowing space here has too + // much potential for inadvertent entry errors and bugs + return ((c != '=') && (c != ':') && (c != '{') && ((static_cast(c) > 32) || c == '\t')); +} + +/// Verify an option/subcommand name +CLI11_INLINE bool valid_name_string(const std::string &str); + +/// Verify an app name +inline bool valid_alias_name_string(const std::string &str) { + static const std::string badChars(std::string("\n") + '\0'); + return (str.find_first_of(badChars) == std::string::npos); +} + +/// check if a string is a container segment separator (empty or "%%") +inline bool is_separator(const std::string &str) { + static const std::string sep("%%"); + return (str.empty() || str == sep); +} + +/// Verify that str consists of letters only +inline bool isalpha(const std::string &str) { + return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// remove underscores from a string +inline std::string remove_underscore(std::string str) { + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; +} + +/// Find and replace a substring with another substring +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to); + +/// check if the flag definitions has possible false flags +inline bool has_default_flag_values(const std::string &flags) { + return (flags.find_first_of("{!") != std::string::npos); +} + +CLI11_INLINE void remove_default_flag_values(std::string &flags); + +/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores +CLI11_INLINE std::ptrdiff_t find_member(std::string name, + const std::vector names, + bool ignore_case = false, + bool ignore_underscore = false); + +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the +/// trigger and returns the position in the string to search for the next trigger string +template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { + std::size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + +/// close a sequence of characters indicated by a closure character. Brackets allows sub sequences +/// recognized bracket sequences include "'`[(<{ other closure characters are assumed to be literal strings +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char); + +/// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket +CLI11_INLINE std::vector split_up(std::string str, char delimiter = '\0'); + +/// get the value of an environmental variable or empty string if empty +CLI11_INLINE std::string get_environment_value(const std::string &env_name); + +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset); + +/// @brief detect if a string has escapable characters +/// @param str the string to do the detection on +/// @return true if the string has escapable characters +CLI11_INLINE bool has_escapable_character(const std::string &str); + +/// @brief escape all escapable characters +/// @param str the string to escape +/// @return a string with the escapble characters escaped with '\' +CLI11_INLINE std::string add_escaped_characters(const std::string &str); + +/// @brief replace the escaped characters with their equivalent +CLI11_INLINE std::string remove_escaped_characters(const std::string &str); + +/// generate a string with all non printable characters escaped to hex codes +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape); + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); + +/// extract an escaped binary_string +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string); + +/// process a quoted string, remove the quotes and if appropriate handle escaped characters +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char = '\"', char literal_char = '\''); + +} // namespace detail + + + + +namespace detail { +CLI11_INLINE std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) { + elems.emplace_back(); + } else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +CLI11_INLINE std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +CLI11_INLINE std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +CLI11_INLINE std::string &remove_quotes(std::string &str) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'' || str.front() == '`')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string &remove_outer(std::string &str, char key) { + if(str.length() > 1 && (str.front() == key)) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +CLI11_INLINE std::ostream & +format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) { + name = " " + name; + out << std::setw(static_cast(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << "\n" << std::setw(static_cast(wid)) << ""; + for(const char c : description) { + out.put(c); + if(c == '\n') { + out << std::setw(static_cast(wid)) << ""; + } + } + } + out << "\n"; + return out; +} + +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid) { + if(!aliases.empty()) { + out << std::setw(static_cast(wid)) << " aliases: "; + bool front = true; + for(const auto &alias : aliases) { + if(!front) { + out << ", "; + } else { + front = false; + } + out << detail::fix_newlines(" ", alias); + } + out << "\n"; + } + return out; +} + +CLI11_INLINE bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) { + return false; + } + auto e = str.end(); + for(auto c = str.begin() + 1; c != e; ++c) + if(!valid_later_char(*c)) + return false; + return true; +} + +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) { + + std::size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +CLI11_INLINE void remove_default_flag_values(std::string &flags) { + auto loc = flags.find_first_of('{', 2); + while(loc != std::string::npos) { + auto finish = flags.find_first_of("},", loc + 1); + if((finish != std::string::npos) && (flags[finish] == '}')) { + flags.erase(flags.begin() + static_cast(loc), + flags.begin() + static_cast(finish) + 1); + } + loc = flags.find_first_of('{', loc + 1); + } + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); +} + +CLI11_INLINE std::ptrdiff_t +find_member(std::string name, const std::vector names, bool ignore_case, bool ignore_underscore) { + auto it = std::end(names); + if(ignore_case) { + if(ignore_underscore) { + name = detail::to_lower(detail::remove_underscore(name)); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(detail::remove_underscore(local_name)) == name; + }); + } else { + name = detail::to_lower(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::to_lower(local_name) == name; + }); + } + + } else if(ignore_underscore) { + name = detail::remove_underscore(name); + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { + return detail::remove_underscore(local_name) == name; + }); + } else { + it = std::find(std::begin(names), std::end(names), name); + } + + return (it != std::end(names)) ? (it - std::begin(names)) : (-1); +} + +static const std::string escapedChars("\b\t\n\f\r\"\\"); +static const std::string escapedCharsCode("btnfr\"\\"); +static const std::string bracketChars{"\"'`[(<{"}; +static const std::string matchBracketChars("\"'`])>}"); + +CLI11_INLINE bool has_escapable_character(const std::string &str) { + return (str.find_first_of(escapedChars) != std::string::npos); +} + +CLI11_INLINE std::string add_escaped_characters(const std::string &str) { + std::string out; + out.reserve(str.size() + 4); + for(char s : str) { + auto sloc = escapedChars.find_first_of(s); + if(sloc != std::string::npos) { + out.push_back('\\'); + out.push_back(escapedCharsCode[sloc]); + } else { + out.push_back(s); + } + } + return out; +} + +CLI11_INLINE std::uint32_t hexConvert(char hc) { + int hcode{0}; + if(hc >= '0' && hc <= '9') { + hcode = (hc - '0'); + } else if(hc >= 'A' && hc <= 'F') { + hcode = (hc - 'A' + 10); + } else if(hc >= 'a' && hc <= 'f') { + hcode = (hc - 'a' + 10); + } else { + hcode = -1; + } + return static_cast(hcode); +} + +CLI11_INLINE char make_char(std::uint32_t code) { return static_cast(static_cast(code)); } + +CLI11_INLINE void append_codepoint(std::string &str, std::uint32_t code) { + if(code < 0x80) { // ascii code equivalent + str.push_back(static_cast(code)); + } else if(code < 0x800) { // \u0080 to \u07FF + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + str.push_back(make_char(0xC0 | code >> 6)); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x10000) { // U+0800...U+FFFF + if(0xD800 <= code && code <= 0xDFFF) { + throw std::invalid_argument("[0xD800, 0xDFFF] are not valid UTF-8."); + } + // 1110yyyy 10yxxxxx 10xxxxxx + str.push_back(make_char(0xE0 | code >> 12)); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x110000) { // U+010000 ... U+10FFFF + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + str.push_back(make_char(0xF0 | code >> 18)); + str.push_back(make_char(0x80 | (code >> 12 & 0x3F))); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } +} + +CLI11_INLINE std::string remove_escaped_characters(const std::string &str) { + + std::string out; + out.reserve(str.size()); + for(auto loc = str.begin(); loc < str.end(); ++loc) { + if(*loc == '\\') { + if(str.end() - loc < 2) { + throw std::invalid_argument("invalid escape sequence " + str); + } + auto ecloc = escapedCharsCode.find_first_of(*(loc + 1)); + if(ecloc != std::string::npos) { + out.push_back(escapedChars[ecloc]); + ++loc; + } else if(*(loc + 1) == 'u') { + // must have 4 hex characters + if(str.end() - loc < 6) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16}; + for(int ii = 2; ii < 6; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 5; + } else if(*(loc + 1) == 'U') { + // must have 8 hex characters + if(str.end() - loc < 10) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16 * 16 * 16 * 16 * 16}; + for(int ii = 2; ii < 10; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 9; + } else if(*(loc + 1) == '0') { + out.push_back('\0'); + ++loc; + } else { + throw std::invalid_argument(std::string("unrecognized escape sequence \\") + *(loc + 1) + " in " + str); + } + } else { + out.push_back(*loc); + } + } + return out; +} + +CLI11_INLINE std::size_t close_string_quote(const std::string &str, std::size_t start, char closure_char) { + std::size_t loc{0}; + for(loc = start + 1; loc < str.size(); ++loc) { + if(str[loc] == closure_char) { + break; + } + if(str[loc] == '\\') { + // skip the next character for escaped sequences + ++loc; + } + } + return loc; +} + +CLI11_INLINE std::size_t close_literal_quote(const std::string &str, std::size_t start, char closure_char) { + auto loc = str.find_first_of(closure_char, start + 1); + return (loc != std::string::npos ? loc : str.size()); +} + +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char) { + + auto bracket_loc = matchBracketChars.find(closure_char); + switch(bracket_loc) { + case 0: + return close_string_quote(str, start, closure_char); + case 1: + case 2: + case std::string::npos: + return close_literal_quote(str, start, closure_char); + default: + break; + } + + std::string closures(1, closure_char); + auto loc = start + 1; + + while(loc < str.size()) { + if(str[loc] == closures.back()) { + closures.pop_back(); + if(closures.empty()) { + return loc; + } + } + bracket_loc = bracketChars.find(str[loc]); + if(bracket_loc != std::string::npos) { + switch(bracket_loc) { + case 0: + loc = close_string_quote(str, loc, str[loc]); + break; + case 1: + case 2: + loc = close_literal_quote(str, loc, str[loc]); + break; + default: + closures.push_back(matchBracketChars[bracket_loc]); + break; + } + } + ++loc; + } + if(loc > str.size()) { + loc = str.size(); + } + return loc; +} + +CLI11_INLINE std::vector split_up(std::string str, char delimiter) { + + auto find_ws = [delimiter](char ch) { + return (delimiter == '\0') ? std::isspace(ch, std::locale()) : (ch == delimiter); + }; + trim(str); + + std::vector output; + while(!str.empty()) { + if(bracketChars.find_first_of(str[0]) != std::string::npos) { + auto bracketLoc = bracketChars.find_first_of(str[0]); + auto end = close_sequence(str, 0, matchBracketChars[bracketLoc]); + if(end >= str.size()) { + output.push_back(std::move(str)); + str.clear(); + } else { + output.push_back(str.substr(0, end + 1)); + if(end + 2 < str.size()) { + str = str.substr(end + 2); + } else { + str.clear(); + } + } + + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it + 1, str.end()); + } else { + output.push_back(str); + str.clear(); + } + } + trim(str); + } + return output; +} + +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape) { + // s is our escaped output string + std::string escaped_string{}; + // loop through all characters + for(char c : string_to_escape) { + // check if a given character is printable + // the cast is necessary to avoid undefined behaviour + if(isprint(static_cast(c)) == 0) { + std::stringstream stream; + // if the character is not printable + // we'll convert it to a hex string using a stringstream + // note that since char is signed we have to cast it to unsigned first + stream << std::hex << static_cast(static_cast(c)); + std::string code = stream.str(); + escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code; + + } else { + escaped_string.push_back(c); + } + } + if(escaped_string != string_to_escape) { + auto sqLoc = escaped_string.find('\''); + while(sqLoc != std::string::npos) { + escaped_string.replace(sqLoc, sqLoc + 1, "\\x27"); + sqLoc = escaped_string.find('\''); + } + escaped_string.insert(0, "'B\"("); + escaped_string.push_back(')'); + escaped_string.push_back('"'); + escaped_string.push_back('\''); + } + return escaped_string; +} + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) { + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + return true; + } + return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0); +} + +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) { + std::size_t start{0}; + std::size_t tail{0}; + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + start = 3; + tail = 2; + } else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) { + start = 4; + tail = 3; + } + + if(start == 0) { + return escaped_string; + } + std::string outstring; + + outstring.reserve(ssize - start - tail); + std::size_t loc = start; + while(loc < ssize - tail) { + // ssize-2 to skip )" at the end + if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) { + auto c1 = escaped_string[loc + 2]; + auto c2 = escaped_string[loc + 3]; + + std::uint32_t res1 = hexConvert(c1); + std::uint32_t res2 = hexConvert(c2); + if(res1 <= 0x0F && res2 <= 0x0F) { + loc += 4; + outstring.push_back(static_cast(res1 * 16 + res2)); + continue; + } + } + outstring.push_back(escaped_string[loc]); + ++loc; + } + return outstring; +} + +CLI11_INLINE void remove_quotes(std::vector &args) { + for(auto &arg : args) { + if(arg.front() == '\"' && arg.back() == '\"') { + remove_quotes(arg); + // only remove escaped for string arguments not literal strings + arg = remove_escaped_characters(arg); + } else { + remove_quotes(arg); + } + } +} + +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char, char literal_char) { + if(str.size() <= 1) { + return false; + } + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + return true; + } + if(str.front() == string_char && str.back() == string_char) { + detail::remove_outer(str, string_char); + if(str.find_first_of('\\') != std::string::npos) { + str = detail::remove_escaped_characters(str); + } + return true; + } + if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { + detail::remove_outer(str, str.front()); + return true; + } + return false; +} + +std::string get_environment_value(const std::string &env_name) { + char *buffer = nullptr; + std::string ename_string; + +#ifdef _MSC_VER + // Windows version + std::size_t sz = 0; + if(_dupenv_s(&buffer, &sz, env_name.c_str()) == 0 && buffer != nullptr) { + ename_string = std::string(buffer); + free(buffer); + } +#else + // This also works on Windows, but gives a warning + buffer = std::getenv(env_name.c_str()); + if(buffer != nullptr) { + ename_string = std::string(buffer); + } +#endif + return ename_string; +} + +} // namespace detail + + + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, ExitCodes exit_code) \ + : parent(std::move(ename), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; + + public: + CLI11_NODISCARD int get_exit_code() const { return actual_exit_code; } + + CLI11_NODISCARD std::string get_name() const { return error_name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString MissingDash(std::string name) { + return BadNameString("Long names strings require 2 dashes " + name); + } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString BadPositionalName(std::string name) { + return BadNameString("Invalid positional Name: " + name); + } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return {name + " requires " + other, ExitCodes::OptionAlreadyAdded}; + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return {name + " excludes " + other, ExitCodes::OptionAlreadyAdded}; + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public Success { + CLI11_ERROR_DEF(Success, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually something like --help-all on command line +class CallForAllHelp : public Success { + CLI11_ERROR_DEF(Success, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// -v or --version on command line +class CallForVersion : public Success { + CLI11_ERROR_DEF(Success, CallForVersion) + CallForVersion() + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(std::size_t min_subcom) { + if(min_subcom == 1) { + return RequiredError("A subcommand"); + } + return {"Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError}; + } + static RequiredError + Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) { + if((min_option == 1) && (max_option == 1) && (used == 0)) + return RequiredError("Exactly 1 option from [" + option_list + "]"); + if((min_option == 1) && (max_option == 1) && (used > 1)) { + return {"Exactly 1 option from [" + option_list + "] is required but " + std::to_string(used) + + " were given", + ExitCodes::RequiredError}; + } + if((min_option == 1) && (used == 0)) + return RequiredError("At least 1 option from [" + option_list + "]"); + if(used < min_option) { + return {"Requires at least " + std::to_string(min_option) + " options used but only " + + std::to_string(used) + " were given from [" + option_list + "]", + ExitCodes::RequiredError}; + } + if(max_option == 1) + return {"Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError}; + + return {"Requires at most " + std::to_string(max_option) + " options be used but " + std::to_string(used) + + " were given from [" + option_list + "]", + ExitCodes::RequiredError}; + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, std::size_t received) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(received)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(received)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { + return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } + static ArgumentMismatch FlagOverride(std::string name) { + return ArgumentMismatch(name + " was given a disallowed flag override"); + } + static ArgumentMismatch PartialType(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) + + " required for each element"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} + ExtrasError(const std::string &name, std::vector args) + : ExtrasError(name, + (args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a non-existent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + + + + +// Type tools + +// Utilities for type enabling +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; +} // namespace detail + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. +template using enable_if_t = typename std::enable_if::type; + +/// A copy of std::void_t from C++17 (helper for C++11 and C++14) +template struct make_void { + using type = void; +}; + +/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine +template using void_t = typename make_void::type; + +/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine +template using conditional_t = typename std::conditional::type; + +/// Check to see if something is bool (fail check by default) +template struct is_bool : std::false_type {}; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool : std::true_type {}; + +/// Check to see if something is a shared pointer +template struct is_shared_ptr : std::false_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is a shared pointer (True if really a shared pointer) +template struct is_shared_ptr> : std::true_type {}; + +/// Check to see if something is copyable pointer +template struct is_copyable_ptr { + static bool const value = is_shared_ptr::value || std::is_pointer::value; +}; + +/// This can be specialized to override the type deduction for IsMember. +template struct IsMemberType { + using type = T; +}; + +/// The main custom type needed here is const char * should be a string. +template <> struct IsMemberType { + using type = std::string; +}; + +namespace adl_detail { +/// Check for existence of user-supplied lexical_cast. +/// +/// This struct has to be in a separate namespace so that it doesn't see our lexical_cast overloads in CLI::detail. +/// Standard says it shouldn't see them if it's defined before the corresponding lexical_cast declarations, but this +/// requires a working implementation of two-phase lookup, and not all compilers can boast that (msvc, ahem). +template class is_lexical_castable { + template + static auto test(int) -> decltype(lexical_cast(std::declval(), std::declval()), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; +} // namespace adl_detail + +namespace detail { + +// These are utilities for IsMember and other transforming objects + +/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that +/// pointer_traits be valid. + +/// not a pointer +template struct element_type { + using type = T; +}; + +template struct element_type::value>::type> { + using type = typename std::pointer_traits::element_type; +}; + +/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of +/// the container +template struct element_value_type { + using type = typename element_type::type::value_type; +}; + +/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. +template struct pair_adaptor : std::false_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::forward(pair_value)) { + return std::forward(pair_value); + } +}; + +/// Adaptor for map-like structure (true version, must have key_type and mapped_type). +/// This wraps a mapped container in a few utilities access it in a general way. +template +struct pair_adaptor< + T, + conditional_t, void>> + : std::true_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; + + /// Get the first value (really just the underlying value) + template static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward(pair_value))) { + return std::get<0>(std::forward(pair_value)); + } + /// Get the second value (really just the underlying value) + template static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward(pair_value))) { + return std::get<1>(std::forward(pair_value)); + } +}; + +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be +// suppressed +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif +// check for constructibility from a specific type and copy assignable used in the parse detection +template class is_direct_constructible { + template + static auto test(int, std::true_type) -> decltype( +// NVCC warns about narrowing conversions here +#ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_suppress 2361 +#else +#pragma diag_suppress 2361 +#endif +#endif + TT{std::declval()} +#ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_default 2361 +#else +#pragma diag_default 2361 +#endif +#endif + , + std::is_move_assignable()); + + template static auto test(int, std::false_type) -> std::false_type; + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0, typename std::is_constructible::type()))::value; +}; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +// Check for output streamability +// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream + +template class is_ostreamable { + template + static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for input streamability +template class is_istreamable { + template + static auto test(int) -> decltype(std::declval() >> std::declval(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Check for complex +template class is_complex { + template + static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), std::true_type()); + + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Templated operation to get a value from a stream +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string &istring, T &obj) { + std::istringstream is; + is.str(istring); + is >> obj; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +template ::value, detail::enabler> = detail::dummy> +bool from_stream(const std::string & /*istring*/, T & /*obj*/) { + return false; +} + +// check to see if an object is a mutable container (fail by default) +template struct is_mutable_container : std::false_type {}; + +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed +/// from a std::string +template +struct is_mutable_container< + T, + conditional_t().end()), + decltype(std::declval().clear()), + decltype(std::declval().insert(std::declval().end())>(), + std::declval()))>, + void>> : public conditional_t::value || + std::is_constructible::value, + std::false_type, + std::true_type> {}; + +// check to see if an object is a mutable container (fail by default) +template struct is_readable_container : std::false_type {}; + +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end +/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from +/// a std::string +template +struct is_readable_container< + T, + conditional_t().end()), decltype(std::declval().begin())>, void>> + : public std::true_type {}; + +// check to see if an object is a wrapper (fail by default) +template struct is_wrapper : std::false_type {}; + +// check if an object is a wrapper (it has a value_type defined) +template +struct is_wrapper, void>> : public std::true_type {}; + +// Check for tuple like types, as in classes with a tuple_size type trait +template class is_tuple_like { + template + // static auto test(int) + // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); + static auto test(int) -> decltype(std::tuple_size::type>::value, std::true_type{}); + template static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; +}; + +/// Convert an object to a string (directly forward if this can become a string) +template ::value, detail::enabler> = detail::dummy> +auto to_string(T &&value) -> decltype(std::forward(value)) { + return std::forward(value); +} + +/// Construct a string from the object +template ::value && !std::is_convertible::value, + detail::enabler> = detail::dummy> +std::string to_string(const T &value) { + return std::string(value); // NOLINT(google-readability-casting) +} + +/// Convert an object to a string (streaming must be supported for that type) +template ::value && !std::is_constructible::value && + is_ostreamable::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&value) { + std::stringstream stream; + stream << value; + return stream.str(); +} + +/// If conversion is not supported, return an empty string (streaming is not supported for that type) +template ::value && !is_ostreamable::value && + !is_readable_container::type>::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&) { + return {}; +} + +/// convert a readable container to a string +template ::value && !is_ostreamable::value && + is_readable_container::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&variable) { + auto cval = variable.begin(); + auto end = variable.end(); + if(cval == end) { + return {"{}"}; + } + std::vector defaults; + while(cval != end) { + defaults.emplace_back(CLI::detail::to_string(*cval)); + ++cval; + } + return {"[" + detail::join(defaults) + "]"}; +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +auto checked_to_string(T &&value) -> decltype(to_string(std::forward(value))) { + return to_string(std::forward(value)); +} + +/// special template overload +template ::value, detail::enabler> = detail::dummy> +std::string checked_to_string(T &&) { + return std::string{}; +} +/// get a string as a convertible value for arithmetic types +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(value); +} +/// get a string as a convertible value for enumerations +template ::value, detail::enabler> = detail::dummy> +std::string value_string(const T &value) { + return std::to_string(static_cast::type>(value)); +} +/// for other types just use the regular to_string function +template ::value && !std::is_arithmetic::value, detail::enabler> = detail::dummy> +auto value_string(const T &value) -> decltype(to_string(value)) { + return to_string(value); +} + +/// template to get the underlying value type if it exists or use a default +template struct wrapped_type { + using type = def; +}; + +/// Type size for regular object types that do not look like a tuple +template struct wrapped_type::value>::type> { + using type = typename T::value_type; +}; + +/// This will only trigger for actual void type +template struct type_count_base { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_base::value && !is_mutable_container::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// the base tuple size +template +struct type_count_base::value && !is_mutable_container::value>::type> { + static constexpr int value{std::tuple_size::value}; +}; + +/// Type count base for containers is the type_count_base of the individual element +template struct type_count_base::value>::type> { + static constexpr int value{type_count_base::value}; +}; + +/// Set of overloads to get the type size of an object + +/// forward declare the subtype_count structure +template struct subtype_count; + +/// forward declare the subtype_count_min structure +template struct subtype_count_min; + +/// This will only trigger for actual void type +template struct type_count { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count::value && !is_tuple_like::value && !is_complex::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count::value>::type> { + static constexpr int value{2}; +}; + +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template struct type_count::value>::type> { + static constexpr int value{subtype_count::value}; +}; + +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) +template +struct type_count::value && !is_complex::value && !is_tuple_like::value && + !is_mutable_container::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size() { + return subtype_count::type>::value + tuple_type_size(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count::value>::type> { + static constexpr int value{tuple_type_size()}; +}; + +/// definition of subtype count +template struct subtype_count { + static constexpr int value{is_mutable_container::value ? expected_max_vector_size : type_count::value}; +}; + +/// This will only trigger for actual void type +template struct type_count_min { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_min< + T, + typename std::enable_if::value && !is_tuple_like::value && !is_wrapper::value && + !is_complex::value && !std::is_void::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count_min::value>::type> { + static constexpr int value{1}; +}; + +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template +struct type_count_min< + T, + typename std::enable_if::value && !is_complex::value && !is_tuple_like::value>::type> { + static constexpr int value{subtype_count_min::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size_min() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size_min() { + return subtype_count_min::type>::value + tuple_type_size_min(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count_min::value>::type> { + static constexpr int value{tuple_type_size_min()}; +}; + +/// definition of subtype count +template struct subtype_count_min { + static constexpr int value{is_mutable_container::value + ? ((type_count::value < expected_max_vector_size) ? type_count::value : 0) + : type_count_min::value}; +}; + +/// This will only trigger for actual void type +template struct expected_count { + static const int value{0}; +}; + +/// For most types the number of expected items is 1 +template +struct expected_count::value && !is_wrapper::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; +/// number of expected items in a vector +template struct expected_count::value>::type> { + static constexpr int value{expected_max_vector_size}; +}; + +/// number of expected items in a vector +template +struct expected_count::value && is_wrapper::value>::type> { + static constexpr int value{expected_count::value}; +}; + +// Enumeration of the different supported categorizations of objects +enum class object_category : int { + char_value = 1, + integral_value = 2, + unsigned_integral = 4, + enumeration = 6, + boolean_value = 8, + floating_point = 10, + number_constructible = 12, + double_constructible = 14, + integer_constructible = 16, + // string like types + string_assignable = 23, + string_constructible = 24, + wstring_assignable = 25, + wstring_constructible = 26, + other = 45, + // special wrapper or container types + wrapper_value = 50, + complex_number = 60, + tuple_value = 70, + container_value = 80, + +}; + +/// Set of overloads to classify an object according to type + +/// some type that is not otherwise recognized +template struct classify_object { + static constexpr object_category value{object_category::other}; +}; + +/// Signed integers +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_same::value && std::is_signed::value && + !is_bool::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::integral_value}; +}; + +/// Unsigned integers +template +struct classify_object::value && std::is_unsigned::value && + !std::is_same::value && !is_bool::value>::type> { + static constexpr object_category value{object_category::unsigned_integral}; +}; + +/// single character values +template +struct classify_object::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::char_value}; +}; + +/// Boolean values +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::boolean_value}; +}; + +/// Floats +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::floating_point}; +}; +#if defined _MSC_VER +// in MSVC wstring should take precedence if available this isn't as useful on other compilers due to the broader use of +// utf-8 encoding +#define WIDE_STRING_CHECK \ + !std::is_assignable::value && !std::is_constructible::value +#define STRING_CHECK true +#else +#define WIDE_STRING_CHECK true +#define STRING_CHECK !std::is_assignable::value && !std::is_constructible::value +#endif + +/// String and similar direct assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && WIDE_STRING_CHECK && + std::is_assignable::value>::type> { + static constexpr object_category value{object_category::string_assignable}; +}; + +/// String and similar constructible and copy assignment +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + WIDE_STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::string_constructible}; +}; + +/// Wide strings +template +struct classify_object::value && !std::is_integral::value && + STRING_CHECK && std::is_assignable::value>::type> { + static constexpr object_category value{object_category::wstring_assignable}; +}; + +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && (type_count::value == 1) && + STRING_CHECK && std::is_constructible::value>::type> { + static constexpr object_category value{object_category::wstring_constructible}; +}; + +/// Enumerations +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::enumeration}; +}; + +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::complex_number}; +}; + +/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, +/// vectors, and enumerations +template struct uncommon_type { + using type = typename std::conditional< + !std::is_floating_point::value && !std::is_integral::value && + !std::is_assignable::value && !std::is_constructible::value && + !std::is_assignable::value && !std::is_constructible::value && + !is_complex::value && !is_mutable_container::value && !std::is_enum::value, + std::true_type, + std::false_type>::type; + static constexpr bool value = type::value; +}; + +/// wrapper type +template +struct classify_object::value && is_wrapper::value && + !is_tuple_like::value && uncommon_type::value)>::type> { + static constexpr object_category value{object_category::wrapper_value}; +}; + +/// Assignable from double or int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::number_constructible}; +}; + +/// Assignable from int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && !is_direct_constructible::value && + is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::integer_constructible}; +}; + +/// Assignable from double +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && is_direct_constructible::value && + !is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::double_constructible}; +}; + +/// Tuple type +template +struct classify_object< + T, + typename std::enable_if::value && + ((type_count::value >= 2 && !is_wrapper::value) || + (uncommon_type::value && !is_direct_constructible::value && + !is_direct_constructible::value) || + (uncommon_type::value && type_count::value >= 2))>::type> { + static constexpr object_category value{object_category::tuple_value}; + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be + // constructed from just the first element so tuples of can be constructed from a string, which + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out + // those cases that are caught by other object classifications +}; + +/// container type +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::container_value}; +}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "CHAR"; +} + +template ::value == object_category::integral_value || + classify_object::value == object_category::integer_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value == object_category::floating_point || + classify_object::value == object_category::number_constructible || + classify_object::value == object_category::double_constructible, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// Print name for enumeration types +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "ENUM"; +} + +/// Print name for enumeration types +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "BOOLEAN"; +} + +/// Print name for enumeration types +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "COMPLEX"; +} + +/// Print for all other types +template ::value >= object_category::string_assignable && + classify_object::value <= object_category::other, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} +/// typename for tuple value +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Generate type name for a wrapper or container value +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Print name for single element tuple types +template ::value == object_category::tuple_value && type_count_base::value == 1, + detail::enabler> = detail::dummy> +inline std::string type_name() { + return type_name::type>::type>(); +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_name() { + return std::string{}; +} + +/// Recursively generate the tuple type name +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_name() { + auto str = std::string{type_name::type>::type>()} + ',' + + tuple_name(); + if(str.back() == ',') + str.pop_back(); + return str; +} + +/// Print type name for tuples with 2 or more elements +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler>> +inline std::string type_name() { + auto tname = std::string(1, '[') + tuple_name(); + tname.push_back(']'); + return tname; +} + +/// get the type name for a type that has a value_type member +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler>> +inline std::string type_name() { + return type_name(); +} + +// Lexical cast + +/// Convert to an unsigned integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty() || input.front() == '-') { + return false; + } + char *val{nullptr}; + errno = 0; + std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + val = nullptr; + std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0); + if(val == (input.c_str() + input.size())) { + output = (output_sll < 0) ? static_cast(0) : static_cast(output_sll); + return (static_cast(output) == output_sll); + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + return false; +} + +/// Convert to a signed integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + errno = 0; + std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + if(input == "true") { + // this is to deal with a few oddities with flags and wrapper int types + output = static_cast(1); + return true; + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast(output_ll); + return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + } + return false; +} + +/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed +inline std::int64_t to_flag_value(std::string val) noexcept { + static const std::string trueString("true"); + static const std::string falseString("false"); + if(val == trueString) { + return 1; + } + if(val == falseString) { + return -1; + } + val = detail::to_lower(val); + std::int64_t ret = 0; + if(val.size() == 1) { + if(val[0] >= '1' && val[0] <= '9') { + return (static_cast(val[0]) - '0'); + } + switch(val[0]) { + case '0': + case 'f': + case 'n': + case '-': + ret = -1; + break; + case 't': + case 'y': + case '+': + ret = 1; + break; + default: + errno = EINVAL; + return -1; + } + return ret; + } + if(val == trueString || val == "on" || val == "yes" || val == "enable") { + ret = 1; + } else if(val == falseString || val == "off" || val == "no" || val == "disable") { + ret = -1; + } else { + char *loc_ptr{nullptr}; + ret = std::strtoll(val.c_str(), &loc_ptr, 0); + if(loc_ptr != (val.c_str() + val.size()) && errno == 0) { + errno = EINVAL; + } + } + return ret; +} + +/// Integer conversion +template ::value == object_category::integral_value || + classify_object::value == object_category::unsigned_integral, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return integral_conversion(input, output); +} + +/// char values +template ::value == object_category::char_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.size() == 1) { + output = static_cast(input[0]); + return true; + } + return integral_conversion(input, output); +} + +/// Boolean values +template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + errno = 0; + auto out = to_flag_value(input); + if(errno == 0) { + output = (out > 0); + } else if(errno == ERANGE) { + output = (input[0] != '-'); + } else { + return false; + } + return true; +} + +/// Floats +template ::value == object_category::floating_point, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.empty()) { + return false; + } + char *val = nullptr; + auto output_ld = std::strtold(input.c_str(), &val); + output = static_cast(output_ld); + if(val == (input.c_str() + input.size())) { + return true; + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return lexical_cast(nstring, output); + } + return false; +} + +/// complex +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + using XC = typename wrapped_type::type; + XC x{0.0}, y{0.0}; + auto str1 = input; + bool worked = false; + auto nloc = str1.find_last_of("+-"); + if(nloc != std::string::npos && nloc > 0) { + worked = lexical_cast(str1.substr(0, nloc), x); + str1 = str1.substr(nloc); + if(str1.back() == 'i' || str1.back() == 'j') + str1.pop_back(); + worked = worked && lexical_cast(str1, y); + } else { + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + worked = lexical_cast(str1, y); + x = XC{0}; + } else { + worked = lexical_cast(str1, x); + y = XC{0}; + } + } + if(worked) { + output = T{x, y}; + return worked; + } + return from_stream(input, output); +} + +/// String and similar direct assignment +template ::value == object_category::string_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = input; + return true; +} + +/// String and similar constructible and copy assignment +template < + typename T, + enable_if_t::value == object_category::string_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T(input); + return true; +} + +/// Wide strings +template < + typename T, + enable_if_t::value == object_category::wstring_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = widen(input); + return true; +} + +template < + typename T, + enable_if_t::value == object_category::wstring_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T{widen(input)}; + return true; +} + +/// Enumerations +template ::value == object_category::enumeration, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename std::underlying_type::type val; + if(!integral_conversion(input, val)) { + return false; + } + output = static_cast(val); + return true; +} + +/// wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return from_stream(input, output); +} + +template ::value == object_category::wrapper_value && + !std::is_assignable::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Assignable from double or int +template < + typename T, + enable_if_t::value == object_category::number_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + + double dval = 0.0; + if(lexical_cast(input, dval)) { + output = T{dval}; + return true; + } + + return from_stream(input, output); +} + +/// Assignable from int +template < + typename T, + enable_if_t::value == object_category::integer_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { + output = T(val); + return true; + } + return from_stream(input, output); +} + +/// Assignable from double +template < + typename T, + enable_if_t::value == object_category::double_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + double val = 0.0; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + +/// Non-string convertible from an int +template ::value == object_category::other && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val = 0; + if(integral_conversion(input, val)) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif + // with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style + // so will most likely still work + output = val; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return true; + } + // LCOV_EXCL_START + // This version of cast is only used for odd cases in an older compilers the fail over + // from_stream is tested elsewhere an not relevant for coverage here + return from_stream(input, output); + // LCOV_EXCL_STOP +} + +/// Non-string parsable by a stream +template ::value == object_category::other && !std::is_assignable::value && + is_istreamable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return from_stream(input, output); +} + +/// Fallback overload that prints a human-readable error for types that we don't recognize and that don't have a +/// user-supplied lexical_cast overload. +template ::value == object_category::other && !std::is_assignable::value && + !is_istreamable::value && !adl_detail::is_lexical_castable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string & /*input*/, T & /*output*/) { + static_assert(!std::is_same::value, // Can't just write false here. + "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " + "is convertible from another type use the add_option(...) with XC being the known type"); + return false; +} + +/// Assign a value through lexical cast operations +/// Strings can be empty so we need to do a little different +template ::value && + (classify_object::value == object_category::string_assignable || + classify_object::value == object_category::string_constructible || + classify_object::value == object_category::wstring_assignable || + classify_object::value == object_category::wstring_constructible), + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && std::is_assignable::value && + classify_object::value != object_category::string_assignable && + classify_object::value != object_category::string_constructible && + classify_object::value != object_category::wstring_assignable && + classify_object::value != object_category::wstring_constructible, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = AssignTo{}; + return true; + } + + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && !std::is_assignable::value && + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + typename AssignTo::value_type emptyVal{}; + output = emptyVal; + return true; + } + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations for int compatible values +/// mainly for atomic operations on some compilers +template ::value && !std::is_assignable::value && + classify_object::value != object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = 0; + return true; + } + int val{0}; + if(lexical_cast(input, val)) { +#if defined(__clang__) +/* on some older clang compilers */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#endif + output = val; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + return true; + } + return false; +} + +/// Assign a value converted from a string in lexical cast to the output value directly +template ::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; + if(parse_result) { + output = val; + } + return parse_result; +} + +/// Assign a value from a lexical cast through constructing a value and move assigning it +template < + typename AssignTo, + typename ConvertTo, + enable_if_t::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = input.empty() ? true : lexical_cast(input, val); + if(parse_result) { + output = AssignTo(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; +} + +/// primary lexical conversion operation, 1 string to 1 type of some kind +template ::value <= object_category::other && + classify_object::value <= object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + return lexical_assign(strings[0], output); +} + +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element +/// constructor +template ::value <= 2) && expected_count::value == 1 && + is_tuple_like::value && type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + // the remove const is to handle pair types coming from a container + using FirstType = typename std::remove_const::type>::type; + using SecondType = typename std::tuple_element<1, ConvertTo>::type; + FirstType v1; + SecondType v2; + bool retval = lexical_assign(strings[0], v1); + retval = retval && lexical_assign((strings.size() > 1) ? strings[1] : std::string{}, v2); + if(retval) { + output = AssignTo{v1, v2}; + } + return retval; +} + +/// Lexical conversion of a container types of single elements +template ::value && is_mutable_container::value && + type_count::value == 1, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + output.erase(output.begin(), output.end()); + if(strings.empty()) { + return true; + } + if(strings.size() == 1 && strings[0] == "{}") { + return true; + } + bool skip_remaining = false; + if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) { + skip_remaining = true; + } + for(const auto &elem : strings) { + typename AssignTo::value_type out; + bool retval = lexical_assign(elem, out); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(out)); + if(skip_remaining) { + break; + } + } + return (!output.empty()); +} + +/// Lexical conversion for complex types +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() >= 2 && !strings[1].empty()) { + using XC2 = typename wrapped_type::type; + XC2 x{0.0}, y{0.0}; + auto str1 = strings[1]; + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + } + auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y); + if(worked) { + output = ConvertTo{x, y}; + } + return worked; + } + return lexical_assign(strings[0], output); +} + +/// Conversion to a vector type using a particular single type as the conversion type +template ::value && (expected_count::value == 1) && + (type_count::value == 1), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + output.reserve(strings.size()); + for(const auto &elem : strings) { + + output.emplace_back(); + retval = retval && lexical_assign(elem, output.back()); + } + return (!output.empty()) && retval; +} + +// forward declaration + +/// Lexical conversion of a container types with conversion type of two elements +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(std::vector strings, AssignTo &output); + +/// Lexical conversion of a vector types with type_size >2 forward declaration +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); + +/// Conversion for tuples +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); // forward declaration + +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large +/// tuple +template ::value && !is_mutable_container::value && + classify_object::value != object_category::wrapper_value && + (is_mutable_container::value || type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { + ConvertTo val; + auto retval = lexical_conversion(strings, val); + output = AssignTo{val}; + return retval; + } + output = AssignTo{}; + return true; +} + +/// function template for converting tuples if the static Index is greater than the tuple size +template +inline typename std::enable_if<(I >= type_count_base::value), bool>::type +tuple_conversion(const std::vector &, AssignTo &) { + return true; +} + +/// Conversion of a tuple element where the type size ==1 and not a mutable container +template +inline typename std::enable_if::value && type_count::value == 1, bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_assign(strings[0], output); + strings.erase(strings.begin()); + return retval; +} + +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container +template +inline typename std::enable_if::value && (type_count::value > 1) && + type_count::value == type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_conversion(strings, output); + strings.erase(strings.begin(), strings.begin() + type_count::value); + return retval; +} + +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes +template +inline typename std::enable_if::value || + type_count::value != type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + + std::size_t index{subtype_count_min::value}; + const std::size_t mx_count{subtype_count::value}; + const std::size_t mx{(std::min)(mx_count, strings.size() - 1)}; + + while(index < mx) { + if(is_separator(strings[index])) { + break; + } + ++index; + } + bool retval = lexical_conversion( + std::vector(strings.begin(), strings.begin() + static_cast(index)), output); + if(strings.size() > index) { + strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + } else { + strings.clear(); + } + return retval; +} + +/// Tuple conversion operation +template +inline typename std::enable_if<(I < type_count_base::value), bool>::type +tuple_conversion(std::vector strings, AssignTo &output) { + bool retval = true; + using ConvertToElement = typename std:: + conditional::value, typename std::tuple_element::type, ConvertTo>::type; + if(!strings.empty()) { + retval = retval && tuple_type_conversion::type, ConvertToElement>( + strings, std::get(output)); + } + retval = retval && tuple_conversion(std::move(strings), output); + return retval; +} + +/// Lexical conversion of a container types with tuple elements of size 2 +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler>> +bool lexical_conversion(std::vector strings, AssignTo &output) { + output.clear(); + while(!strings.empty()) { + + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; + bool retval = tuple_type_conversion(strings, v1); + if(!strings.empty()) { + retval = retval && tuple_type_conversion(strings, v2); + } + if(retval) { + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); + } else { + return false; + } + } + return (!output.empty()); +} + +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + static_assert( + !is_tuple_like::value || type_count_base::value == type_count_base::value, + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); + return tuple_conversion(strings, output); +} + +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1 +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + bool retval = true; + output.clear(); + std::vector temp; + std::size_t ii{0}; + std::size_t icount{0}; + std::size_t xcm{type_count::value}; + auto ii_max = strings.size(); + while(ii < ii_max) { + temp.push_back(strings[ii]); + ++ii; + ++icount; + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { + if(static_cast(xcm) > type_count_min::value && is_separator(temp.back())) { + temp.pop_back(); + } + typename AssignTo::value_type temp_out; + retval = retval && + lexical_conversion(temp, temp_out); + temp.clear(); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(temp_out)); + icount = 0; + } + } + return retval; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + if(strings.empty() || strings.front().empty()) { + output = ConvertTo{}; + return true; + } + typename ConvertTo::value_type val; + if(lexical_conversion(strings, val)) { + output = ConvertTo{val}; + return true; + } + return false; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + using ConvertType = typename ConvertTo::value_type; + if(strings.empty() || strings.front().empty()) { + output = ConvertType{}; + return true; + } + ConvertType val; + if(lexical_conversion(strings, val)) { + output = val; + return true; + } + return false; +} + +/// Sum a vector of strings +inline std::string sum_string_vector(const std::vector &values) { + double val{0.0}; + bool fail{false}; + std::string output; + for(const auto &arg : values) { + double tv{0.0}; + auto comp = lexical_cast(arg, tv); + if(!comp) { + errno = 0; + auto fv = detail::to_flag_value(arg); + fail = (errno != 0); + if(fail) { + break; + } + tv = static_cast(fv); + } + val += tv; + } + if(fail) { + for(const auto &arg : values) { + output.append(arg); + } + } else { + std::ostringstream out; + out.precision(16); + out << val; + output = out.str(); + } + return output; +} + +} // namespace detail + + + +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest); + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value); + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value); + +// Splits a string into multiple long and short names +CLI11_INLINE std::vector split_names(std::string current); + +/// extract default flag values either {def} or starting with a ! +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str); + +/// Get a vector of short names, one of long names, and a single name +CLI11_INLINE std::tuple, std::vector, std::string> +get_names(const std::vector &input); + +} // namespace detail + + + +namespace detail { + +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } + return false; +} + +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.compare(0, 2, "--") == 0 && valid_first_char(current[2])) { + auto loc = current.find_first_of('='); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } + return false; +} + +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { + auto loc = current.find_first_of(':'); + if(loc != std::string::npos) { + name = current.substr(1, loc - 1); + value = current.substr(loc + 1); + } else { + name = current.substr(1); + value = ""; + } + return true; + } + return false; +} + +CLI11_INLINE std::vector split_names(std::string current) { + std::vector output; + std::size_t val = 0; + while((val = current.find(',')) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +CLI11_INLINE std::vector> get_default_flag_values(const std::string &str) { + std::vector flags = split_names(str); + flags.erase(std::remove_if(flags.begin(), + flags.end(), + [](const std::string &name) { + return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && + (name.back() == '}')) || + (name[0] == '!')))); + }), + flags.end()); + std::vector> output; + output.reserve(flags.size()); + for(auto &flag : flags) { + auto def_start = flag.find_first_of('{'); + std::string defval = "false"; + if((def_start != std::string::npos) && (flag.back() == '}')) { + defval = flag.substr(def_start + 1); + defval.pop_back(); + flag.erase(def_start, std::string::npos); // NOLINT(readability-suspicious-call-argument) + } + flag.erase(0, flag.find_first_not_of("-!")); + output.emplace_back(flag, defval); + } + return output; +} + +CLI11_INLINE std::tuple, std::vector, std::string> +get_names(const std::vector &input) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + for(std::string name : input) { + if(name.length() == 0) { + continue; + } + if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) + short_names.emplace_back(1, name[1]); + else if(name.length() > 2) + throw BadNameString::MissingDash(name); + else + throw BadNameString::OneCharName(name); + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) + long_names.push_back(name); + else + throw BadNameString::BadLongName(name); + } else if(name == "-" || name == "--") { + throw BadNameString::DashesOnly(name); + } else { + if(!pos_name.empty()) + throw BadNameString::MultiPositionalNames(name); + if(valid_name_string(name)) { + pos_name = name; + } else { + throw BadNameString::BadPositionalName(name); + } + } + } + return std::make_tuple(short_names, long_names, pos_name); +} + +} // namespace detail + + + +class App; + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents{}; + + /// This is the name + std::string name{}; + /// Listing of inputs + std::vector inputs{}; + + /// The list of parents and name joined by "." + CLI11_NODISCARD std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items{}; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; + + /// Get a flag value + CLI11_NODISCARD virtual std::string to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + return item.inputs.at(0); + } + if(item.inputs.empty()) { + return "{}"; + } + throw ConversionError::TooManyInputsFlag(item.fullname()); // LCOV_EXCL_LINE + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + CLI11_NODISCARD std::vector from_file(const std::string &name) const { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// Virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI/TOML files; to write INI files use ConfigINI +class ConfigBase : public Config { + protected: + /// the character used for comments + char commentChar = '#'; + /// the character used to start an array '\0' is a default to not use + char arrayStart = '['; + /// the character used to end an array '\0' is a default to not use + char arrayEnd = ']'; + /// the character used to separate elements in an array + char arraySeparator = ','; + /// the character used separate the name from the value + char valueDelimiter = '='; + /// the character to use around strings + char stringQuote = '"'; + /// the character to use around single characters and literal strings + char literalQuote = '\''; + /// the maximum number of layers to allow + uint8_t maximumLayers{255}; + /// the separator used to separator parent layers + char parentSeparatorChar{'.'}; + /// Specify the configuration index to use for arrayed sections + int16_t configIndex{-1}; + /// Specify the configuration section that should be used + std::string configSection{}; + + public: + std::string + to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override; + + std::vector from_config(std::istream &input) const override; + /// Specify the configuration for comment characters + ConfigBase *comment(char cchar) { + commentChar = cchar; + return this; + } + /// Specify the start and end characters for an array + ConfigBase *arrayBounds(char aStart, char aEnd) { + arrayStart = aStart; + arrayEnd = aEnd; + return this; + } + /// Specify the delimiter character for an array + ConfigBase *arrayDelimiter(char aSep) { + arraySeparator = aSep; + return this; + } + /// Specify the delimiter between a name and value + ConfigBase *valueSeparator(char vSep) { + valueDelimiter = vSep; + return this; + } + /// Specify the quote characters used around strings and literal strings + ConfigBase *quoteCharacter(char qString, char literalChar) { + stringQuote = qString; + literalQuote = literalChar; + return this; + } + /// Specify the maximum number of parents + ConfigBase *maxLayers(uint8_t layers) { + maximumLayers = layers; + return this; + } + /// Specify the separator to use for parent layers + ConfigBase *parentSeparator(char sep) { + parentSeparatorChar = sep; + return this; + } + /// get a reference to the configuration section + std::string §ionRef() { return configSection; } + /// get the section + CLI11_NODISCARD const std::string §ion() const { return configSection; } + /// specify a particular section of the configuration file to use + ConfigBase *section(const std::string §ionName) { + configSection = sectionName; + return this; + } + + /// get a reference to the configuration index + int16_t &indexRef() { return configIndex; } + /// get the section index + CLI11_NODISCARD int16_t index() const { return configIndex; } + /// specify a particular index in the section to use (-1) for all sections to use + ConfigBase *index(int16_t sectionIndex) { + configIndex = sectionIndex; + return this; + } +}; + +/// the default Config is the TOML file format +using ConfigTOML = ConfigBase; + +/// ConfigINI generates a "standard" INI compliant output +class ConfigINI : public ConfigTOML { + + public: + ConfigINI() { + commentChar = ';'; + arrayStart = '\0'; + arrayEnd = '\0'; + arraySeparator = ' '; + valueDelimiter = '='; + } +}; + + + +class Option; + +/// @defgroup validator_group Validators + +/// @brief Some validators that are provided +/// +/// These are simple `std::string(const std::string&)` validators that are useful. They return +/// a string if the validation fails. A custom struct is provided, as well, with the same user +/// semantics, but with the ability to provide a new type name. +/// @{ + +/// +class Validator { + protected: + /// This is the description function, if empty the description_ will be used + std::function desc_function_{[]() { return std::string{}; }}; + + /// This is the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func_{[](std::string &) { return std::string{}; }}; + /// The name for search purposes of the Validator + std::string name_{}; + /// A Validator will only apply to an indexed value (-1 is all elements) + int application_index_ = -1; + /// Enable for Validator to allow it to be disabled if need be + bool active_{true}; + /// specify that a validator should not modify the input + bool non_modifying_{false}; + + Validator(std::string validator_desc, std::function func) + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(func)) {} + + public: + Validator() = default; + /// Construct a Validator with just the description string + explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {} + /// Construct Validator from basic information + Validator(std::function op, std::string validator_desc, std::string validator_name = "") + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)), + name_(std::move(validator_name)) {} + /// Set the Validator operation function + Validator &operation(std::function op) { + func_ = std::move(op); + return *this; + } + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(std::string &str) const; + + /// This is the required operator for a Validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { + std::string value = str; + return (active_) ? func_(value) : std::string{}; + } + + /// Specify the type string + Validator &description(std::string validator_desc) { + desc_function_ = [validator_desc]() { return validator_desc; }; + return *this; + } + /// Specify the type string + CLI11_NODISCARD Validator description(std::string validator_desc) const; + + /// Generate type description information for the Validator + CLI11_NODISCARD std::string get_description() const { + if(active_) { + return desc_function_(); + } + return std::string{}; + } + /// Specify the type string + Validator &name(std::string validator_name) { + name_ = std::move(validator_name); + return *this; + } + /// Specify the type string + CLI11_NODISCARD Validator name(std::string validator_name) const { + Validator newval(*this); + newval.name_ = std::move(validator_name); + return newval; + } + /// Get the name of the Validator + CLI11_NODISCARD const std::string &get_name() const { return name_; } + /// Specify whether the Validator is active or not + Validator &active(bool active_val = true) { + active_ = active_val; + return *this; + } + /// Specify whether the Validator is active or not + CLI11_NODISCARD Validator active(bool active_val = true) const { + Validator newval(*this); + newval.active_ = active_val; + return newval; + } + + /// Specify whether the Validator can be modifying or not + Validator &non_modifying(bool no_modify = true) { + non_modifying_ = no_modify; + return *this; + } + /// Specify the application index of a validator + Validator &application_index(int app_index) { + application_index_ = app_index; + return *this; + } + /// Specify the application index of a validator + CLI11_NODISCARD Validator application_index(int app_index) const { + Validator newval(*this); + newval.application_index_ = app_index; + return newval; + } + /// Get the current value of the application index + CLI11_NODISCARD int get_application_index() const { return application_index_; } + /// Get a boolean if the validator is active + CLI11_NODISCARD bool get_active() const { return active_; } + + /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input + CLI11_NODISCARD bool get_modifying() const { return !non_modifying_; } + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator&(const Validator &other) const; + + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. + Validator operator|(const Validator &other) const; + + /// Create a validator that fails when a given validator succeeds + Validator operator!() const; + + private: + void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger); +}; + +/// Class wrapping some of the accessors of Validator +class CustomValidator : public Validator { + public: +}; +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// CLI enumeration of different file types +enum class path_type { nonexistent, file, directory }; + +/// get the type of the path from a file name +CLI11_INLINE path_type check_path(const char *file) noexcept; + +/// Check for an existing file (returns error message if check fails) +class ExistingFileValidator : public Validator { + public: + ExistingFileValidator(); +}; + +/// Check for an existing directory (returns error message if check fails) +class ExistingDirectoryValidator : public Validator { + public: + ExistingDirectoryValidator(); +}; + +/// Check for an existing path +class ExistingPathValidator : public Validator { + public: + ExistingPathValidator(); +}; + +/// Check for an non-existing path +class NonexistentPathValidator : public Validator { + public: + NonexistentPathValidator(); +}; + +/// Validate the given string is a legal ipv4 address +class IPV4Validator : public Validator { + public: + IPV4Validator(); +}; + +class EscapedStringTransformer : public Validator { + public: + EscapedStringTransformer(); +}; + +} // namespace detail + +// Static is not needed here, because global const implies static. + +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +const detail::NonexistentPathValidator NonexistentPath; + +/// Check for an IP4 address +const detail::IPV4Validator ValidIPV4; + +/// convert escaped characters into their associated values +const detail::EscapedStringTransformer EscapedString; + +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) + : Validator(validator_name, [](std::string &input_string) { + using CLI::detail::lexical_cast; + auto val = DesiredType(); + if(!lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); + } + return std::string(); + }) {} + TypeValidator() : TypeValidator(detail::type_name()) {} +}; + +/// Check for a number +const TypeValidator Number("NUMBER"); + +/// Modify a path if the file is a particular default location, can be used as Check or transform +/// with the error return optionally disabled +class FileOnDefaultPath : public Validator { + public: + explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true); +}; + +/// Produce a range (factory). Min and max are inclusive. +class Range : public Validator { + public: + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template + Range(T min_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name() << " in [" << min_val << " - " << max_val << "]"; + description(out.str()); + } + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if((!converted) || (val < min_val || val > max_val)) { + std::stringstream out; + out << "Value " << input << " not in range ["; + out << min_val << " - " << max_val << "]"; + return out.str(); + } + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template + explicit Range(T max_val, const std::string &validator_name = std::string{}) + : Range(static_cast(0), max_val, validator_name) {} +}; + +/// Check for a non negative number +const Range NonNegativeNumber((std::numeric_limits::max)(), "NONNEGATIVE"); + +/// Check for a positive valued number (val>0.0), ::min here is the smallest positive number +const Range PositiveNumber((std::numeric_limits::min)(), (std::numeric_limits::max)(), "POSITIVE"); + +/// Produce a bounded range (factory). Min and max are inclusive. +class Bound : public Validator { + public: + /// This bounds a value with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Bound(T min_val, T max_val) { + std::stringstream out; + out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; + description(out.str()); + + func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; + T val; + bool converted = lexical_cast(input, val); + if(!converted) { + return std::string("Value ") + input + " could not be converted"; + } + if(val < min_val) + input = detail::to_string(min_val); + else if(val > max_val) + input = detail::to_string(max_val); + + return std::string{}; + }; + } + + /// Range of one value is 0 to value + template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} +}; + +namespace detail { +template ::type>::value, detail::enabler> = detail::dummy> +auto smart_deref(T value) -> decltype(*value) { + return *value; +} + +template < + typename T, + enable_if_t::type>::value, detail::enabler> = detail::dummy> +typename std::remove_reference::type &smart_deref(T &value) { + return value; +} +/// Generate a string representation of a set +template std::string generate_set(const T &set) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(set), + [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, + ",")); + out.push_back('}'); + return out; +} + +/// Generate a string representation of a map +template std::string generate_map(const T &map, bool key_only = false) { + using element_t = typename detail::element_type::type; + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + std::string out(1, '{'); + out.append(detail::join( + detail::smart_deref(map), + [key_only](const iteration_type_t &v) { + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; + }, + ",")); + out.push_back('}'); + return out; +} + +template struct has_find { + template + static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); + template static auto test(...) -> decltype(std::false_type()); + + static const auto value = decltype(test(0))::value; + using type = std::integral_constant; +}; + +/// A search function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + using element_t = typename detail::element_type::type; + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { + return (detail::pair_adaptor::first(v) == val); + }); + return {(it != std::end(setref)), it}; +} + +/// A search function that uses the built in find function +template ::value, detail::enabler> = detail::dummy> +auto search(const T &set, const V &val) -> std::pair { + auto &setref = detail::smart_deref(set); + auto it = setref.find(val); + return {(it != std::end(setref)), it}; +} + +/// A search function with a filter function +template +auto search(const T &set, const V &val, const std::function &filter_function) + -> std::pair { + using element_t = typename detail::element_type::type; + // do the potentially faster first search + auto res = search(set, val); + if((res.first) || (!(filter_function))) { + return res; + } + // if we haven't found it do the longer linear search with all the element translations + auto &setref = detail::smart_deref(set); + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { + V a{detail::pair_adaptor::first(v)}; + a = filter_function(a); + return (a == val); + }); + return {(it != std::end(setref)), it}; +} + +// the following suggestion was made by Nikita Ofitserov(@himikof) +// done in templates to prevent compiler warnings on negation of unsigned numbers + +/// Do a check for overflow on signed numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + if((a > 0) == (b > 0)) { + return ((std::numeric_limits::max)() / (std::abs)(a) < (std::abs)(b)); + } + return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); +} +/// Do a check for overflow on unsigned numbers +template +inline typename std::enable_if::value, T>::type overflowCheck(const T &a, const T &b) { + return ((std::numeric_limits::max)() / a < b); +} + +/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. +template typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + if(a == 0 || b == 0 || a == 1 || b == 1) { + a *= b; + return true; + } + if(a == (std::numeric_limits::min)() || b == (std::numeric_limits::min)()) { + return false; + } + if(overflowCheck(a, b)) { + return false; + } + a *= b; + return true; +} + +/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise. +template +typename std::enable_if::value, bool>::type checked_multiply(T &a, T b) { + T c = a * b; + if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) { + return false; + } + a = c; + return true; +} + +} // namespace detail +/// Verify items are in a set +class IsMember : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction using an initializer list + template + IsMember(std::initializer_list values, Args &&...args) + : IsMember(std::vector(values), std::forward(args)...) {} + + /// This checks to see if an item is in a set (empty function) + template explicit IsMember(T &&set) : IsMember(std::forward(set), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit IsMember(T set, F filter_function) { + + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + + using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; + + // This is the function that validates + // It stores a copy of the set pointer-like, so shared_ptr will stay alive + func_ = [set, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + throw ValidationError(input); // name is added later + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(set, b, filter_fn); + if(res.first) { + // Make sure the version in the input string is identical to the one in the set + if(filter_fn) { + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); + } + + // Return empty error string (success) + return std::string{}; + } + + // If you reach this point, the result was not found + return input + " not in " + detail::generate_set(detail::smart_deref(set)); + }; + } + + /// You can pass in as many filter functions as you like, they nest (string only currently) + template + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : IsMember( + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// definition of the default transformation object +template using TransformPairs = std::vector>; + +/// Translate named items to other or a value set +class Transformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + Transformer(std::initializer_list> values, Args &&...args) + : Transformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit Transformer(T &&mapping) : Transformer(std::forward(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit Transformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; + + func_ = [mapping, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + if(!lexical_cast(input, b)) { + return std::string(); + // there is no possible way we can match anything in the mapping if we can't convert so just return + } + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + } + return std::string{}; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : Transformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// translate named items to other or a value set +class CheckedTransformer : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction + template + CheckedTransformer(std::initializer_list> values, Args &&...args) + : CheckedTransformer(TransformPairs(values), std::forward(args)...) {} + + /// direct map of std::string to std::string + template explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit CheckedTransformer(T mapping, F filter_function) { + + static_assert(detail::pair_adaptor::type>::value, + "mapping must produce value pairs"); + // Get the type of the contained item - requires a container have ::value_type + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType::type; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + auto tfunc = [mapping]() { + std::string out("value in "); + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; + out += detail::join( + detail::smart_deref(mapping), + [](const iteration_type_t &v) { return detail::to_string(detail::pair_adaptor::second(v)); }, + ","); + out.push_back('}'); + return out; + }; + + desc_function_ = tfunc; + + func_ = [mapping, tfunc, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; + local_item_t b; + bool converted = lexical_cast(input, b); + if(converted) { + if(filter_fn) { + b = filter_fn(b); + } + auto res = detail::search(mapping, b, filter_fn); + if(res.first) { + input = detail::value_string(detail::pair_adaptor::second(*res.second)); + return std::string{}; + } + } + for(const auto &v : detail::smart_deref(mapping)) { + auto output_string = detail::value_string(detail::pair_adaptor::second(v)); + if(output_string == input) { + return std::string(); + } + } + + return "Check " + input + " " + tfunc() + " FAILED"; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) + : CheckedTransformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + +/// Helper function to allow ignore_case to be passed to IsMember or Transform +inline std::string ignore_case(std::string item) { return detail::to_lower(item); } + +/// Helper function to allow ignore_underscore to be passed to IsMember or Transform +inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } + +/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform +inline std::string ignore_space(std::string item) { + item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); + item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); + return item; +} + +/// Multiply a number by a factor using given mapping. +/// Can be used to write transforms for SIZE or DURATION inputs. +/// +/// Example: +/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` +/// one can recognize inputs like "100", "12kb", "100 MB", +/// that will be automatically transformed to 100, 14448, 104857600. +/// +/// Output number type matches the type in the provided mapping. +/// Therefore, if it is required to interpret real inputs like "0.42 s", +/// the mapping should be of a type or . +class AsNumberWithUnit : public Validator { + public: + /// Adjust AsNumberWithUnit behavior. + /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. + /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError + /// if UNIT_REQUIRED is set and unit literal is not found. + enum Options { + CASE_SENSITIVE = 0, + CASE_INSENSITIVE = 1, + UNIT_OPTIONAL = 0, + UNIT_REQUIRED = 2, + DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL + }; + + template + explicit AsNumberWithUnit(std::map mapping, + Options opts = DEFAULT, + const std::string &unit_name = "UNIT") { + description(generate_description(unit_name, opts)); + validate_mapping(mapping, opts); + + // transform function + func_ = [mapping, opts](std::string &input) -> std::string { + Number num{}; + + detail::rtrim(input); + if(input.empty()) { + throw ValidationError("Input is empty"); + } + + // Find split position between number and prefix + auto unit_begin = input.end(); + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { + --unit_begin; + } + + std::string unit{unit_begin, input.end()}; + input.resize(static_cast(std::distance(input.begin(), unit_begin))); + detail::trim(input); + + if(opts & UNIT_REQUIRED && unit.empty()) { + throw ValidationError("Missing mandatory unit"); + } + if(opts & CASE_INSENSITIVE) { + unit = detail::to_lower(unit); + } + if(unit.empty()) { + using CLI::detail::lexical_cast; + if(!lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // No need to modify input if no unit passed + return {}; + } + + // find corresponding factor + auto it = mapping.find(unit); + if(it == mapping.end()) { + throw ValidationError(unit + + " unit not recognized. " + "Allowed values: " + + detail::generate_map(mapping, true)); + } + + if(!input.empty()) { + using CLI::detail::lexical_cast; + bool converted = lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast(it->second); + } + + input = detail::to_string(num); + + return {}; + }; + } + + private: + /// Check that mapping contains valid units. + /// Update mapping for CASE_INSENSITIVE mode. + template static void validate_mapping(std::map &mapping, Options opts) { + for(auto &kv : mapping) { + if(kv.first.empty()) { + throw ValidationError("Unit must not be empty."); + } + if(!detail::isalpha(kv.first)) { + throw ValidationError("Unit must contain only letters."); + } + } + + // make all units lowercase if CASE_INSENSITIVE + if(opts & CASE_INSENSITIVE) { + std::map lower_mapping; + for(auto &kv : mapping) { + auto s = detail::to_lower(kv.first); + if(lower_mapping.count(s)) { + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + + s); + } + lower_mapping[detail::to_lower(kv.first)] = kv.second; + } + mapping = std::move(lower_mapping); + } + } + + /// Generate description like this: NUMBER [UNIT] + template static std::string generate_description(const std::string &name, Options opts) { + std::stringstream out; + out << detail::type_name() << ' '; + if(opts & UNIT_REQUIRED) { + out << name; + } else { + out << '[' << name << ']'; + } + return out.str(); + } +}; + +inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +/// Converts a human-readable size string (with unit literal) to uin64_t size. +/// Example: +/// "100" => 100 +/// "1 b" => 100 +/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) +/// "10 KB" => 10240 +/// "10 kb" => 10240 +/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) +/// "10kb" => 10240 +/// "2 MB" => 2097152 +/// "2 EiB" => 2^61 // Units up to exibyte are supported +class AsSizeValue : public AsNumberWithUnit { + public: + using result_t = std::uint64_t; + + /// If kb_is_1000 is true, + /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 + /// (same applies to higher order units as well). + /// Otherwise, interpret all literals as factors of 1024. + /// The first option is formally correct, but + /// the second interpretation is more wide-spread + /// (see https://en.wikipedia.org/wiki/Binary_prefix). + explicit AsSizeValue(bool kb_is_1000); + + private: + /// Get mapping + static std::map init_mapping(bool kb_is_1000); + + /// Cache calculated mapping + static std::map get_mapping(bool kb_is_1000); +}; + +namespace detail { +/// Split a string into a program name and command line arguments +/// the string is assumed to contain a file name followed by other arguments +/// the return value contains is a pair with the first argument containing the program name and the second +/// everything else. +CLI11_INLINE std::pair split_program_name(std::string commandline); + +} // namespace detail +/// @} + + + + +CLI11_INLINE std::string Validator::operator()(std::string &str) const { + std::string retstring; + if(active_) { + if(non_modifying_) { + std::string value = str; + retstring = func_(value); + } else { + retstring = func_(str); + } + } + return retstring; +} + +CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const { + Validator newval(*this); + newval.desc_function_ = [validator_desc]() { return validator_desc; }; + return newval; +} + +CLI11_INLINE Validator Validator::operator&(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " AND "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(!s1.empty() && !s2.empty()) + return std::string("(") + s1 + ") AND (" + s2 + ")"; + return s1 + s2; + }; + + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator|(const Validator &other) const { + Validator newval; + + newval._merge_description(*this, other, " OR "); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + const std::function &f2 = other.func_; + + newval.func_ = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); + if(s1.empty() || s2.empty()) + return std::string(); + + return std::string("(") + s1 + ") OR (" + s2 + ")"; + }; + newval.active_ = active_ && other.active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE Validator Validator::operator!() const { + Validator newval; + const std::function &dfunc1 = desc_function_; + newval.desc_function_ = [dfunc1]() { + auto str = dfunc1(); + return (!str.empty()) ? std::string("NOT ") + str : std::string{}; + }; + // Give references (will make a copy in lambda function) + const std::function &f1 = func_; + + newval.func_ = [f1, dfunc1](std::string &test) -> std::string { + std::string s1 = f1(test); + if(s1.empty()) { + return std::string("check ") + dfunc1() + " succeeded improperly"; + } + return std::string{}; + }; + newval.active_ = active_; + newval.application_index_ = application_index_; + return newval; +} + +CLI11_INLINE void +Validator::_merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { + + const std::function &dfunc1 = val1.desc_function_; + const std::function &dfunc2 = val2.desc_function_; + + desc_function_ = [=]() { + std::string f1 = dfunc1(); + std::string f2 = dfunc2(); + if((f1.empty()) || (f2.empty())) { + return f1 + f2; + } + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; + }; +} + +namespace detail { + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE path_type check_path(const char *file) noexcept { + std::error_code ec; + auto stat = std::filesystem::status(to_path(file), ec); + if(ec) { + return path_type::nonexistent; + } + switch(stat.type()) { + case std::filesystem::file_type::none: // LCOV_EXCL_LINE + case std::filesystem::file_type::not_found: + return path_type::nonexistent; // LCOV_EXCL_LINE + case std::filesystem::file_type::directory: + return path_type::directory; + case std::filesystem::file_type::symlink: + case std::filesystem::file_type::block: + case std::filesystem::file_type::character: + case std::filesystem::file_type::fifo: + case std::filesystem::file_type::socket: + case std::filesystem::file_type::regular: + case std::filesystem::file_type::unknown: + default: + return path_type::file; + } +} +#else +CLI11_INLINE path_type check_path(const char *file) noexcept { +#if defined(_MSC_VER) + struct __stat64 buffer; + if(_stat64(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if(stat(file, &buffer) == 0) { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistent; +} +#endif + +CLI11_INLINE ExistingFileValidator::ExistingFileValidator() : Validator("FILE") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "File does not exist: " + filename; + } + if(path_result == path_type::directory) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingDirectoryValidator::ExistingDirectoryValidator() : Validator("DIR") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Directory does not exist: " + filename; + } + if(path_result == path_type::file) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE ExistingPathValidator::ExistingPathValidator() : Validator("PATH(existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result == path_type::nonexistent) { + return "Path does not exist: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("PATH(non-existing)") { + func_ = [](std::string &filename) { + auto path_result = check_path(filename.c_str()); + if(path_result != path_type::nonexistent) { + return "Path already exists: " + filename; + } + return std::string(); + }; +} + +CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { + func_ = [](std::string &ip_addr) { + auto result = CLI::detail::split(ip_addr, '.'); + if(result.size() != 4) { + return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')'; + } + int num = 0; + for(const auto &var : result) { + using CLI::detail::lexical_cast; + bool retval = lexical_cast(var, num); + if(!retval) { + return std::string("Failed parsing number (") + var + ')'; + } + if(num < 0 || num > 255) { + return std::string("Each IP number must be between 0 and 255 ") + var; + } + } + return std::string{}; + }; +} + +CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { + func_ = [](std::string &str) { + try { + if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') && + str.front() == str.back()) { + process_quoted_string(str); + } else if(str.find_first_of('\\') != std::string::npos) { + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + } else { + str = remove_escaped_characters(str); + } + } + return std::string{}; + } catch(const std::invalid_argument &ia) { + return std::string(ia.what()); + } + }; +} +} // namespace detail + +CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn) + : Validator("FILE") { + func_ = [default_path, enableErrorReturn](std::string &filename) { + auto path_result = detail::check_path(filename.c_str()); + if(path_result == detail::path_type::nonexistent) { + std::string test_file_path = default_path; + if(default_path.back() != '/' && default_path.back() != '\\') { + // Add folder separator + test_file_path += '/'; + } + test_file_path.append(filename); + path_result = detail::check_path(test_file_path.c_str()); + if(path_result == detail::path_type::file) { + filename = test_file_path; + } else { + if(enableErrorReturn) { + return "File does not exist: " + filename; + } + } + } + return std::string{}; + }; +} + +CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { + if(kb_is_1000) { + description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); + } else { + description("SIZE [b, kb(=1024b), ...]"); + } +} + +CLI11_INLINE std::map AsSizeValue::init_mapping(bool kb_is_1000) { + std::map m; + result_t k_factor = kb_is_1000 ? 1000 : 1024; + result_t ki_factor = 1024; + result_t k = 1; + result_t ki = 1; + m["b"] = 1; + for(std::string p : {"k", "m", "g", "t", "p", "e"}) { + k *= k_factor; + ki *= ki_factor; + m[p] = k; + m[p + "b"] = k; + m[p + "i"] = ki; + m[p + "ib"] = ki; + } + return m; +} + +CLI11_INLINE std::map AsSizeValue::get_mapping(bool kb_is_1000) { + if(kb_is_1000) { + static auto m = init_mapping(true); + return m; + } + static auto m = init_mapping(false); + return m; +} + +namespace detail { + +CLI11_INLINE std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { + bool embeddedQuote = false; + auto keyChar = commandline[0]; + auto end = commandline.find_first_of(keyChar, 1); + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes + end = commandline.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + vals.first = commandline.substr(1, end - 1); + esp = end + 1; + if(embeddedQuote) { + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + + break; + } + } + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + + // strip the program name + vals.second = (esp < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} + +} // namespace detail +/// @} + + + + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode { + Normal, ///< The normal, detailed help + All, ///< A fully expanded help + Sub, ///< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the first column + std::size_t column_width_{30}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_{}; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + FormatterBase &operator=(const FormatterBase &) = default; + FormatterBase &operator=(FormatterBase &&) = default; + + /// Adding a destructor in this form to work around bug in GCC 4.7 + virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" label + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the column width + void column_width(std::size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + CLI11_NODISCARD std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + return labels_.at(key); + } + + /// Get the current column width + CLI11_NODISCARD std::size_t get_column_width() const { return column_width_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// Adding a destructor (mostly to make GCC 4.7 happy) + ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + Formatter &operator=(const Formatter &) = default; + Formatter &operator=(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + CLI11_NODISCARD virtual std::string + make_group(std::string group, bool is_positional, std::vector opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + detail::format_help( + out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); + return out.str(); + } + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + + + + +using results_t = std::vector; +/// callback function definition +using callback_t = std::function; + +class Option; +class App; + +using Option_p = std::unique_ptr