├── src ├── main.cpp └── ClaraTests.cpp ├── codecov.yml ├── .gitignore ├── misc ├── appveyorTestRunScript.bat ├── appveyorMergeCoverageScript.py ├── CMakeLists.txt ├── appveyorBuildConfigurationScript.bat ├── installOpenCppCoverage.ps1 └── coverage-helper.cpp ├── docs └── release-notes.md ├── .gitattributes ├── scripts ├── embedTextFlow.py ├── embed.py ├── release.py └── stitch.py ├── CMake ├── llvm-cov-wrapper ├── FindGcov.cmake ├── Findcodecov.cmake └── FindLcov.cmake ├── CMakeLists.txt ├── LICENSE.txt ├── appveyor.yml ├── Roadmap.md ├── CODE_OF_CONDUCT.md ├── README.md ├── .travis.yml ├── third_party └── TextFlow.hpp ├── include ├── clara_textflow.hpp └── clara.hpp └── single_include └── clara.hpp /src/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch.hpp" 3 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | branch: master 3 | 4 | coverage: 5 | ignore: 6 | - "src/*" 7 | - "third_party/catch.hpp" 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/*.iml 2 | .idea/misc.xml 3 | .idea/modules.xml 4 | .idea/vcs.xml 5 | .idea/workspace.xml 6 | .idea/.name 7 | .idea/dictionaries/* 8 | cmake-build-* 9 | .vs 10 | *.pyc 11 | .idea/markdown-navigator.xml 12 | .idea/codeStyles/Project.xml 13 | .idea/markdown-navigator/profiles_settings.xml 14 | -------------------------------------------------------------------------------- /misc/appveyorTestRunScript.bat: -------------------------------------------------------------------------------- 1 | cd Build 2 | if "%CONFIGURATION%"=="Debug" ( 3 | ctest -j 2 -C %CONFIGURATION% -D ExperimentalMemCheck 4 | python ..\misc\appveyorMergeCoverageScript.py 5 | codecov --root .. --no-color --disable gcov -f cobertura.xml -t %CODECOV_TOKEN% 6 | ) 7 | if "%CONFIGURATION%"=="Release" ( 8 | ctest -j 2 -C %CONFIGURATION% 9 | ) 10 | -------------------------------------------------------------------------------- /misc/appveyorMergeCoverageScript.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import glob 4 | import subprocess 5 | 6 | if __name__ == '__main__': 7 | cov_files = list(glob.glob('cov-report*.bin')) 8 | base_cmd = ['OpenCppCoverage', '--quiet', '--export_type=cobertura:cobertura.xml'] + ['--input_coverage={}'.format(f) for f in cov_files] 9 | subprocess.call(base_cmd) 10 | -------------------------------------------------------------------------------- /docs/release-notes.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 1.1.3 4 | 5 | ## Improvements 6 | * `Args` now take arguments as `int argc, char const * const * argv`. 7 | * This allows the use of string literals for arguments 8 | 9 | 10 | # 1.1.2 11 | * Fix usage of `dynamic_cast` preventing Clara being used with `-fno-rtti` 12 | 13 | 14 | # Older versions (1.1.1 and earlier) 15 | 16 | No release notes have been kept (Maybe some of it will be backfilled later) 17 | -------------------------------------------------------------------------------- /misc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(ClaraCoverageHelper) 4 | 5 | add_executable(CoverageHelper coverage-helper.cpp) 6 | set_property(TARGET CoverageHelper PROPERTY CXX_STANDARD 11) 7 | set_property(TARGET CoverageHelper PROPERTY CXX_STANDARD_REQUIRED ON) 8 | set_property(TARGET CoverageHelper PROPERTY CXX_EXTENSIONS OFF) 9 | if (MSVC) 10 | target_compile_options( CoverageHelper PRIVATE /W4 /w44265 /w44061 /w44062 ) 11 | endif() 12 | -------------------------------------------------------------------------------- /misc/appveyorBuildConfigurationScript.bat: -------------------------------------------------------------------------------- 1 | @REM # In debug build, we want to prebuild memcheck redirecter 2 | @REM # before running the tests 3 | if "%CONFIGURATION%"=="Debug" ( 4 | cmake -Hmisc -Bbuild-misc -A%PLATFORM% 5 | cmake --build build-misc 6 | cmake -H. -BBuild -A%PLATFORM% -DMEMORYCHECK_COMMAND=build-misc\Debug\CoverageHelper.exe -DMEMORYCHECK_COMMAND_OPTIONS=--sep-- -DMEMORYCHECK_TYPE=Valgrind 7 | ) 8 | if "%CONFIGURATION%"=="Release" ( 9 | cmake -H. -BBuild -A%PLATFORM% 10 | ) 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # This sets the default behaviour, overriding core.autocrlf 2 | * text=auto 3 | 4 | # All source files should have unix line-endings in the repository, 5 | # but convert to native line-endings on checkout 6 | *.cpp text 7 | *.h text 8 | *.hpp text 9 | 10 | # Windows specific files should retain windows line-endings 11 | *.sln text eol=crlf 12 | 13 | # Keep executable scripts with LFs so they can be run after being 14 | # checked out on Windows 15 | *.py text eol=lf 16 | 17 | 18 | # Keep the single include header with LFs to make sure it is uploaded, 19 | # hashed etc with LF 20 | single_include/*.hpp eol=lf 21 | # Also keep the LICENCE file with LFs for the same reason 22 | LICENCE.txt eol=lf 23 | -------------------------------------------------------------------------------- /scripts/embedTextFlow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Execute this script any time you import a new copy of textflow into the third_party area 4 | import os 5 | import sys 6 | import embed 7 | 8 | rootPath = os.path.dirname(os.path.realpath( os.path.dirname(sys.argv[0]))) 9 | 10 | filename = os.path.join( rootPath, "third_party", "TextFlow.hpp" ) 11 | outfilename = os.path.join( rootPath, "include", "clara_textflow.hpp" ) 12 | 13 | 14 | # Mapping of pre-processor identifiers 15 | idMap = { 16 | "TEXTFLOW_HPP_INCLUDED": "CLARA_TEXTFLOW_HPP_INCLUDED", 17 | "TEXTFLOW_CONFIG_CONSOLE_WIDTH": "CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH" 18 | } 19 | 20 | # outer namespace to add 21 | outerNamespace = "clara" 22 | 23 | mapper = embed.LineMapper( idMap, outerNamespace ) 24 | mapper.mapFile( filename, outfilename ) -------------------------------------------------------------------------------- /misc/installOpenCppCoverage.ps1: -------------------------------------------------------------------------------- 1 | # Downloads are done from the oficial github release page links 2 | $downloadUrl = "https://github.com/OpenCppCoverage/OpenCppCoverage/releases/download/release-0.9.6.1/OpenCppCoverageSetup-x64-0.9.6.1.exe" 3 | $installerPath = [System.IO.Path]::Combine($Env:USERPROFILE, "Downloads", "OpenCppCoverageSetup.exe") 4 | 5 | if(-Not (Test-Path $installerPath)) { 6 | Write-Host -ForegroundColor White ("Downloading OpenCppCoverage from: " + $downloadUrl) 7 | Start-FileDownload $downloadUrl -FileName $installerPath 8 | } 9 | 10 | Write-Host -ForegroundColor White "About to install OpenCppCoverage..." 11 | 12 | $installProcess = (Start-Process $installerPath -ArgumentList '/VERYSILENT' -PassThru -Wait) 13 | if($installProcess.ExitCode -ne 0) { 14 | throw [System.String]::Format("Failed to install OpenCppCoverage, ExitCode: {0}.", $installProcess.ExitCode) 15 | } 16 | 17 | # Assume standard, boring, installation path of ".../Program Files/OpenCppCoverage" 18 | $installPath = [System.IO.Path]::Combine(${Env:ProgramFiles}, "OpenCppCoverage") 19 | $env:Path="$env:Path;$installPath" 20 | -------------------------------------------------------------------------------- /CMake/llvm-cov-wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This file is part of CMake-codecov. 4 | # 5 | # Copyright (c) 6 | # 2015-2017 RWTH Aachen University, Federal Republic of Germany 7 | # 8 | # See the LICENSE file in the package base directory for details 9 | # 10 | # Written by Alexander Haase, alexander.haase@rwth-aachen.de 11 | # 12 | 13 | if [ -z "$LLVM_COV_BIN" ] 14 | then 15 | echo "LLVM_COV_BIN not set!" >& 2 16 | exit 1 17 | fi 18 | 19 | 20 | # Get LLVM version to find out. 21 | LLVM_VERSION=$($LLVM_COV_BIN -version | grep -i "LLVM version" \ 22 | | sed "s/^\([A-Za-z ]*\)\([0-9]\).\([0-9]\).*$/\2.\3/g") 23 | 24 | if [ "$1" = "-v" ] 25 | then 26 | echo "llvm-cov-wrapper $LLVM_VERSION" 27 | exit 0 28 | fi 29 | 30 | 31 | if [ -n "$LLVM_VERSION" ] 32 | then 33 | MAJOR=$(echo $LLVM_VERSION | cut -d'.' -f1) 34 | MINOR=$(echo $LLVM_VERSION | cut -d'.' -f2) 35 | 36 | if [ $MAJOR -eq 3 ] && [ $MINOR -le 4 ] 37 | then 38 | if [ -f "$1" ] 39 | then 40 | filename=$(basename "$1") 41 | extension="${filename##*.}" 42 | 43 | case "$extension" in 44 | "gcno") exec $LLVM_COV_BIN --gcno="$1" ;; 45 | "gcda") exec $LLVM_COV_BIN --gcda="$1" ;; 46 | esac 47 | fi 48 | fi 49 | 50 | if [ $MAJOR -eq 3 ] && [ $MINOR -le 5 ] 51 | then 52 | exec $LLVM_COV_BIN $@ 53 | fi 54 | fi 55 | 56 | exec $LLVM_COV_BIN gcov $@ 57 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8.2) 2 | project(Clara) 3 | 4 | set(SOURCE_FILES src/main.cpp src/ClaraTests.cpp include/clara.hpp) 5 | include_directories( include third_party ) 6 | add_executable(ClaraTests ${SOURCE_FILES}) 7 | 8 | if(USE_CPP14) 9 | set_property(TARGET ClaraTests PROPERTY CXX_STANDARD 14) 10 | message(STATUS "Enabled C++14") 11 | elseif(USE_CPP17) 12 | set_property(TARGET ClaraTests PROPERTY CXX_STANDARD 17) 13 | message(STATUS "Enabled C++17") 14 | else(USE_CPP11) 15 | set_property(TARGET ClaraTests PROPERTY CXX_STANDARD 11) 16 | message(STATUS "Enabled C++11") 17 | endif() 18 | 19 | set_property(TARGET ClaraTests PROPERTY CXX_STANDARD_REQUIRED ON) 20 | set_property(TARGET ClaraTests PROPERTY CXX_EXTENSIONS OFF) 21 | 22 | 23 | if( CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang|GNU" ) 24 | target_compile_options( ClaraTests PRIVATE -Wall -Wextra -pedantic -Werror ) 25 | endif() 26 | if( CMAKE_CXX_COMPILER_ID MATCHES "MSVC" ) 27 | target_compile_options( ClaraTests PRIVATE /W4 /WX ) 28 | endif() 29 | 30 | if (ENABLE_COVERAGE) 31 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMake") 32 | find_package(codecov) 33 | add_coverage(ClaraTests) 34 | list(APPEND LCOV_REMOVE_PATTERNS "/usr/") 35 | coverage_evaluate() 36 | endif() 37 | 38 | include(CTest) 39 | add_test(NAME RunTests COMMAND $) 40 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | branches: 4 | except: 5 | - /dev-travis.+/ 6 | 7 | os: 8 | - Visual Studio 2017 9 | - Visual Studio 2015 10 | 11 | environment: 12 | matrix: 13 | - additional_flags: "/permissive- /std:c++latest" 14 | - additional_flags: "" 15 | 16 | matrix: 17 | exclude: 18 | - os: Visual Studio 2015 19 | additional_flags: "/permissive- /std:c++latest" 20 | 21 | init: 22 | - git config --global core.autocrlf input 23 | 24 | install: 25 | - ps: if (($env:CONFIGURATION) -eq "Debug" ) { python -m pip --disable-pip-version-check install codecov } 26 | - ps: if (($env:CONFIGURATION) -eq "Debug" ) { .\misc\installOpenCppCoverage.ps1 } 27 | 28 | # Win32 and x64 are CMake-compatible solution platform names. 29 | # This allows us to pass %PLATFORM% to CMake -A. 30 | platform: 31 | - Win32 32 | - x64 33 | 34 | # build Configurations, i.e. Debug, Release, etc. 35 | configuration: 36 | - Debug 37 | - Release 38 | 39 | #Cmake will autodetect the compiler, but we set the arch 40 | before_build: 41 | - set CXXFLAGS=%additional_flags% 42 | # Indirection because appveyor doesn't handle multiline batch scripts properly 43 | # https://stackoverflow.com/questions/37627248/how-to-split-a-command-over-multiple-lines-in-appveyor-yml/37647169#37647169 44 | # https://help.appveyor.com/discussions/questions/3888-multi-line-cmd-or-powershell-warning-ignore 45 | - cmd: .\misc\appveyorBuildConfigurationScript.bat 46 | 47 | 48 | # build with MSBuild 49 | build: 50 | project: Build\Clara.sln # path to Visual Studio solution or project 51 | parallel: true # enable MSBuild parallel builds 52 | verbosity: normal # MSBuild verbosity level {quiet|minimal|normal|detailed} 53 | 54 | test_script: 55 | - set CTEST_OUTPUT_ON_FAILURE=1 56 | - cmd: .\misc\appveyorTestRunScript.bat 57 | -------------------------------------------------------------------------------- /Roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | ## June 2019 Update 4 | 5 | Previously this roadmap said, "I'm not quite ready to throw development fully open to the community effort". The intention had been to clarify the direction in the codebase in some key areas, as many of the PRs were taking it in different directions. 6 | 7 | Unfortunuately the time intended for working on this has not appeared and this project has languished in neglect as a result. I should have acknowledged that sooner, but as of now I (@philsquared), am opening this up fully to the community (anyone who is still interested). That means, of course, that maintainers such as @horenmar, will be able to merge PRs as they see fit (as well as make any other changes). It also means that we'd be keen to hear from anyone that wants to take the lead in maintaining and evolving the library. 8 | 9 | Personally, I still hope to contribute further to the project, but am no longer in a position I can commit to leading it. 10 | 11 | Here are the items I had on the original roadmap, for reference. It is not a hard requirement to follow them: 12 | 13 | - Add more documentation (includes [#49](https://github.com/catchorg/Clara/issues/49)) 14 | - Finish work on "required" parsers [#39](https://github.com/catchorg/Clara/issues/39), [#16](https://github.com/catchorg/Clara/issues/16) & [PR #28](https://github.com/catchorg/Clara/pull/28) 15 | - Finish work on removing exceptions completely (or making them "optional") 16 | - (compiler conditional) Support for `std::optional` and maybe other optional types (e.g. boost) [#4](https://github.com/catchorg/Clara/issues/4) & [PR #45](https://github.com/catchorg/Clara/pull/45) - this is mostly, if not entirely, implemented. Should be checked, and #4 closed if so. 17 | - Arg\[0] support on Windows [#29](https://github.com/catchorg/Clara/issues/29) 18 | - config files and environment variables 19 | - "hidden" options [#29](https://github.com/catchorg/Clara/issues/29) 20 | - Capture general text for help output [#48]((https://github.com/catchorg/Clara/issues/48) 21 | - subcommands [#31](https://github.com/catchorg/Clara/issues/31) (I actually have a design mostly done for this) - but see also [PR #32](https://github.com/catchorg/Clara/pull/32) 22 | 23 | --- 24 | 25 | -------------------------------------------------------------------------------- /scripts/embed.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | preprocessorRe = re.compile( r'\s*#.*' ) 4 | 5 | fdefineRe = re.compile( r'\s*#\s*define\s*(\S*)\s*\(' ) # #defines that take arguments 6 | defineRe = re.compile( r'\s*#\s*define\s*(\S*)\s+(\S*)' ) # all #defines 7 | undefRe = re.compile( r'\s*#\s*undef\s*(\S*)' ) # all #undefs 8 | 9 | ifdefCommonRe = re.compile( r'\s*#\s*if' ) # all #ifdefs 10 | ifdefRe = re.compile( r'\s*#\s*ifdef\s*(\S*)' ) 11 | ifndefRe = re.compile( r'\s*#\s*ifndef\s*(\S*)' ) 12 | endifRe = re.compile( r'\s*#\s*endif\s*//\s*(.*)' ) 13 | elseRe = re.compile( r'\s*#\s*else' ) 14 | ifRe = re.compile( r'\s*#\s*if\s+(.*)' ) 15 | 16 | nsRe = re.compile( r'(\s*\s*namespace\s+)(.+)(\s*{?)' ) 17 | nsCloseRe = re.compile( r'(\s*})(\s*\/\/\s*namespace\s+)(.+)(\s*{?)' ) 18 | 19 | 20 | class LineMapper: 21 | def __init__( self, idMap, outerNamespace ): 22 | self.idMap = idMap 23 | self.outerNamespace = outerNamespace 24 | 25 | def replaceId( self, lineNo, id ): 26 | if not id in self.idMap: 27 | raise ValueError( "Unrecognised macro identifier: '{0}' on line: {1}".format( id, lineNo ) ) 28 | subst = self.idMap[id] 29 | if subst == "": 30 | return id 31 | else: 32 | return subst 33 | 34 | # TBD: 35 | # #if, #ifdef, comments after #else 36 | def mapLine( self, lineNo, line ): 37 | m = ifndefRe.match( line ) 38 | if m: 39 | return "#ifndef " + self.replaceId( lineNo, m.group(1)) + "\n" 40 | m = defineRe.match( line ) 41 | if m: 42 | return "#define " + self.replaceId( lineNo, m.group(1)) + "\n" 43 | m = endifRe.match( line ) 44 | if m: 45 | return "#endif // " + self.replaceId( lineNo, m.group(1)) + "\n" 46 | m = nsRe.match( line ) 47 | if m: 48 | return "{0}{1} {{ namespace {2}{3}".format( m.group(1), self.outerNamespace, m.group(2), m.group(3)) 49 | m = nsCloseRe.match( line ) 50 | if m: 51 | return "{0}}}{1}{2}::{3}{4}".format( m.group(1), m.group(2), self.outerNamespace, m.group(3), m.group(4)) 52 | return line 53 | 54 | def mapFile(self, filenameIn, filenameOut ): 55 | print( "Embedding:\n {0}\nas:\n {1}".format( filenameIn, filenameOut ) ) 56 | with open( filenameIn, 'r' ) as f, open( filenameOut, 'w' ) as outf: 57 | lineNo = 1 58 | for line in f: 59 | outf.write( self.mapLine( lineNo, line ) ) 60 | lineNo = lineNo + 1 61 | print( "Written {0} lines".format( lineNo ) ) -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at clara@philnash.me. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /misc/coverage-helper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | void create_empty_file(std::string const& path) { 15 | std::ofstream ofs(path); 16 | ofs << '\n'; 17 | } 18 | 19 | const std::string separator = "--sep--"; 20 | const std::string logfile_prefix = "--log-file="; 21 | const std::string project_name = "Clara"; 22 | 23 | std::string to_lower(std::string in) { 24 | for (auto& c : in) { 25 | c = std::tolower(c); 26 | } 27 | return in; 28 | } 29 | 30 | bool starts_with(std::string const& str, std::string const& pref) { 31 | return str.find(pref) == 0; 32 | } 33 | 34 | int parse_log_file_arg(std::string const& arg) { 35 | assert(starts_with(arg, logfile_prefix) && "Attempting to parse incorrect arg!"); 36 | auto fname = arg.substr(logfile_prefix.size()); 37 | create_empty_file(fname); 38 | std::regex regex("MemoryChecker\\.(\\d+)\\.log", std::regex::icase); 39 | std::smatch match; 40 | if (std::regex_search(fname, match, regex)) { 41 | return std::stoi(match[1]); 42 | } else { 43 | throw std::domain_error("Couldn't find desired expression in string: " + fname); 44 | } 45 | } 46 | 47 | std::string project_path(std::string path) { 48 | auto start = path.find(project_name); 49 | if (start == std::string::npos) { 50 | start = path.find(to_lower(project_name)); 51 | } 52 | if (start == std::string::npos) { 53 | throw std::domain_error("Couldn't find project's base path"); 54 | } 55 | auto end = path.find_first_of("\\/", start); 56 | return path.substr(0, end); 57 | } 58 | 59 | std::string windowsify_path(std::string path) { 60 | for (auto& c : path) { 61 | if (c == '/') { 62 | c = '\\'; 63 | } 64 | } 65 | return path; 66 | } 67 | 68 | void exec_cmd(std::string const& cmd, int log_num, std::string const& path) { 69 | std::array buffer; 70 | #if defined(_WIN32) 71 | auto real_cmd = "OpenCppCoverage --export_type binary:cov-report" + std::to_string(log_num) 72 | + ".bin --quiet " + "--sources " + path + " --cover_children -- " + cmd; 73 | std::cout << "=== Marker ===: Cmd: " << real_cmd << '\n'; 74 | std::shared_ptr pipe(_popen(real_cmd.c_str(), "r"), _pclose); 75 | #else // Just for testing, in the real world we will always work under WIN32 76 | (void)log_num; (void)path; 77 | std::shared_ptr pipe(popen(cmd.c_str(), "r"), pclose); 78 | #endif 79 | 80 | if (!pipe) { 81 | throw std::runtime_error("popen() failed!"); 82 | } 83 | while (!feof(pipe.get())) { 84 | if (fgets(buffer.data(), 128, pipe.get()) != nullptr) { 85 | std::cout << buffer.data(); 86 | } 87 | } 88 | } 89 | 90 | // argv should be: 91 | // [0]: our path 92 | // [1]: "--log-file=" 93 | // [2]: "--sep--" 94 | // [3]+: the actual command 95 | 96 | int main(int argc, char** argv) { 97 | std::vector args(argv, argv + argc); 98 | auto sep = std::find(begin(args), end(args), separator); 99 | assert(sep - begin(args) == 2 && "Structure differs from expected!"); 100 | 101 | auto num = parse_log_file_arg(args[1]); 102 | 103 | auto cmdline = std::accumulate(++sep, end(args), std::string{}, [] (const std::string& lhs, const std::string& rhs) { 104 | return lhs + ' ' + rhs; 105 | }); 106 | 107 | exec_cmd(cmdline, num, windowsify_path(project_path(args[0]))); 108 | } 109 | -------------------------------------------------------------------------------- /scripts/release.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | import os 5 | import sys 6 | import re 7 | 8 | rootPath = os.path.dirname(os.path.realpath(os.path.dirname(sys.argv[0]))) 9 | claraPath = os.path.join( rootPath, "include/clara.hpp" ) 10 | readmePath = os.path.join( rootPath, "README.md" ) 11 | 12 | versionParser = re.compile( r'\s*\/\/\s*Clara\s+v([0-9]+)\.([0-9]+)(\.([0-9]+)(\-(.*)\.([0-9]*))?)?' ) 13 | readmeParser = re.compile( r'\s*#\s*Clara\s+v(.*)' ) 14 | 15 | warnings = 0 16 | 17 | def precheck(): 18 | global warnings 19 | f = open( claraPath, 'r' ) 20 | lineNo = 0 21 | for line in f: 22 | lineNo = lineNo+1 23 | if "dynamic" in line: 24 | warnings = warnings + 1 25 | print( "** Warning: use of dynamic_cast on line {0}!".format(lineNo) ) 26 | f.close() 27 | 28 | class Version: 29 | def __init__(self): 30 | f = open( claraPath, 'r' ) 31 | for line in f: 32 | m = versionParser.match( line ) 33 | if m: 34 | self.majorVersion = int(m.group(1)) 35 | self.minorVersion = int(m.group(2)) 36 | self.patchNumber = int(m.group(4)) 37 | if m.group(6) == None: 38 | self.branchName = "" 39 | else: 40 | self.branchName = m.group(6) 41 | if m.group(7) == None: 42 | self.buildNumber = 0 43 | else: 44 | self.buildNumber = int(m.group(7)) 45 | f.close() 46 | 47 | def nonDevelopRelease(self): 48 | if self.branchName != "": 49 | self.branchName = "" 50 | self.buildNumber = 0 51 | def developBuild(self): 52 | if self.branchName == "": 53 | self.branchName = "develop" 54 | self.buildNumber = 0 55 | else: 56 | self.buildNumber = self.buildNumber+1 57 | 58 | def incrementBuildNumber(self): 59 | self.developBuild() 60 | self.buildNumber = self.buildNumber+1 61 | 62 | def incrementPatchNumber(self): 63 | self.nonDevelopRelease() 64 | self.patchNumber = self.patchNumber+1 65 | 66 | def incrementMinorVersion(self): 67 | self.nonDevelopRelease() 68 | self.patchNumber = 0 69 | self.minorVersion = self.minorVersion+1 70 | 71 | def incrementMajorVersion(self): 72 | self.nonDevelopRelease() 73 | self.patchNumber = 0 74 | self.minorVersion = 0 75 | self.majorVersion = self.majorVersion+1 76 | 77 | def getVersionString(self): 78 | versionString = '{0}.{1}.{2}'.format( self.majorVersion, self.minorVersion, self.patchNumber ) 79 | if self.branchName != "": 80 | versionString = versionString + '-{0}.{1}'.format( self.branchName, self.buildNumber ) 81 | return versionString 82 | 83 | def updateHeader(self): 84 | f = open( claraPath, 'r' ) 85 | lines = [] 86 | for line in f: 87 | m = versionParser.match( line ) 88 | if m: 89 | lines.append( '// Clara v{0}'.format( self.getVersionString() ) ) 90 | else: 91 | lines.append( line.rstrip() ) 92 | f.close() 93 | f = open( claraPath, 'w' ) 94 | for line in lines: 95 | f.write( line + "\n" ) 96 | 97 | def updateReadme(self): 98 | f = open( readmePath, 'r' ) 99 | lines = [] 100 | changed = False 101 | for line in f: 102 | m = readmeParser.match( line ) 103 | if m: 104 | lines.append( '# Clara v{0}'.format( self.getVersionString() ) ) 105 | changed = True 106 | else: 107 | lines.append( line.rstrip() ) 108 | f.close() 109 | if changed: 110 | f = open( readmePath, 'w' ) 111 | for line in lines: 112 | f.write( line + "\n" ) 113 | else: 114 | print( "*** Did not update README" ) 115 | def usage(): 116 | print( "\n**** Run with patch|minor|major|dev|verify\n" ) 117 | return 1 118 | 119 | if len( sys.argv) == 1: 120 | exit( usage() ) 121 | 122 | precheck() 123 | 124 | v = Version() 125 | oldV = v.getVersionString() 126 | 127 | cmd = sys.argv[1].lower() 128 | 129 | if warnings > 0: 130 | print( "Found {0} issue(s)".format(warnings)) 131 | exit(1) 132 | 133 | if cmd == "verify": 134 | print( "No issues found") 135 | exit(0) 136 | elif cmd == "patch": 137 | v.incrementPatchNumber() 138 | elif cmd == "minor": 139 | v.incrementMinorVersion() 140 | elif cmd == "major": 141 | v.incrementMajorVersion() 142 | elif cmd == "dev": 143 | v.developBuild() 144 | else: 145 | exit( usage() ) 146 | 147 | v.updateHeader() 148 | v.updateReadme() 149 | 150 | print( "Updated clara.hpp and README from {0} to v{1}".format( oldV, v.getVersionString() ) ) 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clara v1.1.5 2 | [![Build Status](https://travis-ci.org/catchorg/Clara.svg?branch=master)](https://travis-ci.org/catchorg/Clara) 3 | [![Build status](https://ci.appveyor.com/api/projects/status/github/catchorg/Clara?brach=master&svg=true)](https://ci.appveyor.com/project/catchorg/clara) 4 | [![codecov](https://codecov.io/gh/catchorg/Clara/branch/master/graph/badge.svg)](https://codecov.io/gh/catchorg/Clara) 5 | 6 | # !! This repository is unmaintained. Go [here](https://github.com/bfgroup/Lyra) for a fork that is somewhat maintained. !! 7 | 8 | ----------------------------- 9 | 10 | 11 | A simple to use, composable, command line parser for C++ 11 and beyond. 12 | 13 | Clara is a single-header library. 14 | 15 | To use, just `#include "clara.hpp"` 16 | 17 | A parser for a single option can be created like this: 18 | 19 | ```c++ 20 | int width = 0; 21 | // ... 22 | using namespace clara; 23 | auto cli 24 | = Opt( width, "width" ) 25 | ["-w"]["--width"] 26 | ("How wide should it be?"); 27 | ``` 28 | 29 | You can use this parser directly like this: 30 | 31 | ```c++ 32 | auto result = cli.parse( Args( argc, argv ) ); 33 | if( !result ) { 34 | std::cerr << "Error in command line: " << result.errorMessage() << std::endl; 35 | exit(1); 36 | } 37 | 38 | // Everything was ok, width will have a value if supplied on command line 39 | ``` 40 | 41 | Note that exceptions are not used for error handling. 42 | 43 | You can combine parsers by composing with `|`, like this: 44 | 45 | ```c++ 46 | int width = 0; 47 | std::string name; 48 | bool doIt = false; 49 | std::string command; 50 | auto cli 51 | = Opt( width, "width" ) 52 | ["-w"]["--width"] 53 | ("How wide should it be?") 54 | | Opt( name, "name" ) 55 | ["-n"]["--name"] 56 | ("By what name should I be known") 57 | | Opt( doIt ) 58 | ["-d"]["--doit"] 59 | ("Do the thing" ) 60 | | Arg( command, "command" ) 61 | ("which command to run"); 62 | ``` 63 | 64 | `Opt`s specify options that start with a short dash (`-`) or long dash (`--`). 65 | On Windows forward slashes are also accepted (and automatically interpretted as a short dash). 66 | Options can be argument taking (such as `-w 42`), in which case the `Opt` takes a second argument - a hint, 67 | or they are pure flags (such as `-d`), in which case the `Opt` has only one argument - which must be a boolean. 68 | The option names are provided in one or more sets of square brackets, and a description string can 69 | be provided in parentheses. The first argument to an `Opt` is any variable, local, global member, of any type 70 | that can be converted from a string using `std::ostream`. 71 | 72 | `Arg`s specify arguments that are not tied to options, and so have no square bracket names. They otherwise work just like `Opt`s. 73 | 74 | A, console optimised, usage string can be obtained by inserting the parser into a stream. 75 | The usage string is built from the information supplied and is formatted for the console width. 76 | 77 | As a convenience, the standard help options (`-h`, `--help` and `-?`) can be specified using the `Help` parser, 78 | which just takes a boolean to bind to. 79 | 80 | For more usage please see the unit tests or look at how it is used in the Catch code-base (catch-lib.net). 81 | Fuller documentation will be coming soon. 82 | 83 | Some of the key features: 84 | 85 | - A single header file with no external dependencies (except the std library). 86 | - Define your interface once to get parsing, type conversions and usage strings with no redundancy. 87 | - Composable. Each `Opt` or `Arg` is an independent parser. Combine these to produce a composite parser - this can be done in stages across multiple function calls - or even projects. 88 | - Bind parsers directly to variables that will receive the results of the parse - no intermediate dictionaries to worry about. 89 | - Or can also bind parsers to lambdas for more custom handling. 90 | - Deduces types from bound variables or lambdas and performs type conversions (via `ostream <<`), with error handling, behind the scenes. 91 | - Bind parsers to vectors for args that can have multiple values. 92 | - Uses Result types for error propagation, rather than exceptions (doesn't yet build with exceptions disabled, but that will be coming later) 93 | - Models POSIX standards for short and long opt behaviour. 94 | 95 | ## Roadmap 96 | 97 | To see which direction Clara is going in, please see [the roadmap](Roadmap.md) 98 | 99 | ## Old version 100 | 101 | If you used the earlier, v0.x, version of Clara please note that this is a complete rewrite which assumes C++11 and has 102 | a different interface (composability was a big step forward). Conversion between v0.x and v1.x is a fairly simple and mechanical task, but is a bit of manual 103 | work - so don't take this version until you're ready (and, of course, able to use C++11). 104 | 105 | I hope you'll find the new interface an improvement - and this will be built on to offer new features moving forwards. 106 | I don't expect to maintain v0.x any further, but it remains on a branch. 107 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | sudo: false 3 | 4 | common_sources: &all_sources 5 | - ubuntu-toolchain-r-test 6 | - llvm-toolchain-trusty 7 | - llvm-toolchain-trusty-3.9 8 | - llvm-toolchain-trusty-4.0 9 | - llvm-toolchain-trusty-5.0 10 | 11 | matrix: 12 | include: 13 | 14 | # 1/ Linux Clang Builds 15 | - os: linux 16 | compiler: clang 17 | addons: 18 | apt: 19 | sources: *all_sources 20 | packages: ['valgrind', 'lcov', 'clang-3.5'] 21 | env: COMPILER='clang++-3.5' VALGRIND=1 22 | 23 | - os: linux 24 | compiler: clang 25 | addons: 26 | apt: 27 | sources: *all_sources 28 | packages: ['valgrind', 'lcov', 'clang-3.6'] 29 | env: COMPILER='clang++-3.6' VALGRIND=1 30 | 31 | # Travis's containers do not seem to have Clang 3.7 in apt, no matter what sources I add. 32 | # - os: linux 33 | # compiler: clang 34 | # addons: 35 | # apt: 36 | # sources: *all_sources 37 | # packages: ['valgrind', 'clang-3.7'] 38 | # env: COMPILER='clang++-3.7' VALGRIND=1 39 | 40 | - os: linux 41 | compiler: clang 42 | addons: 43 | apt: 44 | sources: *all_sources 45 | packages: ['valgrind', 'lcov', 'clang-3.8'] 46 | env: COMPILER='clang++-3.8' VALGRIND=1 47 | 48 | - os: linux 49 | compiler: clang 50 | addons: 51 | apt: 52 | sources: *all_sources 53 | packages: ['clang-3.9', 'valgrind', 'lcov'] 54 | env: COMPILER='clang++-3.9' VALGRIND=1 55 | 56 | - os: linux 57 | compiler: clang 58 | addons: 59 | apt: 60 | sources: *all_sources 61 | packages: ['clang-4.0', 'valgrind', 'lcov'] 62 | env: COMPILER='clang++-4.0' VALGRIND=1 63 | 64 | - os: linux 65 | compiler: clang 66 | addons: 67 | apt: 68 | sources: *all_sources 69 | packages: ['clang-5.0', 'valgrind', 'lcov'] 70 | env: COMPILER='clang++-5.0' VALGRIND=1 71 | 72 | # 2/ Linux GCC Builds 73 | - os: linux 74 | compiler: gcc 75 | addons: 76 | apt: 77 | sources: ['ubuntu-toolchain-r-test'] 78 | packages: ['valgrind', 'lcov', 'g++-4.8'] 79 | env: COMPILER='g++-4.8' VALGRIND=1 COVERAGE=1 80 | 81 | - os: linux 82 | compiler: gcc 83 | addons: 84 | apt: 85 | sources: *all_sources 86 | packages: ['valgrind', 'lcov', 'g++-4.9'] 87 | env: COMPILER='g++-4.9' VALGRIND=1 88 | 89 | - os: linux 90 | compiler: gcc 91 | addons: 92 | apt: 93 | sources: *all_sources 94 | packages: ['valgrind', 'lcov', 'g++-5'] 95 | env: COMPILER='g++-5' VALGRIND=1 96 | 97 | - os: linux 98 | compiler: gcc 99 | addons: &gcc6 100 | apt: 101 | sources: *all_sources 102 | packages: ['valgrind', 'lcov', 'g++-6'] 103 | env: COMPILER='g++-6' VALGRIND=1 104 | 105 | - os: linux 106 | compiler: gcc 107 | addons: &gcc7 108 | apt: 109 | sources: *all_sources 110 | packages: ['valgrind', 'lcov', 'g++-7'] 111 | env: COMPILER='g++-7' VALGRIND=1 112 | 113 | - os: linux 114 | compiler: gcc 115 | addons: *gcc7 116 | env: COMPILER='g++-7' CPP17=1 COVERAGE=1 117 | 118 | # 5/ OSX Clang Builds 119 | - os: osx 120 | osx_image: xcode8.3 121 | compiler: clang 122 | env: COMPILER='clang++' 123 | 124 | - os: osx 125 | osx_image: xcode9 126 | compiler: clang 127 | env: COMPILER='clang++' 128 | 129 | - os: osx 130 | osx_image: xcode9.1 131 | compiler: clang 132 | env: COMPILER='clang++' 133 | 134 | - os: osx 135 | osx_image: xcode9.1 136 | compiler: clang 137 | env: COMPILER='clang++' CPP14=1 138 | 139 | 140 | install: 141 | - DEPS_DIR="${TRAVIS_BUILD_DIR}/deps" 142 | - mkdir -p ${DEPS_DIR} && cd ${DEPS_DIR} 143 | - | 144 | if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then 145 | CMAKE_URL="http://cmake.org/files/v3.8/cmake-3.8.2-Linux-x86_64.tar.gz" 146 | mkdir cmake && travis_retry wget --no-check-certificate --quiet -O - ${CMAKE_URL} | tar --strip-components=1 -xz -C cmake 147 | export PATH=${DEPS_DIR}/cmake/bin:${PATH} 148 | elif [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then 149 | which cmake || brew install cmake; 150 | fi 151 | 152 | before_script: 153 | - export CXX=${COMPILER} 154 | - cd ${TRAVIS_BUILD_DIR} 155 | # Use Debug builds for collecting coverage 156 | - cmake -H. -BBuild-Debug -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=${COVERAGE} -DUSE_CPP14=${CPP14} -DUSE_CPP17=${CPP17} 157 | # Don't bother with release build for coverage build 158 | - cmake -H. -BBuild-Release -DCMAKE_BUILD_TYPE=Release 159 | 160 | 161 | script: 162 | - cd Build-Debug 163 | - make -j 2 164 | - CTEST_OUTPUT_ON_FAILURE=1 ctest -j 2 165 | - | 166 | # Coverage collection does not work for OS X atm 167 | if [[ "${COVERAGE}" == "1" ]]; then 168 | make gcov 169 | make lcov 170 | bash <(curl -s https://codecov.io/bash) -X gcov || echo "Codecov did not collect coverage reports" 171 | fi 172 | # Go to release build 173 | - cd ../Build-Release 174 | - make -j 2 175 | - CTEST_OUTPUT_ON_FAILURE=1 ctest -j 2 176 | -------------------------------------------------------------------------------- /CMake/FindGcov.cmake: -------------------------------------------------------------------------------- 1 | # This file is part of CMake-codecov. 2 | # 3 | # Copyright (c) 4 | # 2015-2017 RWTH Aachen University, Federal Republic of Germany 5 | # 6 | # See the LICENSE file in the package base directory for details 7 | # 8 | # Written by Alexander Haase, alexander.haase@rwth-aachen.de 9 | # 10 | 11 | 12 | # include required Modules 13 | include(FindPackageHandleStandardArgs) 14 | 15 | 16 | # Search for gcov binary. 17 | set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET}) 18 | set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY}) 19 | 20 | get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) 21 | foreach (LANG ${ENABLED_LANGUAGES}) 22 | # Gcov evaluation is dependend on the used compiler. Check gcov support for 23 | # each compiler that is used. If gcov binary was already found for this 24 | # compiler, do not try to find it again. 25 | if (NOT GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN) 26 | get_filename_component(COMPILER_PATH "${CMAKE_${LANG}_COMPILER}" PATH) 27 | 28 | if ("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "GNU") 29 | # Some distributions like OSX (homebrew) ship gcov with the compiler 30 | # version appended as gcov-x. To find this binary we'll build the 31 | # suggested binary name with the compiler version. 32 | string(REGEX MATCH "^[0-9]+" GCC_VERSION 33 | "${CMAKE_${LANG}_COMPILER_VERSION}") 34 | 35 | find_program(GCOV_BIN NAMES gcov-${GCC_VERSION} gcov 36 | HINTS ${COMPILER_PATH}) 37 | 38 | elseif ("${CMAKE_${LANG}_COMPILER_ID}" STREQUAL "Clang") 39 | # Some distributions like Debian ship llvm-cov with the compiler 40 | # version appended as llvm-cov-x.y. To find this binary we'll build 41 | # the suggested binary name with the compiler version. 42 | string(REGEX MATCH "^[0-9]+.[0-9]+" LLVM_VERSION 43 | "${CMAKE_${LANG}_COMPILER_VERSION}") 44 | 45 | # llvm-cov prior version 3.5 seems to be not working with coverage 46 | # evaluation tools, but these versions are compatible with the gcc 47 | # gcov tool. 48 | if(LLVM_VERSION VERSION_GREATER 3.4) 49 | find_program(LLVM_COV_BIN NAMES "llvm-cov-${LLVM_VERSION}" 50 | "llvm-cov" HINTS ${COMPILER_PATH}) 51 | mark_as_advanced(LLVM_COV_BIN) 52 | 53 | if (LLVM_COV_BIN) 54 | find_program(LLVM_COV_WRAPPER "llvm-cov-wrapper" PATHS 55 | ${CMAKE_MODULE_PATH}) 56 | if (LLVM_COV_WRAPPER) 57 | set(GCOV_BIN "${LLVM_COV_WRAPPER}" CACHE FILEPATH "") 58 | 59 | # set additional parameters 60 | set(GCOV_${CMAKE_${LANG}_COMPILER_ID}_ENV 61 | "LLVM_COV_BIN=${LLVM_COV_BIN}" CACHE STRING 62 | "Environment variables for llvm-cov-wrapper.") 63 | mark_as_advanced(GCOV_${CMAKE_${LANG}_COMPILER_ID}_ENV) 64 | endif () 65 | endif () 66 | endif () 67 | 68 | if (NOT GCOV_BIN) 69 | # Fall back to gcov binary if llvm-cov was not found or is 70 | # incompatible. This is the default on OSX, but may crash on 71 | # recent Linux versions. 72 | find_program(GCOV_BIN gcov HINTS ${COMPILER_PATH}) 73 | endif () 74 | endif () 75 | 76 | 77 | if (GCOV_BIN) 78 | set(GCOV_${CMAKE_${LANG}_COMPILER_ID}_BIN "${GCOV_BIN}" CACHE STRING 79 | "${LANG} gcov binary.") 80 | 81 | if (NOT CMAKE_REQUIRED_QUIET) 82 | message("-- Found gcov evaluation for " 83 | "${CMAKE_${LANG}_COMPILER_ID}: ${GCOV_BIN}") 84 | endif() 85 | 86 | unset(GCOV_BIN CACHE) 87 | endif () 88 | endif () 89 | endforeach () 90 | 91 | 92 | 93 | 94 | # Add a new global target for all gcov targets. This target could be used to 95 | # generate the gcov files for the whole project instead of calling -gcov 96 | # for each target. 97 | if (NOT TARGET gcov) 98 | add_custom_target(gcov) 99 | endif (NOT TARGET gcov) 100 | 101 | 102 | 103 | # This function will add gcov evaluation for target . Only sources of 104 | # this target will be evaluated and no dependencies will be added. It will call 105 | # Gcov on any source file of once and store the gcov file in the same 106 | # directory. 107 | function (add_gcov_target TNAME) 108 | set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir) 109 | 110 | # We don't have to check, if the target has support for coverage, thus this 111 | # will be checked by add_coverage_target in Findcoverage.cmake. Instead we 112 | # have to determine which gcov binary to use. 113 | get_target_property(TSOURCES ${TNAME} SOURCES) 114 | set(SOURCES "") 115 | set(TCOMPILER "") 116 | foreach (FILE ${TSOURCES}) 117 | codecov_path_of_source(${FILE} FILE) 118 | if (NOT "${FILE}" STREQUAL "") 119 | codecov_lang_of_source(${FILE} LANG) 120 | if (NOT "${LANG}" STREQUAL "") 121 | list(APPEND SOURCES "${FILE}") 122 | set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID}) 123 | endif () 124 | endif () 125 | endforeach () 126 | 127 | # If no gcov binary was found, coverage data can't be evaluated. 128 | if (NOT GCOV_${TCOMPILER}_BIN) 129 | message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.") 130 | return() 131 | endif () 132 | 133 | set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") 134 | set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") 135 | 136 | 137 | set(BUFFER "") 138 | foreach(FILE ${SOURCES}) 139 | get_filename_component(FILE_PATH "${TDIR}/${FILE}" PATH) 140 | 141 | # call gcov 142 | add_custom_command(OUTPUT ${TDIR}/${FILE}.gcov 143 | COMMAND ${GCOV_ENV} ${GCOV_BIN} ${TDIR}/${FILE}.gcno > /dev/null 144 | DEPENDS ${TNAME} ${TDIR}/${FILE}.gcno 145 | WORKING_DIRECTORY ${FILE_PATH} 146 | ) 147 | 148 | list(APPEND BUFFER ${TDIR}/${FILE}.gcov) 149 | endforeach() 150 | 151 | 152 | # add target for gcov evaluation of 153 | add_custom_target(${TNAME}-gcov DEPENDS ${BUFFER}) 154 | 155 | # add evaluation target to the global gcov target. 156 | add_dependencies(gcov ${TNAME}-gcov) 157 | endfunction (add_gcov_target) 158 | -------------------------------------------------------------------------------- /scripts/stitch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This is an initial cut of a general header stitching script. 4 | # It is currently hard-coded to work with Clara, but that is purely 5 | # a path thing. The next step is the genericise this further and 6 | # apply it to Catch. There will undoubtedly be issues to fix there. 7 | # After that there are further tweaks to make such as suppressing initial 8 | # comments in stitched headers and adding a single new header block to 9 | # the output file, suppressing conditional blocks where the identifiers 10 | # are known and moving all headers not in conditional blocks to the top 11 | # of the file 12 | 13 | import os 14 | import sys 15 | import re 16 | import datetime 17 | import string 18 | 19 | preprocessorRe = re.compile( r'\s*#.*' ) 20 | 21 | includesRe = re.compile( r'\s*#\s*include.*' ) # all #includes 22 | sysIncludesRe = re.compile( r'\s*#\s*include\s*<(.*)>' ) # .e.g #include 23 | prjIncludesRe = re.compile( r'\s*#\s*include\s*"(.*)"' ) # e.g. #include "myheader.h" 24 | 25 | fdefineRe = re.compile( r'\s*#\s*define\s*(\S*)\s*\(' ) # #defines that take arguments 26 | defineRe = re.compile( r'\s*#\s*define\s*(\S*)\s+(\S*)' ) # all #defines 27 | undefRe = re.compile( r'\s*#\s*undef\s*(\S*)' ) # all #undefs 28 | 29 | ifdefCommonRe = re.compile( r'\s*#\s*if' ) # all #ifdefs 30 | ifdefRe = re.compile( r'\s*#\s*ifdef\s*(\S*)' ) 31 | ifndefRe = re.compile( r'\s*#\s*ifndef\s*(\S*)' ) 32 | endifRe = re.compile( r'\s*#\s*endif(.*)' ) 33 | elseRe = re.compile( r'\s*#\s*else' ) 34 | ifRe = re.compile( r'\s*#\s*if\s+(.*)' ) 35 | 36 | rootPath = os.path.dirname(os.path.realpath( os.path.dirname(sys.argv[0]))) 37 | srcsPath = os.path.join( rootPath, 'src' ) 38 | includePath = os.path.join( rootPath, 'include' ) 39 | singleIncludePath = os.path.join( rootPath, 'single_include' ) 40 | extIncludePath = os.path.join( includePath, "external" ) 41 | thirdPartyPath = os.path.join( rootPath, 'third_party' ) 42 | firstHeaderPath = os.path.join( includePath, 'clara.hpp' ) 43 | outPath = os.path.join( singleIncludePath, 'clara.hpp' ) 44 | 45 | o = open( outPath, 'w' ) 46 | 47 | systemHeaders = set([]) 48 | projectHeaders = set([]) 49 | defines = set([]) 50 | LevelMax = 9999 51 | suppressUntilLevel = LevelMax 52 | level = 0 53 | 54 | class FileParser: 55 | filename = "" 56 | 57 | def __init__( self, filename ): 58 | self.filename = filename 59 | 60 | def findHeader( self, headerFile ): 61 | 62 | # !TBD: make this array based 63 | 64 | # First check next to current file 65 | dir, _ = os.path.split( self.filename ) 66 | fullPath = os.path.join( dir, headerFile ) 67 | if os.path.isfile(fullPath): 68 | return fullPath 69 | 70 | # Next check include folder 71 | fullPath = os.path.join( includePath, headerFile ) 72 | if os.path.isfile(fullPath): 73 | return fullPath 74 | 75 | # Then ext folder 76 | fullPath = os.path.join( extIncludePath, headerFile ) 77 | if os.path.isfile(fullPath): 78 | return fullPath 79 | 80 | # Finally check ThirdParty folder 81 | fullPath = os.path.join( thirdPartyPath, headerFile ) 82 | if os.path.isfile(fullPath): 83 | return fullPath 84 | 85 | raise FileNotFoundError( "Cannot locate include file: '" + filename + "'" ) 86 | 87 | 88 | def parse( self ): 89 | with open( self.filename, 'r' ) as f: 90 | for line in f: 91 | if preprocessorRe.match( line ): 92 | self.handlePreprocessor( line ) 93 | else: 94 | self.handleNonPP( line ) 95 | 96 | def handleNonPP( self, line ): 97 | if suppressUntilLevel > level: 98 | self.writeLine( line ) 99 | 100 | def handlePreprocessor( self, line ): 101 | if includesRe.match( line ): 102 | if suppressUntilLevel > level: 103 | self.handleInclude( line ) 104 | else: 105 | self.handleNonIncludePP( line ) 106 | 107 | def handleInclude( self, line ): 108 | global systemHeaders 109 | global projectHeaders 110 | m = sysIncludesRe.match( line ) 111 | if m: 112 | headerFile = m.group(1) 113 | if not headerFile in systemHeaders: 114 | self.writeLine( "#include <{0}>".format( headerFile ) ) 115 | systemHeaders.add( headerFile ) 116 | 117 | m = prjIncludesRe.match( line ) 118 | if m: 119 | headerFile = m.group(1) 120 | if not headerFile in projectHeaders: 121 | self.writeLine( '\n// ----------- #included from {0} -----------'.format( headerFile ) ) 122 | self.writeLine( "" ) 123 | projectHeaders.add( headerFile ) 124 | 125 | _, filename = os.path.split( self.filename ) 126 | headerPath = self.findHeader( headerFile ) 127 | p = FileParser( headerPath ) 128 | p.parse() 129 | self.writeLine( '\n// ----------- end of #include from {0} -----------'.format( headerFile ) ) 130 | self.writeLine( '// ........... back in {0}'.format( filename ) ) 131 | self.writeLine( "" ) 132 | 133 | def handleNonIncludePP( self, line ): 134 | m = endifRe.match( line ) 135 | if m: 136 | self.handleEndif( m.group(1) ) 137 | m = elseRe.match( line ) 138 | if m: 139 | self.handleElse() 140 | 141 | if ifdefCommonRe.match( line ): 142 | self.handleIfdefCommon( line ) 143 | 144 | if suppressUntilLevel > level: 145 | m = defineRe.match( line ) 146 | if m: 147 | if fdefineRe.match( line ): 148 | self.writeLine( line ) 149 | else: 150 | self.handleDefine( m.group(1), m.group(2) ) 151 | m = undefRe.match( line ) 152 | if m: 153 | self.handleUndef( m.group(1) ) 154 | 155 | def handleDefine( self, define, value ): 156 | self.writeLine( "#define {0} {1}".format( define, value ) ) 157 | defines.add( define ) 158 | 159 | def handleUndef( self, define ): 160 | self.writeLine( "#undef {0}".format( define ) ) 161 | defines.remove( define ) 162 | 163 | def handleIfdefCommon( self, line ): 164 | global level 165 | level = level + 1 166 | m = ifndefRe.match( line ) 167 | if m: 168 | self.handleIfndef( m.group(1) ) 169 | else: 170 | m = ifdefRe.match( line ) 171 | if m: 172 | self.handleIfdef( m.group(1) ) 173 | else: 174 | m = ifRe.match( line ) 175 | if m: 176 | self.handleIf( m.group(1) ) 177 | else: 178 | print "****** error ***** " + line 179 | 180 | def handleEndif( self, trailing ): 181 | global level 182 | global suppressUntilLevel 183 | self.writeLine( "#endif{0}".format( trailing ) ) 184 | level = level - 1 185 | if level == suppressUntilLevel: 186 | suppressUntilLevel = LevelMax 187 | 188 | def handleElse( self ): 189 | global suppressUntilLevel 190 | self.writeLine( "#else" ) 191 | if level == suppressUntilLevel+1: 192 | suppressUntilLevel = LevelMax 193 | 194 | def handleIfndef( self, define ): 195 | self.writeLine( "#ifndef {0}".format( define ) ) 196 | if define not in defines: 197 | suppressUntilLevel = level 198 | 199 | def handleIfdef( self, define ): 200 | self.writeLine( "#ifdef {0}".format( define ) ) 201 | if define in defines: 202 | suppressUntilLevel = level 203 | 204 | def handleIf( self, trailing ): 205 | global level 206 | global suppressUntilLevel 207 | self.writeLine( "#if {0}".format( trailing ) ) 208 | level = level + 1 209 | # if level == suppressUntilLevel: 210 | # suppressUntilLevel = LevelMax 211 | 212 | 213 | def writeLine( self, line ): 214 | o.write( line.rstrip() + "\n" ) 215 | 216 | print( "from: " + firstHeaderPath ) 217 | print( "to: " + outPath ) 218 | p = FileParser( firstHeaderPath ) 219 | p.parse() 220 | 221 | o.close() 222 | 223 | print "-------------" 224 | print level 225 | #for h in systemHeaders: 226 | # print "#include <" + h + ">" 227 | #print 228 | # 229 | #for h in projectHeaders: 230 | # print '#include "' + h + '"' 231 | #print 232 | #for d in defines: 233 | # print d 234 | #print 235 | -------------------------------------------------------------------------------- /CMake/Findcodecov.cmake: -------------------------------------------------------------------------------- 1 | # This file is part of CMake-codecov. 2 | # 3 | # Copyright (c) 4 | # 2015-2017 RWTH Aachen University, Federal Republic of Germany 5 | # 6 | # See the LICENSE file in the package base directory for details 7 | # 8 | # Written by Alexander Haase, alexander.haase@rwth-aachen.de 9 | # 10 | 11 | 12 | # Add an option to choose, if coverage should be enabled or not. If enabled 13 | # marked targets will be build with coverage support and appropriate targets 14 | # will be added. If disabled coverage will be ignored for *ALL* targets. 15 | option(ENABLE_COVERAGE "Enable coverage build." OFF) 16 | 17 | set(COVERAGE_FLAG_CANDIDATES 18 | # gcc and clang 19 | "-O0 -g -fprofile-arcs -ftest-coverage" 20 | 21 | # gcc and clang fallback 22 | "-O0 -g --coverage" 23 | ) 24 | 25 | 26 | # Add coverage support for target ${TNAME} and register target for coverage 27 | # evaluation. If coverage is disabled or not supported, this function will 28 | # simply do nothing. 29 | # 30 | # Note: This function is only a wrapper to define this function always, even if 31 | # coverage is not supported by the compiler or disabled. This function must 32 | # be defined here, because the module will be exited, if there is no coverage 33 | # support by the compiler or it is disabled by the user. 34 | function (add_coverage TNAME) 35 | # only add coverage for target, if coverage is support and enabled. 36 | if (ENABLE_COVERAGE) 37 | foreach (TNAME ${ARGV}) 38 | add_coverage_target(${TNAME}) 39 | endforeach () 40 | endif () 41 | endfunction (add_coverage) 42 | 43 | 44 | # Add global target to gather coverage information after all targets have been 45 | # added. Other evaluation functions could be added here, after checks for the 46 | # specific module have been passed. 47 | # 48 | # Note: This function is only a wrapper to define this function always, even if 49 | # coverage is not supported by the compiler or disabled. This function must 50 | # be defined here, because the module will be exited, if there is no coverage 51 | # support by the compiler or it is disabled by the user. 52 | function (coverage_evaluate) 53 | # add lcov evaluation 54 | if (LCOV_FOUND) 55 | lcov_capture_initial() 56 | lcov_capture() 57 | endif (LCOV_FOUND) 58 | endfunction () 59 | 60 | 61 | # Exit this module, if coverage is disabled. add_coverage is defined before this 62 | # return, so this module can be exited now safely without breaking any build- 63 | # scripts. 64 | if (NOT ENABLE_COVERAGE) 65 | return() 66 | endif () 67 | 68 | 69 | 70 | 71 | # Find the reuired flags foreach language. 72 | set(CMAKE_REQUIRED_QUIET_SAVE ${CMAKE_REQUIRED_QUIET}) 73 | set(CMAKE_REQUIRED_QUIET ${codecov_FIND_QUIETLY}) 74 | 75 | get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) 76 | foreach (LANG ${ENABLED_LANGUAGES}) 77 | # Coverage flags are not dependend on language, but the used compiler. So 78 | # instead of searching flags foreach language, search flags foreach compiler 79 | # used. 80 | set(COMPILER ${CMAKE_${LANG}_COMPILER_ID}) 81 | if (NOT COVERAGE_${COMPILER}_FLAGS) 82 | foreach (FLAG ${COVERAGE_FLAG_CANDIDATES}) 83 | if(NOT CMAKE_REQUIRED_QUIET) 84 | message(STATUS "Try ${COMPILER} code coverage flag = [${FLAG}]") 85 | endif() 86 | 87 | set(CMAKE_REQUIRED_FLAGS "${FLAG}") 88 | unset(COVERAGE_FLAG_DETECTED CACHE) 89 | 90 | if (${LANG} STREQUAL "C") 91 | include(CheckCCompilerFlag) 92 | check_c_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED) 93 | 94 | elseif (${LANG} STREQUAL "CXX") 95 | include(CheckCXXCompilerFlag) 96 | check_cxx_compiler_flag("${FLAG}" COVERAGE_FLAG_DETECTED) 97 | 98 | elseif (${LANG} STREQUAL "Fortran") 99 | # CheckFortranCompilerFlag was introduced in CMake 3.x. To be 100 | # compatible with older Cmake versions, we will check if this 101 | # module is present before we use it. Otherwise we will define 102 | # Fortran coverage support as not available. 103 | include(CheckFortranCompilerFlag OPTIONAL 104 | RESULT_VARIABLE INCLUDED) 105 | if (INCLUDED) 106 | check_fortran_compiler_flag("${FLAG}" 107 | COVERAGE_FLAG_DETECTED) 108 | elseif (NOT CMAKE_REQUIRED_QUIET) 109 | message("-- Performing Test COVERAGE_FLAG_DETECTED") 110 | message("-- Performing Test COVERAGE_FLAG_DETECTED - Failed" 111 | " (Check not supported)") 112 | endif () 113 | endif() 114 | 115 | if (COVERAGE_FLAG_DETECTED) 116 | set(COVERAGE_${COMPILER}_FLAGS "${FLAG}" 117 | CACHE STRING "${COMPILER} flags for code coverage.") 118 | mark_as_advanced(COVERAGE_${COMPILER}_FLAGS) 119 | break() 120 | else () 121 | message(WARNING "Code coverage is not available for ${COMPILER}" 122 | " compiler. Targets using this compiler will be " 123 | "compiled without it.") 124 | endif () 125 | endforeach () 126 | endif () 127 | endforeach () 128 | 129 | set(CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET_SAVE}) 130 | 131 | 132 | 133 | 134 | # Helper function to get the language of a source file. 135 | function (codecov_lang_of_source FILE RETURN_VAR) 136 | get_filename_component(FILE_EXT "${FILE}" EXT) 137 | string(TOLOWER "${FILE_EXT}" FILE_EXT) 138 | string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT) 139 | 140 | get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) 141 | foreach (LANG ${ENABLED_LANGUAGES}) 142 | list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP) 143 | if (NOT ${TEMP} EQUAL -1) 144 | set(${RETURN_VAR} "${LANG}" PARENT_SCOPE) 145 | return() 146 | endif () 147 | endforeach() 148 | 149 | set(${RETURN_VAR} "" PARENT_SCOPE) 150 | endfunction () 151 | 152 | 153 | # Helper function to get the relative path of the source file destination path. 154 | # This path is needed by FindGcov and FindLcov cmake files to locate the 155 | # captured data. 156 | function (codecov_path_of_source FILE RETURN_VAR) 157 | string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _source ${FILE}) 158 | 159 | # If expression was found, SOURCEFILE is a generator-expression for an 160 | # object library. Currently we found no way to call this function automatic 161 | # for the referenced target, so it must be called in the directoryso of the 162 | # object library definition. 163 | if (NOT "${_source}" STREQUAL "") 164 | set(${RETURN_VAR} "" PARENT_SCOPE) 165 | return() 166 | endif () 167 | 168 | 169 | string(REPLACE "${CMAKE_CURRENT_BINARY_DIR}/" "" FILE "${FILE}") 170 | if(IS_ABSOLUTE ${FILE}) 171 | file(RELATIVE_PATH FILE ${CMAKE_CURRENT_SOURCE_DIR} ${FILE}) 172 | endif() 173 | 174 | # get the right path for file 175 | string(REPLACE ".." "__" PATH "${FILE}") 176 | 177 | set(${RETURN_VAR} "${PATH}" PARENT_SCOPE) 178 | endfunction() 179 | 180 | 181 | 182 | 183 | # Add coverage support for target ${TNAME} and register target for coverage 184 | # evaluation. 185 | function(add_coverage_target TNAME) 186 | # Check if all sources for target use the same compiler. If a target uses 187 | # e.g. C and Fortran mixed and uses different compilers (e.g. clang and 188 | # gfortran) this can trigger huge problems, because different compilers may 189 | # use different implementations for code coverage. 190 | get_target_property(TSOURCES ${TNAME} SOURCES) 191 | set(TARGET_COMPILER "") 192 | set(ADDITIONAL_FILES "") 193 | foreach (FILE ${TSOURCES}) 194 | # If expression was found, FILE is a generator-expression for an object 195 | # library. Object libraries will be ignored. 196 | string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE}) 197 | if ("${_file}" STREQUAL "") 198 | codecov_lang_of_source(${FILE} LANG) 199 | if (LANG) 200 | list(APPEND TARGET_COMPILER ${CMAKE_${LANG}_COMPILER_ID}) 201 | 202 | list(APPEND ADDITIONAL_FILES "${FILE}.gcno") 203 | list(APPEND ADDITIONAL_FILES "${FILE}.gcda") 204 | endif () 205 | endif () 206 | endforeach () 207 | 208 | list(REMOVE_DUPLICATES TARGET_COMPILER) 209 | list(LENGTH TARGET_COMPILER NUM_COMPILERS) 210 | 211 | if (NUM_COMPILERS GREATER 1) 212 | message(WARNING "Can't use code coverage for target ${TNAME}, because " 213 | "it will be compiled by incompatible compilers. Target will be " 214 | "compiled without code coverage.") 215 | return() 216 | 217 | elseif (NUM_COMPILERS EQUAL 0) 218 | message(WARNING "Can't use code coverage for target ${TNAME}, because " 219 | "it uses an unknown compiler. Target will be compiled without " 220 | "code coverage.") 221 | return() 222 | 223 | elseif (NOT DEFINED "COVERAGE_${TARGET_COMPILER}_FLAGS") 224 | # A warning has been printed before, so just return if flags for this 225 | # compiler aren't available. 226 | return() 227 | endif() 228 | 229 | 230 | # enable coverage for target 231 | set_property(TARGET ${TNAME} APPEND_STRING 232 | PROPERTY COMPILE_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}") 233 | set_property(TARGET ${TNAME} APPEND_STRING 234 | PROPERTY LINK_FLAGS " ${COVERAGE_${TARGET_COMPILER}_FLAGS}") 235 | 236 | 237 | # Add gcov files generated by compiler to clean target. 238 | set(CLEAN_FILES "") 239 | foreach (FILE ${ADDITIONAL_FILES}) 240 | codecov_path_of_source(${FILE} FILE) 241 | list(APPEND CLEAN_FILES "CMakeFiles/${TNAME}.dir/${FILE}") 242 | endforeach() 243 | 244 | set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES 245 | "${CLEAN_FILES}") 246 | 247 | 248 | add_gcov_target(${TNAME}) 249 | add_lcov_target(${TNAME}) 250 | endfunction(add_coverage_target) 251 | 252 | 253 | 254 | 255 | # Include modules for parsing the collected data and output it in a readable 256 | # format (like gcov and lcov). 257 | find_package(Gcov) 258 | find_package(Lcov) 259 | -------------------------------------------------------------------------------- /third_party/TextFlow.hpp: -------------------------------------------------------------------------------- 1 | // TextFlowCpp 2 | // 3 | // A single-header library for wrapping and laying out basic text, by Phil Nash 4 | // 5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 6 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // This project is hosted at https://github.com/philsquared/textflowcpp 9 | 10 | #ifndef TEXTFLOW_HPP_INCLUDED 11 | #define TEXTFLOW_HPP_INCLUDED 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #ifndef TEXTFLOW_CONFIG_CONSOLE_WIDTH 19 | #define TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 20 | #endif 21 | 22 | 23 | namespace TextFlow { 24 | 25 | inline auto isWhitespace( char c ) -> bool { 26 | static std::string chars = " \t\n\r"; 27 | return chars.find( c ) != std::string::npos; 28 | } 29 | inline auto isBreakableBefore( char c ) -> bool { 30 | static std::string chars = "[({<|"; 31 | return chars.find( c ) != std::string::npos; 32 | } 33 | inline auto isBreakableAfter( char c ) -> bool { 34 | static std::string chars = "])}>.,:;*+-=&/\\"; 35 | return chars.find( c ) != std::string::npos; 36 | } 37 | 38 | class Columns; 39 | 40 | class Column { 41 | std::vector m_strings; 42 | size_t m_width = TEXTFLOW_CONFIG_CONSOLE_WIDTH; 43 | size_t m_indent = 0; 44 | size_t m_initialIndent = std::string::npos; 45 | 46 | public: 47 | class iterator { 48 | friend Column; 49 | 50 | Column const& m_column; 51 | size_t m_stringIndex = 0; 52 | size_t m_pos = 0; 53 | 54 | size_t m_len = 0; 55 | size_t m_end = 0; 56 | bool m_suffix = false; 57 | 58 | iterator( Column const& column, size_t stringIndex ) 59 | : m_column( column ), 60 | m_stringIndex( stringIndex ) 61 | {} 62 | 63 | auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } 64 | 65 | auto isBoundary( size_t at ) const -> bool { 66 | assert( at > 0 ); 67 | assert( at <= line().size() ); 68 | 69 | return at == line().size() || 70 | ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || 71 | isBreakableBefore( line()[at] ) || 72 | isBreakableAfter( line()[at-1] ); 73 | } 74 | 75 | void calcLength() { 76 | assert( m_stringIndex < m_column.m_strings.size() ); 77 | 78 | m_suffix = false; 79 | auto width = m_column.m_width-indent(); 80 | m_end = m_pos; 81 | while( m_end < line().size() && line()[m_end] != '\n' ) 82 | ++m_end; 83 | 84 | if( m_end < m_pos + width ) { 85 | m_len = m_end - m_pos; 86 | } 87 | else { 88 | size_t len = width; 89 | while (len > 0 && !isBoundary(m_pos + len)) 90 | --len; 91 | while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) 92 | --len; 93 | 94 | if (len > 0) { 95 | m_len = len; 96 | } else { 97 | m_suffix = true; 98 | m_len = width - 1; 99 | } 100 | } 101 | } 102 | 103 | auto indent() const -> size_t { 104 | auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; 105 | return initial == std::string::npos ? m_column.m_indent : initial; 106 | } 107 | 108 | auto addIndentAndSuffix(std::string const &plain) const -> std::string { 109 | return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); 110 | } 111 | 112 | public: 113 | using difference_type = std::ptrdiff_t; 114 | using value_type = std::string; 115 | using pointer = value_type*; 116 | using reference = value_type&; 117 | using iterator_category = std::forward_iterator_tag; 118 | 119 | explicit iterator( Column const& column ) : m_column( column ) { 120 | assert( m_column.m_width > m_column.m_indent ); 121 | assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); 122 | calcLength(); 123 | if( m_len == 0 ) 124 | m_stringIndex++; // Empty string 125 | } 126 | 127 | auto operator *() const -> std::string { 128 | assert( m_stringIndex < m_column.m_strings.size() ); 129 | assert( m_pos <= m_end ); 130 | return addIndentAndSuffix(line().substr(m_pos, m_len)); 131 | } 132 | 133 | auto operator ++() -> iterator& { 134 | m_pos += m_len; 135 | if( m_pos < line().size() && line()[m_pos] == '\n' ) 136 | m_pos += 1; 137 | else 138 | while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) 139 | ++m_pos; 140 | 141 | if( m_pos == line().size() ) { 142 | m_pos = 0; 143 | ++m_stringIndex; 144 | } 145 | if( m_stringIndex < m_column.m_strings.size() ) 146 | calcLength(); 147 | return *this; 148 | } 149 | auto operator ++(int) -> iterator { 150 | iterator prev( *this ); 151 | operator++(); 152 | return prev; 153 | } 154 | 155 | auto operator ==( iterator const& other ) const -> bool { 156 | return 157 | m_pos == other.m_pos && 158 | m_stringIndex == other.m_stringIndex && 159 | &m_column == &other.m_column; 160 | } 161 | auto operator !=( iterator const& other ) const -> bool { 162 | return !operator==( other ); 163 | } 164 | }; 165 | using const_iterator = iterator; 166 | 167 | explicit Column( std::string const& text ) { m_strings.push_back( text ); } 168 | 169 | auto width( size_t newWidth ) -> Column& { 170 | assert( newWidth > 0 ); 171 | m_width = newWidth; 172 | return *this; 173 | } 174 | auto indent( size_t newIndent ) -> Column& { 175 | m_indent = newIndent; 176 | return *this; 177 | } 178 | auto initialIndent( size_t newIndent ) -> Column& { 179 | m_initialIndent = newIndent; 180 | return *this; 181 | } 182 | 183 | auto width() const -> size_t { return m_width; } 184 | auto begin() const -> iterator { return iterator( *this ); } 185 | auto end() const -> iterator { return { *this, m_strings.size() }; } 186 | 187 | inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { 188 | bool first = true; 189 | for( auto line : col ) { 190 | if( first ) 191 | first = false; 192 | else 193 | os << "\n"; 194 | os << line; 195 | } 196 | return os; 197 | } 198 | 199 | auto operator + ( Column const& other ) -> Columns; 200 | 201 | auto toString() const -> std::string { 202 | std::ostringstream oss; 203 | oss << *this; 204 | return oss.str(); 205 | } 206 | }; 207 | 208 | class Spacer : public Column { 209 | 210 | public: 211 | explicit Spacer( size_t spaceWidth ) : Column( "" ) { 212 | width( spaceWidth ); 213 | } 214 | }; 215 | 216 | class Columns { 217 | std::vector m_columns; 218 | 219 | public: 220 | 221 | class iterator { 222 | friend Columns; 223 | struct EndTag {}; 224 | 225 | std::vector const& m_columns; 226 | std::vector m_iterators; 227 | size_t m_activeIterators; 228 | 229 | iterator( Columns const& columns, EndTag ) 230 | : m_columns( columns.m_columns ), 231 | m_activeIterators( 0 ) 232 | { 233 | m_iterators.reserve( m_columns.size() ); 234 | 235 | for( auto const& col : m_columns ) 236 | m_iterators.push_back( col.end() ); 237 | } 238 | 239 | public: 240 | using difference_type = std::ptrdiff_t; 241 | using value_type = std::string; 242 | using pointer = value_type*; 243 | using reference = value_type&; 244 | using iterator_category = std::forward_iterator_tag; 245 | 246 | explicit iterator( Columns const& columns ) 247 | : m_columns( columns.m_columns ), 248 | m_activeIterators( m_columns.size() ) 249 | { 250 | m_iterators.reserve( m_columns.size() ); 251 | 252 | for( auto const& col : m_columns ) 253 | m_iterators.push_back( col.begin() ); 254 | } 255 | 256 | auto operator ==( iterator const& other ) const -> bool { 257 | return m_iterators == other.m_iterators; 258 | } 259 | auto operator !=( iterator const& other ) const -> bool { 260 | return m_iterators != other.m_iterators; 261 | } 262 | auto operator *() const -> std::string { 263 | std::string row, padding; 264 | 265 | for( size_t i = 0; i < m_columns.size(); ++i ) { 266 | auto width = m_columns[i].width(); 267 | if( m_iterators[i] != m_columns[i].end() ) { 268 | std::string col = *m_iterators[i]; 269 | row += padding + col; 270 | if( col.size() < width ) 271 | padding = std::string( width - col.size(), ' ' ); 272 | else 273 | padding = ""; 274 | } 275 | else { 276 | padding += std::string( width, ' ' ); 277 | } 278 | } 279 | return row; 280 | } 281 | auto operator ++() -> iterator& { 282 | for( size_t i = 0; i < m_columns.size(); ++i ) { 283 | if (m_iterators[i] != m_columns[i].end()) 284 | ++m_iterators[i]; 285 | } 286 | return *this; 287 | } 288 | auto operator ++(int) -> iterator { 289 | iterator prev( *this ); 290 | operator++(); 291 | return prev; 292 | } 293 | }; 294 | using const_iterator = iterator; 295 | 296 | auto begin() const -> iterator { return iterator( *this ); } 297 | auto end() const -> iterator { return { *this, iterator::EndTag() }; } 298 | 299 | auto operator += ( Column const& col ) -> Columns& { 300 | m_columns.push_back( col ); 301 | return *this; 302 | } 303 | auto operator + ( Column const& col ) -> Columns { 304 | Columns combined = *this; 305 | combined += col; 306 | return combined; 307 | } 308 | 309 | inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { 310 | 311 | bool first = true; 312 | for( auto line : cols ) { 313 | if( first ) 314 | first = false; 315 | else 316 | os << "\n"; 317 | os << line; 318 | } 319 | return os; 320 | } 321 | 322 | auto toString() const -> std::string { 323 | std::ostringstream oss; 324 | oss << *this; 325 | return oss.str(); 326 | } 327 | }; 328 | 329 | inline auto Column::operator + ( Column const& other ) -> Columns { 330 | Columns cols; 331 | cols += *this; 332 | cols += other; 333 | return cols; 334 | } 335 | } 336 | 337 | #endif // TEXTFLOW_HPP_INCLUDED 338 | -------------------------------------------------------------------------------- /include/clara_textflow.hpp: -------------------------------------------------------------------------------- 1 | // TextFlowCpp 2 | // 3 | // A single-header library for wrapping and laying out basic text, by Phil Nash 4 | // 5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 6 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // This project is hosted at https://github.com/philsquared/textflowcpp 9 | 10 | #ifndef CLARA_TEXTFLOW_HPP_INCLUDED 11 | #define CLARA_TEXTFLOW_HPP_INCLUDED 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 19 | #define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 20 | #endif 21 | 22 | 23 | namespace clara { namespace TextFlow { 24 | 25 | inline auto isWhitespace( char c ) -> bool { 26 | static std::string chars = " \t\n\r"; 27 | return chars.find( c ) != std::string::npos; 28 | } 29 | inline auto isBreakableBefore( char c ) -> bool { 30 | static std::string chars = "[({<|"; 31 | return chars.find( c ) != std::string::npos; 32 | } 33 | inline auto isBreakableAfter( char c ) -> bool { 34 | static std::string chars = "])}>.,:;*+-=&/\\"; 35 | return chars.find( c ) != std::string::npos; 36 | } 37 | 38 | class Columns; 39 | 40 | class Column { 41 | std::vector m_strings; 42 | size_t m_width = CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; 43 | size_t m_indent = 0; 44 | size_t m_initialIndent = std::string::npos; 45 | 46 | public: 47 | class iterator { 48 | friend Column; 49 | 50 | Column const& m_column; 51 | size_t m_stringIndex = 0; 52 | size_t m_pos = 0; 53 | 54 | size_t m_len = 0; 55 | size_t m_end = 0; 56 | bool m_suffix = false; 57 | 58 | iterator( Column const& column, size_t stringIndex ) 59 | : m_column( column ), 60 | m_stringIndex( stringIndex ) 61 | {} 62 | 63 | auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } 64 | 65 | auto isBoundary( size_t at ) const -> bool { 66 | assert( at > 0 ); 67 | assert( at <= line().size() ); 68 | 69 | return at == line().size() || 70 | ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || 71 | isBreakableBefore( line()[at] ) || 72 | isBreakableAfter( line()[at-1] ); 73 | } 74 | 75 | void calcLength() { 76 | assert( m_stringIndex < m_column.m_strings.size() ); 77 | 78 | m_suffix = false; 79 | auto width = m_column.m_width-indent(); 80 | m_end = m_pos; 81 | while( m_end < line().size() && line()[m_end] != '\n' ) 82 | ++m_end; 83 | 84 | if( m_end < m_pos + width ) { 85 | m_len = m_end - m_pos; 86 | } 87 | else { 88 | size_t len = width; 89 | while (len > 0 && !isBoundary(m_pos + len)) 90 | --len; 91 | while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) 92 | --len; 93 | 94 | if (len > 0) { 95 | m_len = len; 96 | } else { 97 | m_suffix = true; 98 | m_len = width - 1; 99 | } 100 | } 101 | } 102 | 103 | auto indent() const -> size_t { 104 | auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; 105 | return initial == std::string::npos ? m_column.m_indent : initial; 106 | } 107 | 108 | auto addIndentAndSuffix(std::string const &plain) const -> std::string { 109 | return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); 110 | } 111 | 112 | public: 113 | using difference_type = std::ptrdiff_t; 114 | using value_type = std::string; 115 | using pointer = value_type*; 116 | using reference = value_type&; 117 | using iterator_category = std::forward_iterator_tag; 118 | 119 | explicit iterator( Column const& column ) : m_column( column ) { 120 | assert( m_column.m_width > m_column.m_indent ); 121 | assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); 122 | calcLength(); 123 | if( m_len == 0 ) 124 | m_stringIndex++; // Empty string 125 | } 126 | 127 | auto operator *() const -> std::string { 128 | assert( m_stringIndex < m_column.m_strings.size() ); 129 | assert( m_pos <= m_end ); 130 | return addIndentAndSuffix(line().substr(m_pos, m_len)); 131 | } 132 | 133 | auto operator ++() -> iterator& { 134 | m_pos += m_len; 135 | if( m_pos < line().size() && line()[m_pos] == '\n' ) 136 | m_pos += 1; 137 | else 138 | while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) 139 | ++m_pos; 140 | 141 | if( m_pos == line().size() ) { 142 | m_pos = 0; 143 | ++m_stringIndex; 144 | } 145 | if( m_stringIndex < m_column.m_strings.size() ) 146 | calcLength(); 147 | return *this; 148 | } 149 | auto operator ++(int) -> iterator { 150 | iterator prev( *this ); 151 | operator++(); 152 | return prev; 153 | } 154 | 155 | auto operator ==( iterator const& other ) const -> bool { 156 | return 157 | m_pos == other.m_pos && 158 | m_stringIndex == other.m_stringIndex && 159 | &m_column == &other.m_column; 160 | } 161 | auto operator !=( iterator const& other ) const -> bool { 162 | return !operator==( other ); 163 | } 164 | }; 165 | using const_iterator = iterator; 166 | 167 | explicit Column( std::string const& text ) { m_strings.push_back( text ); } 168 | 169 | auto width( size_t newWidth ) -> Column& { 170 | assert( newWidth > 0 ); 171 | m_width = newWidth; 172 | return *this; 173 | } 174 | auto indent( size_t newIndent ) -> Column& { 175 | m_indent = newIndent; 176 | return *this; 177 | } 178 | auto initialIndent( size_t newIndent ) -> Column& { 179 | m_initialIndent = newIndent; 180 | return *this; 181 | } 182 | 183 | auto width() const -> size_t { return m_width; } 184 | auto begin() const -> iterator { return iterator( *this ); } 185 | auto end() const -> iterator { return { *this, m_strings.size() }; } 186 | 187 | inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { 188 | bool first = true; 189 | for( auto line : col ) { 190 | if( first ) 191 | first = false; 192 | else 193 | os << "\n"; 194 | os << line; 195 | } 196 | return os; 197 | } 198 | 199 | auto operator + ( Column const& other ) -> Columns; 200 | 201 | auto toString() const -> std::string { 202 | std::ostringstream oss; 203 | oss << *this; 204 | return oss.str(); 205 | } 206 | }; 207 | 208 | class Spacer : public Column { 209 | 210 | public: 211 | explicit Spacer( size_t spaceWidth ) : Column( "" ) { 212 | width( spaceWidth ); 213 | } 214 | }; 215 | 216 | class Columns { 217 | std::vector m_columns; 218 | 219 | public: 220 | 221 | class iterator { 222 | friend Columns; 223 | struct EndTag {}; 224 | 225 | std::vector const& m_columns; 226 | std::vector m_iterators; 227 | size_t m_activeIterators; 228 | 229 | iterator( Columns const& columns, EndTag ) 230 | : m_columns( columns.m_columns ), 231 | m_activeIterators( 0 ) 232 | { 233 | m_iterators.reserve( m_columns.size() ); 234 | 235 | for( auto const& col : m_columns ) 236 | m_iterators.push_back( col.end() ); 237 | } 238 | 239 | public: 240 | using difference_type = std::ptrdiff_t; 241 | using value_type = std::string; 242 | using pointer = value_type*; 243 | using reference = value_type&; 244 | using iterator_category = std::forward_iterator_tag; 245 | 246 | explicit iterator( Columns const& columns ) 247 | : m_columns( columns.m_columns ), 248 | m_activeIterators( m_columns.size() ) 249 | { 250 | m_iterators.reserve( m_columns.size() ); 251 | 252 | for( auto const& col : m_columns ) 253 | m_iterators.push_back( col.begin() ); 254 | } 255 | 256 | auto operator ==( iterator const& other ) const -> bool { 257 | return m_iterators == other.m_iterators; 258 | } 259 | auto operator !=( iterator const& other ) const -> bool { 260 | return m_iterators != other.m_iterators; 261 | } 262 | auto operator *() const -> std::string { 263 | std::string row, padding; 264 | 265 | for( size_t i = 0; i < m_columns.size(); ++i ) { 266 | auto width = m_columns[i].width(); 267 | if( m_iterators[i] != m_columns[i].end() ) { 268 | std::string col = *m_iterators[i]; 269 | row += padding + col; 270 | if( col.size() < width ) 271 | padding = std::string( width - col.size(), ' ' ); 272 | else 273 | padding = ""; 274 | } 275 | else { 276 | padding += std::string( width, ' ' ); 277 | } 278 | } 279 | return row; 280 | } 281 | auto operator ++() -> iterator& { 282 | for( size_t i = 0; i < m_columns.size(); ++i ) { 283 | if (m_iterators[i] != m_columns[i].end()) 284 | ++m_iterators[i]; 285 | } 286 | return *this; 287 | } 288 | auto operator ++(int) -> iterator { 289 | iterator prev( *this ); 290 | operator++(); 291 | return prev; 292 | } 293 | }; 294 | using const_iterator = iterator; 295 | 296 | auto begin() const -> iterator { return iterator( *this ); } 297 | auto end() const -> iterator { return { *this, iterator::EndTag() }; } 298 | 299 | auto operator += ( Column const& col ) -> Columns& { 300 | m_columns.push_back( col ); 301 | return *this; 302 | } 303 | auto operator + ( Column const& col ) -> Columns { 304 | Columns combined = *this; 305 | combined += col; 306 | return combined; 307 | } 308 | 309 | inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { 310 | 311 | bool first = true; 312 | for( auto line : cols ) { 313 | if( first ) 314 | first = false; 315 | else 316 | os << "\n"; 317 | os << line; 318 | } 319 | return os; 320 | } 321 | 322 | auto toString() const -> std::string { 323 | std::ostringstream oss; 324 | oss << *this; 325 | return oss.str(); 326 | } 327 | }; 328 | 329 | inline auto Column::operator + ( Column const& other ) -> Columns { 330 | Columns cols; 331 | cols += *this; 332 | cols += other; 333 | return cols; 334 | } 335 | }} 336 | 337 | #endif // CLARA_TEXTFLOW_HPP_INCLUDED 338 | -------------------------------------------------------------------------------- /CMake/FindLcov.cmake: -------------------------------------------------------------------------------- 1 | # This file is part of CMake-codecov. 2 | # 3 | # Copyright (c) 4 | # 2015-2017 RWTH Aachen University, Federal Republic of Germany 5 | # 6 | # See the LICENSE file in the package base directory for details 7 | # 8 | # Written by Alexander Haase, alexander.haase@rwth-aachen.de 9 | # 10 | 11 | 12 | # configuration 13 | set(LCOV_DATA_PATH "${CMAKE_BINARY_DIR}/lcov/data") 14 | set(LCOV_DATA_PATH_INIT "${LCOV_DATA_PATH}/init") 15 | set(LCOV_DATA_PATH_CAPTURE "${LCOV_DATA_PATH}/capture") 16 | set(LCOV_HTML_PATH "${CMAKE_BINARY_DIR}/lcov/html") 17 | 18 | 19 | 20 | 21 | # Search for Gcov which is used by Lcov. 22 | find_package(Gcov) 23 | 24 | 25 | 26 | 27 | # This function will add lcov evaluation for target . Only sources of 28 | # this target will be evaluated and no dependencies will be added. It will call 29 | # geninfo on any source file of once and store the info file in the same 30 | # directory. 31 | # 32 | # Note: This function is only a wrapper to define this function always, even if 33 | # coverage is not supported by the compiler or disabled. This function must 34 | # be defined here, because the module will be exited, if there is no coverage 35 | # support by the compiler or it is disabled by the user. 36 | function (add_lcov_target TNAME) 37 | if (LCOV_FOUND) 38 | # capture initial coverage data 39 | lcov_capture_initial_tgt(${TNAME}) 40 | 41 | # capture coverage data after execution 42 | lcov_capture_tgt(${TNAME}) 43 | endif () 44 | endfunction (add_lcov_target) 45 | 46 | 47 | 48 | 49 | # include required Modules 50 | include(FindPackageHandleStandardArgs) 51 | 52 | # Search for required lcov binaries. 53 | find_program(LCOV_BIN lcov) 54 | find_program(GENINFO_BIN geninfo) 55 | find_program(GENHTML_BIN genhtml) 56 | find_package_handle_standard_args(lcov 57 | REQUIRED_VARS LCOV_BIN GENINFO_BIN GENHTML_BIN 58 | ) 59 | 60 | # enable genhtml C++ demangeling, if c++filt is found. 61 | set(GENHTML_CPPFILT_FLAG "") 62 | find_program(CPPFILT_BIN c++filt) 63 | if (NOT CPPFILT_BIN STREQUAL "") 64 | set(GENHTML_CPPFILT_FLAG "--demangle-cpp") 65 | endif (NOT CPPFILT_BIN STREQUAL "") 66 | 67 | # enable no-external flag for lcov, if available. 68 | if (GENINFO_BIN AND NOT DEFINED GENINFO_EXTERN_FLAG) 69 | set(FLAG "") 70 | execute_process(COMMAND ${GENINFO_BIN} --help OUTPUT_VARIABLE GENINFO_HELP) 71 | string(REGEX MATCH "external" GENINFO_RES "${GENINFO_HELP}") 72 | if (GENINFO_RES) 73 | set(FLAG "--no-external") 74 | endif () 75 | 76 | set(GENINFO_EXTERN_FLAG "${FLAG}" 77 | CACHE STRING "Geninfo flag to exclude system sources.") 78 | endif () 79 | 80 | # If Lcov was not found, exit module now. 81 | if (NOT LCOV_FOUND) 82 | return() 83 | endif (NOT LCOV_FOUND) 84 | 85 | 86 | 87 | 88 | # Create directories to be used. 89 | file(MAKE_DIRECTORY ${LCOV_DATA_PATH_INIT}) 90 | file(MAKE_DIRECTORY ${LCOV_DATA_PATH_CAPTURE}) 91 | 92 | set(LCOV_REMOVE_PATTERNS "") 93 | 94 | # This function will merge lcov files to a single target file. Additional lcov 95 | # flags may be set with setting LCOV_EXTRA_FLAGS before calling this function. 96 | function (lcov_merge_files OUTFILE ...) 97 | # Remove ${OUTFILE} from ${ARGV} and generate lcov parameters with files. 98 | list(REMOVE_AT ARGV 0) 99 | 100 | # Generate merged file. 101 | string(REPLACE "${CMAKE_BINARY_DIR}/" "" FILE_REL "${OUTFILE}") 102 | add_custom_command(OUTPUT "${OUTFILE}.raw" 103 | COMMAND cat ${ARGV} > ${OUTFILE}.raw 104 | DEPENDS ${ARGV} 105 | COMMENT "Generating ${FILE_REL}" 106 | ) 107 | 108 | add_custom_command(OUTPUT "${OUTFILE}" 109 | COMMAND ${LCOV_BIN} --quiet -a ${OUTFILE}.raw --output-file ${OUTFILE} 110 | --base-directory ${PROJECT_SOURCE_DIR} ${LCOV_EXTRA_FLAGS} 111 | COMMAND ${LCOV_BIN} --quiet -r ${OUTFILE} ${LCOV_REMOVE_PATTERNS} 112 | --output-file ${OUTFILE} ${LCOV_EXTRA_FLAGS} 113 | DEPENDS ${OUTFILE}.raw 114 | COMMENT "Post-processing ${FILE_REL}" 115 | ) 116 | endfunction () 117 | 118 | 119 | 120 | 121 | # Add a new global target to generate initial coverage reports for all targets. 122 | # This target will be used to generate the global initial info file, which is 123 | # used to gather even empty report data. 124 | if (NOT TARGET lcov-capture-init) 125 | add_custom_target(lcov-capture-init) 126 | set(LCOV_CAPTURE_INIT_FILES "" CACHE INTERNAL "") 127 | endif (NOT TARGET lcov-capture-init) 128 | 129 | 130 | # This function will add initial capture of coverage data for target , 131 | # which is needed to get also data for objects, which were not loaded at 132 | # execution time. It will call geninfo for every source file of once and 133 | # store the info file in the same directory. 134 | function (lcov_capture_initial_tgt TNAME) 135 | # We don't have to check, if the target has support for coverage, thus this 136 | # will be checked by add_coverage_target in Findcoverage.cmake. Instead we 137 | # have to determine which gcov binary to use. 138 | get_target_property(TSOURCES ${TNAME} SOURCES) 139 | set(SOURCES "") 140 | set(TCOMPILER "") 141 | foreach (FILE ${TSOURCES}) 142 | codecov_path_of_source(${FILE} FILE) 143 | if (NOT "${FILE}" STREQUAL "") 144 | codecov_lang_of_source(${FILE} LANG) 145 | if (NOT "${LANG}" STREQUAL "") 146 | list(APPEND SOURCES "${FILE}") 147 | set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID}) 148 | endif () 149 | endif () 150 | endforeach () 151 | 152 | # If no gcov binary was found, coverage data can't be evaluated. 153 | if (NOT GCOV_${TCOMPILER}_BIN) 154 | message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.") 155 | return() 156 | endif () 157 | 158 | set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") 159 | set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") 160 | 161 | 162 | set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir) 163 | set(GENINFO_FILES "") 164 | foreach(FILE ${SOURCES}) 165 | # generate empty coverage files 166 | set(OUTFILE "${TDIR}/${FILE}.info.init") 167 | list(APPEND GENINFO_FILES ${OUTFILE}) 168 | 169 | add_custom_command(OUTPUT ${OUTFILE} COMMAND ${GCOV_ENV} ${GENINFO_BIN} 170 | --quiet --base-directory ${PROJECT_SOURCE_DIR} --initial 171 | --gcov-tool ${GCOV_BIN} --output-filename ${OUTFILE} 172 | ${GENINFO_EXTERN_FLAG} ${TDIR}/${FILE}.gcno 173 | DEPENDS ${TNAME} 174 | COMMENT "Capturing initial coverage data for ${FILE}" 175 | ) 176 | endforeach() 177 | 178 | # Concatenate all files generated by geninfo to a single file per target. 179 | set(OUTFILE "${LCOV_DATA_PATH_INIT}/${TNAME}.info") 180 | set(LCOV_EXTRA_FLAGS "--initial") 181 | lcov_merge_files("${OUTFILE}" ${GENINFO_FILES}) 182 | add_custom_target(${TNAME}-capture-init ALL DEPENDS ${OUTFILE}) 183 | 184 | # add geninfo file generation to global lcov-geninfo target 185 | add_dependencies(lcov-capture-init ${TNAME}-capture-init) 186 | set(LCOV_CAPTURE_INIT_FILES "${LCOV_CAPTURE_INIT_FILES}" 187 | "${OUTFILE}" CACHE INTERNAL "" 188 | ) 189 | endfunction (lcov_capture_initial_tgt) 190 | 191 | 192 | # This function will generate the global info file for all targets. It has to be 193 | # called after all other CMake functions in the root CMakeLists.txt file, to get 194 | # a full list of all targets that generate coverage data. 195 | function (lcov_capture_initial) 196 | # Skip this function (and do not create the following targets), if there are 197 | # no input files. 198 | if ("${LCOV_CAPTURE_INIT_FILES}" STREQUAL "") 199 | return() 200 | endif () 201 | 202 | # Add a new target to merge the files of all targets. 203 | set(OUTFILE "${LCOV_DATA_PATH_INIT}/all_targets.info") 204 | lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_INIT_FILES}) 205 | add_custom_target(lcov-geninfo-init ALL DEPENDS ${OUTFILE} 206 | lcov-capture-init 207 | ) 208 | endfunction (lcov_capture_initial) 209 | 210 | 211 | 212 | 213 | # Add a new global target to generate coverage reports for all targets. This 214 | # target will be used to generate the global info file. 215 | if (NOT TARGET lcov-capture) 216 | add_custom_target(lcov-capture) 217 | set(LCOV_CAPTURE_FILES "" CACHE INTERNAL "") 218 | endif (NOT TARGET lcov-capture) 219 | 220 | 221 | # This function will add capture of coverage data for target , which is 222 | # needed to get also data for objects, which were not loaded at execution time. 223 | # It will call geninfo for every source file of once and store the info 224 | # file in the same directory. 225 | function (lcov_capture_tgt TNAME) 226 | # We don't have to check, if the target has support for coverage, thus this 227 | # will be checked by add_coverage_target in Findcoverage.cmake. Instead we 228 | # have to determine which gcov binary to use. 229 | get_target_property(TSOURCES ${TNAME} SOURCES) 230 | set(SOURCES "") 231 | set(TCOMPILER "") 232 | foreach (FILE ${TSOURCES}) 233 | codecov_path_of_source(${FILE} FILE) 234 | if (NOT "${FILE}" STREQUAL "") 235 | codecov_lang_of_source(${FILE} LANG) 236 | if (NOT "${LANG}" STREQUAL "") 237 | list(APPEND SOURCES "${FILE}") 238 | set(TCOMPILER ${CMAKE_${LANG}_COMPILER_ID}) 239 | endif () 240 | endif () 241 | endforeach () 242 | 243 | # If no gcov binary was found, coverage data can't be evaluated. 244 | if (NOT GCOV_${TCOMPILER}_BIN) 245 | message(WARNING "No coverage evaluation binary found for ${TCOMPILER}.") 246 | return() 247 | endif () 248 | 249 | set(GCOV_BIN "${GCOV_${TCOMPILER}_BIN}") 250 | set(GCOV_ENV "${GCOV_${TCOMPILER}_ENV}") 251 | 252 | 253 | set(TDIR ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${TNAME}.dir) 254 | set(GENINFO_FILES "") 255 | foreach(FILE ${SOURCES}) 256 | # Generate coverage files. If no .gcda file was generated during 257 | # execution, the empty coverage file will be used instead. 258 | set(OUTFILE "${TDIR}/${FILE}.info") 259 | list(APPEND GENINFO_FILES ${OUTFILE}) 260 | 261 | add_custom_command(OUTPUT ${OUTFILE} 262 | COMMAND test -f "${TDIR}/${FILE}.gcda" 263 | && ${GCOV_ENV} ${GENINFO_BIN} --quiet --base-directory 264 | ${PROJECT_SOURCE_DIR} --gcov-tool ${GCOV_BIN} 265 | --output-filename ${OUTFILE} ${GENINFO_EXTERN_FLAG} 266 | ${TDIR}/${FILE}.gcda 267 | || cp ${OUTFILE}.init ${OUTFILE} 268 | DEPENDS ${TNAME} ${TNAME}-capture-init 269 | COMMENT "Capturing coverage data for ${FILE}" 270 | ) 271 | endforeach() 272 | 273 | # Concatenate all files generated by geninfo to a single file per target. 274 | set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/${TNAME}.info") 275 | lcov_merge_files("${OUTFILE}" ${GENINFO_FILES}) 276 | add_custom_target(${TNAME}-geninfo DEPENDS ${OUTFILE}) 277 | 278 | # add geninfo file generation to global lcov-capture target 279 | add_dependencies(lcov-capture ${TNAME}-geninfo) 280 | set(LCOV_CAPTURE_FILES "${LCOV_CAPTURE_FILES}" "${OUTFILE}" CACHE INTERNAL 281 | "" 282 | ) 283 | 284 | # Add target for generating html output for this target only. 285 | file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/${TNAME}) 286 | add_custom_target(${TNAME}-genhtml 287 | COMMAND ${GENHTML_BIN} --quiet --sort --prefix ${PROJECT_SOURCE_DIR} 288 | --baseline-file ${LCOV_DATA_PATH_INIT}/${TNAME}.info 289 | --output-directory ${LCOV_HTML_PATH}/${TNAME} 290 | --title "${CMAKE_PROJECT_NAME} - target ${TNAME}" 291 | ${GENHTML_CPPFILT_FLAG} ${OUTFILE} 292 | DEPENDS ${TNAME}-geninfo ${TNAME}-capture-init 293 | ) 294 | endfunction (lcov_capture_tgt) 295 | 296 | 297 | # This function will generate the global info file for all targets. It has to be 298 | # called after all other CMake functions in the root CMakeLists.txt file, to get 299 | # a full list of all targets that generate coverage data. 300 | function (lcov_capture) 301 | # Skip this function (and do not create the following targets), if there are 302 | # no input files. 303 | if ("${LCOV_CAPTURE_FILES}" STREQUAL "") 304 | return() 305 | endif () 306 | 307 | # Add a new target to merge the files of all targets. 308 | set(OUTFILE "${LCOV_DATA_PATH_CAPTURE}/all_targets.info") 309 | lcov_merge_files("${OUTFILE}" ${LCOV_CAPTURE_FILES}) 310 | add_custom_target(lcov-geninfo DEPENDS ${OUTFILE} lcov-capture) 311 | 312 | # Add a new global target for all lcov targets. This target could be used to 313 | # generate the lcov html output for the whole project instead of calling 314 | # -geninfo and -genhtml for each target. It will also be 315 | # used to generate a html site for all project data together instead of one 316 | # for each target. 317 | if (NOT TARGET lcov) 318 | file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/all_targets) 319 | add_custom_target(lcov 320 | COMMAND ${GENHTML_BIN} --quiet --sort 321 | --baseline-file ${LCOV_DATA_PATH_INIT}/all_targets.info 322 | --output-directory ${LCOV_HTML_PATH}/all_targets 323 | --title "${CMAKE_PROJECT_NAME}" --prefix "${PROJECT_SOURCE_DIR}" 324 | ${GENHTML_CPPFILT_FLAG} ${OUTFILE} 325 | DEPENDS lcov-geninfo-init lcov-geninfo 326 | ) 327 | endif () 328 | endfunction (lcov_capture) 329 | 330 | 331 | 332 | 333 | # Add a new global target to generate the lcov html report for the whole project 334 | # instead of calling -genhtml for each target (to create an own report 335 | # for each target). Instead of the lcov target it does not require geninfo for 336 | # all targets, so you have to call -geninfo to generate the info files 337 | # the targets you'd like to have in your report or lcov-geninfo for generating 338 | # info files for all targets before calling lcov-genhtml. 339 | file(MAKE_DIRECTORY ${LCOV_HTML_PATH}/selected_targets) 340 | if (NOT TARGET lcov-genhtml) 341 | add_custom_target(lcov-genhtml 342 | COMMAND ${GENHTML_BIN} 343 | --quiet 344 | --output-directory ${LCOV_HTML_PATH}/selected_targets 345 | --title \"${CMAKE_PROJECT_NAME} - targets `find 346 | ${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name 347 | \"all_targets.info\" -exec basename {} .info \\\;`\" 348 | --prefix ${PROJECT_SOURCE_DIR} 349 | --sort 350 | ${GENHTML_CPPFILT_FLAG} 351 | `find ${LCOV_DATA_PATH_CAPTURE} -name \"*.info\" ! -name 352 | \"all_targets.info\"` 353 | ) 354 | endif (NOT TARGET lcov-genhtml) 355 | -------------------------------------------------------------------------------- /src/ClaraTests.cpp: -------------------------------------------------------------------------------- 1 | #include "clara.hpp" 2 | 3 | #include "catch.hpp" 4 | 5 | #include 6 | 7 | using namespace clara; 8 | 9 | namespace Catch { 10 | template<> 11 | struct StringMaker { 12 | static std::string convert( clara::detail::InternalParseResult const& result ) { 13 | switch( result.type() ) { 14 | case clara::detail::ResultBase::Ok: 15 | return "Ok"; 16 | case clara::detail::ResultBase::LogicError: 17 | return "LogicError '" + result.errorMessage() + "'"; 18 | case clara::detail::ResultBase::RuntimeError: 19 | return "RuntimeError: '" + result.errorMessage() + "'"; 20 | default: 21 | return "Unknow type: " + std::to_string( static_cast( result.type() ) ); 22 | } 23 | } 24 | }; 25 | } 26 | 27 | std::string toString( Opt const& opt ) { 28 | std::ostringstream oss; 29 | oss << (Parser() | opt); 30 | return oss.str(); 31 | } 32 | std::string toString( Parser const& p ) { 33 | std::ostringstream oss; 34 | oss << p; 35 | return oss.str(); 36 | } 37 | 38 | // !TBD 39 | // for Catch: 40 | // error on unrecognised? 41 | 42 | // Beyond Catch: 43 | // exceptions or not 44 | // error on unmet requireds 45 | // enum mapping 46 | // sets of values (in addition to vectors) 47 | // arg literals 48 | // --help for option names/ args/ arg literals 49 | // other dependencies/ hierarchical parsers 50 | // Exclusive() parser for choices 51 | 52 | TEST_CASE( "single parsers" ) { 53 | 54 | std::string name; 55 | auto p = Opt(name, "name") 56 | ["-n"]["--name"] 57 | ("the name to use"); 58 | 59 | REQUIRE( name == "" ); 60 | 61 | SECTION( "-n" ) { 62 | p.parse( Args{ "TestApp", "-n", "Vader" } ); 63 | REQUIRE( name == "Vader"); 64 | } 65 | SECTION( "--name" ) { 66 | p.parse( Args{ "TestApp", "--name", "Vader" } ); 67 | REQUIRE( name == "Vader"); 68 | } 69 | SECTION( "-n:" ) { 70 | p.parse( Args{ "TestApp", "-n:Vader" } ); 71 | REQUIRE( name == "Vader"); 72 | } 73 | SECTION( "-n=" ) { 74 | p.parse( Args{ "TestApp", "-n=Vader" } ); 75 | REQUIRE( name == "Vader"); 76 | } 77 | SECTION( "no args" ) { 78 | p.parse( Args{ "TestApp" } ); 79 | REQUIRE( name == ""); 80 | } 81 | SECTION( "different args" ) { 82 | p.parse( Args{ "TestApp", "-f" } ); 83 | REQUIRE( name == ""); 84 | } 85 | } 86 | 87 | struct Config { 88 | int m_rngSeed; 89 | std::string m_name; 90 | std::vector m_tests; 91 | bool m_flag = false; 92 | double m_value = 0; 93 | }; 94 | 95 | TEST_CASE( "Combined parser" ) { 96 | Config config; 97 | 98 | bool showHelp = false; 99 | auto parser 100 | = Help( showHelp ) 101 | | Opt( config.m_rngSeed, "time|value" ) 102 | ["--rng-seed"]["-r"] 103 | ("set a specific seed for random numbers" ) 104 | .required() 105 | | Opt( config.m_name, "name" ) 106 | ["-n"]["--name"] 107 | ( "the name to use" ) 108 | | Opt( config.m_flag ) 109 | ["-f"]["--flag"] 110 | ( "a flag to set" ) 111 | | Opt( [&]( double value ){ config.m_value = value; }, "number" ) 112 | ["-d"]["--double"] 113 | ( "just some number" ) 114 | | Arg( config.m_tests, "test name|tags|pattern" ) 115 | ( "which test or tests to use" ); 116 | 117 | SECTION( "usage" ) { 118 | REQUIRE(toString(parser) == 119 | "usage:\n" 120 | " [ ... ] options\n" 121 | "\n" 122 | "where options are:\n" 123 | " -?, -h, --help display usage information\n" 124 | " --rng-seed, -r set a specific seed for random numbers\n" 125 | " -n, --name the name to use\n" 126 | " -f, --flag a flag to set\n" 127 | " -d, --double just some number\n" 128 | ); 129 | } 130 | SECTION( "some args" ) { 131 | auto result = parser.parse( Args{ "TestApp", "-n", "Bill", "-d:123.45", "-f", "test1", "test2" } ); 132 | CHECK( result ); 133 | CHECK( result.value().type() == ParseResultType::Matched ); 134 | 135 | REQUIRE( config.m_name == "Bill" ); 136 | REQUIRE( config.m_value == 123.45 ); 137 | REQUIRE( config.m_tests == std::vector { "test1", "test2" } ); 138 | CHECK( showHelp == false ); 139 | } 140 | SECTION( "help" ) { 141 | auto result = parser.parse( Args{ "TestApp", "-?", "-n:NotSet" } ); 142 | CHECK( result ); 143 | CHECK( result.value().type() == ParseResultType::ShortCircuitAll ); 144 | CHECK( config.m_name == "" ); // We should never have processed -n:NotSet 145 | CHECK( showHelp == true ); 146 | } 147 | } 148 | 149 | struct TestOpt { 150 | std::string processName; 151 | std::string fileName; 152 | int number = 0; 153 | int index = 0; 154 | bool flag = false; 155 | std::string firstPos; 156 | std::string secondPos; 157 | std::vector unpositional; 158 | 159 | auto makeCli() -> Parser { 160 | return ExeName( processName ) 161 | | Opt( fileName, "filename" ) 162 | ["-o"]["--output"] 163 | ( "specifies output file" ) 164 | | Opt( number, "an integral value" ) 165 | ["-n"] 166 | | Opt( [&]( int i ) { 167 | if (i < 0 || i > 10) 168 | return ParserResult::runtimeError("index must be between 0 and 10"); 169 | else { 170 | index = i; 171 | return ParserResult::ok( ParseResultType::Matched ); 172 | } 173 | }, "index" ) 174 | ["-i"] 175 | ( "An index, which is an integer between 0 and 10, inclusive" ) 176 | | Opt( flag ) 177 | ["-f"] 178 | ( "A flag" ) 179 | | Arg( firstPos, "first arg" ) 180 | ( "First position" ) 181 | | Arg( secondPos, "second arg" ) 182 | ( "Second position" ); 183 | } 184 | }; 185 | 186 | struct TestOpt2 { 187 | std::string description; 188 | }; 189 | 190 | TEST_CASE( "cmdline" ) { 191 | 192 | TestOpt config; 193 | auto cli = config.makeCli(); 194 | 195 | SECTION( "exe name" ) { 196 | auto result = cli.parse( { "TestApp", "-o", "filename.ext" } ); 197 | CHECK( result ); 198 | CHECK( config.processName == "TestApp" ); 199 | } 200 | SECTION( "args" ) { 201 | auto result = cli.parse( { "TestApp", "-o", "filename.ext" } ); 202 | CHECK( result ); 203 | CHECK( config.fileName == "filename.ext" ); 204 | } 205 | SECTION( "arg separated by colon" ) { 206 | auto result = cli.parse( { "TestApp", "-o:filename.ext" } ); 207 | CHECK( result ); 208 | CHECK( config.fileName == "filename.ext" ); 209 | } 210 | SECTION( "arg separated by =" ) { 211 | auto result = cli.parse( { "TestApp", "-o=filename.ext" } ); 212 | CHECK( result ); 213 | CHECK( config.fileName == "filename.ext" ); 214 | } 215 | SECTION( "long opt" ) { 216 | auto result = cli.parse( { "TestApp", "--output", "%stdout" } ); 217 | CHECK( result ); 218 | CHECK( config.fileName == "%stdout" ); 219 | } 220 | SECTION( "a number" ) { 221 | auto result = cli.parse( { "TestApp", "-n", "42" } ); 222 | CHECK( result ); 223 | CHECK( config.number == 42 ); 224 | } 225 | SECTION( "not a number" ) { 226 | auto result = cli.parse( { "TestApp", "-n", "forty-two" } ); 227 | CHECK( !result ); 228 | CHECK( result.errorMessage() == "Unable to convert 'forty-two' to destination type" ); 229 | 230 | CHECK( config.number == 0 ); 231 | } 232 | 233 | SECTION( "methods" ) { 234 | 235 | SECTION( "in range" ) { 236 | auto result = cli.parse( { "TestApp", "-i", "3" } ); 237 | CHECK( result ); 238 | 239 | REQUIRE( config.index == 3 ); 240 | } 241 | SECTION( "out of range" ) { 242 | auto result = cli.parse( { "TestApp", "-i", "42" } ); 243 | CHECK( !result ); 244 | CHECK( result.errorMessage() == "index must be between 0 and 10" ); 245 | } 246 | } 247 | 248 | SECTION( "flags" ) { 249 | 250 | SECTION("set") { 251 | auto result = cli.parse({ "TestApp", "-f" }); 252 | CHECK( result ); 253 | 254 | REQUIRE(config.flag); 255 | } 256 | SECTION("not set") { 257 | auto result = cli.parse({ "TestApp" }); 258 | CHECK( result ); 259 | CHECK( result.value().type() == ParseResultType::NoMatch); 260 | 261 | REQUIRE(config.flag == false); 262 | } 263 | 264 | SECTION( "arg before flag" ) 265 | { 266 | auto result = cli.parse({ "TestApp", "-f", "something" }); 267 | REQUIRE( result ); 268 | REQUIRE( config.flag ); 269 | REQUIRE( config.firstPos == "something" ); 270 | } 271 | 272 | SECTION("following flag") 273 | { 274 | auto result = cli.parse({ "TestApp", "something", "-f" }); 275 | REQUIRE( result ); 276 | REQUIRE( config.flag ); 277 | REQUIRE( config.firstPos == "something" ); 278 | } 279 | 280 | SECTION("no flag") 281 | { 282 | auto result = cli.parse({ "TestApp", "something" }); 283 | REQUIRE( result ); 284 | REQUIRE( config.flag == false ); 285 | REQUIRE( config.firstPos == "something" ); 286 | } 287 | } 288 | 289 | #ifdef CLARA_PLATFORM_WINDOWS 290 | SECTION( "forward slash" ) { 291 | auto result = cli.parse( { "TestApp", "/f" } ); 292 | CHECK(result); 293 | 294 | REQUIRE( config.flag ); 295 | } 296 | #endif 297 | 298 | SECTION( "args" ) { 299 | 300 | auto result = cli.parse( { "TestApp", "-f", "1st", "-o", "filename", "2nd" } ); 301 | CHECK( result ); 302 | 303 | REQUIRE( config.firstPos == "1st" ); 304 | REQUIRE( config.secondPos == "2nd" ); 305 | } 306 | } 307 | 308 | TEST_CASE( "flag parser" ) { 309 | 310 | bool flag = false; 311 | auto p = Opt( flag, "true|false" ) 312 | ["-f"] 313 | ("A flag"); 314 | 315 | SECTION( "set flag with true" ) { 316 | auto result = p.parse( {"TestApp", "-f", "true"} ); 317 | REQUIRE( result ); 318 | REQUIRE( flag ); 319 | } 320 | SECTION( "set flag with yes" ) { 321 | auto result = p.parse( {"TestApp", "-f", "yes"} ); 322 | REQUIRE( result ); 323 | REQUIRE( flag ); 324 | } 325 | SECTION( "set flag with y" ) { 326 | auto result = p.parse( {"TestApp", "-f", "y"} ); 327 | REQUIRE( result ); 328 | REQUIRE( flag ); 329 | } 330 | SECTION( "set flag with 1" ) { 331 | auto result = p.parse( {"TestApp", "-f", "1"} ); 332 | REQUIRE( result ); 333 | REQUIRE( flag ); 334 | } 335 | SECTION( "set flag with on" ) { 336 | auto result = p.parse( {"TestApp", "-f", "on"} ); 337 | REQUIRE( result ); 338 | REQUIRE( flag ); 339 | } 340 | SECTION( "set flag with tRUe" ) { 341 | auto result = p.parse( {"TestApp", "-f", "tRUe"} ); 342 | REQUIRE( result ); 343 | REQUIRE( flag ); 344 | } 345 | 346 | SECTION( "unset flag with false" ) { 347 | flag = true; 348 | auto result = p.parse( {"TestApp", "-f", "false"} ); 349 | REQUIRE( result) ; 350 | REQUIRE( flag == false ); 351 | } 352 | SECTION( "invalid inputs" ) { 353 | using namespace Catch::Matchers; 354 | auto result = p.parse( {"TestApp", "-f", "what"} ); 355 | REQUIRE( !result ) ; 356 | REQUIRE_THAT( result.errorMessage(), Contains( "Expected a boolean value" ) ); 357 | 358 | result = p.parse( {"TestApp", "-f"} ); 359 | REQUIRE( !result ) ; 360 | REQUIRE_THAT( result.errorMessage(), Contains( "Expected argument following -f" ) ); 361 | } 362 | } 363 | 364 | TEST_CASE( "usage", "[.]" ) { 365 | 366 | TestOpt config; 367 | auto cli = config.makeCli(); 368 | std::cout << cli << std::endl; 369 | } 370 | 371 | TEST_CASE( "Invalid parsers" ) 372 | { 373 | using namespace Catch::Matchers; 374 | 375 | TestOpt config; 376 | 377 | SECTION( "no options" ) 378 | { 379 | auto cli = Opt( config.number, "number" ); 380 | auto result = cli.parse( { "TestApp", "-o", "filename" } ); 381 | CHECK( !result ); 382 | CHECK( result.errorMessage() == "No options supplied to Opt" ); 383 | } 384 | SECTION( "no option name" ) 385 | { 386 | auto cli = Opt( config.number, "number" )[""]; 387 | auto result = cli.parse( { "TestApp", "-o", "filename" } ); 388 | CHECK( !result ); 389 | CHECK( result.errorMessage() == "Option name cannot be empty" ); 390 | } 391 | SECTION( "invalid option name" ) 392 | { 393 | auto cli = Opt( config.number, "number" )["invalid"]; 394 | auto result = cli.parse( { "TestApp", "-o", "filename" } ); 395 | CHECK( !result ); 396 | CHECK_THAT( result.errorMessage(), StartsWith( "Option name must begin with '-'" ) ); 397 | } 398 | } 399 | 400 | TEST_CASE( "Multiple flags" ) { 401 | bool a = false, b = false, c = false; 402 | auto cli = Opt( a )["-a"] | Opt( b )["-b"] | Opt( c )["-c"]; 403 | 404 | SECTION( "separately" ) { 405 | auto result = cli.parse({ "TestApp", "-a", "-b", "-c" }); 406 | CHECK(result); 407 | CHECK(a); 408 | CHECK(b); 409 | CHECK(c); 410 | } 411 | SECTION( "combined" ) { 412 | auto result = cli.parse({ "TestApp", "-abc" }); 413 | CHECK(result); 414 | CHECK(a); 415 | CHECK(b); 416 | CHECK(c); 417 | } 418 | } 419 | 420 | TEST_CASE( "Unrecognised opts" ) { 421 | using namespace Catch::Matchers; 422 | 423 | bool a = false; 424 | Parser cli = Parser() | Opt( a )["-a"]; 425 | 426 | auto result = cli.parse( { "TestApp", "-b" } ); 427 | CHECK( !result ); 428 | CHECK_THAT( result.errorMessage(), Contains( "Unrecognised token") && Contains( "-b" ) ); 429 | } 430 | 431 | TEST_CASE( "char* args" ) { 432 | 433 | std::string value; 434 | Parser cli = Parser() | Arg( value, "value" ); 435 | 436 | SECTION( "char*" ) { 437 | char* args[] = { (char*)"TestApp", (char*)"hello" }; 438 | 439 | auto result = cli.parse( Args( 2, args ) ); 440 | REQUIRE( result ); 441 | REQUIRE( value == "hello" ); 442 | } 443 | SECTION( "char*" ) { 444 | const char* args[] = { "TestApp", "hello" }; 445 | 446 | auto result = cli.parse( Args( 2, args ) ); 447 | REQUIRE( result ); 448 | REQUIRE( value == "hello" ); 449 | } 450 | } 451 | 452 | TEST_CASE( "different widths" ) { 453 | 454 | std::string s; 455 | 456 | auto shortOpt 457 | = Opt( s, "short" ) 458 | ["-s"]["--short"] 459 | ( "not much" ); 460 | auto longHint 461 | = Opt( s, "A very very long hint that should force the whole line to wrap awkwardly. I hope no-one ever writes anything like thus - but there's always *someone*" ) 462 | ["-x"] 463 | ("short description"); 464 | 465 | auto longDesc 466 | = Opt( s, "hint") 467 | ["-y"] 468 | ( "In this one it's the description field that is really really long. We should be split over several lines, but complete the description before starting to show the next option" ); 469 | 470 | auto longOptName 471 | = Opt( s, "hint") 472 | ["--this-one-just-has-an-overly-long-option-name-that-should-push-the-left-hand-column-out"] 473 | ( "short desc" ); 474 | 475 | auto longEverything 476 | = Opt( s, "this is really over the top, but it has to be tested. In this case we have a very long hint (far longer than anyone should ever even think of using), which should be enough to wrap just on its own...") 477 | ["--and-a-ridiculously-long-long-option-name-that-would-be-silly-to-write-but-hey-if-it-can-handle-this-it-can-handle-anything-right"] 478 | ( "*and* a stupid long description, which seems a bit redundant give all the other verbosity. But some people just love to write. And read. You have to be prepared to do a lot of both for this to be useful."); 479 | 480 | SECTION( "long hint" ) 481 | REQUIRE_NOTHROW( toString( longHint ) == "?" ); 482 | 483 | SECTION( "long desc" ) 484 | REQUIRE_NOTHROW( toString( longDesc ) ); 485 | 486 | SECTION( "long opt name" ) 487 | REQUIRE_NOTHROW( toString( longOptName ) == "?" ); 488 | 489 | SECTION( "long everything" ) 490 | REQUIRE_NOTHROW( toString( longEverything ) == "?" ); 491 | } 492 | 493 | TEST_CASE( "newlines in description" ) { 494 | 495 | SECTION( "single, long description" ) { 496 | int i; 497 | auto opt = Opt(i, "i")["-i"]( 498 | "This string should be long enough to force a wrap in the first instance. But what we really want to test is where if we put an explicit newline in the string, say, here\nthat it is formatted correctly"); 499 | 500 | REQUIRE(toString(opt) == 501 | "usage:\n" 502 | " options\n" 503 | "\n" 504 | "where options are:\n" 505 | " -i This string should be long enough to force a wrap in the first\n" 506 | " instance. But what we really want to test is where if we put an\n" 507 | " explicit newline in the string, say, here\n" 508 | " that it is formatted correctly\n"); 509 | } 510 | SECTION( "multiple entries" ) { 511 | int a,b,c; 512 | auto p 513 | = Opt(a, "a") 514 | ["-a"]["--longishOption"] 515 | ("A description with:\nA new line right in the middle") 516 | | Opt(b, "b") 517 | ["-b"]["--bb"] 518 | ("This description also has\nA new line") 519 | | Opt(c, "c") 520 | ["-c"]["--cc"] 521 | ("Another\nnewline. In fact this one has line-wraps, as well as mutiple\nnewlines and\n\n- leading hyphens"); 522 | 523 | REQUIRE(toString(p) == 524 | "usage:\n" 525 | " options\n" 526 | "\n" 527 | "where options are:\n" 528 | " -a, --longishOption A description with:\n" 529 | " A new line right in the middle\n" 530 | " -b, --bb This description also has\n" 531 | " A new line\n" 532 | " -c, --cc Another\n" 533 | " newline. In fact this one has line-wraps, as\n" 534 | " well as mutiple\n" 535 | " newlines and\n" 536 | " \n" 537 | " - leading hyphens\n"); 538 | 539 | 540 | 541 | } 542 | } 543 | 544 | #if defined(CLARA_CONFIG_OPTIONAL_TYPE) 545 | TEST_CASE("Reading into std::optional") { 546 | CLARA_CONFIG_OPTIONAL_TYPE name; 547 | auto p = Opt(name, "name") 548 | ["-n"]["--name"] 549 | ("the name to use"); 550 | SECTION("Not set") { 551 | auto result = p.parse(Args{ "TestApp", "-q", "Pixie" }); 552 | REQUIRE( result ); 553 | REQUIRE_FALSE( name.has_value() ); 554 | } 555 | SECTION("Provided") { 556 | auto result = p.parse(Args{ "TestApp", "-n", "Pixie" }); 557 | REQUIRE( result ); 558 | REQUIRE( name.has_value() ); 559 | REQUIRE( name.value() == "Pixie" ); 560 | } 561 | } 562 | #endif // CLARA_CONFIG_OPTIONAL_TYPE 563 | -------------------------------------------------------------------------------- /include/clara.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Two Blue Cubes Ltd. All rights reserved. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | // See https://github.com/philsquared/Clara for more details 7 | 8 | // Clara v1.1.5 9 | 10 | #ifndef CLARA_HPP_INCLUDED 11 | #define CLARA_HPP_INCLUDED 12 | 13 | #ifndef CLARA_CONFIG_CONSOLE_WIDTH 14 | #define CLARA_CONFIG_CONSOLE_WIDTH 80 15 | #endif 16 | 17 | #ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 18 | #define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH 19 | #endif 20 | 21 | #ifndef CLARA_CONFIG_OPTIONAL_TYPE 22 | # ifdef __has_include 23 | # if __has_include() && __cplusplus >= 201703L 24 | # include 25 | # define CLARA_CONFIG_OPTIONAL_TYPE std::optional 26 | # endif 27 | # endif 28 | #endif 29 | 30 | #include "clara_textflow.hpp" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #if !defined(CLARA_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) 41 | #define CLARA_PLATFORM_WINDOWS 42 | #endif 43 | 44 | namespace clara { 45 | namespace detail { 46 | 47 | // Traits for extracting arg and return type of lambdas (for single argument lambdas) 48 | template 49 | struct UnaryLambdaTraits : UnaryLambdaTraits {}; 50 | 51 | template 52 | struct UnaryLambdaTraits { 53 | static const bool isValid = false; 54 | }; 55 | 56 | template 57 | struct UnaryLambdaTraits { 58 | static const bool isValid = true; 59 | using ArgType = typename std::remove_const::type>::type; 60 | using ReturnType = ReturnT; 61 | }; 62 | 63 | class TokenStream; 64 | 65 | // Transport for raw args (copied from main args, or supplied via init list for testing) 66 | class Args { 67 | friend TokenStream; 68 | std::string m_exeName; 69 | std::vector m_args; 70 | 71 | public: 72 | Args( int argc, char const* const* argv ) 73 | : m_exeName(argv[0]), 74 | m_args(argv + 1, argv + argc) {} 75 | 76 | Args( std::initializer_list args ) 77 | : m_exeName( *args.begin() ), 78 | m_args( args.begin()+1, args.end() ) 79 | {} 80 | 81 | auto exeName() const -> std::string { 82 | return m_exeName; 83 | } 84 | }; 85 | 86 | // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string 87 | // may encode an option + its argument if the : or = form is used 88 | enum class TokenType { 89 | Option, Argument 90 | }; 91 | struct Token { 92 | TokenType type; 93 | std::string token; 94 | }; 95 | 96 | inline auto isOptPrefix( char c ) -> bool { 97 | return c == '-' 98 | #ifdef CLARA_PLATFORM_WINDOWS 99 | || c == '/' 100 | #endif 101 | ; 102 | } 103 | 104 | // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled 105 | class TokenStream { 106 | using Iterator = std::vector::const_iterator; 107 | Iterator it; 108 | Iterator itEnd; 109 | std::vector m_tokenBuffer; 110 | 111 | void loadBuffer() { 112 | m_tokenBuffer.resize( 0 ); 113 | 114 | // Skip any empty strings 115 | while( it != itEnd && it->empty() ) 116 | ++it; 117 | 118 | if( it != itEnd ) { 119 | auto const &next = *it; 120 | if( isOptPrefix( next[0] ) ) { 121 | auto delimiterPos = next.find_first_of( " :=" ); 122 | if( delimiterPos != std::string::npos ) { 123 | m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); 124 | m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); 125 | } else { 126 | if( next[1] != '-' && next.size() > 2 ) { 127 | std::string opt = "- "; 128 | for( size_t i = 1; i < next.size(); ++i ) { 129 | opt[1] = next[i]; 130 | m_tokenBuffer.push_back( { TokenType::Option, opt } ); 131 | } 132 | } else { 133 | m_tokenBuffer.push_back( { TokenType::Option, next } ); 134 | } 135 | } 136 | } else { 137 | m_tokenBuffer.push_back( { TokenType::Argument, next } ); 138 | } 139 | } 140 | } 141 | 142 | public: 143 | explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} 144 | 145 | TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { 146 | loadBuffer(); 147 | } 148 | 149 | explicit operator bool() const { 150 | return !m_tokenBuffer.empty() || it != itEnd; 151 | } 152 | 153 | auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } 154 | 155 | auto operator*() const -> Token { 156 | assert( !m_tokenBuffer.empty() ); 157 | return m_tokenBuffer.front(); 158 | } 159 | 160 | auto operator->() const -> Token const * { 161 | assert( !m_tokenBuffer.empty() ); 162 | return &m_tokenBuffer.front(); 163 | } 164 | 165 | auto operator++() -> TokenStream & { 166 | if( m_tokenBuffer.size() >= 2 ) { 167 | m_tokenBuffer.erase( m_tokenBuffer.begin() ); 168 | } else { 169 | if( it != itEnd ) 170 | ++it; 171 | loadBuffer(); 172 | } 173 | return *this; 174 | } 175 | }; 176 | 177 | 178 | class ResultBase { 179 | public: 180 | enum Type { 181 | Ok, LogicError, RuntimeError 182 | }; 183 | 184 | protected: 185 | ResultBase( Type type ) : m_type( type ) {} 186 | virtual ~ResultBase() = default; 187 | 188 | virtual void enforceOk() const = 0; 189 | 190 | Type m_type; 191 | }; 192 | 193 | template 194 | class ResultValueBase : public ResultBase { 195 | public: 196 | auto value() const -> T const & { 197 | enforceOk(); 198 | return m_value; 199 | } 200 | 201 | protected: 202 | ResultValueBase( Type type ) : ResultBase( type ) {} 203 | 204 | ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { 205 | if( m_type == ResultBase::Ok ) 206 | new( &m_value ) T( other.m_value ); 207 | } 208 | 209 | ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { 210 | new( &m_value ) T( value ); 211 | } 212 | 213 | auto operator=( ResultValueBase const &other ) -> ResultValueBase & { 214 | if( m_type == ResultBase::Ok ) 215 | m_value.~T(); 216 | ResultBase::operator=(other); 217 | if( m_type == ResultBase::Ok ) 218 | new( &m_value ) T( other.m_value ); 219 | return *this; 220 | } 221 | 222 | ~ResultValueBase() override { 223 | if( m_type == Ok ) 224 | m_value.~T(); 225 | } 226 | 227 | union { 228 | T m_value; 229 | }; 230 | }; 231 | 232 | template<> 233 | class ResultValueBase : public ResultBase { 234 | protected: 235 | using ResultBase::ResultBase; 236 | }; 237 | 238 | template 239 | class BasicResult : public ResultValueBase { 240 | public: 241 | template 242 | explicit BasicResult( BasicResult const &other ) 243 | : ResultValueBase( other.type() ), 244 | m_errorMessage( other.errorMessage() ) 245 | { 246 | assert( type() != ResultBase::Ok ); 247 | } 248 | 249 | template 250 | static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } 251 | static auto ok() -> BasicResult { return { ResultBase::Ok }; } 252 | static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } 253 | static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } 254 | 255 | explicit operator bool() const { return m_type == ResultBase::Ok; } 256 | auto type() const -> ResultBase::Type { return m_type; } 257 | auto errorMessage() const -> std::string { return m_errorMessage; } 258 | 259 | protected: 260 | void enforceOk() const override { 261 | 262 | // Errors shouldn't reach this point, but if they do 263 | // the actual error message will be in m_errorMessage 264 | assert( m_type != ResultBase::LogicError ); 265 | assert( m_type != ResultBase::RuntimeError ); 266 | if( m_type != ResultBase::Ok ) 267 | std::abort(); 268 | } 269 | 270 | std::string m_errorMessage; // Only populated if resultType is an error 271 | 272 | BasicResult( ResultBase::Type type, std::string const &message ) 273 | : ResultValueBase(type), 274 | m_errorMessage(message) 275 | { 276 | assert( m_type != ResultBase::Ok ); 277 | } 278 | 279 | using ResultValueBase::ResultValueBase; 280 | using ResultBase::m_type; 281 | }; 282 | 283 | enum class ParseResultType { 284 | Matched, NoMatch, ShortCircuitAll, ShortCircuitSame 285 | }; 286 | 287 | class ParseState { 288 | public: 289 | 290 | ParseState( ParseResultType type, TokenStream const &remainingTokens ) 291 | : m_type(type), 292 | m_remainingTokens( remainingTokens ) 293 | {} 294 | 295 | auto type() const -> ParseResultType { return m_type; } 296 | auto remainingTokens() const -> TokenStream { return m_remainingTokens; } 297 | 298 | private: 299 | ParseResultType m_type; 300 | TokenStream m_remainingTokens; 301 | }; 302 | 303 | using Result = BasicResult; 304 | using ParserResult = BasicResult; 305 | using InternalParseResult = BasicResult; 306 | 307 | struct HelpColumns { 308 | std::string left; 309 | std::string right; 310 | }; 311 | 312 | template 313 | inline auto convertInto( std::string const &source, T& target ) -> ParserResult { 314 | std::stringstream ss; 315 | ss << source; 316 | ss >> target; 317 | if( ss.fail() ) 318 | return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); 319 | else 320 | return ParserResult::ok( ParseResultType::Matched ); 321 | } 322 | inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { 323 | target = source; 324 | return ParserResult::ok( ParseResultType::Matched ); 325 | } 326 | inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { 327 | std::string srcLC = source; 328 | std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( std::tolower(c) ); } ); 329 | if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") 330 | target = true; 331 | else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") 332 | target = false; 333 | else 334 | return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); 335 | return ParserResult::ok( ParseResultType::Matched ); 336 | } 337 | #ifdef CLARA_CONFIG_OPTIONAL_TYPE 338 | template 339 | inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE& target ) -> ParserResult { 340 | T temp; 341 | auto result = convertInto( source, temp ); 342 | if( result ) 343 | target = std::move(temp); 344 | return result; 345 | } 346 | #endif // CLARA_CONFIG_OPTIONAL_TYPE 347 | 348 | struct NonCopyable { 349 | NonCopyable() = default; 350 | NonCopyable( NonCopyable const & ) = delete; 351 | NonCopyable( NonCopyable && ) = delete; 352 | NonCopyable &operator=( NonCopyable const & ) = delete; 353 | NonCopyable &operator=( NonCopyable && ) = delete; 354 | }; 355 | 356 | struct BoundRef : NonCopyable { 357 | virtual ~BoundRef() = default; 358 | virtual auto isContainer() const -> bool { return false; } 359 | virtual auto isFlag() const -> bool { return false; } 360 | }; 361 | struct BoundValueRefBase : BoundRef { 362 | virtual auto setValue( std::string const &arg ) -> ParserResult = 0; 363 | }; 364 | struct BoundFlagRefBase : BoundRef { 365 | virtual auto setFlag( bool flag ) -> ParserResult = 0; 366 | virtual auto isFlag() const -> bool { return true; } 367 | }; 368 | 369 | template 370 | struct BoundValueRef : BoundValueRefBase { 371 | T &m_ref; 372 | 373 | explicit BoundValueRef( T &ref ) : m_ref( ref ) {} 374 | 375 | auto setValue( std::string const &arg ) -> ParserResult override { 376 | return convertInto( arg, m_ref ); 377 | } 378 | }; 379 | 380 | template 381 | struct BoundValueRef> : BoundValueRefBase { 382 | std::vector &m_ref; 383 | 384 | explicit BoundValueRef( std::vector &ref ) : m_ref( ref ) {} 385 | 386 | auto isContainer() const -> bool override { return true; } 387 | 388 | auto setValue( std::string const &arg ) -> ParserResult override { 389 | T temp; 390 | auto result = convertInto( arg, temp ); 391 | if( result ) 392 | m_ref.push_back( temp ); 393 | return result; 394 | } 395 | }; 396 | 397 | struct BoundFlagRef : BoundFlagRefBase { 398 | bool &m_ref; 399 | 400 | explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} 401 | 402 | auto setFlag( bool flag ) -> ParserResult override { 403 | m_ref = flag; 404 | return ParserResult::ok( ParseResultType::Matched ); 405 | } 406 | }; 407 | 408 | template 409 | struct LambdaInvoker { 410 | static_assert( std::is_same::value, "Lambda must return void or clara::ParserResult" ); 411 | 412 | template 413 | static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { 414 | return lambda( arg ); 415 | } 416 | }; 417 | 418 | template<> 419 | struct LambdaInvoker { 420 | template 421 | static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { 422 | lambda( arg ); 423 | return ParserResult::ok( ParseResultType::Matched ); 424 | } 425 | }; 426 | 427 | template 428 | inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { 429 | ArgType temp{}; 430 | auto result = convertInto( arg, temp ); 431 | return !result 432 | ? result 433 | : LambdaInvoker::ReturnType>::invoke( lambda, temp ); 434 | } 435 | 436 | 437 | template 438 | struct BoundLambda : BoundValueRefBase { 439 | L m_lambda; 440 | 441 | static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); 442 | explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} 443 | 444 | auto setValue( std::string const &arg ) -> ParserResult override { 445 | return invokeLambda::ArgType>( m_lambda, arg ); 446 | } 447 | }; 448 | 449 | template 450 | struct BoundFlagLambda : BoundFlagRefBase { 451 | L m_lambda; 452 | 453 | static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); 454 | static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); 455 | 456 | explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} 457 | 458 | auto setFlag( bool flag ) -> ParserResult override { 459 | return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); 460 | } 461 | }; 462 | 463 | enum class Optionality { Optional, Required }; 464 | 465 | struct Parser; 466 | 467 | class ParserBase { 468 | public: 469 | virtual ~ParserBase() = default; 470 | virtual auto validate() const -> Result { return Result::ok(); } 471 | virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; 472 | virtual auto cardinality() const -> size_t { return 1; } 473 | 474 | auto parse( Args const &args ) const -> InternalParseResult { 475 | return parse( args.exeName(), TokenStream( args ) ); 476 | } 477 | }; 478 | 479 | template 480 | class ComposableParserImpl : public ParserBase { 481 | public: 482 | template 483 | auto operator|( T const &other ) const -> Parser; 484 | 485 | template 486 | auto operator+( T const &other ) const -> Parser; 487 | }; 488 | 489 | // Common code and state for Args and Opts 490 | template 491 | class ParserRefImpl : public ComposableParserImpl { 492 | protected: 493 | Optionality m_optionality = Optionality::Optional; 494 | std::shared_ptr m_ref; 495 | std::string m_hint; 496 | std::string m_description; 497 | 498 | explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} 499 | 500 | public: 501 | template 502 | ParserRefImpl( T &ref, std::string const &hint ) 503 | : m_ref( std::make_shared>( ref ) ), 504 | m_hint( hint ) 505 | {} 506 | 507 | template 508 | ParserRefImpl( LambdaT const &ref, std::string const &hint ) 509 | : m_ref( std::make_shared>( ref ) ), 510 | m_hint(hint) 511 | {} 512 | 513 | auto operator()( std::string const &description ) -> DerivedT & { 514 | m_description = description; 515 | return static_cast( *this ); 516 | } 517 | 518 | auto optional() -> DerivedT & { 519 | m_optionality = Optionality::Optional; 520 | return static_cast( *this ); 521 | }; 522 | 523 | auto required() -> DerivedT & { 524 | m_optionality = Optionality::Required; 525 | return static_cast( *this ); 526 | }; 527 | 528 | auto isOptional() const -> bool { 529 | return m_optionality == Optionality::Optional; 530 | } 531 | 532 | auto cardinality() const -> size_t override { 533 | if( m_ref->isContainer() ) 534 | return 0; 535 | else 536 | return 1; 537 | } 538 | 539 | auto hint() const -> std::string { return m_hint; } 540 | }; 541 | 542 | class ExeName : public ComposableParserImpl { 543 | std::shared_ptr m_name; 544 | std::shared_ptr m_ref; 545 | 546 | template 547 | static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { 548 | return std::make_shared>( lambda) ; 549 | } 550 | 551 | public: 552 | ExeName() : m_name( std::make_shared( "" ) ) {} 553 | 554 | explicit ExeName( std::string &ref ) : ExeName() { 555 | m_ref = std::make_shared>( ref ); 556 | } 557 | 558 | template 559 | explicit ExeName( LambdaT const& lambda ) : ExeName() { 560 | m_ref = std::make_shared>( lambda ); 561 | } 562 | 563 | // The exe name is not parsed out of the normal tokens, but is handled specially 564 | auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { 565 | return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); 566 | } 567 | 568 | auto name() const -> std::string { return *m_name; } 569 | auto set( std::string const& newName ) -> ParserResult { 570 | 571 | auto lastSlash = newName.find_last_of( "\\/" ); 572 | auto filename = ( lastSlash == std::string::npos ) 573 | ? newName 574 | : newName.substr( lastSlash+1 ); 575 | 576 | *m_name = filename; 577 | if( m_ref ) 578 | return m_ref->setValue( filename ); 579 | else 580 | return ParserResult::ok( ParseResultType::Matched ); 581 | } 582 | }; 583 | 584 | class Arg : public ParserRefImpl { 585 | public: 586 | using ParserRefImpl::ParserRefImpl; 587 | 588 | auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { 589 | auto validationResult = validate(); 590 | if( !validationResult ) 591 | return InternalParseResult( validationResult ); 592 | 593 | auto remainingTokens = tokens; 594 | auto const &token = *remainingTokens; 595 | if( token.type != TokenType::Argument ) 596 | return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); 597 | 598 | assert( !m_ref->isFlag() ); 599 | auto valueRef = static_cast( m_ref.get() ); 600 | 601 | auto result = valueRef->setValue( remainingTokens->token ); 602 | if( !result ) 603 | return InternalParseResult( result ); 604 | else 605 | return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); 606 | } 607 | }; 608 | 609 | inline auto normaliseOpt( std::string const &optName ) -> std::string { 610 | #ifdef CLARA_PLATFORM_WINDOWS 611 | if( optName[0] == '/' ) 612 | return "-" + optName.substr( 1 ); 613 | else 614 | #endif 615 | return optName; 616 | } 617 | 618 | class Opt : public ParserRefImpl { 619 | protected: 620 | std::vector m_optNames; 621 | 622 | public: 623 | template 624 | explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} 625 | 626 | explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} 627 | 628 | template 629 | Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} 630 | 631 | template 632 | Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} 633 | 634 | auto operator[]( std::string const &optName ) -> Opt & { 635 | m_optNames.push_back( optName ); 636 | return *this; 637 | } 638 | 639 | auto getHelpColumns() const -> std::vector { 640 | std::ostringstream oss; 641 | bool first = true; 642 | for( auto const &opt : m_optNames ) { 643 | if (first) 644 | first = false; 645 | else 646 | oss << ", "; 647 | oss << opt; 648 | } 649 | if( !m_hint.empty() ) 650 | oss << " <" << m_hint << ">"; 651 | return { { oss.str(), m_description } }; 652 | } 653 | 654 | auto isMatch( std::string const &optToken ) const -> bool { 655 | auto normalisedToken = normaliseOpt( optToken ); 656 | for( auto const &name : m_optNames ) { 657 | if( normaliseOpt( name ) == normalisedToken ) 658 | return true; 659 | } 660 | return false; 661 | } 662 | 663 | using ParserBase::parse; 664 | 665 | auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { 666 | auto validationResult = validate(); 667 | if( !validationResult ) 668 | return InternalParseResult( validationResult ); 669 | 670 | auto remainingTokens = tokens; 671 | if( remainingTokens && remainingTokens->type == TokenType::Option ) { 672 | auto const &token = *remainingTokens; 673 | if( isMatch(token.token ) ) { 674 | if( m_ref->isFlag() ) { 675 | auto flagRef = static_cast( m_ref.get() ); 676 | auto result = flagRef->setFlag( true ); 677 | if( !result ) 678 | return InternalParseResult( result ); 679 | if( result.value() == ParseResultType::ShortCircuitAll ) 680 | return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); 681 | } else { 682 | auto valueRef = static_cast( m_ref.get() ); 683 | ++remainingTokens; 684 | if( !remainingTokens ) 685 | return InternalParseResult::runtimeError( "Expected argument following " + token.token ); 686 | auto const &argToken = *remainingTokens; 687 | if( argToken.type != TokenType::Argument ) 688 | return InternalParseResult::runtimeError( "Expected argument following " + token.token ); 689 | auto result = valueRef->setValue( argToken.token ); 690 | if( !result ) 691 | return InternalParseResult( result ); 692 | if( result.value() == ParseResultType::ShortCircuitAll ) 693 | return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); 694 | } 695 | return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); 696 | } 697 | } 698 | return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); 699 | } 700 | 701 | auto validate() const -> Result override { 702 | if( m_optNames.empty() ) 703 | return Result::logicError( "No options supplied to Opt" ); 704 | for( auto const &name : m_optNames ) { 705 | if( name.empty() ) 706 | return Result::logicError( "Option name cannot be empty" ); 707 | #ifdef CLARA_PLATFORM_WINDOWS 708 | if( name[0] != '-' && name[0] != '/' ) 709 | return Result::logicError( "Option name must begin with '-' or '/'" ); 710 | #else 711 | if( name[0] != '-' ) 712 | return Result::logicError( "Option name must begin with '-'" ); 713 | #endif 714 | } 715 | return ParserRefImpl::validate(); 716 | } 717 | }; 718 | 719 | struct Help : Opt { 720 | Help( bool &showHelpFlag ) 721 | : Opt([&]( bool flag ) { 722 | showHelpFlag = flag; 723 | return ParserResult::ok( ParseResultType::ShortCircuitAll ); 724 | }) 725 | { 726 | static_cast( *this ) 727 | ("display usage information") 728 | ["-?"]["-h"]["--help"] 729 | .optional(); 730 | } 731 | }; 732 | 733 | 734 | struct Parser : ParserBase { 735 | 736 | mutable ExeName m_exeName; 737 | std::vector m_options; 738 | std::vector m_args; 739 | 740 | auto operator|=( ExeName const &exeName ) -> Parser & { 741 | m_exeName = exeName; 742 | return *this; 743 | } 744 | 745 | auto operator|=( Arg const &arg ) -> Parser & { 746 | m_args.push_back(arg); 747 | return *this; 748 | } 749 | 750 | auto operator|=( Opt const &opt ) -> Parser & { 751 | m_options.push_back(opt); 752 | return *this; 753 | } 754 | 755 | auto operator|=( Parser const &other ) -> Parser & { 756 | m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); 757 | m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); 758 | return *this; 759 | } 760 | 761 | template 762 | auto operator|( T const &other ) const -> Parser { 763 | return Parser( *this ) |= other; 764 | } 765 | 766 | // Forward deprecated interface with '+' instead of '|' 767 | template 768 | auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } 769 | template 770 | auto operator+( T const &other ) const -> Parser { return operator|( other ); } 771 | 772 | auto getHelpColumns() const -> std::vector { 773 | std::vector cols; 774 | for (auto const &o : m_options) { 775 | auto childCols = o.getHelpColumns(); 776 | cols.insert( cols.end(), childCols.begin(), childCols.end() ); 777 | } 778 | return cols; 779 | } 780 | 781 | void writeToStream( std::ostream &os ) const { 782 | if (!m_exeName.name().empty()) { 783 | os << "usage:\n" << " " << m_exeName.name() << " "; 784 | bool required = true, first = true; 785 | for( auto const &arg : m_args ) { 786 | if (first) 787 | first = false; 788 | else 789 | os << " "; 790 | if( arg.isOptional() && required ) { 791 | os << "["; 792 | required = false; 793 | } 794 | os << "<" << arg.hint() << ">"; 795 | if( arg.cardinality() == 0 ) 796 | os << " ... "; 797 | } 798 | if( !required ) 799 | os << "]"; 800 | if( !m_options.empty() ) 801 | os << " options"; 802 | os << "\n\nwhere options are:" << std::endl; 803 | } 804 | 805 | auto rows = getHelpColumns(); 806 | size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; 807 | size_t optWidth = 0; 808 | for( auto const &cols : rows ) 809 | optWidth = (std::max)(optWidth, cols.left.size() + 2); 810 | 811 | optWidth = (std::min)(optWidth, consoleWidth/2); 812 | 813 | for( auto const &cols : rows ) { 814 | auto row = 815 | TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + 816 | TextFlow::Spacer(4) + 817 | TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); 818 | os << row << std::endl; 819 | } 820 | } 821 | 822 | friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { 823 | parser.writeToStream( os ); 824 | return os; 825 | } 826 | 827 | auto validate() const -> Result override { 828 | for( auto const &opt : m_options ) { 829 | auto result = opt.validate(); 830 | if( !result ) 831 | return result; 832 | } 833 | for( auto const &arg : m_args ) { 834 | auto result = arg.validate(); 835 | if( !result ) 836 | return result; 837 | } 838 | return Result::ok(); 839 | } 840 | 841 | using ParserBase::parse; 842 | 843 | auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { 844 | 845 | struct ParserInfo { 846 | ParserBase const* parser = nullptr; 847 | size_t count = 0; 848 | }; 849 | const size_t totalParsers = m_options.size() + m_args.size(); 850 | assert( totalParsers < 512 ); 851 | // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do 852 | ParserInfo parseInfos[512]; 853 | 854 | { 855 | size_t i = 0; 856 | for (auto const &opt : m_options) parseInfos[i++].parser = &opt; 857 | for (auto const &arg : m_args) parseInfos[i++].parser = &arg; 858 | } 859 | 860 | m_exeName.set( exeName ); 861 | 862 | auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); 863 | while( result.value().remainingTokens() ) { 864 | bool tokenParsed = false; 865 | 866 | for( size_t i = 0; i < totalParsers; ++i ) { 867 | auto& parseInfo = parseInfos[i]; 868 | if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { 869 | result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); 870 | if (!result) 871 | return result; 872 | if (result.value().type() != ParseResultType::NoMatch) { 873 | tokenParsed = true; 874 | ++parseInfo.count; 875 | break; 876 | } 877 | } 878 | } 879 | 880 | if( result.value().type() == ParseResultType::ShortCircuitAll ) 881 | return result; 882 | if( !tokenParsed ) 883 | return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); 884 | } 885 | // !TBD Check missing required options 886 | return result; 887 | } 888 | }; 889 | 890 | template 891 | template 892 | auto ComposableParserImpl::operator|( T const &other ) const -> Parser { 893 | return Parser() | static_cast( *this ) | other; 894 | } 895 | } // namespace detail 896 | 897 | 898 | // A Combined parser 899 | using detail::Parser; 900 | 901 | // A parser for options 902 | using detail::Opt; 903 | 904 | // A parser for arguments 905 | using detail::Arg; 906 | 907 | // Wrapper for argc, argv from main() 908 | using detail::Args; 909 | 910 | // Specifies the name of the executable 911 | using detail::ExeName; 912 | 913 | // Convenience wrapper for option parser that specifies the help option 914 | using detail::Help; 915 | 916 | // enum of result types from a parse 917 | using detail::ParseResultType; 918 | 919 | // Result type for parser operation 920 | using detail::ParserResult; 921 | 922 | 923 | } // namespace clara 924 | 925 | #endif // CLARA_HPP_INCLUDED 926 | -------------------------------------------------------------------------------- /single_include/clara.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Two Blue Cubes Ltd. All rights reserved. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 4 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | // See https://github.com/philsquared/Clara for more details 7 | 8 | // Clara v1.1.5 9 | 10 | #ifndef CLARA_HPP_INCLUDED 11 | #define CLARA_HPP_INCLUDED 12 | 13 | #ifndef CLARA_CONFIG_CONSOLE_WIDTH 14 | #define CLARA_CONFIG_CONSOLE_WIDTH 80 15 | #endif 16 | 17 | #ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 18 | #define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH 19 | #endif 20 | 21 | #ifndef CLARA_CONFIG_OPTIONAL_TYPE 22 | #ifdef __has_include 23 | #if __has_include() && __cplusplus >= 201703L 24 | #include 25 | #define CLARA_CONFIG_OPTIONAL_TYPE std::optional 26 | #endif 27 | #endif 28 | #endif 29 | 30 | 31 | // ----------- #included from clara_textflow.hpp ----------- 32 | 33 | // TextFlowCpp 34 | // 35 | // A single-header library for wrapping and laying out basic text, by Phil Nash 36 | // 37 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 38 | // file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 39 | // 40 | // This project is hosted at https://github.com/philsquared/textflowcpp 41 | 42 | #ifndef CLARA_TEXTFLOW_HPP_INCLUDED 43 | #define CLARA_TEXTFLOW_HPP_INCLUDED 44 | 45 | #include 46 | #include 47 | #include 48 | #include 49 | 50 | #ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 51 | #define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 52 | #endif 53 | 54 | 55 | namespace clara { namespace TextFlow { 56 | 57 | inline auto isWhitespace( char c ) -> bool { 58 | static std::string chars = " \t\n\r"; 59 | return chars.find( c ) != std::string::npos; 60 | } 61 | inline auto isBreakableBefore( char c ) -> bool { 62 | static std::string chars = "[({<|"; 63 | return chars.find( c ) != std::string::npos; 64 | } 65 | inline auto isBreakableAfter( char c ) -> bool { 66 | static std::string chars = "])}>.,:;*+-=&/\\"; 67 | return chars.find( c ) != std::string::npos; 68 | } 69 | 70 | class Columns; 71 | 72 | class Column { 73 | std::vector m_strings; 74 | size_t m_width = CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; 75 | size_t m_indent = 0; 76 | size_t m_initialIndent = std::string::npos; 77 | 78 | public: 79 | class iterator { 80 | friend Column; 81 | 82 | Column const& m_column; 83 | size_t m_stringIndex = 0; 84 | size_t m_pos = 0; 85 | 86 | size_t m_len = 0; 87 | size_t m_end = 0; 88 | bool m_suffix = false; 89 | 90 | iterator( Column const& column, size_t stringIndex ) 91 | : m_column( column ), 92 | m_stringIndex( stringIndex ) 93 | {} 94 | 95 | auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } 96 | 97 | auto isBoundary( size_t at ) const -> bool { 98 | assert( at > 0 ); 99 | assert( at <= line().size() ); 100 | 101 | return at == line().size() || 102 | ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || 103 | isBreakableBefore( line()[at] ) || 104 | isBreakableAfter( line()[at-1] ); 105 | } 106 | 107 | void calcLength() { 108 | assert( m_stringIndex < m_column.m_strings.size() ); 109 | 110 | m_suffix = false; 111 | auto width = m_column.m_width-indent(); 112 | m_end = m_pos; 113 | while( m_end < line().size() && line()[m_end] != '\n' ) 114 | ++m_end; 115 | 116 | if( m_end < m_pos + width ) { 117 | m_len = m_end - m_pos; 118 | } 119 | else { 120 | size_t len = width; 121 | while (len > 0 && !isBoundary(m_pos + len)) 122 | --len; 123 | while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) 124 | --len; 125 | 126 | if (len > 0) { 127 | m_len = len; 128 | } else { 129 | m_suffix = true; 130 | m_len = width - 1; 131 | } 132 | } 133 | } 134 | 135 | auto indent() const -> size_t { 136 | auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; 137 | return initial == std::string::npos ? m_column.m_indent : initial; 138 | } 139 | 140 | auto addIndentAndSuffix(std::string const &plain) const -> std::string { 141 | return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); 142 | } 143 | 144 | public: 145 | using difference_type = std::ptrdiff_t; 146 | using value_type = std::string; 147 | using pointer = value_type*; 148 | using reference = value_type&; 149 | using iterator_category = std::forward_iterator_tag; 150 | 151 | explicit iterator( Column const& column ) : m_column( column ) { 152 | assert( m_column.m_width > m_column.m_indent ); 153 | assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); 154 | calcLength(); 155 | if( m_len == 0 ) 156 | m_stringIndex++; // Empty string 157 | } 158 | 159 | auto operator *() const -> std::string { 160 | assert( m_stringIndex < m_column.m_strings.size() ); 161 | assert( m_pos <= m_end ); 162 | return addIndentAndSuffix(line().substr(m_pos, m_len)); 163 | } 164 | 165 | auto operator ++() -> iterator& { 166 | m_pos += m_len; 167 | if( m_pos < line().size() && line()[m_pos] == '\n' ) 168 | m_pos += 1; 169 | else 170 | while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) 171 | ++m_pos; 172 | 173 | if( m_pos == line().size() ) { 174 | m_pos = 0; 175 | ++m_stringIndex; 176 | } 177 | if( m_stringIndex < m_column.m_strings.size() ) 178 | calcLength(); 179 | return *this; 180 | } 181 | auto operator ++(int) -> iterator { 182 | iterator prev( *this ); 183 | operator++(); 184 | return prev; 185 | } 186 | 187 | auto operator ==( iterator const& other ) const -> bool { 188 | return 189 | m_pos == other.m_pos && 190 | m_stringIndex == other.m_stringIndex && 191 | &m_column == &other.m_column; 192 | } 193 | auto operator !=( iterator const& other ) const -> bool { 194 | return !operator==( other ); 195 | } 196 | }; 197 | using const_iterator = iterator; 198 | 199 | explicit Column( std::string const& text ) { m_strings.push_back( text ); } 200 | 201 | auto width( size_t newWidth ) -> Column& { 202 | assert( newWidth > 0 ); 203 | m_width = newWidth; 204 | return *this; 205 | } 206 | auto indent( size_t newIndent ) -> Column& { 207 | m_indent = newIndent; 208 | return *this; 209 | } 210 | auto initialIndent( size_t newIndent ) -> Column& { 211 | m_initialIndent = newIndent; 212 | return *this; 213 | } 214 | 215 | auto width() const -> size_t { return m_width; } 216 | auto begin() const -> iterator { return iterator( *this ); } 217 | auto end() const -> iterator { return { *this, m_strings.size() }; } 218 | 219 | inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { 220 | bool first = true; 221 | for( auto line : col ) { 222 | if( first ) 223 | first = false; 224 | else 225 | os << "\n"; 226 | os << line; 227 | } 228 | return os; 229 | } 230 | 231 | auto operator + ( Column const& other ) -> Columns; 232 | 233 | auto toString() const -> std::string { 234 | std::ostringstream oss; 235 | oss << *this; 236 | return oss.str(); 237 | } 238 | }; 239 | 240 | class Spacer : public Column { 241 | 242 | public: 243 | explicit Spacer( size_t spaceWidth ) : Column( "" ) { 244 | width( spaceWidth ); 245 | } 246 | }; 247 | 248 | class Columns { 249 | std::vector m_columns; 250 | 251 | public: 252 | 253 | class iterator { 254 | friend Columns; 255 | struct EndTag {}; 256 | 257 | std::vector const& m_columns; 258 | std::vector m_iterators; 259 | size_t m_activeIterators; 260 | 261 | iterator( Columns const& columns, EndTag ) 262 | : m_columns( columns.m_columns ), 263 | m_activeIterators( 0 ) 264 | { 265 | m_iterators.reserve( m_columns.size() ); 266 | 267 | for( auto const& col : m_columns ) 268 | m_iterators.push_back( col.end() ); 269 | } 270 | 271 | public: 272 | using difference_type = std::ptrdiff_t; 273 | using value_type = std::string; 274 | using pointer = value_type*; 275 | using reference = value_type&; 276 | using iterator_category = std::forward_iterator_tag; 277 | 278 | explicit iterator( Columns const& columns ) 279 | : m_columns( columns.m_columns ), 280 | m_activeIterators( m_columns.size() ) 281 | { 282 | m_iterators.reserve( m_columns.size() ); 283 | 284 | for( auto const& col : m_columns ) 285 | m_iterators.push_back( col.begin() ); 286 | } 287 | 288 | auto operator ==( iterator const& other ) const -> bool { 289 | return m_iterators == other.m_iterators; 290 | } 291 | auto operator !=( iterator const& other ) const -> bool { 292 | return m_iterators != other.m_iterators; 293 | } 294 | auto operator *() const -> std::string { 295 | std::string row, padding; 296 | 297 | for( size_t i = 0; i < m_columns.size(); ++i ) { 298 | auto width = m_columns[i].width(); 299 | if( m_iterators[i] != m_columns[i].end() ) { 300 | std::string col = *m_iterators[i]; 301 | row += padding + col; 302 | if( col.size() < width ) 303 | padding = std::string( width - col.size(), ' ' ); 304 | else 305 | padding = ""; 306 | } 307 | else { 308 | padding += std::string( width, ' ' ); 309 | } 310 | } 311 | return row; 312 | } 313 | auto operator ++() -> iterator& { 314 | for( size_t i = 0; i < m_columns.size(); ++i ) { 315 | if (m_iterators[i] != m_columns[i].end()) 316 | ++m_iterators[i]; 317 | } 318 | return *this; 319 | } 320 | auto operator ++(int) -> iterator { 321 | iterator prev( *this ); 322 | operator++(); 323 | return prev; 324 | } 325 | }; 326 | using const_iterator = iterator; 327 | 328 | auto begin() const -> iterator { return iterator( *this ); } 329 | auto end() const -> iterator { return { *this, iterator::EndTag() }; } 330 | 331 | auto operator += ( Column const& col ) -> Columns& { 332 | m_columns.push_back( col ); 333 | return *this; 334 | } 335 | auto operator + ( Column const& col ) -> Columns { 336 | Columns combined = *this; 337 | combined += col; 338 | return combined; 339 | } 340 | 341 | inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { 342 | 343 | bool first = true; 344 | for( auto line : cols ) { 345 | if( first ) 346 | first = false; 347 | else 348 | os << "\n"; 349 | os << line; 350 | } 351 | return os; 352 | } 353 | 354 | auto toString() const -> std::string { 355 | std::ostringstream oss; 356 | oss << *this; 357 | return oss.str(); 358 | } 359 | }; 360 | 361 | inline auto Column::operator + ( Column const& other ) -> Columns { 362 | Columns cols; 363 | cols += *this; 364 | cols += other; 365 | return cols; 366 | } 367 | }} 368 | 369 | #endif // CLARA_TEXTFLOW_HPP_INCLUDED 370 | 371 | // ----------- end of #include from clara_textflow.hpp ----------- 372 | // ........... back in clara.hpp 373 | 374 | 375 | #include 376 | #include 377 | #include 378 | 379 | #if !defined(CLARA_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) 380 | #define CLARA_PLATFORM_WINDOWS 381 | #endif 382 | 383 | namespace clara { 384 | namespace detail { 385 | 386 | // Traits for extracting arg and return type of lambdas (for single argument lambdas) 387 | template 388 | struct UnaryLambdaTraits : UnaryLambdaTraits {}; 389 | 390 | template 391 | struct UnaryLambdaTraits { 392 | static const bool isValid = false; 393 | }; 394 | 395 | template 396 | struct UnaryLambdaTraits { 397 | static const bool isValid = true; 398 | using ArgType = typename std::remove_const::type>::type; 399 | using ReturnType = ReturnT; 400 | }; 401 | 402 | class TokenStream; 403 | 404 | // Transport for raw args (copied from main args, or supplied via init list for testing) 405 | class Args { 406 | friend TokenStream; 407 | std::string m_exeName; 408 | std::vector m_args; 409 | 410 | public: 411 | Args( int argc, char const* const* argv ) 412 | : m_exeName(argv[0]), 413 | m_args(argv + 1, argv + argc) {} 414 | 415 | Args( std::initializer_list args ) 416 | : m_exeName( *args.begin() ), 417 | m_args( args.begin()+1, args.end() ) 418 | {} 419 | 420 | auto exeName() const -> std::string { 421 | return m_exeName; 422 | } 423 | }; 424 | 425 | // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string 426 | // may encode an option + its argument if the : or = form is used 427 | enum class TokenType { 428 | Option, Argument 429 | }; 430 | struct Token { 431 | TokenType type; 432 | std::string token; 433 | }; 434 | 435 | inline auto isOptPrefix( char c ) -> bool { 436 | return c == '-' 437 | #ifdef CLARA_PLATFORM_WINDOWS 438 | || c == '/' 439 | #endif 440 | ; 441 | } 442 | 443 | // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled 444 | class TokenStream { 445 | using Iterator = std::vector::const_iterator; 446 | Iterator it; 447 | Iterator itEnd; 448 | std::vector m_tokenBuffer; 449 | 450 | void loadBuffer() { 451 | m_tokenBuffer.resize( 0 ); 452 | 453 | // Skip any empty strings 454 | while( it != itEnd && it->empty() ) 455 | ++it; 456 | 457 | if( it != itEnd ) { 458 | auto const &next = *it; 459 | if( isOptPrefix( next[0] ) ) { 460 | auto delimiterPos = next.find_first_of( " :=" ); 461 | if( delimiterPos != std::string::npos ) { 462 | m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); 463 | m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); 464 | } else { 465 | if( next[1] != '-' && next.size() > 2 ) { 466 | std::string opt = "- "; 467 | for( size_t i = 1; i < next.size(); ++i ) { 468 | opt[1] = next[i]; 469 | m_tokenBuffer.push_back( { TokenType::Option, opt } ); 470 | } 471 | } else { 472 | m_tokenBuffer.push_back( { TokenType::Option, next } ); 473 | } 474 | } 475 | } else { 476 | m_tokenBuffer.push_back( { TokenType::Argument, next } ); 477 | } 478 | } 479 | } 480 | 481 | public: 482 | explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} 483 | 484 | TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { 485 | loadBuffer(); 486 | } 487 | 488 | explicit operator bool() const { 489 | return !m_tokenBuffer.empty() || it != itEnd; 490 | } 491 | 492 | auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } 493 | 494 | auto operator*() const -> Token { 495 | assert( !m_tokenBuffer.empty() ); 496 | return m_tokenBuffer.front(); 497 | } 498 | 499 | auto operator->() const -> Token const * { 500 | assert( !m_tokenBuffer.empty() ); 501 | return &m_tokenBuffer.front(); 502 | } 503 | 504 | auto operator++() -> TokenStream & { 505 | if( m_tokenBuffer.size() >= 2 ) { 506 | m_tokenBuffer.erase( m_tokenBuffer.begin() ); 507 | } else { 508 | if( it != itEnd ) 509 | ++it; 510 | loadBuffer(); 511 | } 512 | return *this; 513 | } 514 | }; 515 | 516 | 517 | class ResultBase { 518 | public: 519 | enum Type { 520 | Ok, LogicError, RuntimeError 521 | }; 522 | 523 | protected: 524 | ResultBase( Type type ) : m_type( type ) {} 525 | virtual ~ResultBase() = default; 526 | 527 | virtual void enforceOk() const = 0; 528 | 529 | Type m_type; 530 | }; 531 | 532 | template 533 | class ResultValueBase : public ResultBase { 534 | public: 535 | auto value() const -> T const & { 536 | enforceOk(); 537 | return m_value; 538 | } 539 | 540 | protected: 541 | ResultValueBase( Type type ) : ResultBase( type ) {} 542 | 543 | ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { 544 | if( m_type == ResultBase::Ok ) 545 | new( &m_value ) T( other.m_value ); 546 | } 547 | 548 | ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { 549 | new( &m_value ) T( value ); 550 | } 551 | 552 | auto operator=( ResultValueBase const &other ) -> ResultValueBase & { 553 | if( m_type == ResultBase::Ok ) 554 | m_value.~T(); 555 | ResultBase::operator=(other); 556 | if( m_type == ResultBase::Ok ) 557 | new( &m_value ) T( other.m_value ); 558 | return *this; 559 | } 560 | 561 | ~ResultValueBase() override { 562 | if( m_type == Ok ) 563 | m_value.~T(); 564 | } 565 | 566 | union { 567 | T m_value; 568 | }; 569 | }; 570 | 571 | template<> 572 | class ResultValueBase : public ResultBase { 573 | protected: 574 | using ResultBase::ResultBase; 575 | }; 576 | 577 | template 578 | class BasicResult : public ResultValueBase { 579 | public: 580 | template 581 | explicit BasicResult( BasicResult const &other ) 582 | : ResultValueBase( other.type() ), 583 | m_errorMessage( other.errorMessage() ) 584 | { 585 | assert( type() != ResultBase::Ok ); 586 | } 587 | 588 | template 589 | static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } 590 | static auto ok() -> BasicResult { return { ResultBase::Ok }; } 591 | static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } 592 | static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } 593 | 594 | explicit operator bool() const { return m_type == ResultBase::Ok; } 595 | auto type() const -> ResultBase::Type { return m_type; } 596 | auto errorMessage() const -> std::string { return m_errorMessage; } 597 | 598 | protected: 599 | void enforceOk() const override { 600 | 601 | // Errors shouldn't reach this point, but if they do 602 | // the actual error message will be in m_errorMessage 603 | assert( m_type != ResultBase::LogicError ); 604 | assert( m_type != ResultBase::RuntimeError ); 605 | if( m_type != ResultBase::Ok ) 606 | std::abort(); 607 | } 608 | 609 | std::string m_errorMessage; // Only populated if resultType is an error 610 | 611 | BasicResult( ResultBase::Type type, std::string const &message ) 612 | : ResultValueBase(type), 613 | m_errorMessage(message) 614 | { 615 | assert( m_type != ResultBase::Ok ); 616 | } 617 | 618 | using ResultValueBase::ResultValueBase; 619 | using ResultBase::m_type; 620 | }; 621 | 622 | enum class ParseResultType { 623 | Matched, NoMatch, ShortCircuitAll, ShortCircuitSame 624 | }; 625 | 626 | class ParseState { 627 | public: 628 | 629 | ParseState( ParseResultType type, TokenStream const &remainingTokens ) 630 | : m_type(type), 631 | m_remainingTokens( remainingTokens ) 632 | {} 633 | 634 | auto type() const -> ParseResultType { return m_type; } 635 | auto remainingTokens() const -> TokenStream { return m_remainingTokens; } 636 | 637 | private: 638 | ParseResultType m_type; 639 | TokenStream m_remainingTokens; 640 | }; 641 | 642 | using Result = BasicResult; 643 | using ParserResult = BasicResult; 644 | using InternalParseResult = BasicResult; 645 | 646 | struct HelpColumns { 647 | std::string left; 648 | std::string right; 649 | }; 650 | 651 | template 652 | inline auto convertInto( std::string const &source, T& target ) -> ParserResult { 653 | std::stringstream ss; 654 | ss << source; 655 | ss >> target; 656 | if( ss.fail() ) 657 | return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); 658 | else 659 | return ParserResult::ok( ParseResultType::Matched ); 660 | } 661 | inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { 662 | target = source; 663 | return ParserResult::ok( ParseResultType::Matched ); 664 | } 665 | inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { 666 | std::string srcLC = source; 667 | std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( ::tolower(c) ); } ); 668 | if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") 669 | target = true; 670 | else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") 671 | target = false; 672 | else 673 | return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); 674 | return ParserResult::ok( ParseResultType::Matched ); 675 | } 676 | #ifdef CLARA_CONFIG_OPTIONAL_TYPE 677 | template 678 | inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE& target ) -> ParserResult { 679 | T temp; 680 | auto result = convertInto( source, temp ); 681 | if( result ) 682 | target = std::move(temp); 683 | return result; 684 | } 685 | #endif // CLARA_CONFIG_OPTIONAL_TYPE 686 | 687 | struct NonCopyable { 688 | NonCopyable() = default; 689 | NonCopyable( NonCopyable const & ) = delete; 690 | NonCopyable( NonCopyable && ) = delete; 691 | NonCopyable &operator=( NonCopyable const & ) = delete; 692 | NonCopyable &operator=( NonCopyable && ) = delete; 693 | }; 694 | 695 | struct BoundRef : NonCopyable { 696 | virtual ~BoundRef() = default; 697 | virtual auto isContainer() const -> bool { return false; } 698 | virtual auto isFlag() const -> bool { return false; } 699 | }; 700 | struct BoundValueRefBase : BoundRef { 701 | virtual auto setValue( std::string const &arg ) -> ParserResult = 0; 702 | }; 703 | struct BoundFlagRefBase : BoundRef { 704 | virtual auto setFlag( bool flag ) -> ParserResult = 0; 705 | virtual auto isFlag() const -> bool { return true; } 706 | }; 707 | 708 | template 709 | struct BoundValueRef : BoundValueRefBase { 710 | T &m_ref; 711 | 712 | explicit BoundValueRef( T &ref ) : m_ref( ref ) {} 713 | 714 | auto setValue( std::string const &arg ) -> ParserResult override { 715 | return convertInto( arg, m_ref ); 716 | } 717 | }; 718 | 719 | template 720 | struct BoundValueRef> : BoundValueRefBase { 721 | std::vector &m_ref; 722 | 723 | explicit BoundValueRef( std::vector &ref ) : m_ref( ref ) {} 724 | 725 | auto isContainer() const -> bool override { return true; } 726 | 727 | auto setValue( std::string const &arg ) -> ParserResult override { 728 | T temp; 729 | auto result = convertInto( arg, temp ); 730 | if( result ) 731 | m_ref.push_back( temp ); 732 | return result; 733 | } 734 | }; 735 | 736 | struct BoundFlagRef : BoundFlagRefBase { 737 | bool &m_ref; 738 | 739 | explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} 740 | 741 | auto setFlag( bool flag ) -> ParserResult override { 742 | m_ref = flag; 743 | return ParserResult::ok( ParseResultType::Matched ); 744 | } 745 | }; 746 | 747 | template 748 | struct LambdaInvoker { 749 | static_assert( std::is_same::value, "Lambda must return void or clara::ParserResult" ); 750 | 751 | template 752 | static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { 753 | return lambda( arg ); 754 | } 755 | }; 756 | 757 | template<> 758 | struct LambdaInvoker { 759 | template 760 | static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { 761 | lambda( arg ); 762 | return ParserResult::ok( ParseResultType::Matched ); 763 | } 764 | }; 765 | 766 | template 767 | inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { 768 | ArgType temp{}; 769 | auto result = convertInto( arg, temp ); 770 | return !result 771 | ? result 772 | : LambdaInvoker::ReturnType>::invoke( lambda, temp ); 773 | } 774 | 775 | 776 | template 777 | struct BoundLambda : BoundValueRefBase { 778 | L m_lambda; 779 | 780 | static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); 781 | explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} 782 | 783 | auto setValue( std::string const &arg ) -> ParserResult override { 784 | return invokeLambda::ArgType>( m_lambda, arg ); 785 | } 786 | }; 787 | 788 | template 789 | struct BoundFlagLambda : BoundFlagRefBase { 790 | L m_lambda; 791 | 792 | static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" ); 793 | static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" ); 794 | 795 | explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} 796 | 797 | auto setFlag( bool flag ) -> ParserResult override { 798 | return LambdaInvoker::ReturnType>::invoke( m_lambda, flag ); 799 | } 800 | }; 801 | 802 | enum class Optionality { Optional, Required }; 803 | 804 | struct Parser; 805 | 806 | class ParserBase { 807 | public: 808 | virtual ~ParserBase() = default; 809 | virtual auto validate() const -> Result { return Result::ok(); } 810 | virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; 811 | virtual auto cardinality() const -> size_t { return 1; } 812 | 813 | auto parse( Args const &args ) const -> InternalParseResult { 814 | return parse( args.exeName(), TokenStream( args ) ); 815 | } 816 | }; 817 | 818 | template 819 | class ComposableParserImpl : public ParserBase { 820 | public: 821 | template 822 | auto operator|( T const &other ) const -> Parser; 823 | 824 | template 825 | auto operator+( T const &other ) const -> Parser; 826 | }; 827 | 828 | // Common code and state for Args and Opts 829 | template 830 | class ParserRefImpl : public ComposableParserImpl { 831 | protected: 832 | Optionality m_optionality = Optionality::Optional; 833 | std::shared_ptr m_ref; 834 | std::string m_hint; 835 | std::string m_description; 836 | 837 | explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {} 838 | 839 | public: 840 | template 841 | ParserRefImpl( T &ref, std::string const &hint ) 842 | : m_ref( std::make_shared>( ref ) ), 843 | m_hint( hint ) 844 | {} 845 | 846 | template 847 | ParserRefImpl( LambdaT const &ref, std::string const &hint ) 848 | : m_ref( std::make_shared>( ref ) ), 849 | m_hint(hint) 850 | {} 851 | 852 | auto operator()( std::string const &description ) -> DerivedT & { 853 | m_description = description; 854 | return static_cast( *this ); 855 | } 856 | 857 | auto optional() -> DerivedT & { 858 | m_optionality = Optionality::Optional; 859 | return static_cast( *this ); 860 | }; 861 | 862 | auto required() -> DerivedT & { 863 | m_optionality = Optionality::Required; 864 | return static_cast( *this ); 865 | }; 866 | 867 | auto isOptional() const -> bool { 868 | return m_optionality == Optionality::Optional; 869 | } 870 | 871 | auto cardinality() const -> size_t override { 872 | if( m_ref->isContainer() ) 873 | return 0; 874 | else 875 | return 1; 876 | } 877 | 878 | auto hint() const -> std::string { return m_hint; } 879 | }; 880 | 881 | class ExeName : public ComposableParserImpl { 882 | std::shared_ptr m_name; 883 | std::shared_ptr m_ref; 884 | 885 | template 886 | static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { 887 | return std::make_shared>( lambda) ; 888 | } 889 | 890 | public: 891 | ExeName() : m_name( std::make_shared( "" ) ) {} 892 | 893 | explicit ExeName( std::string &ref ) : ExeName() { 894 | m_ref = std::make_shared>( ref ); 895 | } 896 | 897 | template 898 | explicit ExeName( LambdaT const& lambda ) : ExeName() { 899 | m_ref = std::make_shared>( lambda ); 900 | } 901 | 902 | // The exe name is not parsed out of the normal tokens, but is handled specially 903 | auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { 904 | return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); 905 | } 906 | 907 | auto name() const -> std::string { return *m_name; } 908 | auto set( std::string const& newName ) -> ParserResult { 909 | 910 | auto lastSlash = newName.find_last_of( "\\/" ); 911 | auto filename = ( lastSlash == std::string::npos ) 912 | ? newName 913 | : newName.substr( lastSlash+1 ); 914 | 915 | *m_name = filename; 916 | if( m_ref ) 917 | return m_ref->setValue( filename ); 918 | else 919 | return ParserResult::ok( ParseResultType::Matched ); 920 | } 921 | }; 922 | 923 | class Arg : public ParserRefImpl { 924 | public: 925 | using ParserRefImpl::ParserRefImpl; 926 | 927 | auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { 928 | auto validationResult = validate(); 929 | if( !validationResult ) 930 | return InternalParseResult( validationResult ); 931 | 932 | auto remainingTokens = tokens; 933 | auto const &token = *remainingTokens; 934 | if( token.type != TokenType::Argument ) 935 | return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); 936 | 937 | assert( !m_ref->isFlag() ); 938 | auto valueRef = static_cast( m_ref.get() ); 939 | 940 | auto result = valueRef->setValue( remainingTokens->token ); 941 | if( !result ) 942 | return InternalParseResult( result ); 943 | else 944 | return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); 945 | } 946 | }; 947 | 948 | inline auto normaliseOpt( std::string const &optName ) -> std::string { 949 | #ifdef CLARA_PLATFORM_WINDOWS 950 | if( optName[0] == '/' ) 951 | return "-" + optName.substr( 1 ); 952 | else 953 | #endif 954 | return optName; 955 | } 956 | 957 | class Opt : public ParserRefImpl { 958 | protected: 959 | std::vector m_optNames; 960 | 961 | public: 962 | template 963 | explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {} 964 | 965 | explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {} 966 | 967 | template 968 | Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} 969 | 970 | template 971 | Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} 972 | 973 | auto operator[]( std::string const &optName ) -> Opt & { 974 | m_optNames.push_back( optName ); 975 | return *this; 976 | } 977 | 978 | auto getHelpColumns() const -> std::vector { 979 | std::ostringstream oss; 980 | bool first = true; 981 | for( auto const &opt : m_optNames ) { 982 | if (first) 983 | first = false; 984 | else 985 | oss << ", "; 986 | oss << opt; 987 | } 988 | if( !m_hint.empty() ) 989 | oss << " <" << m_hint << ">"; 990 | return { { oss.str(), m_description } }; 991 | } 992 | 993 | auto isMatch( std::string const &optToken ) const -> bool { 994 | auto normalisedToken = normaliseOpt( optToken ); 995 | for( auto const &name : m_optNames ) { 996 | if( normaliseOpt( name ) == normalisedToken ) 997 | return true; 998 | } 999 | return false; 1000 | } 1001 | 1002 | using ParserBase::parse; 1003 | 1004 | auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { 1005 | auto validationResult = validate(); 1006 | if( !validationResult ) 1007 | return InternalParseResult( validationResult ); 1008 | 1009 | auto remainingTokens = tokens; 1010 | if( remainingTokens && remainingTokens->type == TokenType::Option ) { 1011 | auto const &token = *remainingTokens; 1012 | if( isMatch(token.token ) ) { 1013 | if( m_ref->isFlag() ) { 1014 | auto flagRef = static_cast( m_ref.get() ); 1015 | auto result = flagRef->setFlag( true ); 1016 | if( !result ) 1017 | return InternalParseResult( result ); 1018 | if( result.value() == ParseResultType::ShortCircuitAll ) 1019 | return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); 1020 | } else { 1021 | auto valueRef = static_cast( m_ref.get() ); 1022 | ++remainingTokens; 1023 | if( !remainingTokens ) 1024 | return InternalParseResult::runtimeError( "Expected argument following " + token.token ); 1025 | auto const &argToken = *remainingTokens; 1026 | if( argToken.type != TokenType::Argument ) 1027 | return InternalParseResult::runtimeError( "Expected argument following " + token.token ); 1028 | auto result = valueRef->setValue( argToken.token ); 1029 | if( !result ) 1030 | return InternalParseResult( result ); 1031 | if( result.value() == ParseResultType::ShortCircuitAll ) 1032 | return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); 1033 | } 1034 | return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); 1035 | } 1036 | } 1037 | return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); 1038 | } 1039 | 1040 | auto validate() const -> Result override { 1041 | if( m_optNames.empty() ) 1042 | return Result::logicError( "No options supplied to Opt" ); 1043 | for( auto const &name : m_optNames ) { 1044 | if( name.empty() ) 1045 | return Result::logicError( "Option name cannot be empty" ); 1046 | #ifdef CLARA_PLATFORM_WINDOWS 1047 | if( name[0] != '-' && name[0] != '/' ) 1048 | return Result::logicError( "Option name must begin with '-' or '/'" ); 1049 | #else 1050 | if( name[0] != '-' ) 1051 | return Result::logicError( "Option name must begin with '-'" ); 1052 | #endif 1053 | } 1054 | return ParserRefImpl::validate(); 1055 | } 1056 | }; 1057 | 1058 | struct Help : Opt { 1059 | Help( bool &showHelpFlag ) 1060 | : Opt([&]( bool flag ) { 1061 | showHelpFlag = flag; 1062 | return ParserResult::ok( ParseResultType::ShortCircuitAll ); 1063 | }) 1064 | { 1065 | static_cast( *this ) 1066 | ("display usage information") 1067 | ["-?"]["-h"]["--help"] 1068 | .optional(); 1069 | } 1070 | }; 1071 | 1072 | 1073 | struct Parser : ParserBase { 1074 | 1075 | mutable ExeName m_exeName; 1076 | std::vector m_options; 1077 | std::vector m_args; 1078 | 1079 | auto operator|=( ExeName const &exeName ) -> Parser & { 1080 | m_exeName = exeName; 1081 | return *this; 1082 | } 1083 | 1084 | auto operator|=( Arg const &arg ) -> Parser & { 1085 | m_args.push_back(arg); 1086 | return *this; 1087 | } 1088 | 1089 | auto operator|=( Opt const &opt ) -> Parser & { 1090 | m_options.push_back(opt); 1091 | return *this; 1092 | } 1093 | 1094 | auto operator|=( Parser const &other ) -> Parser & { 1095 | m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); 1096 | m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); 1097 | return *this; 1098 | } 1099 | 1100 | template 1101 | auto operator|( T const &other ) const -> Parser { 1102 | return Parser( *this ) |= other; 1103 | } 1104 | 1105 | // Forward deprecated interface with '+' instead of '|' 1106 | template 1107 | auto operator+=( T const &other ) -> Parser & { return operator|=( other ); } 1108 | template 1109 | auto operator+( T const &other ) const -> Parser { return operator|( other ); } 1110 | 1111 | auto getHelpColumns() const -> std::vector { 1112 | std::vector cols; 1113 | for (auto const &o : m_options) { 1114 | auto childCols = o.getHelpColumns(); 1115 | cols.insert( cols.end(), childCols.begin(), childCols.end() ); 1116 | } 1117 | return cols; 1118 | } 1119 | 1120 | void writeToStream( std::ostream &os ) const { 1121 | if (!m_exeName.name().empty()) { 1122 | os << "usage:\n" << " " << m_exeName.name() << " "; 1123 | bool required = true, first = true; 1124 | for( auto const &arg : m_args ) { 1125 | if (first) 1126 | first = false; 1127 | else 1128 | os << " "; 1129 | if( arg.isOptional() && required ) { 1130 | os << "["; 1131 | required = false; 1132 | } 1133 | os << "<" << arg.hint() << ">"; 1134 | if( arg.cardinality() == 0 ) 1135 | os << " ... "; 1136 | } 1137 | if( !required ) 1138 | os << "]"; 1139 | if( !m_options.empty() ) 1140 | os << " options"; 1141 | os << "\n\nwhere options are:" << std::endl; 1142 | } 1143 | 1144 | auto rows = getHelpColumns(); 1145 | size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; 1146 | size_t optWidth = 0; 1147 | for( auto const &cols : rows ) 1148 | optWidth = (std::max)(optWidth, cols.left.size() + 2); 1149 | 1150 | optWidth = (std::min)(optWidth, consoleWidth/2); 1151 | 1152 | for( auto const &cols : rows ) { 1153 | auto row = 1154 | TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + 1155 | TextFlow::Spacer(4) + 1156 | TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); 1157 | os << row << std::endl; 1158 | } 1159 | } 1160 | 1161 | friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { 1162 | parser.writeToStream( os ); 1163 | return os; 1164 | } 1165 | 1166 | auto validate() const -> Result override { 1167 | for( auto const &opt : m_options ) { 1168 | auto result = opt.validate(); 1169 | if( !result ) 1170 | return result; 1171 | } 1172 | for( auto const &arg : m_args ) { 1173 | auto result = arg.validate(); 1174 | if( !result ) 1175 | return result; 1176 | } 1177 | return Result::ok(); 1178 | } 1179 | 1180 | using ParserBase::parse; 1181 | 1182 | auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { 1183 | 1184 | struct ParserInfo { 1185 | ParserBase const* parser = nullptr; 1186 | size_t count = 0; 1187 | }; 1188 | const size_t totalParsers = m_options.size() + m_args.size(); 1189 | assert( totalParsers < 512 ); 1190 | // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do 1191 | ParserInfo parseInfos[512]; 1192 | 1193 | { 1194 | size_t i = 0; 1195 | for (auto const &opt : m_options) parseInfos[i++].parser = &opt; 1196 | for (auto const &arg : m_args) parseInfos[i++].parser = &arg; 1197 | } 1198 | 1199 | m_exeName.set( exeName ); 1200 | 1201 | auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); 1202 | while( result.value().remainingTokens() ) { 1203 | bool tokenParsed = false; 1204 | 1205 | for( size_t i = 0; i < totalParsers; ++i ) { 1206 | auto& parseInfo = parseInfos[i]; 1207 | if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { 1208 | result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); 1209 | if (!result) 1210 | return result; 1211 | if (result.value().type() != ParseResultType::NoMatch) { 1212 | tokenParsed = true; 1213 | ++parseInfo.count; 1214 | break; 1215 | } 1216 | } 1217 | } 1218 | 1219 | if( result.value().type() == ParseResultType::ShortCircuitAll ) 1220 | return result; 1221 | if( !tokenParsed ) 1222 | return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); 1223 | } 1224 | // !TBD Check missing required options 1225 | return result; 1226 | } 1227 | }; 1228 | 1229 | template 1230 | template 1231 | auto ComposableParserImpl::operator|( T const &other ) const -> Parser { 1232 | return Parser() | static_cast( *this ) | other; 1233 | } 1234 | } // namespace detail 1235 | 1236 | 1237 | // A Combined parser 1238 | using detail::Parser; 1239 | 1240 | // A parser for options 1241 | using detail::Opt; 1242 | 1243 | // A parser for arguments 1244 | using detail::Arg; 1245 | 1246 | // Wrapper for argc, argv from main() 1247 | using detail::Args; 1248 | 1249 | // Specifies the name of the executable 1250 | using detail::ExeName; 1251 | 1252 | // Convenience wrapper for option parser that specifies the help option 1253 | using detail::Help; 1254 | 1255 | // enum of result types from a parse 1256 | using detail::ParseResultType; 1257 | 1258 | // Result type for parser operation 1259 | using detail::ParserResult; 1260 | 1261 | 1262 | } // namespace clara 1263 | 1264 | #endif // CLARA_HPP_INCLUDED 1265 | --------------------------------------------------------------------------------