├── .appveyor.yml ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE.magic_enum ├── LICENSE.visit_struct ├── README.md ├── clang-format.bash ├── conanfile.py ├── img └── logo.png ├── include └── structopt │ ├── app.hpp │ ├── array_size.hpp │ ├── exception.hpp │ ├── is_number.hpp │ ├── is_specialization.hpp │ ├── is_stl_container.hpp │ ├── parser.hpp │ ├── string.hpp │ ├── sub_command.hpp │ ├── third_party │ ├── magic_enum │ │ └── magic_enum.hpp │ └── visit_struct │ │ └── visit_struct.hpp │ └── visitor.hpp ├── run_tests.sh ├── samples ├── CMakeLists.txt ├── compound_arguments.cpp ├── demo.cpp ├── double_dash_argument.cpp ├── enum_class_argument.cpp ├── flag_arguments.cpp ├── float_literals.cpp ├── integer_literals.cpp ├── nested_structures.cpp ├── nested_structures_2.cpp ├── option_delimiters.cpp ├── optional_arguments.cpp ├── pair_argument.cpp ├── positional_and_compound_arguments.cpp ├── positional_and_optional_arguments.cpp ├── positional_arguments.cpp ├── printing_help.cpp ├── printing_help_custom.cpp ├── remaining_arguments.cpp └── tuple_arguments.cpp ├── single_include.json ├── single_include └── structopt │ └── structopt.hpp ├── structopt.pc.in ├── structoptConfig.cmake.in ├── tests ├── CMakeLists.txt ├── doctest.hpp ├── main.cpp ├── test_3d_array_positional_argument.cpp ├── test_array_optional_argument.cpp ├── test_array_positional_argument.cpp ├── test_combined_flag_arguments.cpp ├── test_container_adapter_positional_argument.cpp ├── test_deque_optional_argument.cpp ├── test_deque_positional_argument.cpp ├── test_double_dash_option_delimiter.cpp ├── test_enum_optional_argument.cpp ├── test_enum_positional_argument.cpp ├── test_equal_delimiter_optional_argument.cpp ├── test_excess_positional_arguments.cpp ├── test_list_positional_argument.cpp ├── test_multiple_nested_structs_positional.cpp ├── test_nested_struct.cpp ├── test_option_delimiter.cpp ├── test_optional_argument_naming.cpp ├── test_optional_flag_argument.cpp ├── test_set_positional_argument.cpp ├── test_single_optional_argument.cpp ├── test_single_positional_and_optional.cpp ├── test_single_positional_argument.cpp ├── test_trailing_positional_arguments.cpp ├── test_tuple_optional_argument.cpp ├── test_tuple_positional_argument.cpp ├── test_unknown_optional_argument.cpp ├── test_vector_optional_argument.cpp └── test_vector_positional_argument.cpp └── utils └── amalgamate ├── CHANGES.md ├── LICENSE.md ├── README.md ├── amalgamate.py └── config.json /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{branch} #{build}" 2 | 3 | image: 4 | - Visual Studio 2017 5 | - Visual Studio 2019 6 | 7 | platform: 8 | - Win32 9 | - x64 10 | 11 | build: 12 | parallel: true 13 | 14 | before_build: 15 | - if exist build RMDIR /S /Q build 16 | - if not exist build mkdir build 17 | - cd build 18 | 19 | build_script: 20 | - cmake -DSTRUCTOPT_SAMPLES=on -DSTRUCTOPT_TESTS=on -A %PLATFORM% .. 21 | - cmake --build . --config Debug 22 | - cmake --build . --config Release 23 | 24 | test_script: 25 | - call "C:\projects\structopt\build\tests\Debug\structopt_tests.exe" 26 | - call "C:\projects\structopt\build\tests\Release\structopt_tests.exe" 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | .vscode/ 28 | # Uncomment if you have tasks that create the project's static files in wwwroot 29 | #wwwroot/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | project.fragment.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opendb 82 | *.opensdf 83 | *.sdf 84 | *.cachefile 85 | *.VC.db 86 | *.VC.VC.opendb 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | *.sap 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding add-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | _NCrunch_* 116 | .*crunch*.local.xml 117 | nCrunchTemp_* 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | # TODO: Comment the next line if you want to checkin your web deploy settings 146 | # but database connection strings (with potential passwords) will be unencrypted 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directories and files 176 | AppPackages/ 177 | BundleArtifacts/ 178 | Package.StoreAssociation.xml 179 | _pkginfo.txt 180 | 181 | # Visual Studio cache files 182 | # files ending in .cache can be ignored 183 | *.[Cc]ache 184 | # but keep track of directories ending in .cache 185 | !*.[Cc]ache/ 186 | 187 | # Others 188 | ClientBin/ 189 | ~$* 190 | *~ 191 | *.dbmdl 192 | *.dbproj.schemaview 193 | *.jfm 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc 263 | 264 | # CMake build directory 265 | build 266 | 267 | # Cppcheck build directory 268 | analysis-cppcheck-build-dir 269 | 270 | # Ideas directory 271 | ideas 272 | 273 | desktop.iniimages/ 274 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux # Use linux unless specified otherwise. 2 | dist: bionic 3 | sudo: required 4 | 5 | language: cpp 6 | 7 | matrix: 8 | include: 9 | - os: linux 10 | compiler: g++ 11 | addons: 12 | apt: 13 | sources: 14 | - ubuntu-toolchain-r-test 15 | packages: 16 | - g++-9 17 | env: 18 | - CXX_COMPILER=g++-9 CC_COMPILER=gcc-9 19 | 20 | - os: linux 21 | compiler: clang++ 22 | addons: 23 | apt: 24 | sources: 25 | - sourceline: 'deb https://apt.llvm.org/bionic/ llvm-toolchain-bionic-7 main' 26 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 27 | packages: 28 | - clang-7 29 | env: 30 | - CXX_COMPILER=clang++-7 CC_COMPILER=clang-7 31 | 32 | - os: linux 33 | compiler: clang++ 34 | addons: 35 | apt: 36 | sources: 37 | - sourceline: 'deb https://apt.llvm.org/bionic/ llvm-toolchain-bionic-8 main' 38 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 39 | packages: 40 | - clang-8 41 | env: 42 | - CXX_COMPILER=clang++-8 CC_COMPILER=clang-8 43 | 44 | - os: linux 45 | compiler: clang++ 46 | addons: 47 | apt: 48 | sources: 49 | - sourceline: 'deb https://apt.llvm.org/bionic/ llvm-toolchain-bionic-9 main' 50 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 51 | packages: 52 | - clang-9 53 | env: 54 | - CXX_COMPILER=clang++-9 CC_COMPILER=clang-9 55 | 56 | - os: linux 57 | compiler: clang++ 58 | addons: 59 | apt: 60 | sources: 61 | - sourceline: 'deb https://apt.llvm.org/bionic/ llvm-toolchain-bionic-10 main' 62 | key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' 63 | packages: 64 | - clang-10 65 | env: 66 | - CXX_COMPILER=clang++-10 CC_COMPILER=clang-10 67 | 68 | - os: osx 69 | compiler: clang++ 70 | osx_image: xcode10.3 71 | env: 72 | - CXX_COMPILER=clang++ CC_COMPILER=clang 73 | 74 | - os: osx 75 | compiler: clang++ 76 | osx_image: xcode11.6 77 | env: 78 | - CXX_COMPILER=clang++ CC_COMPILER=clang 79 | 80 | - os: osx 81 | compiler: clang++ 82 | osx_image: xcode12 83 | env: 84 | - CXX_COMPILER=clang++ CC_COMPILER=clang 85 | 86 | install: 87 | - export CC=${CC_COMPILER} 88 | - export CXX=${CXX_COMPILER} 89 | - JOBS=2 # Travis machines have 2 cores. 90 | 91 | before_script: 92 | - rm -rf build 93 | - mkdir -p build 94 | - cd build 95 | 96 | script: 97 | - cmake -G "Unix Makefiles" -DSTRUCTOPT_SAMPLES=on -DSTRUCTOPT_TESTS=on -DCMAKE_BUILD_TYPE=Debug .. 98 | - cmake --build . --config Debug -- -j${JOBS} 99 | - ./tests/structopt_tests 100 | - rm -rf ./* 101 | - cmake -G "Unix Makefiles" -DSTRUCTOPT_SAMPLES=on -DSTRUCTOPT_TESTS=on -DCMAKE_BUILD_TYPE=Release .. 102 | - cmake --build . --config Release -- -j${JOBS} 103 | - ./tests/structopt_tests -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | if(DEFINED PROJECT_NAME) 4 | set(STRUCTOPT_SUBPROJECT ON) 5 | endif() 6 | 7 | if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.12") 8 | project(structopt VERSION 0.1.2 LANGUAGES CXX 9 | HOMEPAGE_URL "https://github.com/p-ranav/structopt" 10 | DESCRIPTION "Parse command line arguments by defining a struct") 11 | elseif(CMAKE_VERSION VERSION_GREATER_EQUAL "3.9") 12 | project(structopt VERSION 0.1.2 LANGUAGES CXX 13 | DESCRIPTION "Parse command line arguments by defining a struct") 14 | else() 15 | project(structopt VERSION 0.1.2 LANGUAGES CXX) 16 | endif() 17 | 18 | if(EXISTS "${CMAKE_BINARY_DIR}/conanbuildinfo.cmake") 19 | include("${CMAKE_BINARY_DIR}/conanbuildinfo.cmake") 20 | conan_basic_setup() 21 | endif() 22 | 23 | if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 24 | if (MSVC_VERSION GREATER_EQUAL "1900") 25 | include(CheckCXXCompilerFlag) 26 | CHECK_CXX_COMPILER_FLAG("/std:c++latest" _cpp_latest_flag_supported) 27 | if (_cpp_latest_flag_supported) 28 | add_compile_options("/std:c++latest") 29 | endif() 30 | endif() 31 | endif() 32 | 33 | option(STRUCTOPT_TESTS "Build structopt tests + enable CTest") 34 | option(STRUCTOPT_SAMPLES "Build structopt samples") 35 | 36 | include(CMakePackageConfigHelpers) 37 | include(GNUInstallDirs) 38 | 39 | add_library(structopt INTERFACE) 40 | add_library(structopt::structopt ALIAS structopt) 41 | 42 | target_compile_features(structopt INTERFACE cxx_std_17) 43 | target_include_directories(structopt INTERFACE 44 | $ 45 | $) 46 | 47 | if(STRUCTOPT_SAMPLES) 48 | add_subdirectory(samples) 49 | endif() 50 | 51 | if(STRUCTOPT_TESTS) 52 | add_subdirectory(tests) 53 | endif() 54 | 55 | if(NOT STRUCTOPT_SUBPROJECT) 56 | configure_package_config_file(structoptConfig.cmake.in 57 | ${CMAKE_CURRENT_BINARY_DIR}/structoptConfig.cmake 58 | INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/structopt) 59 | write_basic_package_version_file(structoptConfigVersion.cmake 60 | COMPATIBILITY AnyNewerVersion) 61 | 62 | configure_file(structopt.pc.in structopt.pc @ONLY) 63 | 64 | install(TARGETS structopt EXPORT structoptTargets) 65 | install(EXPORT structoptTargets 66 | FILE structoptTargets.cmake 67 | NAMESPACE structopt:: 68 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/structopt) 69 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/structoptConfig.cmake 70 | ${CMAKE_CURRENT_BINARY_DIR}/structoptConfigVersion.cmake 71 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/structopt) 72 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/structopt.pc 73 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 74 | install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/include/structopt 75 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 76 | USE_SOURCE_PERMISSIONS 77 | PATTERN "*.hpp") 78 | install(FILES LICENSE LICENSE.magic_enum LICENSE.visit_struct 79 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/licenses/structopt) 80 | 81 | if(EXISTS "${PROJECT_SOURCE_DIR}/.gitignore") 82 | # Simple glob to regex conversion (.gitignore => CPACK_SOURCE_IGNORE_FILES) 83 | file(READ ".gitignore" DOT_GITIGNORE) 84 | string(REPLACE ";" "RANDOMSEQUENCE" DOT_GITIGNORE "${DOT_GITIGNORE}") 85 | string(REPLACE "\n" ";" DOT_GITIGNORE "${DOT_GITIGNORE}") 86 | string(REPLACE "RANDOMSEQUENCE" "\\;" DOT_GITIGNORE "${DOT_GITIGNORE}") 87 | foreach(IGNORE_LINE ${DOT_GITIGNORE}) 88 | if(NOT IGNORE_LINE OR IGNORE_LINE MATCHES "^#") 89 | continue() 90 | endif() 91 | string(REPLACE "\\" "\\\\" IGNORE_LINE "${IGNORE_LINE}") 92 | string(REPLACE "." "\\\\." IGNORE_LINE "${IGNORE_LINE}") 93 | string(REPLACE "*" ".*" IGNORE_LINE "${IGNORE_LINE}") 94 | string(REPLACE "+" "\\\\+" IGNORE_LINE "${IGNORE_LINE}") 95 | list(APPEND CPACK_SOURCE_IGNORE_FILES "${IGNORE_LINE}") 96 | endforeach() 97 | endif() 98 | 99 | # extra ignored files 100 | list(APPEND CPACK_SOURCE_IGNORE_FILES 101 | .editorconfig 102 | .git 103 | .gitignore 104 | .travis.yml 105 | .appveyor.yml 106 | ) 107 | set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}") 108 | set(CPACK_GENERATOR "TGZ;TXZ") 109 | set(CPACK_SOURCE_GENERATOR "TGZ;TXZ") 110 | include(CPack) 111 | endif() 112 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Contributions are welcomed. Open a pull-request or an issue. 3 | 4 | ## Code of conduct 5 | This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. 6 | 7 | [code-of-conduct]: https://github.com/spotify/code-of-conduct/blob/master/code-of-conduct.md 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Pranav 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.magic_enum: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 - 2020 Daniil Goncharov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.visit_struct: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | Parse command line arguments by defining a struct 7 |

8 | 9 |

10 | 11 | ci status 12 | 13 | 14 | conan package 15 | 16 | 17 | ci status 18 | 19 | 20 | ci status 21 | 22 | 23 | codacy 24 | 25 | 26 | standard 27 | 28 | 29 | license 30 | 31 |

32 | 33 | ## Quick Start 34 | 35 | ```cpp 36 | #include 37 | 38 | struct Options { 39 | // positional argument 40 | // e.g., ./main 41 | std::string config_file; 42 | 43 | // optional argument 44 | // e.g., -b "192.168.5.3" 45 | // e.g., --bind_address "192.168.5.3" 46 | // 47 | // options can be delimited with `=` or `:` 48 | // note: single dash (`-`) is enough for short & long option 49 | // e.g., -bind_address=localhost 50 | // e.g., -b:192.168.5.3 51 | // 52 | // the long option can also be provided in kebab case: 53 | // e.g., --bind-address 192.168.5.3 54 | std::optional bind_address; 55 | 56 | // flag argument 57 | // Use `std::optional` and provide a default value. 58 | // e.g., -v 59 | // e.g., --verbose 60 | // e.g., -verbose 61 | std::optional verbose = false; 62 | 63 | // directly define and use enum classes to limit user choice 64 | // e.g., --log-level debug 65 | // e.g., -l error 66 | enum class LogLevel { debug, info, warn, error, critical }; 67 | std::optional log_level = LogLevel::info; 68 | 69 | // pair argument 70 | // e.g., -u 71 | // e.g., --user 72 | std::optional> user; 73 | 74 | // use containers like std::vector 75 | // to collect "remaining arguments" into a list 76 | std::vector files; 77 | }; 78 | STRUCTOPT(Options, config_file, bind_address, verbose, log_level, user, files); 79 | ``` 80 | 81 | Create a `structopt::app` and parse the command line arguments into the `Options` struct: 82 | 83 | ```cpp 84 | int main(int argc, char *argv[]) { 85 | 86 | try { 87 | 88 | // Line of code that does all the work: 89 | auto options = structopt::app("my_app").parse(argc, argv); 90 | 91 | // Print out parsed arguments: 92 | 93 | // std::cout << "config_file = " << options.config_file << "\n"; 94 | // std::cout << "bind_address = " << options.bind_address.value_or("not provided") << "\n"; 95 | // std::cout << "verbose = " << std::boolalpha << options.verbose.value() << "\n"; 96 | // ... 97 | 98 | } catch (structopt::exception& e) { 99 | std::cout << e.what() << "\n"; 100 | std::cout << e.help(); 101 | } 102 | } 103 | ``` 104 | 105 | Now let's pass some arguments to this program: 106 | 107 | ```console 108 | foo@bar:~$ ./main config.csv file5.csv file6.json 109 | config_file = config.csv 110 | bind_address = not provided 111 | verbose = false 112 | log_level = 1 113 | user = not provided 114 | files = { file5.csv file6.json } 115 | 116 | foo@bar:~$ ./main config.csv --bind-address localhost:9000 -v -log-level error file1.txt file2.txt 117 | config_file = config.csv 118 | bind_address = localhost:9000 119 | verbose = true 120 | log_level = 3 121 | user = not provided 122 | files = { file1.txt file2.txt } 123 | 124 | foo@bar:~$ ./main config_2.csv --bind-address 192.168.7.3 -log-level debug file1.txt file3.txt file4.txt --user "John Doe" "john.doe@foo.com" 125 | config_file = config_2.csv 126 | bind_address = 192.168.7.3 127 | verbose = false 128 | log_level = 0 129 | user = John Doe 130 | files = { file1.txt file3.txt file4.txt } 131 | ``` 132 | 133 | ## Table of Contents 134 | 135 | * [Getting Started](#getting-started) 136 | * [Positional Arguments](#positional-arguments) 137 | * [Optional Arguments](#optional-arguments) 138 | * [Double dash (`--`) Argument](#double-dash----argument) 139 | * [Flag Arguments](#flag-arguments) 140 | * [Enum Class Arguments (Choices)](#enum-class-arguments) 141 | * [Tuple Arguments](#tuple-arguments) 142 | * [Vector Arguments](#vector-arguments) 143 | * [Compound Arguments](#compound-arguments) 144 | * [Parsing Numbers](#parsing-numbers) 145 | * [Integer Literals](#integer-literals) 146 | * [Floating point Literals](#floating-point-literals) 147 | * [Nested Structures (Sub-commands)](#nested-structures) 148 | * [Sub-Commands, Vector Arguments, and Delimited Positional Arguments](#sub-commands-vector-arguments-and-delimited-positional-arguments) 149 | * [Printing Help](#printing-help) 150 | * [Printing CUSTOM Help](#printing-custom-help) 151 | * [Building Samples and Tests](#building-samples-and-tests) 152 | * [Compiler Compatibility](#compiler-compatibility) 153 | * [Generating Single Header](#generating-single-header) 154 | * [Contributing](#contributing) 155 | * [License](#license) 156 | 157 | ## Getting Started 158 | 159 | `structopt` is a header-only library. Just add `include/` to your _include_directories_ and you should be good to go. A single header file version is also available in `single_include/`. 160 | 161 | ### Positional Arguments 162 | 163 | Here's an example of two positional arguments: `input_file` and `output_file`. `input_file` is expected to be the first argument and `output_file` is expected to be the second argument 164 | 165 | ```cpp 166 | #include 167 | 168 | struct FileOptions { 169 | // Positional arguments 170 | // ./main 171 | std::string input_file; 172 | std::string output_file; 173 | }; 174 | STRUCTOPT(FileOptions, input_file, output_file); 175 | 176 | 177 | 178 | int main(int argc, char *argv[]) { 179 | 180 | try { 181 | auto options = structopt::app("my_app").parse(argc, argv); 182 | 183 | // Print parsed arguments: 184 | std::cout << "\nInput file : " << options.input_file << "\n"; 185 | std::cout << "Output file : " << options.output_file << "\n"; 186 | 187 | } catch (structopt::exception& e) { 188 | std::cout << e.what() << "\n"; 189 | std::cout << e.help(); 190 | } 191 | } 192 | ``` 193 | 194 | ```console 195 | foo@bar:~$ ./main foo.txt bar.csv 196 | 197 | Input file : foo.txt 198 | Output file : bar.csv 199 | 200 | foo@bar:~$ ./main foo.csv 201 | Error: expected value for positional argument `output_file`. 202 | 203 | USAGE: ./my_app input_file output_file 204 | 205 | ARGS: 206 | input_file 207 | output_file 208 | ``` 209 | 210 | ### Optional Arguments 211 | 212 | Now, let's look at optional arguments. To configure an optional argument, use `std::optional` in the options struct like below. 213 | 214 | ```cpp 215 | #include 216 | 217 | struct GccOptions { 218 | // language standard 219 | // e.g., -std=c++17 220 | // e.g., --std c++20 221 | std::optional std = "c++11"; 222 | 223 | // verbosity enabled with `-v` or `--verbose` 224 | // or `-verbose` 225 | std::optional verbose = false; 226 | 227 | // enable all warnings with `-Wall` 228 | std::optional Wall = false; 229 | 230 | // produce only the compiled code 231 | // e.g., gcc -C main.c 232 | std::optional Compile = false; 233 | 234 | // produce output with `-o ` 235 | std::optional output = "a.out"; 236 | 237 | std::string input_file; 238 | }; 239 | STRUCTOPT(GccOptions, std, verbose, Wall, Compile, output, input_file); 240 | 241 | 242 | int main(int argc, char *argv[]) { 243 | try { 244 | auto options = structopt::app("gcc").parse(argc, argv); 245 | 246 | // Print parsed arguments 247 | 248 | std::cout << "std : " << options.std.value() << "\n"; 249 | std::cout << "verbose : " << std::boolalpha << options.verbose.value() << "\n"; 250 | std::cout << "Wall : " << std::boolalpha << options.Wall.value() << "\n"; 251 | std::cout << "Compile : " << std::boolalpha << options.Compile.value() << "\n"; 252 | std::cout << "Output : " << options.output.value() << "\n"; 253 | std::cout << "Input file : " << options.input_file << "\n"; 254 | } catch (structopt::exception &e) { 255 | std::cout << e.what() << "\n"; 256 | std::cout << e.help(); 257 | } 258 | } 259 | ``` 260 | 261 | ***NOTE*** `structopt` supports two option delimiters, `=` and `:` for optional arguments. This is meaningful and commonly used in single-valued optional arguments, e.g., `--std=c++17`. 262 | 263 | ```console 264 | foo@bar:~$ ./main -C main.cpp 265 | std : c++11 266 | verbose : false 267 | Wall : false 268 | Compile : true 269 | Output : a.out 270 | Input file : main.cpp 271 | 272 | foo@bar:~$ ./main -std=c++17 -o main main.cpp 273 | std : c++17 274 | verbose : false 275 | Wall : false 276 | Compile : false 277 | Output : main 278 | Input file : main.cpp 279 | 280 | foo@bar:~$ ./main main.cpp -v -std:c++14 --output:main -Wall 281 | std : c++14 282 | verbose : true 283 | Wall : true 284 | Compile : false 285 | Output : main 286 | Input file : main.cpp 287 | ``` 288 | 289 | ***NOTE*** In summary, for a field in your struct named `bind_address`, the following are all legal ways to provide a value: 290 | 291 | * Short form: 292 | * `-b ` 293 | * Long form: 294 | * `--bind_address ` 295 | * `-bind_address ` 296 | * Kebab case: 297 | * `--bind-address ` 298 | * `-bind-address ` 299 | * Equal (`'='`) option delimiter 300 | * `-b=` 301 | * `--bind_address=` 302 | * `-bind_address=` 303 | * `--bind-address=` 304 | * `-bind-address=` 305 | * Colon `':'` option delimiter 306 | * `-b:` 307 | * `--bind_address:` 308 | * `-bind_address:` 309 | * `--bind-address:` 310 | * `-bind-address:` 311 | 312 | #### Double dash (`--`) Argument 313 | 314 | A double dash (`--`) is used in most bash built-in commands and many other commands to signify the end of command options, after which only positional parameters are accepted. 315 | 316 | Example use: lets say you want to `grep` a file for the string `-v` - normally `-v` will be considered the option to reverse the matching meaning (only show lines that do not match), but with `--` you can `grep` for string `-v` like this: 317 | 318 | ```cpp 319 | #include 320 | 321 | struct GrepOptions { 322 | // reverse the matching 323 | // enable with `-v` 324 | std::optional v = false; 325 | 326 | // positional arguments 327 | std::string search; 328 | std::string pathspec; 329 | }; 330 | STRUCTOPT(GrepOptions, v, search, pathspec); 331 | 332 | 333 | 334 | int main(int argc, char *argv[]) { 335 | 336 | try { 337 | auto options = structopt::app("my_app").parse(argc, argv); 338 | 339 | if (options.v == true) { 340 | std::cout << "`-v` provided - Matching is now reversed\n"; 341 | } 342 | 343 | std::cout << "Search : " << options.search << "\n"; 344 | std::cout << "Pathspec : " << options.pathspec << "\n"; 345 | } 346 | catch (structopt::exception& e) { 347 | std::cout << e.what(); 348 | std::cout << e.help(); 349 | } 350 | 351 | } 352 | ``` 353 | 354 | ```console 355 | foo@bar:~$ ./main -v foo bar.txt 356 | `-v` provided - Matching is now reversed 357 | Search : foo 358 | Pathspec : bar.txt 359 | 360 | foo@bar:~$ ./main -- -v bar.txt 361 | Search : -v 362 | Pathspec : bar.txt 363 | ``` 364 | 365 | ### Flag Arguments 366 | 367 | Flag arguments are `std::optional` with a default value. 368 | 369 | ***NOTE*** The default value here is important. It is not a flag if a default value isn't provided. It will simply be an optional argument. 370 | 371 | ***NOTE*** If `--verbose` is a flag argument with a default value of `false`, then providing the argument will set it to `true`. If `--verbose` does not have a default value, then `structopt` will expect the user to provide a value, e.g., `--verbose true`. 372 | 373 | ```cpp 374 | #include 375 | 376 | struct Options { 377 | // verbosity flag 378 | // -v, --verbose 379 | // remember to provide a default value 380 | std::optional verbose = false; 381 | }; 382 | STRUCTOPT(Options, verbose); 383 | 384 | 385 | 386 | int main(int argc, char *argv[]) { 387 | auto options = structopt::app("my_app").parse(argc, argv); 388 | 389 | if (options.verbose == true) { 390 | std::cout << "Verbosity enabled\n"; 391 | } 392 | } 393 | ``` 394 | 395 | ```console 396 | foo@bar:~$ ./main 397 | 398 | foo@bar:~$ ./main -v 399 | Verbosity enabled 400 | 401 | foo@bar:~$ ./main --verbose 402 | Verbosity enabled 403 | ``` 404 | 405 | ### Enum Class Arguments 406 | 407 | Thanks to [magic_enum](https://github.com/Neargye/magic_enum), `structopt` supports enum classes. You can use an enum classes to ask the user to provide a value given a choice of values, restricting the possible set of allowed input arguments. 408 | 409 | ```cpp 410 | #include 411 | 412 | struct StyleOptions { 413 | enum class Color {red, green, blue}; 414 | 415 | // e.g., `--color red` 416 | std::optional color = Color::red; 417 | }; 418 | STRUCTOPT(StyleOptions, color); 419 | 420 | 421 | 422 | int main(int argc, char *argv[]) { 423 | 424 | try { 425 | auto options = structopt::app("my_app").parse(argc, argv); 426 | 427 | // Use parsed argument `options.color` 428 | 429 | if (options.color == StyleOptions::Color::red) { 430 | std::cout << "#ff0000\n"; 431 | } 432 | else if (options.color == StyleOptions::Color::blue) { 433 | std::cout << "#0000ff\n"; 434 | } 435 | else if (options.color == StyleOptions::Color::green) { 436 | std::cout << "#00ff00\n"; 437 | } 438 | 439 | } catch (structopt::exception& e) { 440 | std::cout << e.what() << "\n"; 441 | std::cout << e.help(); 442 | } 443 | } 444 | ``` 445 | 446 | ```console 447 | foo@bar:~$ ./main --color red 448 | #ff0000 449 | 450 | foo@bar:~$ ./main -c blue 451 | #0000ff 452 | 453 | foo@bar:~$ ./main --color green 454 | #00ff00 455 | 456 | foo@bar:~$ ./main -c black 457 | Error: unexpected input `black` provided for enum argument `color`. Allowed values are {red, green, blue} 458 | 459 | USAGE: ./my_app [OPTIONS] 460 | 461 | OPTIONS: 462 | -c, --color 463 | ``` 464 | 465 | ### Tuple Arguments 466 | 467 | Now that we've looked at enum class support, let's build a simple calculator. In this sample, we will use an `std::tuple` to pack all the arguments to the calculator: 468 | 469 | ```cpp 470 | #include 471 | 472 | struct CalculatorOptions { 473 | 474 | // types of operations supported 475 | enum class operation { add, subtract, multiply, divide }; 476 | 477 | // single tuple positional argument 478 | std::tuple input; 479 | 480 | }; 481 | STRUCTOPT(CalculatorOptions, input); 482 | 483 | 484 | 485 | int main(int argc, char *argv[]) { 486 | 487 | try { 488 | auto options = structopt::app("my_app").parse(argc, argv); 489 | 490 | auto op = std::get<0>(options.input); 491 | auto lhs = std::get<1>(options.input); 492 | auto rhs = std::get<2>(options.input); 493 | switch(op) 494 | { 495 | case CalculatorOptions::operation::add: 496 | std::cout << lhs + rhs << "\n"; 497 | break; 498 | case CalculatorOptions::operation::subtract: 499 | std::cout << lhs - rhs << "\n"; 500 | break; 501 | case CalculatorOptions::operation::multiply: 502 | std::cout << lhs * rhs << "\n"; 503 | break; 504 | case CalculatorOptions::operation::divide: 505 | std::cout << lhs / rhs << "\n"; 506 | break; 507 | } 508 | } 509 | catch (structopt::exception& e) { 510 | std::cout << e.what(); 511 | std::cout << e.help(); 512 | } 513 | 514 | } 515 | ``` 516 | 517 | ```console 518 | foo@bar:~$ ./main add 1 2 519 | 3 520 | 521 | foo@bar:~$ ./main subtract 5 9 522 | -4 523 | 524 | foo@bar:~$ ./main multiply 16 5 525 | 80 526 | 527 | foo@bar:~$ ./main divide 1331 11 528 | 121 529 | 530 | foo@bar:~$ ./main add 5 531 | Error: failed to correctly parse tuple `input`. Expected 3 arguments, 2 provided. 532 | 533 | USAGE: my_app input 534 | 535 | ARGS: 536 | input 537 | ``` 538 | 539 | ### Vector Arguments 540 | 541 | `structopt` supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler: 542 | 543 | ```bash 544 | $ compiler file1 file2 file3 545 | ``` 546 | 547 | Do this by using an `std::vector` (or other STL containers with `.push_back()`, e.g, `std::deque` or `std::list`). 548 | 549 | ***NOTE*** Vector arguments have a cardinality of `0..*`, i.e., zero or more arguments. Unlike array types, you can provide zero arguments to a vector and `structopt` will (try to) not complain. 550 | 551 | ```cpp 552 | #include 553 | 554 | struct CompilerOptions { 555 | // Language standard 556 | // e.g., --std c++17 557 | std::optional std; 558 | 559 | // remaining arguments 560 | // e.g., ./compiler file1 file2 file3 561 | std::vector files{}; 562 | }; 563 | STRUCTOPT(CompilerOptions, std, files); 564 | 565 | 566 | 567 | int main(int argc, char *argv[]) { 568 | try { 569 | auto options = structopt::app("my_app").parse(argc, argv); 570 | 571 | std::cout << "Standard : " << options.std.value_or("not provided") << "\n"; 572 | std::cout << "Files : { "; 573 | std::copy(options.files.begin(), options.files.end(), 574 | std::ostream_iterator(std::cout, " ")); 575 | std::cout << "}" << std::endl; 576 | } catch (structopt::exception &e) { 577 | std::cout << e.what() << "\n"; 578 | std::cout << e.help(); 579 | } 580 | } 581 | ``` 582 | 583 | ***NOTE*** Notice below that the act of gathering remaining arguments is arrested as soon as an optional argument is detected. See the output of `./main file1.cpp file2.cpp --std c++17` below. Notice that `--std=c++17` is not part of the vector. This is because `--std` is a valid optional argument. 584 | 585 | ```console 586 | foo@bar:~$ ./main 587 | Standard : not provided 588 | Files : { } 589 | 590 | foo@bar:~$ ./main file1.cpp file2.cpp 591 | Standard : not provided 592 | Files : { file1.cpp file2.cpp } 593 | 594 | foo@bar:~$ ./main file1.cpp file2.cpp --std=c++17 595 | Standard : c++17 596 | Files : { file1.cpp file2.cpp } 597 | 598 | foo@bar:~$ ./main --std:c++20 file1.cpp file2.cpp 599 | Standard : c++20 600 | Files : { file1.cpp file2.cpp } 601 | ``` 602 | 603 | ### Compound Arguments 604 | 605 | Compound arguments are optional arguments that are combined and provided as a single argument. Example: `ps -aux` 606 | 607 | ```cpp 608 | #include 609 | 610 | struct Options { 611 | // Flag arguments 612 | std::optional a = false; 613 | std::optional b = false; 614 | 615 | // Optional argument 616 | // e.g., -c 1.1 2.2 617 | std::optional> c = {}; 618 | }; 619 | STRUCTOPT(Options, a, b, c); 620 | 621 | 622 | 623 | int main(int argc, char *argv[]) { 624 | try { 625 | auto options = structopt::app("my_app").parse(argc, argv); 626 | 627 | // Print parsed arguments: 628 | 629 | std::cout << std::boolalpha << "a = " << options.a.value() 630 | << ", b = " << options.b.value() << "\n"; 631 | if (options.c.has_value()) { 632 | std::cout << "c = [" << options.c.value()[0] << ", " << options.c.value()[1] 633 | << "]\n"; 634 | } 635 | } catch (structopt::exception &e) { 636 | std::cout << e.what() << "\n"; 637 | std::cout << e.help(); 638 | } 639 | } 640 | ``` 641 | 642 | ```console 643 | foo@bar:~$ ./main -ac 3.14 2.718 644 | a = true, b = false 645 | c = [3.14, 2.718] 646 | 647 | foo@bar:~$ ./main -ba 648 | a = true, b = true 649 | 650 | foo@bar:~$ ./main -c 1.5 3.0 -ab 651 | a = true, b = true 652 | c = [1.5, 3] 653 | ``` 654 | 655 | ### Parsing Numbers 656 | 657 | #### Integer Literals 658 | 659 | `structopt` supports parsing integer literals including hexadecimal, octal, and binary notation. 660 | 661 | ```cpp 662 | #include 663 | 664 | struct IntegerLiterals { 665 | std::vector numbers; 666 | }; 667 | STRUCTOPT(IntegerLiterals, numbers); 668 | 669 | int main(int argc, char *argv[]) { 670 | try { 671 | auto options = structopt::app("my_app").parse(argc, argv); 672 | 673 | for (auto &n : options.numbers) 674 | std::cout << n << "\n"; 675 | } catch (structopt::exception &e) { 676 | std::cout << e.what() << "\n"; 677 | std::cout << e.help(); 678 | } 679 | } 680 | ``` 681 | 682 | ```console 683 | foo@bar:~$ ./main 1 0x5B 071 0b0101 -35 +98 684 | 1 685 | 91 686 | 57 687 | 5 688 | -35 689 | 98 690 | ``` 691 | 692 | #### Floating point Literals 693 | 694 | As for floating point numbers, `structopt` supports parsing scientific notation (e/E-notation): 695 | 696 | ```cpp 697 | #include 698 | 699 | struct FloatLiterals { 700 | std::vector numbers; 701 | }; 702 | STRUCTOPT(FloatLiterals, numbers); 703 | 704 | int main(int argc, char *argv[]) { 705 | try { 706 | auto options = structopt::app("my_app").parse(argc, argv); 707 | 708 | for (auto &n : options.numbers) 709 | std::cout << n << "\n"; 710 | } catch (structopt::exception &e) { 711 | std::cout << e.what() << "\n"; 712 | std::cout << e.help(); 713 | } 714 | } 715 | ``` 716 | 717 | ```console 718 | foo@bar:~$ ./main -3.15 +2.717 2E-4 0.1e2 .5 -.3 +5.999 719 | -3.15 720 | 2.717 721 | 0.0002 722 | 10 723 | 0.5 724 | -0.3 725 | 5.999 726 | ``` 727 | 728 | ### Nested Structures 729 | 730 | With `structopt`, you can define sub-commands, e.g., `git init args` or `git config [flags] args` using nested structures. 731 | 732 | * Simply create a nested structure that inherits from `structopt::sub_command` 733 | * You can use `.has_value()` to check if it has been invoked. 734 | 735 | The following program support two sub-commands: `config` and `init`: 736 | 737 | ```cpp 738 | #include 739 | 740 | struct Git { 741 | // Subcommand: git config 742 | struct Config : structopt::sub_command { 743 | // flag argument `--global` 744 | std::optional global = false; 745 | 746 | // key-value pair, e.g., `user.name "John Doe"` 747 | std::array name_value_pair{}; 748 | }; 749 | Config config; 750 | 751 | // Subcommand: git init 752 | struct Init : structopt::sub_command { 753 | 754 | // required argument 755 | // repository name 756 | std::string name; 757 | }; 758 | Init init; 759 | }; 760 | STRUCTOPT(Git::Config, global, name_value_pair); 761 | STRUCTOPT(Git::Init, name); 762 | STRUCTOPT(Git, config, init); 763 | 764 | 765 | 766 | int main(int argc, char *argv[]) { 767 | 768 | 769 | try { 770 | auto options = structopt::app("my_app").parse(argc, argv); 771 | 772 | if (options.config.has_value()) { 773 | // config was invoked 774 | std::cout << "You invoked `git config`:\n"; 775 | std::cout << "Global : " << std::boolalpha << options.config.global.value() << "\n"; 776 | std::cout << "Input : (" << options.config.name_value_pair[0] << ", " << options.config.name_value_pair[1] << ")\n"; 777 | } 778 | else if (options.init.has_value()) { 779 | // init was invoked 780 | std::cout << "You invoked `git init`:\n"; 781 | std::cout << "Repository name : " << options.init.name << "\n"; 782 | } 783 | 784 | 785 | } catch (structopt::exception& e) { 786 | std::cout << e.what() << "\n"; 787 | std::cout << e.help(); 788 | } 789 | } 790 | ``` 791 | 792 | ```console 793 | foo@bar:~$ ./main config user.email "john.doe@foo.com" 794 | You invoked `git config`: 795 | Global : false 796 | Input : (user.email, john.doe@foo.com) 797 | 798 | foo@bar:~$ ./main config user.name "John Doe" --global 799 | You invoked `git config`: 800 | Global : true 801 | Input : (user.name, John Doe) 802 | 803 | foo@bar:~$ ./main init my_repo 804 | You invoked `git init`: 805 | Repository name : my_repo 806 | 807 | 808 | 809 | foo@bar:~$ ./main -h 810 | 811 | USAGE: my_app [OPTIONS] [SUBCOMMANDS] 812 | 813 | OPTIONS: 814 | -h, --help 815 | -v, --version 816 | 817 | SUBCOMMANDS: 818 | config 819 | init 820 | 821 | 822 | 823 | 824 | foo@bar:~$ ./main config -h 825 | 826 | USAGE: config [FLAGS] [OPTIONS] name_value_pair 827 | 828 | FLAGS: 829 | -g, --global 830 | 831 | OPTIONS: 832 | -h, --help 833 | -v, --version 834 | 835 | ARGS: 836 | name_value_pair 837 | 838 | 839 | 840 | 841 | foo@bar:~$ ./main init -h 842 | 843 | USAGE: init [OPTIONS] name 844 | 845 | OPTIONS: 846 | -h, --help 847 | -v, --version 848 | 849 | ARGS: 850 | name 851 | ``` 852 | 853 | ***NOTE*** Notice in the above stdout that the `-h` help option supports printing help both at the top-level struct and at the sub-command level. 854 | 855 | ***NOTE*** `structopt` does not allow to invoke multiple sub-commands. If one has already been invoked, you will see the following error: 856 | 857 | ```console 858 | foo@bar:~$ ./main config user.name "John Doe" init my_repo 859 | Error: failed to invoke sub-command `init` because a different sub-command, `config`, has already been invoked. 860 | ``` 861 | 862 | ### Sub-Commands, Vector Arguments, and Delimited Positional Arguments 863 | 864 | Here's a second example for nested structures with vector arguments and the double dash (`--`) delimiter 865 | 866 | ```cpp 867 | #include 868 | 869 | struct CommandOptions { 870 | struct Sed : structopt::sub_command { 871 | // --trace 872 | std::optional trace = false; 873 | 874 | // remaining args 875 | std::vector args; 876 | 877 | // pattern 878 | std::string pattern; 879 | 880 | // file 881 | std::string file; 882 | }; 883 | Sed sed; 884 | }; 885 | STRUCTOPT(CommandOptions::Sed, trace, args, pattern, file); 886 | STRUCTOPT(CommandOptions, sed); 887 | 888 | 889 | 890 | int main(int argc, char *argv[]) { 891 | 892 | auto app = structopt::app("my_app"); 893 | 894 | try { 895 | 896 | auto options = app.parse(argc, argv); 897 | 898 | if (options.sed.has_value()) { 899 | // sed has been invoked 900 | 901 | if (options.sed.trace == true) { 902 | std::cout << "Trace enabled!\n"; 903 | } 904 | 905 | std::cout << "Args : "; 906 | for (auto& a : options.sed.args) std::cout << a << " "; 907 | std::cout << "\n"; 908 | std::cout << "Pattern : " << options.sed.pattern << "\n"; 909 | std::cout << "File : " << options.sed.file << "\n"; 910 | } 911 | else { 912 | std::cout << app.help(); 913 | } 914 | 915 | } catch (structopt::exception &e) { 916 | std::cout << e.what() << "\n"; 917 | std::cout << e.help(); 918 | } 919 | } 920 | ``` 921 | 922 | ```console 923 | foo@bar:~$ ./main 924 | 925 | USAGE: my_app [OPTIONS] [SUBCOMMANDS] 926 | 927 | OPTIONS: 928 | -h, --help 929 | -v, --version 930 | 931 | SUBCOMMANDS: 932 | sed 933 | 934 | 935 | 936 | foo@bar:~$ ./main sed --trace X=1 Y=2 Z=3 -- 's/foo/bar/g' foo.txt 937 | Trace enabled! 938 | Args : X=1 Y=2 Z=3 939 | Pattern : s/foo/bar/g 940 | File : foo.txt 941 | ``` 942 | 943 | ### Printing Help 944 | 945 | `structopt` will insert two optional arguments for the user: `help` and `version`. 946 | 947 | * Using `-h` or `--help` will print the help message and exit. 948 | * Using `-v` or `--version` will print the program version and exit. 949 | 950 | ```cpp 951 | #include 952 | 953 | struct Options { 954 | // positional arguments 955 | std::string input_file; 956 | std::string output_file; 957 | 958 | // optional arguments 959 | std::optional bind_address; 960 | 961 | // remaining arguments 962 | std::vector files; 963 | }; 964 | STRUCTOPT(Options, input_file, output_file, bind_address, files); 965 | 966 | 967 | 968 | int main(int argc, char *argv[]) { 969 | auto options = structopt::app("my_app", "1.0.3").parse(argc, argv); 970 | } 971 | ``` 972 | 973 | ```console 974 | foo@bar:~$ ./main -h 975 | 976 | USAGE: my_app [OPTIONS] input_file output_file files 977 | 978 | OPTIONS: 979 | -b, --bind-address 980 | -h, --help 981 | -v, --version 982 | 983 | ARGS: 984 | input_file 985 | output_file 986 | files 987 | 988 | foo@bar:~$ ./main -v 989 | 1.0.3 990 | ``` 991 | 992 | ### Printing CUSTOM Help 993 | 994 | `structopt` allows users to provide a custom help messages. Simply pass in your custom help as a string argument to `structopt::app` 995 | 996 | ```cpp 997 | #include 998 | 999 | struct Options { 1000 | // positional arguments 1001 | std::string input_file; 1002 | std::string output_file; 1003 | 1004 | // optional arguments 1005 | std::optional bind_address; 1006 | 1007 | // remaining arguments 1008 | std::vector files; 1009 | }; 1010 | STRUCTOPT(Options, input_file, output_file, bind_address, files); 1011 | 1012 | int main(int argc, char *argv[]) { 1013 | 1014 | try { 1015 | const std::string& custom_help = "Usage: ./my_app input_file output_file [--bind-address BIND_ADDRESS] [files...]\n"; 1016 | auto options = structopt::app("my_app", "1.0.3", custom_help).parse(argc, argv); 1017 | } catch (structopt::exception &e) { 1018 | std::cout << e.what() << "\n"; 1019 | std::cout << e.help(); 1020 | } 1021 | } 1022 | ``` 1023 | 1024 | ```console 1025 | foo@bar:~$ ./main -h 1026 | Usage: ./my_app input_file output_file [--bind-address BIND_ADDRESS] [files...] 1027 | ``` 1028 | 1029 | ## Building Samples and Tests 1030 | 1031 | ```bash 1032 | git clone https://github.com/p-ranav/structopt 1033 | cd structopt 1034 | mkdir build && cd build 1035 | cmake -DSTRUCTOPT_SAMPLES=ON -DSTRUCTOPT_TESTS=ON .. 1036 | make 1037 | ``` 1038 | 1039 | ### WinLibs + MinGW 1040 | 1041 | For Windows, if you use [WinLibs](http://winlibs.com/) like I do, the cmake command would look like this: 1042 | 1043 | ```console 1044 | foo@bar:~$ mkdir build && cd build 1045 | foo@bar:~$ cmake -G "MinGW Makefiles" -DCMAKE_CXX_COMPILER="C:/WinLibs/mingw64/bin/g++.exe" -DSTRUCTOPT_SAMPLES=ON -DSTRUCTOPT_TESTS=ON .. 1046 | foo@bar:~$ make 1047 | 1048 | foo@bar:~$ .\tests\structopt_tests.exe 1049 | [doctest] doctest version is "2.3.5" 1050 | [doctest] run with "--help" for options 1051 | =============================================================================== 1052 | [doctest] test cases: 54 | 54 passed | 0 failed | 0 skipped 1053 | [doctest] assertions: 393 | 393 passed | 0 failed | 1054 | [doctest] Status: SUCCESS! 1055 | ``` 1056 | 1057 | ## Compiler Compatibility 1058 | 1059 | * Clang/LLVM >= 5 1060 | * MSVC++ >= 14.11 / Visual Studio >= 2017 1061 | * Xcode >= 10 1062 | * GCC >= 9 1063 | 1064 | ## Generating Single Header 1065 | 1066 | ```bash 1067 | python3 utils/amalgamate/amalgamate.py -c single_include.json -s . 1068 | ``` 1069 | 1070 | ## Contributing 1071 | Contributions are welcome, have a look at the [CONTRIBUTING.md](CONTRIBUTING.md) document for more information. 1072 | 1073 | ## License 1074 | The project is available under the [MIT](https://opensource.org/licenses/MIT) license. 1075 | -------------------------------------------------------------------------------- /clang-format.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | find ./include ./samples -type f \( -iname \*.cpp -o -iname \*.hpp \) | xargs clang-format -style="{ColumnLimit : 90}" -i 3 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | from conans import CMake, ConanFile, tools 2 | from conans.errors import ConanException 3 | from contextlib import contextmanager 4 | import os 5 | 6 | 7 | class StructoptConan(ConanFile): 8 | name = "structopt" 9 | description = "Parse command line arguments by defining a struct" 10 | topics = ("structopt", "argument-parser", "cpp17", "header-only", 11 | "single-header-lib", "header-library", "command-line", "arguments", 12 | "mit-license", "modern-cpp", "structopt", "lightweight", "reflection", 13 | "cross-platform", "library", "type-safety", "type-safe", "argparse", 14 | "clap", "visit-struct-library", "magic-enum") 15 | homepage = "https://github.com/p-ranav/structopt" 16 | url = "https://github.com/p-ranav/structopt" 17 | license = ("MIT", "BSD-3-Clause") 18 | exports_sources = "include/**", "single_include/**", "samples/**", "tests/**", "CMakeLists.txt", "LICENSE*", \ 19 | "structopt.pc.in", "structoptConfig.cmake.in", "README*", "img/**" 20 | exports = "LICENSE" 21 | no_copy_source = True 22 | settings = "os", "arch", "compiler", "build_type" 23 | generators = "cmake" 24 | 25 | def set_version(self): 26 | import re 27 | m = re.search(r"project\(.*VERSION ([0-9a-zA-Z.-]+)[ )]", 28 | open(os.path.join(self.recipe_folder, "CMakeLists.txt")).read()) 29 | if not m: 30 | raise ConanException("Could not extract version from CMakeLists.txt") 31 | self.version = m.group(1) 32 | 33 | _cmake = None 34 | 35 | def _configure_cmake(self): 36 | if self._cmake: 37 | return self._cmake 38 | generator = None 39 | if self.settings.compiler == "Visual Studio": 40 | generator = "NMake Makefiles" 41 | self._cmake = CMake(self, generator=generator) 42 | if tools.get_env("CONAN_RUN_TESTS", default=False): 43 | self._cmake.definitions["STRUCTOPT_SAMPLES"] = True 44 | self._cmake.definitions["STRUCTOPT_TESTS"] = True 45 | self._cmake.configure() 46 | return self._cmake 47 | 48 | @property 49 | def _test_programs(self): 50 | programs = [] 51 | import re 52 | for subdir in ("tests", "samples", ): 53 | for match in re.finditer(r"add_executable\((\S+)", 54 | open(os.path.join(self.source_folder, subdir, "CMakeLists.txt")).read()): 55 | programs.append(os.path.join(self.build_folder, "bin", match.group(1))) 56 | return programs 57 | 58 | @contextmanager 59 | def _build_context(self): 60 | with tools.vcvars(self.settings) if self.settings.compiler == "Visual Studio" else tools.no_op(): 61 | yield 62 | 63 | def build(self): 64 | with self._build_context(): 65 | cmake = self._configure_cmake() 66 | cmake.build() 67 | cmake.build(target="package_source") 68 | # if tools.get_env("CONAN_RUN_TESTS", default=False): 69 | # for program in self._test_programs: 70 | # self.output.info("Running program '{}'".format(program)) 71 | # self.run(program, run_environment=True) 72 | 73 | def package(self): 74 | with self._build_context(): 75 | cmake = self._configure_cmake() 76 | cmake.install() 77 | 78 | def package_id(self): 79 | self.info.header_only() 80 | 81 | def package_info(self): 82 | self.cpp_info.includedirs.append(os.path.join("include", "structopt")) 83 | -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/p-ranav/structopt/daac5f2965eaf4a7052bbf7cc3c8ed87d267454f/img/logo.png -------------------------------------------------------------------------------- /include/structopt/app.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define STRUCTOPT VISITABLE_STRUCT 14 | 15 | namespace structopt { 16 | 17 | class app { 18 | details::visitor visitor; 19 | 20 | public: 21 | explicit app(std::string name, std::string version = "", std::string help = "") 22 | : visitor(std::move(name), std::move(version), std::move(help)) {} 23 | 24 | template T parse(const std::vector &arguments) { 25 | T argument_struct = T(); 26 | 27 | // Visit the struct and save flag, optional and positional field names 28 | visit_struct::for_each(argument_struct, visitor); 29 | 30 | // add `help` and `version` optional arguments 31 | visitor.optional_field_names.push_back("help"); 32 | visitor.optional_field_names.push_back("version"); 33 | 34 | // Construct the argument parser 35 | structopt::details::parser parser; 36 | parser.visitor = visitor; 37 | parser.arguments = arguments; 38 | 39 | for (std::size_t i = 1; i < parser.arguments.size(); i++) { 40 | parser.current_index = i; 41 | visit_struct::for_each(argument_struct, parser); 42 | } 43 | 44 | // directly call the parser to check for `help` and `version` flags 45 | std::optional help = false, version = false; 46 | for (std::size_t i = 1; i < parser.arguments.size(); i++) { 47 | parser.operator()("help", help); 48 | parser.operator()("version", version); 49 | 50 | if (help == true) { 51 | // if help is requested, print help and exit 52 | visitor.print_help(std::cout); 53 | exit(EXIT_SUCCESS); 54 | } else if (version == true) { 55 | // if version is requested, print version and exit 56 | std::cout << visitor.version << "\n"; 57 | exit(EXIT_SUCCESS); 58 | } 59 | } 60 | 61 | // if all positional arguments were provided 62 | // this list would be empty 63 | if (!parser.visitor.positional_field_names.empty()) { 64 | for (auto &field_name : parser.visitor.positional_field_names) { 65 | if (std::find(parser.visitor.vector_like_positional_field_names.begin(), 66 | parser.visitor.vector_like_positional_field_names.end(), 67 | field_name) == 68 | parser.visitor.vector_like_positional_field_names.end()) { 69 | // this positional argument is not a vector-like argument 70 | // it expects value(s) 71 | throw structopt::exception("Error: expected value for positional argument `" + 72 | std::string(field_name) + "`.", 73 | parser.visitor); 74 | } 75 | } 76 | } 77 | 78 | if (parser.current_index < parser.arguments.size()) { 79 | throw structopt::exception("Error: unrecognized argument '" + parser.arguments[parser.current_index] + "'", parser.visitor); 80 | } 81 | 82 | return argument_struct; 83 | } 84 | 85 | template T parse(int argc, char *argv[]) { 86 | std::vector arguments; 87 | std::copy(argv, argv + argc, std::back_inserter(arguments)); 88 | return parse(arguments); 89 | } 90 | 91 | std::string help() const { 92 | std::stringstream os; 93 | visitor.print_help(os); 94 | return os.str(); 95 | } 96 | }; 97 | 98 | } // namespace structopt 99 | -------------------------------------------------------------------------------- /include/structopt/array_size.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | using std::array; 5 | 6 | namespace structopt { 7 | 8 | template struct array_size; 9 | template struct array_size> { 10 | static size_t const size = N; 11 | }; 12 | 13 | } // namespace structopt -------------------------------------------------------------------------------- /include/structopt/exception.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace structopt { 9 | 10 | class exception : public std::exception { 11 | std::string what_{""}; 12 | std::string help_{""}; 13 | details::visitor visitor_; 14 | 15 | public: 16 | exception(const std::string &what, const details::visitor &visitor) 17 | : what_(what), help_(""), visitor_(visitor) { 18 | std::stringstream os; 19 | visitor_.print_help(os); 20 | help_ = os.str(); 21 | } 22 | 23 | const char *what() const throw() { return what_.c_str(); } 24 | 25 | const char *help() const throw() { return help_.c_str(); } 26 | }; 27 | 28 | } // namespace structopt -------------------------------------------------------------------------------- /include/structopt/is_number.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | 5 | namespace structopt { 6 | 7 | namespace details { 8 | 9 | static inline bool is_binary_notation(std::string_view input) { 10 | return input.compare(0, 2, "0b") == 0 && input.size() > 2 && 11 | input.find_first_not_of("01", 2) == std::string_view::npos; 12 | } 13 | 14 | static inline bool is_hex_notation(std::string_view input) { 15 | return input.compare(0, 2, "0x") == 0 && input.size() > 2 && 16 | input.find_first_not_of("0123456789abcdefABCDEF", 2) == std::string_view::npos; 17 | } 18 | 19 | static inline bool is_octal_notation(std::string_view input) { 20 | return input.compare(0, 1, "0") == 0 && input.size() > 1 && 21 | input.find_first_not_of("01234567", 1) == std::string_view::npos; 22 | } 23 | 24 | static inline bool is_valid_number(std::string_view input) { 25 | if (is_binary_notation(input) || is_hex_notation(input) || is_octal_notation(input)) { 26 | return true; 27 | } 28 | 29 | if (input.empty()) { 30 | return false; 31 | } 32 | 33 | std::size_t i = 0, j = input.length() - 1; 34 | 35 | // Handling whitespaces 36 | while (i < input.length() && input[i] == ' ') 37 | i++; 38 | while (input[j] == ' ') 39 | j--; 40 | 41 | if (i > j) 42 | return false; 43 | 44 | // if string is of length 1 and the only 45 | // character is not a digit 46 | if (i == j && !(input[i] >= '0' && input[i] <= '9')) 47 | return false; 48 | 49 | // If the 1st char is not '+', '-', '.' or digit 50 | if (input[i] != '.' && input[i] != '+' && input[i] != '-' && 51 | !(input[i] >= '0' && input[i] <= '9')) 52 | return false; 53 | 54 | // To check if a '.' or 'e' is found in given 55 | // string. We use this flag to make sure that 56 | // either of them appear only once. 57 | bool dot_or_exp = false; 58 | 59 | for (; i <= j; i++) { 60 | // If any of the char does not belong to 61 | // {digit, +, -, ., e} 62 | if (input[i] != 'e' && input[i] != '.' && input[i] != '+' && input[i] != '-' && 63 | !(input[i] >= '0' && input[i] <= '9')) 64 | return false; 65 | 66 | if (input[i] == '.') { 67 | // checks if the char 'e' has already 68 | // occurred before '.' If yes, return false;. 69 | if (dot_or_exp == true) 70 | return false; 71 | 72 | // If '.' is the last character. 73 | if (i + 1 > input.length()) 74 | return false; 75 | 76 | // if '.' is not followed by a digit. 77 | if (!(input[i + 1] >= '0' && input[i + 1] <= '9')) 78 | return false; 79 | } 80 | 81 | else if (input[i] == 'e') { 82 | // set dot_or_exp = 1 when e is encountered. 83 | dot_or_exp = true; 84 | 85 | // if there is no digit before 'e'. 86 | if (!(input[i - 1] >= '0' && input[i - 1] <= '9')) 87 | return false; 88 | 89 | // If 'e' is the last Character 90 | if (i + 1 > input.length()) 91 | return false; 92 | 93 | // if e is not followed either by 94 | // '+', '-' or a digit 95 | if (input[i + 1] != '+' && input[i + 1] != '-' && 96 | (input[i + 1] >= '0' && input[i] <= '9')) 97 | return false; 98 | } 99 | } 100 | 101 | /* If the string skips all above cases, then 102 | it is numeric*/ 103 | return true; 104 | } 105 | 106 | } // namespace details 107 | 108 | } // namespace structopt 109 | -------------------------------------------------------------------------------- /include/structopt/is_specialization.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | namespace structopt { 5 | 6 | template class Ref> 7 | struct is_specialization : std::false_type {}; 8 | 9 | template