Initial commit
33 files changed +16301 −0
- + .clang-format
- + .gitignore
- + CMakeLists.txt
- + LICENSE
- + README.md
- + cmake/config-realmachine.h.cmake
- + cmake/realmachineConfig.cmake.in
- + main.cpp
- + src/realmachine/backend.cpp
- + src/realmachine/backend.h
- + src/realmachine/decompressors.cpp
- + src/realmachine/decompressors.h
- + src/realmachine/globals.h
- + src/realmachine/lib/cli11/CLI11.h
- + src/realmachine/lib/cli11/LICENSE
- + src/realmachine/lib/cli11/cli11_wrapper.cpp
- + src/realmachine/lib/cli11/cli11_wrapper.h
- + src/realmachine/lib/utils.cpp
- + src/realmachine/lib/utils.h
- + src/realmachine/machine.cpp
- + src/realmachine/machine.h
- + src/realmachine/models.cpp
- + src/realmachine/models.h
- + src/realmachine/module_cache.cpp
- + src/realmachine/module_cache.h
- + src/realmachine/template.cpp
- + src/realmachine/template.h
- + tests/CMakeLists.txt
- + tests/test
- + tests/test.cpp
- + tests/test.gz
- + tests/test.xz
- + tests/test.zst
| 1 | commit d8ecbb5048dbe290d7a5c4c9b6d07a70a8f92a04 |
| 2 | Author: Sander <sander@sanderf.nl> |
| 3 | Date: Fri Sep 4 17:28:29 2026 +0300 |
| 4 | |
| 5 | Initial commit |
| 6 | |
| 7 | diff --git a/.clang-format b/.clang-format |
| 8 | new file mode 100644 |
| 9 | index 0000000..a8d8af0 |
| 10 | --- /dev/null |
| 11 | +++ b/.clang-format |
| 12 | @@ -0,0 +1,55 @@ |
| 13 | +# Generated from CLion C/C++ Code Style settings |
| 14 | +--- |
| 15 | +Language: Cpp |
| 16 | +BasedOnStyle: LLVM |
| 17 | +AlignConsecutiveAssignments: false |
| 18 | +AlignConsecutiveDeclarations: false |
| 19 | +AlignOperands: true |
| 20 | +AlignTrailingComments: false |
| 21 | +AlwaysBreakTemplateDeclarations: Yes |
| 22 | +BraceWrapping: |
| 23 | + AfterCaseLabel: false |
| 24 | + AfterClass: false |
| 25 | + AfterControlStatement: false |
| 26 | + AfterEnum: false |
| 27 | + AfterFunction: false |
| 28 | + AfterNamespace: false |
| 29 | + AfterStruct: false |
| 30 | + AfterUnion: false |
| 31 | + AfterExternBlock: false |
| 32 | + BeforeCatch: false |
| 33 | + BeforeElse: false |
| 34 | + BeforeLambdaBody: false |
| 35 | + BeforeWhile: false |
| 36 | + SplitEmptyFunction: true |
| 37 | + SplitEmptyRecord: true |
| 38 | + SplitEmptyNamespace: true |
| 39 | +BreakBeforeBraces: Custom |
| 40 | +BreakConstructorInitializers: AfterColon |
| 41 | +BreakConstructorInitializersBeforeComma: false |
| 42 | +ColumnLimit: 120 |
| 43 | +ConstructorInitializerAllOnOneLineOrOnePerLine: false |
| 44 | +IncludeCategories: |
| 45 | + - Regex: '^<.*' |
| 46 | + Priority: 1 |
| 47 | + - Regex: '^".*' |
| 48 | + Priority: 2 |
| 49 | + - Regex: '.*' |
| 50 | + Priority: 3 |
| 51 | +IncludeIsMainRegex: '([-_](test|unittest))?$' |
| 52 | +IndentCaseLabels: true |
| 53 | +InsertNewlineAtEOF: true |
| 54 | +MacroBlockBegin: '' |
| 55 | +MacroBlockEnd: '' |
| 56 | +MaxEmptyLinesToKeep: 2 |
| 57 | +NamespaceIndentation: All |
| 58 | +SpaceAfterCStyleCast: true |
| 59 | +SpaceAfterTemplateKeyword: false |
| 60 | +SpaceBeforeRangeBasedForLoopColon: false |
| 61 | +SpaceInEmptyParentheses: false |
| 62 | +SpacesInAngles: false |
| 63 | +SpacesInConditionalStatement: false |
| 64 | +SpacesInCStyleCastParentheses: false |
| 65 | +SpacesInParentheses: false |
| 66 | +TabWidth: 2 |
| 67 | +... |
| 68 | diff --git a/.gitignore b/.gitignore |
| 69 | new file mode 100644 |
| 70 | index 0000000..5213fb2 |
| 71 | --- /dev/null |
| 72 | +++ b/.gitignore |
| 73 | @@ -0,0 +1,7 @@ |
| 74 | +.idea |
| 75 | +build |
| 76 | +cmake-build-debug |
| 77 | +cmake-build-release |
| 78 | +tests/a.img |
| 79 | +tests/b.img |
| 80 | +tests/test.img |
| 81 | \ No newline at end of file |
| 82 | diff --git a/CMakeLists.txt b/CMakeLists.txt |
| 83 | new file mode 100644 |
| 84 | index 0000000..b78e7c0 |
| 85 | --- /dev/null |
| 86 | +++ b/CMakeLists.txt |
| 87 | @@ -0,0 +1,161 @@ |
| 88 | +cmake_minimum_required(VERSION 3.26) |
| 89 | +project(realmachine VERSION 0.0.17 DESCRIPTION "Boot a transient virtual machine based on the host system") |
| 90 | + |
| 91 | +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") |
| 92 | + message(FATAL_ERROR "realmachine supports only Linux") |
| 93 | +endif() |
| 94 | + |
| 95 | +include(GNUInstallDirs) |
| 96 | + |
| 97 | +set(REALMACHINE_MAIN_PROJECT OFF) |
| 98 | +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) |
| 99 | + set(REALMACHINE_MAIN_PROJECT ON) |
| 100 | +endif() |
| 101 | + |
| 102 | +set(CMAKE_CXX_STANDARD 20) |
| 103 | +set(CMAKE_POSITION_INDEPENDENT_CODE ON) |
| 104 | + |
| 105 | +option(REALMACHINE_STATIC_LINK "Force -static on the realmachine executable" OFF) |
| 106 | +if(REALMACHINE_STATIC_LINK) |
| 107 | + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") |
| 108 | + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static") |
| 109 | + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static") |
| 110 | +endif() |
| 111 | + |
| 112 | +option(REALMACHINE_INSTALL "Enable generation of install targets" ${REALMACHINE_MAIN_PROJECT}) |
| 113 | +option(REALMACHINE_TESTS "Build tests" OFF) |
| 114 | + |
| 115 | +file(GLOB SOURCE_FILES CONFIGURE_DEPENDS |
| 116 | + "src/realmachine/*.h" |
| 117 | + "src/realmachine/*.cpp" |
| 118 | + "src/realmachine/lib/*.h" |
| 119 | + "src/realmachine/lib/*.cpp" |
| 120 | +) |
| 121 | + |
| 122 | +find_program(GIT_EXECUTABLE git) |
| 123 | +set(PROJECT_GIT_COMMIT "unknown") |
| 124 | +if(GIT_EXECUTABLE) |
| 125 | + execute_process( |
| 126 | + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD |
| 127 | + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} |
| 128 | + OUTPUT_VARIABLE GIT_HEAD |
| 129 | + OUTPUT_STRIP_TRAILING_WHITESPACE |
| 130 | + ERROR_QUIET |
| 131 | + ) |
| 132 | + if(GIT_HEAD) |
| 133 | + set(PROJECT_GIT_COMMIT "${GIT_HEAD}") |
| 134 | + endif() |
| 135 | +endif() |
| 136 | + |
| 137 | +configure_file( |
| 138 | + cmake/config-realmachine.h.cmake |
| 139 | + ${CMAKE_BINARY_DIR}/src/realmachine/config-realmachine.h |
| 140 | +) |
| 141 | + |
| 142 | +set(ZLIB_USE_STATIC_LIBS ON) |
| 143 | +find_package(ZLIB REQUIRED) |
| 144 | +find_package(PkgConfig REQUIRED) |
| 145 | +find_package(shellescape REQUIRED) |
| 146 | +find_package(cpiocpplame REQUIRED) |
| 147 | +find_package(flogger REQUIRED) |
| 148 | + |
| 149 | +set(ENV{PKG_CONFIG_ALL_STATIC} "1") |
| 150 | +pkg_check_modules(ZSTD REQUIRED libzstd) |
| 151 | +pkg_check_modules(LZMA REQUIRED liblzma) |
| 152 | + |
| 153 | +add_library(realmachine_lib STATIC ${SOURCE_FILES}) |
| 154 | + |
| 155 | +target_include_directories(realmachine_lib |
| 156 | + PUBLIC |
| 157 | + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src> |
| 158 | + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> |
| 159 | + ${ZSTD_INCLUDE_DIRS} |
| 160 | + ${LZMA_INCLUDE_DIRS} |
| 161 | +) |
| 162 | + |
| 163 | +target_link_libraries(realmachine_lib PUBLIC |
| 164 | + ZLIB::ZLIB |
| 165 | + ${ZSTD_LIBRARIES} |
| 166 | + ${LZMA_LIBRARIES} |
| 167 | + shellescape::shellescape |
| 168 | + cpiocpplame::cpiocpplame |
| 169 | + flogger::flogger |
| 170 | +) |
| 171 | + |
| 172 | +set_target_properties(realmachine_lib PROPERTIES |
| 173 | + OUTPUT_NAME "realmachine" |
| 174 | + VERSION ${PROJECT_VERSION} |
| 175 | + SOVERSION ${PROJECT_VERSION_MAJOR} |
| 176 | +) |
| 177 | + |
| 178 | +add_executable(realmachine main.cpp) |
| 179 | +target_sources(realmachine PRIVATE src/realmachine/lib/cli11/cli11_wrapper.cpp) |
| 180 | +target_include_directories(realmachine PRIVATE src/realmachine/lib/cli11) |
| 181 | + |
| 182 | +target_include_directories(realmachine PRIVATE src) |
| 183 | +target_link_libraries(realmachine PRIVATE realmachine_lib) |
| 184 | + |
| 185 | +find_program(STRIP_TOOL strip REQUIRED) |
| 186 | +if(CMAKE_BUILD_TYPE STREQUAL "Release") |
| 187 | + add_custom_command(TARGET realmachine POST_BUILD |
| 188 | + COMMAND ${STRIP_TOOL} --strip-unneeded $<TARGET_FILE:realmachine> |
| 189 | + VERBATIM |
| 190 | + ) |
| 191 | +endif() |
| 192 | + |
| 193 | +if(REALMACHINE_INSTALL) |
| 194 | + install(FILES ${CMAKE_BINARY_DIR}/src/realmachine/config-realmachine.h |
| 195 | + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/ |
| 196 | + ) |
| 197 | + |
| 198 | + file(GLOB realmachine_headers CONFIGURE_DEPENDS |
| 199 | + src/realmachine/*.h |
| 200 | + src/realmachine/lib/*.h |
| 201 | + ) |
| 202 | + |
| 203 | + install(FILES ${realmachine_headers} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/realmachine) |
| 204 | + |
| 205 | + install(TARGETS realmachine_lib realmachine |
| 206 | + EXPORT realmachineTargets |
| 207 | + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} |
| 208 | + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} |
| 209 | + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} |
| 210 | + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} |
| 211 | + ) |
| 212 | + |
| 213 | + install(EXPORT realmachineTargets |
| 214 | + FILE realmachineTargets.cmake |
| 215 | + NAMESPACE realmachine:: |
| 216 | + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/realmachine |
| 217 | + ) |
| 218 | + |
| 219 | + include(CMakePackageConfigHelpers) |
| 220 | + |
| 221 | + configure_package_config_file( |
| 222 | + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/realmachineConfig.cmake.in |
| 223 | + ${CMAKE_CURRENT_BINARY_DIR}/realmachineConfig.cmake |
| 224 | + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/realmachine |
| 225 | + ) |
| 226 | + |
| 227 | + write_basic_package_version_file( |
| 228 | + ${CMAKE_CURRENT_BINARY_DIR}/realmachineConfigVersion.cmake |
| 229 | + VERSION ${PROJECT_VERSION} |
| 230 | + COMPATIBILITY SameMajorVersion |
| 231 | + ) |
| 232 | + |
| 233 | + install(FILES |
| 234 | + ${CMAKE_CURRENT_BINARY_DIR}/realmachineConfig.cmake |
| 235 | + ${CMAKE_CURRENT_BINARY_DIR}/realmachineConfigVersion.cmake |
| 236 | + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/realmachine |
| 237 | + ) |
| 238 | + |
| 239 | + if(${CMAKE_BUILD_TYPE} STREQUAL Debug) |
| 240 | + target_compile_definitions(realmachine_lib PRIVATE DEBUG=1) |
| 241 | + target_compile_definitions(realmachine PRIVATE DEBUG=1) |
| 242 | + endif() |
| 243 | +endif() |
| 244 | + |
| 245 | +if(BUILD_TESTS) |
| 246 | + enable_testing() |
| 247 | + add_subdirectory(tests) |
| 248 | +endif() |
| 249 | diff --git a/LICENSE b/LICENSE |
| 250 | new file mode 100644 |
| 251 | index 0000000..c98b370 |
| 252 | --- /dev/null |
| 253 | +++ b/LICENSE |
| 254 | @@ -0,0 +1,201 @@ |
| 255 | + Apache License |
| 256 | + Version 2.0, January 2004 |
| 257 | + http://www.apache.org/licenses/ |
| 258 | + |
| 259 | + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
| 260 | + |
| 261 | + 1. Definitions. |
| 262 | + |
| 263 | + "License" shall mean the terms and conditions for use, reproduction, |
| 264 | + and distribution as defined by Sections 1 through 9 of this document. |
| 265 | + |
| 266 | + "Licensor" shall mean the copyright owner or entity authorized by |
| 267 | + the copyright owner that is granting the License. |
| 268 | + |
| 269 | + "Legal Entity" shall mean the union of the acting entity and all |
| 270 | + other entities that control, are controlled by, or are under common |
| 271 | + control with that entity. For the purposes of this definition, |
| 272 | + "control" means (i) the power, direct or indirect, to cause the |
| 273 | + direction or management of such entity, whether by contract or |
| 274 | + otherwise, or (ii) ownership of fifty percent (50%) or more of the |
| 275 | + outstanding shares, or (iii) beneficial ownership of such entity. |
| 276 | + |
| 277 | + "You" (or "Your") shall mean an individual or Legal Entity |
| 278 | + exercising permissions granted by this License. |
| 279 | + |
| 280 | + "Source" form shall mean the preferred form for making modifications, |
| 281 | + including but not limited to software source code, documentation |
| 282 | + source, and configuration files. |
| 283 | + |
| 284 | + "Object" form shall mean any form resulting from mechanical |
| 285 | + transformation or translation of a Source form, including but |
| 286 | + not limited to compiled object code, generated documentation, |
| 287 | + and conversions to other media types. |
| 288 | + |
| 289 | + "Work" shall mean the work of authorship, whether in Source or |
| 290 | + Object form, made available under the License, as indicated by a |
| 291 | + copyright notice that is included in or attached to the work |
| 292 | + (an example is provided in the Appendix below). |
| 293 | + |
| 294 | + "Derivative Works" shall mean any work, whether in Source or Object |
| 295 | + form, that is based on (or derived from) the Work and for which the |
| 296 | + editorial revisions, annotations, elaborations, or other modifications |
| 297 | + represent, as a whole, an original work of authorship. For the purposes |
| 298 | + of this License, Derivative Works shall not include works that remain |
| 299 | + separable from, or merely link (or bind by name) to the interfaces of, |
| 300 | + the Work and Derivative Works thereof. |
| 301 | + |
| 302 | + "Contribution" shall mean any work of authorship, including |
| 303 | + the original version of the Work and any modifications or additions |
| 304 | + to that Work or Derivative Works thereof, that is intentionally |
| 305 | + submitted to Licensor for inclusion in the Work by the copyright owner |
| 306 | + or by an individual or Legal Entity authorized to submit on behalf of |
| 307 | + the copyright owner. For the purposes of this definition, "submitted" |
| 308 | + means any form of electronic, verbal, or written communication sent |
| 309 | + to the Licensor or its representatives, including but not limited to |
| 310 | + communication on electronic mailing lists, source code control systems, |
| 311 | + and issue tracking systems that are managed by, or on behalf of, the |
| 312 | + Licensor for the purpose of discussing and improving the Work, but |
| 313 | + excluding communication that is conspicuously marked or otherwise |
| 314 | + designated in writing by the copyright owner as "Not a Contribution." |
| 315 | + |
| 316 | + "Contributor" shall mean Licensor and any individual or Legal Entity |
| 317 | + on behalf of whom a Contribution has been received by Licensor and |
| 318 | + subsequently incorporated within the Work. |
| 319 | + |
| 320 | + 2. Grant of Copyright License. Subject to the terms and conditions of |
| 321 | + this License, each Contributor hereby grants to You a perpetual, |
| 322 | + worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
| 323 | + copyright license to reproduce, prepare Derivative Works of, |
| 324 | + publicly display, publicly perform, sublicense, and distribute the |
| 325 | + Work and such Derivative Works in Source or Object form. |
| 326 | + |
| 327 | + 3. Grant of Patent License. Subject to the terms and conditions of |
| 328 | + this License, each Contributor hereby grants to You a perpetual, |
| 329 | + worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
| 330 | + (except as stated in this section) patent license to make, have made, |
| 331 | + use, offer to sell, sell, import, and otherwise transfer the Work, |
| 332 | + where such license applies only to those patent claims licensable |
| 333 | + by such Contributor that are necessarily infringed by their |
| 334 | + Contribution(s) alone or by combination of their Contribution(s) |
| 335 | + with the Work to which such Contribution(s) was submitted. If You |
| 336 | + institute patent litigation against any entity (including a |
| 337 | + cross-claim or counterclaim in a lawsuit) alleging that the Work |
| 338 | + or a Contribution incorporated within the Work constitutes direct |
| 339 | + or contributory patent infringement, then any patent licenses |
| 340 | + granted to You under this License for that Work shall terminate |
| 341 | + as of the date such litigation is filed. |
| 342 | + |
| 343 | + 4. Redistribution. You may reproduce and distribute copies of the |
| 344 | + Work or Derivative Works thereof in any medium, with or without |
| 345 | + modifications, and in Source or Object form, provided that You |
| 346 | + meet the following conditions: |
| 347 | + |
| 348 | + (a) You must give any other recipients of the Work or |
| 349 | + Derivative Works a copy of this License; and |
| 350 | + |
| 351 | + (b) You must cause any modified files to carry prominent notices |
| 352 | + stating that You changed the files; and |
| 353 | + |
| 354 | + (c) You must retain, in the Source form of any Derivative Works |
| 355 | + that You distribute, all copyright, patent, trademark, and |
| 356 | + attribution notices from the Source form of the Work, |
| 357 | + excluding those notices that do not pertain to any part of |
| 358 | + the Derivative Works; and |
| 359 | + |
| 360 | + (d) If the Work includes a "NOTICE" text file as part of its |
| 361 | + distribution, then any Derivative Works that You distribute must |
| 362 | + include a readable copy of the attribution notices contained |
| 363 | + within such NOTICE file, excluding those notices that do not |
| 364 | + pertain to any part of the Derivative Works, in at least one |
| 365 | + of the following places: within a NOTICE text file distributed |
| 366 | + as part of the Derivative Works; within the Source form or |
| 367 | + documentation, if provided along with the Derivative Works; or, |
| 368 | + within a display generated by the Derivative Works, if and |
| 369 | + wherever such third-party notices normally appear. The contents |
| 370 | + of the NOTICE file are for informational purposes only and |
| 371 | + do not modify the License. You may add Your own attribution |
| 372 | + notices within Derivative Works that You distribute, alongside |
| 373 | + or as an addendum to the NOTICE text from the Work, provided |
| 374 | + that such additional attribution notices cannot be construed |
| 375 | + as modifying the License. |
| 376 | + |
| 377 | + You may add Your own copyright statement to Your modifications and |
| 378 | + may provide additional or different license terms and conditions |
| 379 | + for use, reproduction, or distribution of Your modifications, or |
| 380 | + for any such Derivative Works as a whole, provided Your use, |
| 381 | + reproduction, and distribution of the Work otherwise complies with |
| 382 | + the conditions stated in this License. |
| 383 | + |
| 384 | + 5. Submission of Contributions. Unless You explicitly state otherwise, |
| 385 | + any Contribution intentionally submitted for inclusion in the Work |
| 386 | + by You to the Licensor shall be under the terms and conditions of |
| 387 | + this License, without any additional terms or conditions. |
| 388 | + Notwithstanding the above, nothing herein shall supersede or modify |
| 389 | + the terms of any separate license agreement you may have executed |
| 390 | + with Licensor regarding such Contributions. |
| 391 | + |
| 392 | + 6. Trademarks. This License does not grant permission to use the trade |
| 393 | + names, trademarks, service marks, or product names of the Licensor, |
| 394 | + except as required for reasonable and customary use in describing the |
| 395 | + origin of the Work and reproducing the content of the NOTICE file. |
| 396 | + |
| 397 | + 7. Disclaimer of Warranty. Unless required by applicable law or |
| 398 | + agreed to in writing, Licensor provides the Work (and each |
| 399 | + Contributor provides its Contributions) on an "AS IS" BASIS, |
| 400 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
| 401 | + implied, including, without limitation, any warranties or conditions |
| 402 | + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
| 403 | + PARTICULAR PURPOSE. You are solely responsible for determining the |
| 404 | + appropriateness of using or redistributing the Work and assume any |
| 405 | + risks associated with Your exercise of permissions under this License. |
| 406 | + |
| 407 | + 8. Limitation of Liability. In no event and under no legal theory, |
| 408 | + whether in tort (including negligence), contract, or otherwise, |
| 409 | + unless required by applicable law (such as deliberate and grossly |
| 410 | + negligent acts) or agreed to in writing, shall any Contributor be |
| 411 | + liable to You for damages, including any direct, indirect, special, |
| 412 | + incidental, or consequential damages of any character arising as a |
| 413 | + result of this License or out of the use or inability to use the |
| 414 | + Work (including but not limited to damages for loss of goodwill, |
| 415 | + work stoppage, computer failure or malfunction, or any and all |
| 416 | + other commercial damages or losses), even if such Contributor |
| 417 | + has been advised of the possibility of such damages. |
| 418 | + |
| 419 | + 9. Accepting Warranty or Additional Liability. While redistributing |
| 420 | + the Work or Derivative Works thereof, You may choose to offer, |
| 421 | + and charge a fee for, acceptance of support, warranty, indemnity, |
| 422 | + or other liability obligations and/or rights consistent with this |
| 423 | + License. However, in accepting such obligations, You may act only |
| 424 | + on Your own behalf and on Your sole responsibility, not on behalf |
| 425 | + of any other Contributor, and only if You agree to indemnify, |
| 426 | + defend, and hold each Contributor harmless for any liability |
| 427 | + incurred by, or claims asserted against, such Contributor by reason |
| 428 | + of your accepting any such warranty or additional liability. |
| 429 | + |
| 430 | + END OF TERMS AND CONDITIONS |
| 431 | + |
| 432 | + APPENDIX: How to apply the Apache License to your work. |
| 433 | + |
| 434 | + To apply the Apache License to your work, attach the following |
| 435 | + boilerplate notice, with the fields enclosed by brackets "{}" |
| 436 | + replaced with your own identifying information. (Don't include |
| 437 | + the brackets!) The text should be enclosed in the appropriate |
| 438 | + comment syntax for the file format. We also recommend that a |
| 439 | + file or class name and description of purpose be included on the |
| 440 | + same "printed page" as the copyright notice for easier |
| 441 | + identification within third-party archives. |
| 442 | + |
| 443 | + Copyright 2017-2025 Collabora Ltd. |
| 444 | + |
| 445 | + Licensed under the Apache License, Version 2.0 (the "License"); |
| 446 | + you may not use this file except in compliance with the License. |
| 447 | + You may obtain a copy of the License at |
| 448 | + |
| 449 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 450 | + |
| 451 | + Unless required by applicable law or agreed to in writing, software |
| 452 | + distributed under the License is distributed on an "AS IS" BASIS, |
| 453 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 454 | + See the License for the specific language governing permissions and |
| 455 | + limitations under the License. |
| 456 | diff --git a/README.md b/README.md |
| 457 | new file mode 100644 |
| 458 | index 0000000..582d5e6 |
| 459 | --- /dev/null |
| 460 | +++ b/README.md |
| 461 | @@ -0,0 +1,116 @@ |
| 462 | +# librealmachine - fake a machine |
| 463 | + |
| 464 | +Boot a transient virtual machine based on the host system. |
| 465 | + |
| 466 | +C++ port of the Go project [fakemachine](https://github.com/go-debos/fakemachine/). |
| 467 | + |
| 468 | +Made for [ferdiOS/build-tools](https://codeberg.org/ferdios/build-tools). |
| 469 | + |
| 470 | +### Features |
| 471 | + |
| 472 | +- Mount host volumes into the VM |
| 473 | +- Create and expose disk images |
| 474 | +- Configurable memory, CPU count, and sector size |
| 475 | +- Scratch storage (tmpfs or disk-backed) |
| 476 | +- Environment variable propagation with overrides |
| 477 | +- Command execution inside VM |
| 478 | +- Built-in integration test suite |
| 479 | +- KVM based |
| 480 | + |
| 481 | +## Installation |
| 482 | + |
| 483 | +```bash |
| 484 | +cmake -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/ . |
| 485 | +sudo make -Cbuild -j4 install |
| 486 | +``` |
| 487 | + |
| 488 | +## Dependencies |
| 489 | + |
| 490 | +ferdiOS libraries: |
| 491 | + |
| 492 | +- https://codeberg.org/ferdios/libcpiocpplame |
| 493 | +- https://codeberg.org/ferdios/libflogger |
| 494 | +- https://codeberg.org/ferdios/libshellescape |
| 495 | + |
| 496 | +generic libraries: |
| 497 | + |
| 498 | +`sudo apt install -y pkg-config zlib1g-dev libzstd-dev liblzma-dev libcli11-dev` |
| 499 | + |
| 500 | +### Dependencies: runtime |
| 501 | + |
| 502 | +`sudo apt install -y qemu-system-x86 busybox` |
| 503 | + |
| 504 | +## CMake Integration |
| 505 | + |
| 506 | +After install: |
| 507 | + |
| 508 | +```cmake |
| 509 | +find_package(realmachine REQUIRED) |
| 510 | +target_link_libraries(your_target PRIVATE realmachine::realmachine) |
| 511 | +``` |
| 512 | + |
| 513 | +## API usage |
| 514 | + |
| 515 | +See `tests/test.cpp` |
| 516 | + |
| 517 | +`fakemachine::Machine` |
| 518 | + |
| 519 | +Main methods: |
| 520 | + |
| 521 | +- `run(command)` |
| 522 | +- `run_in_machine_with_args(args)` |
| 523 | +- `create_image(path, size)` |
| 524 | +- `add_volume(path)` |
| 525 | +- `add_volume_at(host, guest)` |
| 526 | +- `set_memory(mb)` |
| 527 | +- `set_num_cpus(n)` |
| 528 | +- `set_scratch(size, path)` |
| 529 | +- `set_sector_size(size)` |
| 530 | +- `set_environ(env)` |
| 531 | + |
| 532 | +## CLI Usage |
| 533 | + |
| 534 | +Run a command inside a VM: |
| 535 | + |
| 536 | +``` |
| 537 | +realmachine echo hello |
| 538 | +``` |
| 539 | + |
| 540 | +Mount a directory: |
| 541 | + |
| 542 | +``` |
| 543 | +realmachine -v /host/data:/data ls /data |
| 544 | +``` |
| 545 | + |
| 546 | +Create and expose an image: |
| 547 | + |
| 548 | +``` |
| 549 | +realmachine -i disk.img:1G bash |
| 550 | +``` |
| 551 | + |
| 552 | +Increase memory and CPU count: |
| 553 | + |
| 554 | +``` |
| 555 | +realmachine -m 4096 -c 4 bash |
| 556 | +``` |
| 557 | + |
| 558 | +Enable verbose boot output: |
| 559 | + |
| 560 | +``` |
| 561 | +realmachine --show-boot bash |
| 562 | +``` |
| 563 | + |
| 564 | +### Unit tests |
| 565 | + |
| 566 | +```bash |
| 567 | +cmake -Bbuild -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=1 . |
| 568 | +sudo make -Cbuild -j4 install |
| 569 | + |
| 570 | +# and run it |
| 571 | +cd tests |
| 572 | +../build/tests/realmachine_tests |
| 573 | +``` |
| 574 | + |
| 575 | +## License |
| 576 | + |
| 577 | +Apache 2.0, same as original (fakemachine) project. |
| 578 | \ No newline at end of file |
| 579 | diff --git a/cmake/config-realmachine.h.cmake b/cmake/config-realmachine.h.cmake |
| 580 | new file mode 100644 |
| 581 | index 0000000..1e49b5a |
| 582 | --- /dev/null |
| 583 | +++ b/cmake/config-realmachine.h.cmake |
| 584 | @@ -0,0 +1,13 @@ |
| 585 | +#pragma once |
| 586 | +#include <string> |
| 587 | + |
| 588 | +namespace realmachine { |
| 589 | + |
| 590 | +inline constexpr std::string_view GENERATED_VERSION = "@CMAKE_PROJECT_VERSION@"; |
| 591 | +inline constexpr std::string_view GENERATED_GIT_HEAD = "@GIT_HEAD@"; |
| 592 | + |
| 593 | +inline std::string get_generated_version() { |
| 594 | + return std::string(GENERATED_VERSION) + " (" + std::string(GENERATED_GIT_HEAD) + ")"; |
| 595 | +} |
| 596 | + |
| 597 | +} |
| 598 | \ No newline at end of file |
| 599 | diff --git a/cmake/realmachineConfig.cmake.in b/cmake/realmachineConfig.cmake.in |
| 600 | new file mode 100644 |
| 601 | index 0000000..d80e8e4 |
| 602 | --- /dev/null |
| 603 | +++ b/cmake/realmachineConfig.cmake.in |
| 604 | @@ -0,0 +1,13 @@ |
| 605 | +@PACKAGE_INIT@ |
| 606 | + |
| 607 | +include(CMakeFindDependencyMacro) |
| 608 | +find_dependency(flogger) |
| 609 | +find_dependency(cpiocpplame) |
| 610 | +find_dependency(shellescape) |
| 611 | +find_dependency(ZLIB) |
| 612 | +find_dependency(PkgConfig) |
| 613 | +pkg_check_modules(ZSTD REQUIRED libzstd) |
| 614 | +pkg_check_modules(LZMA REQUIRED liblzma) |
| 615 | + |
| 616 | +include("${CMAKE_CURRENT_LIST_DIR}/realmachineTargets.cmake") |
| 617 | +check_required_components(realmachine) |
| 618 | \ No newline at end of file |
| 619 | diff --git a/main.cpp b/main.cpp |
| 620 | new file mode 100644 |
| 621 | index 0000000..994cb97 |
| 622 | --- /dev/null |
| 623 | +++ b/main.cpp |
| 624 | @@ -0,0 +1,249 @@ |
| 625 | +#include <iostream> |
| 626 | +#include <string> |
| 627 | +#include <vector> |
| 628 | +#include <unordered_map> |
| 629 | +#include <filesystem> |
| 630 | +#include <cstdlib> |
| 631 | +#include <algorithm> |
| 632 | + |
| 633 | +#include <shellescape/shellescape.h> |
| 634 | +#include <flogger/flogger.h> |
| 635 | + |
| 636 | +#include "src/realmachine/lib/cli11/cli11_wrapper.h" |
| 637 | +#include "src/realmachine/machine.h" |
| 638 | +#include "src/realmachine/backend.h" |
| 639 | +#include "src/realmachine/lib/utils.h" |
| 640 | + |
| 641 | +auto log_main = flogger::bind_log("realmachine/main"); |
| 642 | + |
| 643 | +using namespace std; |
| 644 | + |
| 645 | +string version; |
| 646 | + |
| 647 | +// Determine version string similar to Go implementation |
| 648 | +string get_determined_version(const string& v) { |
| 649 | + if (!v.empty()) return v; |
| 650 | + return "unknown"; |
| 651 | +} |
| 652 | + |
| 653 | +void warn_localhost(const string& variable, const string& value) { |
| 654 | + if (value.find("localhost") != string::npos || |
| 655 | + value.find("127.0.0.1") != string::npos || |
| 656 | + value.find("::1") != string::npos) { |
| 657 | + cerr << "WARNING: Environment variable " << variable << " contains a reference to\n" |
| 658 | + << "localhost. This may not work when running from realmachine.\n" |
| 659 | + << "Consider using an address that is valid on your network.\n"; |
| 660 | + } |
| 661 | +} |
| 662 | + |
| 663 | +struct options_t { |
| 664 | + string backend = "auto"; |
| 665 | + vector<string> volumes; |
| 666 | + vector<string> images; |
| 667 | + unordered_map<string,string> environ_vars; |
| 668 | + int memory = 0; |
| 669 | + int cpus = 0; |
| 670 | + int sector_size = 0; |
| 671 | + string scratch_size; |
| 672 | + bool show_boot = false; |
| 673 | + bool quiet = false; |
| 674 | + bool show_version = false; |
| 675 | +}; |
| 676 | + |
| 677 | +void setup_volumes(std::shared_ptr<realmachine::Machine>& m, const options_t& options) { |
| 678 | + printf_debug(log_main, "setup_volumes start count=%zu", options.volumes.size()); |
| 679 | + for (const auto& v : options.volumes) { |
| 680 | + printf_debug(log_main, "volume raw=%s", v.c_str()); |
| 681 | + size_t pos = v.find(':'); |
| 682 | + if (pos == string::npos) { |
| 683 | + printf_debug(log_main, "volume single mount %s", v.c_str()); |
| 684 | + m->add_volume(filesystem::path(v)); |
| 685 | + } else { |
| 686 | + string a = v.substr(0, pos); |
| 687 | + string b = v.substr(pos + 1); |
| 688 | + if (b.find(':') != string::npos) { |
| 689 | + printf_error(log_main, "Failed to parse volume: %s", v.c_str()); |
| 690 | + exit(1); |
| 691 | + } |
| 692 | + printf_debug(log_main, "volume mapping %s -> %s", a.c_str(), b.c_str()); |
| 693 | + m->add_volume_at(filesystem::path(a), filesystem::path(b)); |
| 694 | + } |
| 695 | + } |
| 696 | + printf_debug(log_main, "setup_volumes done"); |
| 697 | +} |
| 698 | + |
| 699 | +void setup_images(std::shared_ptr<realmachine::Machine>& m, const options_t& options) { |
| 700 | + printf_debug(log_main, "setup_images start count=%zu", options.images.size()); |
| 701 | + |
| 702 | + for (const auto& i : options.images) { |
| 703 | + printf_debug(log_main, "image raw=%s", i.c_str()); |
| 704 | + |
| 705 | + const size_t pos = i.find(':'); |
| 706 | + string label; |
| 707 | + |
| 708 | + try { |
| 709 | + if (pos == string::npos) { |
| 710 | + printf_debug(log_main, "image create default size path=%s", i.c_str()); |
| 711 | + label = m->create_image(filesystem::path(i), -1); |
| 712 | + } else { |
| 713 | + string name = i.substr(0, pos); |
| 714 | + string size_str = i.substr(pos + 1); |
| 715 | + |
| 716 | + printf_debug(log_main, "image create path=%s size_str=%s", name.c_str(), size_str.c_str()); |
| 717 | + |
| 718 | + int64_t size = utils_realmachine::parse_human_size(size_str); |
| 719 | + printf_debug(log_main, "parsed size=%ld", size); |
| 720 | + |
| 721 | + label = m->create_image(filesystem::path(name), size); |
| 722 | + } |
| 723 | + } catch (const exception& e) { |
| 724 | + printf_error(log_main, "Failed to create image: %s %s", i.c_str(), e.what()); |
| 725 | + exit(1); |
| 726 | + } |
| 727 | + |
| 728 | + if (!options.quiet) { |
| 729 | + string name = (pos == string::npos) ? i : i.substr(0, pos); |
| 730 | + printf_debug(log_main, "expose image name=%s label=%s", name.c_str(), label.c_str()); |
| 731 | + cout << "Exposing " << name << " as " << label << "\n"; |
| 732 | + } |
| 733 | + } |
| 734 | + |
| 735 | + printf_debug(log_main, "setup_images done"); |
| 736 | +} |
| 737 | + |
| 738 | +void setup_environ(std::shared_ptr<realmachine::Machine>& m, const options_t& options) { |
| 739 | + printf_debug(log_main, "setup_environ start"); |
| 740 | + |
| 741 | + unordered_map<string,string> env; |
| 742 | + |
| 743 | + vector<string> vars = { |
| 744 | + "http_proxy","https_proxy","ftp_proxy","rsync_proxy","all_proxy","no_proxy" |
| 745 | + }; |
| 746 | + |
| 747 | + for (const auto& e : vars) { |
| 748 | + string lower = e; |
| 749 | + string upper = e; |
| 750 | + transform(upper.begin(), upper.end(), upper.begin(), ::toupper); |
| 751 | + |
| 752 | + printf_debug(log_main, "env probe key=%s", e.c_str()); |
| 753 | + |
| 754 | + const char* lv = getenv(lower.c_str()); |
| 755 | + if (lv && string(lv).size()) { |
| 756 | + printf_debug(log_main, "env inherit lower=%s", lower.c_str()); |
| 757 | + env[lower] = lv; |
| 758 | + } |
| 759 | + |
| 760 | + const char* uv = getenv(upper.c_str()); |
| 761 | + if (uv && string(uv).size()) { |
| 762 | + printf_debug(log_main, "env inherit upper=%s", upper.c_str()); |
| 763 | + env[upper] = uv; |
| 764 | + } |
| 765 | + } |
| 766 | + |
| 767 | + printf_debug(log_main, "env overrides count=%zu", options.environ_vars.size()); |
| 768 | + |
| 769 | + for (const auto& [k,v] : options.environ_vars) { |
| 770 | + printf_debug(log_main, "env override key=%s empty=%d", k.c_str(), v.empty()); |
| 771 | + if (v.empty()) env.erase(k); |
| 772 | + else env[k] = v; |
| 773 | + } |
| 774 | + |
| 775 | + vector<string> env_strings; |
| 776 | + env_strings.reserve(env.size()); |
| 777 | + |
| 778 | + for (const auto& [k,v] : env) { |
| 779 | + printf_debug(log_main, "env final key=%s", k.c_str()); |
| 780 | + warn_localhost(k, v); |
| 781 | + env_strings.push_back(k + "=" + v); |
| 782 | + } |
| 783 | + |
| 784 | + printf_debug(log_main, "env set count=%zu", env_strings.size()); |
| 785 | + m->set_environ(env_strings); |
| 786 | + printf_debug(log_main, "setup_environ done"); |
| 787 | +} |
| 788 | + |
| 789 | +int main(const int argc, char** argv) { |
| 790 | + flogger::load_env(); |
| 791 | +#ifdef DEBUG |
| 792 | + flogger::enable_time_measurement(true); |
| 793 | + flogger::set_log_level(flogger::log_level::trace); |
| 794 | +#endif |
| 795 | + CLI::App app{"realmachine"}; |
| 796 | + |
| 797 | + options_t options; |
| 798 | + vector<string> backend_choices = {"auto", "kvm", "qemu"}; |
| 799 | + |
| 800 | + app.add_option("-b,--backend", options.backend)->check(CLI::IsMember(backend_choices)); |
| 801 | + app.add_option("-v,--volume", options.volumes); |
| 802 | + app.add_option("-i,--image", options.images); |
| 803 | + |
| 804 | + std::vector<std::string> env_raw; |
| 805 | + app.add_option("-e,--environ-var", env_raw)->expected(-1); |
| 806 | + |
| 807 | + CLI11_PARSE(app, argc, argv); |
| 808 | + |
| 809 | + for (auto& s : env_raw) { |
| 810 | + auto pos = s.find(':'); |
| 811 | + if (pos == std::string::npos) options.environ_vars[s] = ""; |
| 812 | + else options.environ_vars[s.substr(0,pos)] = s.substr(pos+1); |
| 813 | + } |
| 814 | + |
| 815 | + app.add_option("-m,--memory", options.memory); |
| 816 | + app.add_option("-c,--cpus", options.cpus); |
| 817 | + app.add_option("-S,--sectorsize", options.sector_size); |
| 818 | + app.add_option("-s,--scratchsize", options.scratch_size); |
| 819 | + |
| 820 | + app.add_flag("--show-boot", options.show_boot); |
| 821 | + app.add_flag("-q,--quiet", options.quiet); |
| 822 | + app.add_flag("--version", options.show_version); |
| 823 | + |
| 824 | + vector<string> args; |
| 825 | + app.add_option("command", args); |
| 826 | + |
| 827 | + CLI11_PARSE(app, argc, argv); |
| 828 | + |
| 829 | + if (options.show_version) { |
| 830 | + cout << "realmachine " << get_determined_version(version) << "\n"; |
| 831 | + return 0; |
| 832 | + } |
| 833 | + |
| 834 | + auto m = realmachine::Machine::new_machine_with_backend(options.backend); |
| 835 | + |
| 836 | + m->set_show_boot(options.show_boot); |
| 837 | + m->set_quiet(options.quiet); |
| 838 | + |
| 839 | + setup_volumes(m, options); |
| 840 | + setup_images(m, options); |
| 841 | + setup_environ(m, options); |
| 842 | + |
| 843 | + if (!options.scratch_size.empty()) { |
| 844 | + try { |
| 845 | + const int64_t size = utils_realmachine::parse_human_size(options.scratch_size); |
| 846 | + m->set_scratch(size, ""); |
| 847 | + } catch (const exception& e) { |
| 848 | + cerr << "realmachine: Couldn't parse scratch size: " << e.what() << "\n"; |
| 849 | + return 1; |
| 850 | + } |
| 851 | + } |
| 852 | + |
| 853 | + if (options.memory > 0) |
| 854 | + m->set_memory(options.memory); |
| 855 | + if (options.cpus > 0) |
| 856 | + m->set_num_cpus(options.cpus); |
| 857 | + if (options.sector_size > 0) |
| 858 | + m->set_sector_size(options.sector_size); |
| 859 | + |
| 860 | + string command = "/bin/bash"; |
| 861 | + if (!args.empty()) { |
| 862 | + command = shellescape::quote_command(args); |
| 863 | + } |
| 864 | + |
| 865 | + int ret = 0; |
| 866 | + try { |
| 867 | + ret = m->run(command); |
| 868 | + } catch (const exception& e) { |
| 869 | + cerr << "realmachine: " << e.what() << "\n"; |
| 870 | + } |
| 871 | + |
| 872 | + return ret; |
| 873 | +} |
| 874 | \ No newline at end of file |
| 875 | diff --git a/src/realmachine/backend.cpp b/src/realmachine/backend.cpp |
| 876 | new file mode 100644 |
| 877 | index 0000000..a45f988 |
| 878 | --- /dev/null |
| 879 | +++ b/src/realmachine/backend.cpp |
| 880 | @@ -0,0 +1,496 @@ |
| 881 | +#include <fcntl.h> |
| 882 | +#include <unistd.h> |
| 883 | +#include <cerrno> |
| 884 | +#include <cstring> |
| 885 | +#include <sstream> |
| 886 | +#include <fstream> |
| 887 | +#include <sys/utsname.h> |
| 888 | +#include <sys/wait.h> |
| 889 | + |
| 890 | +#include <flogger/flogger.h> |
| 891 | +#include <shellescape/shellescape.h> |
| 892 | + |
| 893 | +#include "backend.h" |
| 894 | + |
| 895 | +auto log_realmachine_backend = flogger::bind_log("realmachine/backend"); |
| 896 | + |
| 897 | +namespace realmachine { |
| 898 | + std::unique_ptr<Backend> new_qemu_backend(const std::shared_ptr<Machine>& m) { |
| 899 | + auto b = std::make_unique<QemuBackend>(m); |
| 900 | + printf_debug(log_realmachine_backend, "new_qemu_backend created"); |
| 901 | + return b; |
| 902 | + } |
| 903 | + |
| 904 | + std::unique_ptr<Backend> new_kvm_backend(const std::shared_ptr<Machine>& m) { |
| 905 | + auto b = std::make_unique<KvmBackend>(m); |
| 906 | + printf_debug(log_realmachine_backend, "new_kvm_backend created"); |
| 907 | + return b; |
| 908 | + } |
| 909 | + |
| 910 | + // List of backends in order of their priority in the "auto" algorithm |
| 911 | + std::vector<std::unique_ptr<Backend>> implemented_backends(const std::shared_ptr<Machine>& m) { |
| 912 | + std::vector<std::unique_ptr<Backend>> backends; |
| 913 | + |
| 914 | + backends.push_back(new_kvm_backend(m)); |
| 915 | + backends.push_back(new_qemu_backend(m)); |
| 916 | + return backends; |
| 917 | + } |
| 918 | + |
| 919 | + /* A list of backends which are implemented - sorted in order in which the |
| 920 | + * "auto" backend chooses them. |
| 921 | + */ |
| 922 | + std::vector<std::string> backend_names() { |
| 923 | + std::vector<std::string> names = {"auto"}; |
| 924 | + |
| 925 | + auto backends = implemented_backends(nullptr); |
| 926 | + for (const auto& backend : backends) { |
| 927 | + names.push_back(backend->name()); |
| 928 | + } |
| 929 | + |
| 930 | + return names; |
| 931 | + } |
| 932 | + |
| 933 | + /* The "auto" backend loops through each backend, starting with the lowest order. |
| 934 | + * The backend is created and checked if the creation was successful (i.e. it is |
| 935 | + * supported on this machine). If so, that backend is used for the realmachine. If |
| 936 | + * unsuccessful, the next backend is created until no more backends remain then |
| 937 | + * an error is thrown explaining why each backend was unsuccessful. |
| 938 | + */ |
| 939 | + std::unique_ptr<Backend> new_backend(const std::string& name, const std::shared_ptr<Machine>& m) { |
| 940 | + auto backends = implemented_backends(m); |
| 941 | + |
| 942 | + printf_debug(log_realmachine_backend, "new_backend enter name=%s", name.c_str()); |
| 943 | + |
| 944 | + std::unique_ptr<Backend> b = nullptr; |
| 945 | + |
| 946 | + if (name == "auto") { |
| 947 | + std::string combined_errors; |
| 948 | + |
| 949 | + printf_debug(log_realmachine_backend, "backend_auto start count=%zu", backends.size()); |
| 950 | + |
| 951 | + for (const auto& backend : backends) { |
| 952 | + std::string backend_name = backend->name(); |
| 953 | + |
| 954 | + printf_debug(log_realmachine_backend, "backend_auto candidate=%s", backend_name.c_str()); |
| 955 | + |
| 956 | + /* The user-mode-linux backend is flaky, don't allow users to auto-select it */ |
| 957 | + if (backend_name == "uml") { |
| 958 | + printf_debug(log_realmachine_backend, "backend_auto skip_uml"); |
| 959 | + continue; |
| 960 | + } |
| 961 | + |
| 962 | + auto candidate = new_backend(backend_name, m); |
| 963 | + if (!candidate) { |
| 964 | + if (!combined_errors.empty()) { |
| 965 | + combined_errors += ", "; |
| 966 | + } |
| 967 | + combined_errors += "failed backend: " + backend_name; |
| 968 | + printf_debug(log_realmachine_backend, "backend_auto failed backend=%s", backend_name.c_str()); |
| 969 | + continue; |
| 970 | + } |
| 971 | + |
| 972 | + printf_debug(log_realmachine_backend, "backend_auto selected=%s", backend_name.c_str()); |
| 973 | + return candidate; |
| 974 | + } |
| 975 | + |
| 976 | + printf_debug(log_realmachine_backend, "backend_auto exhausted backends errors=%s", combined_errors.c_str()); |
| 977 | + return nullptr; |
| 978 | + } |
| 979 | + |
| 980 | + for (auto& backend : backends) { |
| 981 | + if (backend->name() == name) { |
| 982 | + printf_debug(log_realmachine_backend, "backend_match name=%s", name.c_str()); |
| 983 | + b = std::move(backend); |
| 984 | + } |
| 985 | + } |
| 986 | + |
| 987 | + if (!b) { |
| 988 | + printf_debug(log_realmachine_backend, "backend_missing name=%s", name.c_str()); |
| 989 | + throw std::runtime_error(name + " backend does not exist"); |
| 990 | + } |
| 991 | + |
| 992 | + if (const auto supported = b->supported(); !supported) { |
| 993 | + printf_debug(log_realmachine_backend, "backend_unsupported name=%s", name.c_str()); |
| 994 | + throw std::runtime_error(name + " backend not supported"); |
| 995 | + } |
| 996 | + |
| 997 | + printf_debug(log_realmachine_backend, "backend_created name=%s", name.c_str()); |
| 998 | + return b; |
| 999 | + } |
| 1000 | + |
| 1001 | + QemuBackend::QemuBackend(const std::shared_ptr<Machine>& m) : m_machine(m) {} |
| 1002 | + |
| 1003 | + std::string QemuBackend::name() const { |
| 1004 | + return "qemu"; |
| 1005 | + } |
| 1006 | + |
| 1007 | + std::string QemuBackend::qemu_path() const { |
| 1008 | + return "/usr/bin/qemu-system-x86_64"; // @TODO: remove |
| 1009 | + const char* bin = nullptr; |
| 1010 | + |
| 1011 | + if (m_machine->arch == Arch::amd64) { |
| 1012 | + bin = qemu_machine_amd64.binary.c_str(); |
| 1013 | + } else if (m_machine->arch == Arch::arm64) { |
| 1014 | + bin = qemu_machine_arm64.binary.c_str(); |
| 1015 | + } else { |
| 1016 | + printf_debug(log_realmachine_backend, "qemu_path unsupported arch=%d", (int)m_machine->arch); |
| 1017 | + return ""; |
| 1018 | + } |
| 1019 | + |
| 1020 | + char* resolved = std::getenv("PATH"); |
| 1021 | + (void)resolved; |
| 1022 | + |
| 1023 | + printf_debug(log_realmachine_backend, "qemu_path bin=%s", bin); |
| 1024 | + |
| 1025 | + std::string cmd = std::string("which ") + bin; |
| 1026 | + FILE* pipe = popen(cmd.c_str(), "r"); |
| 1027 | + if (!pipe) { |
| 1028 | + printf_debug(log_realmachine_backend, "qemu_path popen_failed cmd=%s", cmd.c_str()); |
| 1029 | + return ""; |
| 1030 | + } |
| 1031 | + |
| 1032 | + char buffer[512]; |
| 1033 | + std::string result; |
| 1034 | + if (fgets(buffer, sizeof(buffer), pipe)) { |
| 1035 | + result = buffer; |
| 1036 | + } |
| 1037 | + |
| 1038 | + pclose(pipe); |
| 1039 | + |
| 1040 | + printf_debug(log_realmachine_backend, "qemu_path raw_result=%s", result.c_str()); |
| 1041 | + |
| 1042 | + if (result.empty()) { |
| 1043 | + printf_debug(log_realmachine_backend, "qemu_path empty_result"); |
| 1044 | + return ""; |
| 1045 | + } |
| 1046 | + |
| 1047 | + if (!result.empty() && result.back() == '\n') { |
| 1048 | + result.pop_back(); |
| 1049 | + } |
| 1050 | + |
| 1051 | + printf_debug(log_realmachine_backend, "qemu_path resolved=%s", result.c_str()); |
| 1052 | + |
| 1053 | + return result; |
| 1054 | + } |
| 1055 | + |
| 1056 | + bool QemuBackend::supported() const { |
| 1057 | + const auto path = qemu_path(); |
| 1058 | + return !path.empty(); |
| 1059 | + } |
| 1060 | + |
| 1061 | + std::string QemuBackend::kernel_release() const { |
| 1062 | + /* First try the kernel the current system is running, but if there are no |
| 1063 | + * modules for that try the latest from /lib/modules. The former works best |
| 1064 | + * for systems directly running realmachine, the latter makes sense in docker |
| 1065 | + * environments */ |
| 1066 | + struct utsname u {}; |
| 1067 | + if (uname(&u) != 0) { |
| 1068 | + return ""; |
| 1069 | + } |
| 1070 | + |
| 1071 | + std::string release(u.release); |
| 1072 | + |
| 1073 | + const std::filesystem::path moddir = "/lib/modules/" + release; |
| 1074 | + if (std::filesystem::exists(moddir)) |
| 1075 | + return release; |
| 1076 | + |
| 1077 | + const std::filesystem::path base = "/lib/modules"; |
| 1078 | + for (auto it = std::filesystem::directory_iterator(base); |
| 1079 | + it != std::filesystem::directory_iterator(); |
| 1080 | + ++it) { |
| 1081 | + |
| 1082 | + auto name = it->path().filename().string(); |
| 1083 | + if (!name.empty() && name[0] >= '0' && name[0] <= '9') { |
| 1084 | + return name; |
| 1085 | + } |
| 1086 | + } |
| 1087 | + |
| 1088 | + return ""; |
| 1089 | + } |
| 1090 | + |
| 1091 | + std::filesystem::path QemuBackend::kernel_path() const { |
| 1092 | + auto moddir = module_path(); |
| 1093 | + if (!moddir.empty()) { |
| 1094 | + std::filesystem::path p = moddir / "vmlinuz"; |
| 1095 | + if (std::filesystem::exists(p)) { |
| 1096 | + return p; |
| 1097 | + } |
| 1098 | + } |
| 1099 | + |
| 1100 | + const auto release = kernel_release(); |
| 1101 | + if (release.empty()) { |
| 1102 | + return {}; |
| 1103 | + } |
| 1104 | + |
| 1105 | + std::filesystem::path p = "/boot/vmlinuz-" + release; |
| 1106 | + if (!std::filesystem::exists(p)) { |
| 1107 | + return {}; |
| 1108 | + } |
| 1109 | + |
| 1110 | + return p; |
| 1111 | + } |
| 1112 | + |
| 1113 | + std::filesystem::path QemuBackend::module_path() const { |
| 1114 | + const auto release = kernel_release(); |
| 1115 | + if (release.empty()) { |
| 1116 | + return {}; |
| 1117 | + } |
| 1118 | + |
| 1119 | + std::filesystem::path base = "/lib/modules"; |
| 1120 | + if (merged_usr_system()) { |
| 1121 | + base = "/usr/lib/modules"; |
| 1122 | + } |
| 1123 | + |
| 1124 | + std::filesystem::path moddir = base / release; |
| 1125 | + if (!std::filesystem::exists(moddir)) { |
| 1126 | + return {}; |
| 1127 | + } |
| 1128 | + |
| 1129 | + return moddir; |
| 1130 | + } |
| 1131 | + |
| 1132 | + std::vector<std::string> QemuBackend::udev_rules() const { |
| 1133 | + std::vector<std::string> rules; |
| 1134 | + |
| 1135 | + for (size_t i = 0; i < m_machine->images.size(); i++) { |
| 1136 | + const char drive_letter = 'a' + static_cast<char>(i); |
| 1137 | + |
| 1138 | + rules.push_back( |
| 1139 | + "KERNEL==\"vd" + std::string(1, drive_letter) + |
| 1140 | + "\", SYMLINK+=\"disk/by-realmachine-label/" + |
| 1141 | + m_machine->images[i]->label + "\"" |
| 1142 | + ); |
| 1143 | + |
| 1144 | + rules.push_back( |
| 1145 | + "KERNEL==\"vd" + std::string(1, drive_letter) + |
| 1146 | + "[0-9]\", SYMLINK+=\"disk/by-realmachine-label/" + |
| 1147 | + m_machine->images[i]->label + "-part%n\"" |
| 1148 | + ); |
| 1149 | + } |
| 1150 | + |
| 1151 | + return rules; |
| 1152 | + } |
| 1153 | + |
| 1154 | + std::string QemuBackend::job_output_tty() const { |
| 1155 | + if (m_machine->show_boot) { |
| 1156 | + return "/dev/console"; |
| 1157 | + } |
| 1158 | + return "/dev/hvc0"; |
| 1159 | + } |
| 1160 | + |
| 1161 | + std::pair<std::string, std::vector<std::string>> |
| 1162 | + QemuBackend::mount_parameters(const std::shared_ptr<MountPoint>& mount) const { |
| 1163 | + return { |
| 1164 | + "9p", |
| 1165 | + {"trans=virtio", "version=9p2000.L", "cache=loose", "msize=262144"} |
| 1166 | + }; |
| 1167 | + } |
| 1168 | + |
| 1169 | + std::vector<std::string> QemuBackend::init_modules() const { |
| 1170 | + return {"virtio_pci", "virtio_console", "9pnet_virtio", "9p"}; |
| 1171 | + } |
| 1172 | + |
| 1173 | + std::vector<std::shared_ptr<MountPoint>> QemuBackend::init_static_volumes() const { |
| 1174 | + return {}; |
| 1175 | + } |
| 1176 | + |
| 1177 | + bool QemuBackend::start() { |
| 1178 | + return start_qemu(false); |
| 1179 | + } |
| 1180 | + |
| 1181 | + bool QemuBackend::start_qemu(const bool kvm) { |
| 1182 | + const auto m = m_machine; |
| 1183 | + |
| 1184 | + std::string kernel; |
| 1185 | + { |
| 1186 | + auto k = kernel_path(); |
| 1187 | + kernel = k.string(); |
| 1188 | + } |
| 1189 | + |
| 1190 | + std::vector<std::string> args; |
| 1191 | + |
| 1192 | + args.emplace_back("qemu-system"); |
| 1193 | + args.emplace_back("-smp"); |
| 1194 | + args.emplace_back(std::to_string(m->num_cpus)); |
| 1195 | + args.emplace_back("-m"); |
| 1196 | + args.emplace_back(std::to_string(m->memory)); |
| 1197 | + args.emplace_back("-kernel"); |
| 1198 | + args.emplace_back(kernel); |
| 1199 | + args.emplace_back("-initrd"); |
| 1200 | + args.emplace_back(m->initrd_path.string()); |
| 1201 | + args.emplace_back("-display"); |
| 1202 | + args.emplace_back("none"); |
| 1203 | + args.emplace_back("-nic"); |
| 1204 | + args.emplace_back("user,model=virtio-net-pci"); |
| 1205 | + args.emplace_back("-no-reboot"); |
| 1206 | + |
| 1207 | + printf_debug(log_realmachine_backend, "qemu_base cpus=%d mem=%d kernel=%s initrd=%s kvm=%d", |
| 1208 | + m->num_cpus, |
| 1209 | + m->memory, |
| 1210 | + kernel.c_str(), |
| 1211 | + m->initrd_path.string().c_str(), |
| 1212 | + kvm |
| 1213 | + ); |
| 1214 | + |
| 1215 | + if (kvm) { |
| 1216 | + args.emplace_back("-cpu"); |
| 1217 | + args.emplace_back("host"); |
| 1218 | + args.emplace_back("-enable-kvm"); |
| 1219 | + printf_debug(log_realmachine_backend, "kvm_enabled"); |
| 1220 | + } |
| 1221 | + |
| 1222 | + std::string console; |
| 1223 | + if (m->arch == Arch::amd64) { |
| 1224 | + console = "ttyS0"; |
| 1225 | + } else { |
| 1226 | + console = "ttyAMA0"; |
| 1227 | + } |
| 1228 | + |
| 1229 | + printf_debug(log_realmachine_backend, "console=%s arch=%d", console.c_str(), (int)m->arch); |
| 1230 | + |
| 1231 | + std::vector<std::string> kernelargs = { |
| 1232 | + "console=" + console, |
| 1233 | + "panic=-1", |
| 1234 | + "plymouth.enable=0", |
| 1235 | + "systemd.unit=realmachine.service" |
| 1236 | + }; |
| 1237 | + |
| 1238 | + if (m->show_boot) { |
| 1239 | + args.emplace_back("-chardev"); |
| 1240 | + args.emplace_back("stdio,id=for-ttyS0,signal=off"); |
| 1241 | + args.emplace_back("-serial"); |
| 1242 | + args.emplace_back("chardev:for-ttyS0"); |
| 1243 | + kernelargs.emplace_back("loglevel=7"); |
| 1244 | + printf_debug(log_realmachine_backend, "boot_console enabled"); |
| 1245 | + } else { |
| 1246 | + args.emplace_back("-device"); |
| 1247 | + args.emplace_back("virtio-serial"); |
| 1248 | + args.emplace_back("-chardev"); |
| 1249 | + args.emplace_back("null,id=for-ttyS0"); |
| 1250 | + args.emplace_back("-serial"); |
| 1251 | + args.emplace_back("chardev:for-ttyS0"); |
| 1252 | + args.emplace_back("-chardev"); |
| 1253 | + args.emplace_back("stdio,id=for-hvc0,signal=off"); |
| 1254 | + args.emplace_back("-device"); |
| 1255 | + args.emplace_back("virtconsole,chardev=for-hvc0"); |
| 1256 | + printf_debug(log_realmachine_backend, "boot_console disabled virtio_serial+hvc0"); |
| 1257 | + } |
| 1258 | + |
| 1259 | + for (const auto& mnt : m->mounts) { |
| 1260 | + args.emplace_back("-virtfs"); |
| 1261 | + args.emplace_back( |
| 1262 | + "local,mount_tag=" + mnt->label + |
| 1263 | + ",path=" + mnt->host_directory.string() + |
| 1264 | + ",security_model=none,multidevs=remap" |
| 1265 | + ); |
| 1266 | + printf_debug(log_realmachine_backend, "virtfs mount tag=%s path=%s", |
| 1267 | + mnt->label.c_str(), |
| 1268 | + mnt->host_directory.string().c_str() |
| 1269 | + ); |
| 1270 | + } |
| 1271 | + |
| 1272 | + for (size_t i = 0; i < m->images.size(); i++) { |
| 1273 | + args.emplace_back("-drive"); |
| 1274 | + args.emplace_back( |
| 1275 | + "file=" + m->images[i]->path.string() + |
| 1276 | + ",if=none,format=raw,cache=unsafe,id=drive-virtio-disk" + std::to_string(i) |
| 1277 | + ); |
| 1278 | + |
| 1279 | + args.emplace_back("-device"); |
| 1280 | + args.emplace_back( |
| 1281 | + "virtio-blk-pci,drive=drive-virtio-disk" + std::to_string(i) + |
| 1282 | + ",serial=" + m->images[i]->label + |
| 1283 | + ",logical_block_size=" + std::to_string(m->sector_size) + |
| 1284 | + ",physical_block_size=" + std::to_string(m->sector_size) |
| 1285 | + ); |
| 1286 | + |
| 1287 | + printf_debug(log_realmachine_backend, "drive index=%zu path=%s label=%s", |
| 1288 | + i, |
| 1289 | + m->images[i]->path.string().c_str(), |
| 1290 | + m->images[i]->label.c_str() |
| 1291 | + ); |
| 1292 | + } |
| 1293 | + |
| 1294 | + std::string append; |
| 1295 | + for (size_t i = 0; i < kernelargs.size(); i++) { |
| 1296 | + if (i) append += " "; |
| 1297 | + append += kernelargs[i]; |
| 1298 | + } |
| 1299 | + |
| 1300 | + args.emplace_back("-append"); |
| 1301 | + args.emplace_back(append); |
| 1302 | + |
| 1303 | + printf_debug(log_realmachine_backend, "kernel_append=%s", append.c_str()); |
| 1304 | + |
| 1305 | + const auto qemu_bin = qemu_path(); |
| 1306 | + printf_debug(log_realmachine_backend, "qemu_bin=%s args_count=%zu", qemu_bin.c_str(), args.size()); |
| 1307 | + |
| 1308 | + const pid_t pid = fork(); |
| 1309 | + if (pid == 0) { |
| 1310 | + std::vector<char*> exec_args; |
| 1311 | + for (auto& a : args) { |
| 1312 | + exec_args.emplace_back(const_cast<char*>(a.c_str())); |
| 1313 | + } |
| 1314 | + |
| 1315 | + exec_args.emplace_back(nullptr); |
| 1316 | + printf_debug(log_realmachine_backend, "execv qemu_bin=%s args_count=%zu", qemu_bin.c_str(), exec_args.size()); |
| 1317 | + |
| 1318 | + std::vector<std::string> args; |
| 1319 | + args.reserve(exec_args.size() - 1); |
| 1320 | + |
| 1321 | + for (size_t i = 0; i + 1 < exec_args.size(); i++) { |
| 1322 | + args.emplace_back(exec_args[i]); |
| 1323 | + } |
| 1324 | + |
| 1325 | + std::string cmd = shellescape::quote_command(args); |
| 1326 | + |
| 1327 | + printf_debug(log_realmachine_backend, "execv cmd=%s", cmd.c_str()); |
| 1328 | + |
| 1329 | + execv(qemu_bin.c_str(), exec_args.data()); |
| 1330 | + printf_error(log_realmachine_backend, "execv failed: %s", strerror(errno)); |
| 1331 | + _exit(1); |
| 1332 | + } |
| 1333 | + |
| 1334 | + printf_debug(log_realmachine_backend, "forked pid=%d", static_cast<int>(pid)); |
| 1335 | + |
| 1336 | + int status = 0; |
| 1337 | + waitpid(pid, &status, 0); |
| 1338 | + |
| 1339 | + printf_debug(log_realmachine_backend, "qemu_exit status=%d exited=%d code=%d", |
| 1340 | + status, |
| 1341 | + WIFEXITED(status), |
| 1342 | + WEXITSTATUS(status) |
| 1343 | + ); |
| 1344 | + |
| 1345 | + return WIFEXITED(status) && WEXITSTATUS(status) == 0; |
| 1346 | + } |
| 1347 | + |
| 1348 | + KvmBackend::KvmBackend(const std::shared_ptr<Machine>& m) : QemuBackend(m) {} |
| 1349 | + |
| 1350 | + std::string KvmBackend::name() const { |
| 1351 | + return "kvm"; |
| 1352 | + } |
| 1353 | + |
| 1354 | + bool KvmBackend::supported() const { |
| 1355 | + int fd = open("/dev/kvm", O_RDWR); |
| 1356 | + |
| 1357 | + if (fd < 0) { |
| 1358 | + if (errno == EACCES) { |
| 1359 | + printf_error(log_realmachine_backend, "no permission for /dev/kvm (missing kvm group)"); |
| 1360 | + printf_error(log_realmachine_backend, "possible fix (debian/ubuntu): sudo usermod -aG kvm $USER"); |
| 1361 | + } else { |
| 1362 | + printf_error(log_realmachine_backend, "failed to open /dev/kvm: %s", strerror(errno)); |
| 1363 | + } |
| 1364 | + return false; |
| 1365 | + } |
| 1366 | + |
| 1367 | + printf_debug(log_realmachine_backend, "opened /dev/kvm successfully"); |
| 1368 | + close(fd); |
| 1369 | + |
| 1370 | + return QemuBackend::supported(); |
| 1371 | + } |
| 1372 | + |
| 1373 | + bool KvmBackend::start() { |
| 1374 | + return start_qemu(true); |
| 1375 | + } |
| 1376 | +} |
| 1377 | \ No newline at end of file |
| 1378 | diff --git a/src/realmachine/backend.h b/src/realmachine/backend.h |
| 1379 | new file mode 100644 |
| 1380 | index 0000000..da07c6c |
| 1381 | --- /dev/null |
| 1382 | +++ b/src/realmachine/backend.h |
| 1383 | @@ -0,0 +1,111 @@ |
| 1384 | +#pragma once |
| 1385 | + |
| 1386 | +#include <string> |
| 1387 | +#include <vector> |
| 1388 | +#include <filesystem> |
| 1389 | +#include <memory> |
| 1390 | +#include <utility> |
| 1391 | + |
| 1392 | +#include "models.h" |
| 1393 | +#include "machine.h" |
| 1394 | + |
| 1395 | +namespace realmachine { |
| 1396 | + |
| 1397 | + class Backend { |
| 1398 | + public: |
| 1399 | + virtual ~Backend() = default; |
| 1400 | + |
| 1401 | + // The name of the backend |
| 1402 | + [[nodiscard]] virtual std::string name() const = 0; |
| 1403 | + |
| 1404 | + // Whether the backend is supported on this machine |
| 1405 | + [[nodiscard]] virtual bool supported() const = 0; |
| 1406 | + |
| 1407 | + // Get kernel release version |
| 1408 | + [[nodiscard]] virtual std::string kernel_release() const = 0; |
| 1409 | + |
| 1410 | + // The path to the kernel |
| 1411 | + [[nodiscard]] virtual std::filesystem::path kernel_path() const = 0; |
| 1412 | + |
| 1413 | + // The path to the modules |
| 1414 | + [[nodiscard]] virtual std::filesystem::path module_path() const = 0; |
| 1415 | + |
| 1416 | + // A list of udev rules |
| 1417 | + [[nodiscard]] virtual std::vector<std::string> udev_rules() const = 0; |
| 1418 | + |
| 1419 | + // The tty used for the job output |
| 1420 | + [[nodiscard]] virtual std::string job_output_tty() const = 0; |
| 1421 | + |
| 1422 | + // The parameters used to mount a specific volume into the machine |
| 1423 | + [[nodiscard]] virtual std::pair<std::string, std::vector<std::string>> |
| 1424 | + mount_parameters(const std::shared_ptr<MountPoint>& mount) const = 0; |
| 1425 | + |
| 1426 | + // A list of modules to be added to initrd and probed in the initscript |
| 1427 | + [[nodiscard]] virtual std::vector<std::string> init_modules() const = 0; |
| 1428 | + |
| 1429 | + // A list of additional volumes which should be mounted in the initscript |
| 1430 | + [[nodiscard]] virtual std::vector<std::shared_ptr<MountPoint>> init_static_volumes() const = 0; |
| 1431 | + |
| 1432 | + // Start an instance of the backend |
| 1433 | + virtual bool start() = 0; |
| 1434 | + }; |
| 1435 | + |
| 1436 | + // List of backends in order of their priority in the "auto" algorithm |
| 1437 | + std::vector<std::unique_ptr<Backend>> implemented_backends(std::shared_ptr<Machine>& m); |
| 1438 | + |
| 1439 | + // A list of backends which are implemented - sorted in order in which the |
| 1440 | + // "auto" backend chooses them. |
| 1441 | + std::vector<std::string> backend_names(); |
| 1442 | + |
| 1443 | + // The "auto" backend loops through each backend in priority order. |
| 1444 | + // Each backend is instantiated and checked for support on this machine. |
| 1445 | + // The first supported backend is selected. |
| 1446 | + std::unique_ptr<Backend> new_backend(const std::string& name, const std::shared_ptr<Machine>& m); |
| 1447 | + |
| 1448 | + class QemuBackend : public Backend { |
| 1449 | + public: |
| 1450 | + explicit QemuBackend(const std::shared_ptr<Machine>& m); |
| 1451 | + |
| 1452 | + [[nodiscard]] std::string name() const override; |
| 1453 | + |
| 1454 | + [[nodiscard]] bool supported() const override; |
| 1455 | + |
| 1456 | + [[nodiscard]] std::string qemu_path() const; |
| 1457 | + |
| 1458 | + [[nodiscard]] std::string kernel_release() const override; |
| 1459 | + |
| 1460 | + [[nodiscard]] std::filesystem::path kernel_path() const override; |
| 1461 | + |
| 1462 | + [[nodiscard]] std::filesystem::path module_path() const override; |
| 1463 | + |
| 1464 | + [[nodiscard]] std::vector<std::string> udev_rules() const override; |
| 1465 | + |
| 1466 | + [[nodiscard]] std::string job_output_tty() const override; |
| 1467 | + |
| 1468 | + [[nodiscard]] std::pair<std::string, std::vector<std::string>> |
| 1469 | + mount_parameters(const std::shared_ptr<MountPoint>& mount) const override; |
| 1470 | + |
| 1471 | + [[nodiscard]] std::vector<std::string> init_modules() const override; |
| 1472 | + |
| 1473 | + [[nodiscard]] std::vector<std::shared_ptr<MountPoint>> init_static_volumes() const override; |
| 1474 | + |
| 1475 | + bool start() override; |
| 1476 | + |
| 1477 | + bool start_qemu(bool kvm); |
| 1478 | + |
| 1479 | + private: |
| 1480 | + std::shared_ptr<Machine> m_machine; |
| 1481 | + }; |
| 1482 | + |
| 1483 | + class KvmBackend : public QemuBackend { |
| 1484 | + public: |
| 1485 | + explicit KvmBackend(const std::shared_ptr<Machine>& m); |
| 1486 | + |
| 1487 | + [[nodiscard]] std::string name() const override; |
| 1488 | + |
| 1489 | + [[nodiscard]] bool supported() const override; |
| 1490 | + |
| 1491 | + bool start() override; |
| 1492 | + }; |
| 1493 | + |
| 1494 | +} |
| 1495 | \ No newline at end of file |
| 1496 | diff --git a/src/realmachine/decompressors.cpp b/src/realmachine/decompressors.cpp |
| 1497 | new file mode 100644 |
| 1498 | index 0000000..4b8c1b2 |
| 1499 | --- /dev/null |
| 1500 | +++ b/src/realmachine/decompressors.cpp |
| 1501 | @@ -0,0 +1,135 @@ |
| 1502 | +#include <istream> |
| 1503 | +#include <ostream> |
| 1504 | +#include <vector> |
| 1505 | +#include <cstring> |
| 1506 | + |
| 1507 | +#include <zstd.h> |
| 1508 | +#include <lzma.h> |
| 1509 | +#include <zlib.h> |
| 1510 | + |
| 1511 | +#include "decompressors.h" |
| 1512 | + |
| 1513 | +namespace decompressors { |
| 1514 | + |
| 1515 | +void zstd_decompressor(std::ostream& dst, std::istream& src) { |
| 1516 | + ZSTD_DStream* strm = ZSTD_createDStream(); |
| 1517 | + if (!strm) { |
| 1518 | + throw std::runtime_error("zstd: Failed to create ZSTD_DStream"); |
| 1519 | + } |
| 1520 | + |
| 1521 | + ZSTD_initDStream(strm); |
| 1522 | + |
| 1523 | + std::vector<char> in(1 << 16); |
| 1524 | + std::vector<char> out(1 << 16); |
| 1525 | + |
| 1526 | + while (src.good()) { |
| 1527 | + src.read(in.data(), in.size()); |
| 1528 | + std::streamsize n = src.gcount(); |
| 1529 | + if (n <= 0) break; |
| 1530 | + |
| 1531 | + ZSTD_inBuffer input{in.data(), static_cast<size_t>(n), 0}; |
| 1532 | + |
| 1533 | + while (input.pos < input.size) { |
| 1534 | + ZSTD_outBuffer output{out.data(), out.size(), 0}; |
| 1535 | + |
| 1536 | + size_t ret = ZSTD_decompressStream(strm, &output, &input); |
| 1537 | + if (ZSTD_isError(ret)) { |
| 1538 | + ZSTD_freeDStream(strm); |
| 1539 | + throw std::runtime_error("zstd: ZSTD_isError"); |
| 1540 | + } |
| 1541 | + |
| 1542 | + dst.write(out.data(), output.pos); |
| 1543 | + } |
| 1544 | + } |
| 1545 | + |
| 1546 | + ZSTD_freeDStream(strm); |
| 1547 | +} |
| 1548 | + |
| 1549 | +void xz_decompressor(std::ostream& dst, std::istream& src) { |
| 1550 | + lzma_stream strm = LZMA_STREAM_INIT; |
| 1551 | + |
| 1552 | + if (lzma_stream_decoder(&strm, UINT64_MAX, 0) != LZMA_OK) { |
| 1553 | + throw std::runtime_error("xz: lzma_stream_decoder(&strm, UINT64_MAX, 0) != LZMA_OK"); |
| 1554 | + } |
| 1555 | + |
| 1556 | + std::vector<uint8_t> in_buf(1 << 16); |
| 1557 | + std::vector<uint8_t> out_buf(1 << 16); |
| 1558 | + |
| 1559 | + while (true) { |
| 1560 | + if (strm.avail_in == 0 && src.good()) { |
| 1561 | + src.read(reinterpret_cast<char*>(in_buf.data()), in_buf.size()); |
| 1562 | + strm.avail_in = static_cast<size_t>(src.gcount()); |
| 1563 | + strm.next_in = in_buf.data(); |
| 1564 | + } |
| 1565 | + |
| 1566 | + strm.avail_out = out_buf.size(); |
| 1567 | + strm.next_out = out_buf.data(); |
| 1568 | + |
| 1569 | + lzma_ret ret = lzma_code(&strm, LZMA_RUN); |
| 1570 | + |
| 1571 | + if (ret == LZMA_STREAM_END) { |
| 1572 | + dst.write(reinterpret_cast<char*>(out_buf.data()), out_buf.size() - strm.avail_out); |
| 1573 | + break; |
| 1574 | + } |
| 1575 | + |
| 1576 | + if (ret != LZMA_OK && ret != LZMA_BUF_ERROR) { |
| 1577 | + lzma_end(&strm); |
| 1578 | + throw std::runtime_error("xz: ret != LZMA_OK && ret != LZMA_BUF_ERROR"); |
| 1579 | + } |
| 1580 | + |
| 1581 | + dst.write(reinterpret_cast<char*>(out_buf.data()), out_buf.size() - strm.avail_out); |
| 1582 | + } |
| 1583 | + |
| 1584 | + lzma_end(&strm); |
| 1585 | +} |
| 1586 | + |
| 1587 | +void gzip_decompressor(std::ostream& dst, std::istream& src) { |
| 1588 | + z_stream zs{}; |
| 1589 | + if (inflateInit2(&zs, 16 + MAX_WBITS) != Z_OK) { |
| 1590 | + throw std::runtime_error("gzip: inflateInit2(&zs, 16 + MAX_WBITS) != Z_OK"); |
| 1591 | + } |
| 1592 | + |
| 1593 | + std::vector<unsigned char> in(1 << 16); |
| 1594 | + std::vector<unsigned char> out(1 << 16); |
| 1595 | + |
| 1596 | + while (true) { |
| 1597 | + if (zs.avail_in == 0 && src.good()) { |
| 1598 | + src.read(reinterpret_cast<char*>(in.data()), in.size()); |
| 1599 | + zs.avail_in = static_cast<uInt>(src.gcount()); |
| 1600 | + zs.next_in = in.data(); |
| 1601 | + } |
| 1602 | + |
| 1603 | + zs.avail_out = out.size(); |
| 1604 | + zs.next_out = out.data(); |
| 1605 | + |
| 1606 | + int ret = inflate(&zs, Z_NO_FLUSH); |
| 1607 | + |
| 1608 | + if (ret == Z_STREAM_END) { |
| 1609 | + dst.write(reinterpret_cast<char*>(out.data()), |
| 1610 | + out.size() - zs.avail_out); |
| 1611 | + break; |
| 1612 | + } |
| 1613 | + |
| 1614 | + if (ret != Z_OK) { |
| 1615 | + inflateEnd(&zs); |
| 1616 | + throw std::runtime_error("gzip: ret != Z_OK"); |
| 1617 | + } |
| 1618 | + |
| 1619 | + dst.write(reinterpret_cast<char*>(out.data()), out.size() - zs.avail_out); |
| 1620 | + } |
| 1621 | + |
| 1622 | + inflateEnd(&zs); |
| 1623 | +} |
| 1624 | + |
| 1625 | +void null_decompressor(std::ostream& dst, std::istream& src) { |
| 1626 | + std::vector<char> buf(1 << 16); |
| 1627 | + |
| 1628 | + while (src.good()) { |
| 1629 | + src.read(buf.data(), buf.size()); |
| 1630 | + std::streamsize n = src.gcount(); |
| 1631 | + if (n <= 0) break; |
| 1632 | + dst.write(buf.data(), n); |
| 1633 | + } |
| 1634 | +} |
| 1635 | + |
| 1636 | +} |
| 1637 | \ No newline at end of file |
| 1638 | diff --git a/src/realmachine/decompressors.h b/src/realmachine/decompressors.h |
| 1639 | new file mode 100644 |
| 1640 | index 0000000..7eecc09 |
| 1641 | --- /dev/null |
| 1642 | +++ b/src/realmachine/decompressors.h |
| 1643 | @@ -0,0 +1,11 @@ |
| 1644 | +#pragma once |
| 1645 | + |
| 1646 | +#include <iosfwd> |
| 1647 | +#include <system_error> |
| 1648 | + |
| 1649 | +namespace decompressors { |
| 1650 | + void zstd_decompressor(std::ostream& dst, std::istream& src); |
| 1651 | + void xz_decompressor(std::ostream& dst, std::istream& src); |
| 1652 | + void gzip_decompressor(std::ostream& dst, std::istream& src); |
| 1653 | + void null_decompressor(std::ostream& dst, std::istream& src); |
| 1654 | +} |
| 1655 | \ No newline at end of file |
| 1656 | diff --git a/src/realmachine/globals.h b/src/realmachine/globals.h |
| 1657 | new file mode 100644 |
| 1658 | index 0000000..662d9bf |
| 1659 | --- /dev/null |
| 1660 | +++ b/src/realmachine/globals.h |
| 1661 | @@ -0,0 +1,23 @@ |
| 1662 | +#pragma once |
| 1663 | + |
| 1664 | +#include <string> |
| 1665 | +#include <vector> |
| 1666 | +#include <optional> |
| 1667 | +#include <cstdint> |
| 1668 | +#include <variant> |
| 1669 | +#include <filesystem> |
| 1670 | + |
| 1671 | +namespace realmachine_g { |
| 1672 | + enum class log_level { |
| 1673 | + trace = 0, |
| 1674 | + debug, |
| 1675 | + info, |
| 1676 | + error |
| 1677 | + }; |
| 1678 | + |
| 1679 | + inline bool APP_NO_STATE = false; |
| 1680 | + inline bool APP_THROW_ON_BAD_MOUNTS = true; // only 'false' during unit-testing |
| 1681 | + inline bool APP_INIT = false; |
| 1682 | + inline std::string APP_ARCH = "x86_64"; |
| 1683 | + inline auto current_log_level = log_level::trace; |
| 1684 | +} |
| 1685 | \ No newline at end of file |
| 1686 | diff --git a/src/realmachine/lib/cli11/CLI11.h b/src/realmachine/lib/cli11/CLI11.h |
| 1687 | new file mode 100644 |
| 1688 | index 0000000..4524c54 |
| 1689 | --- /dev/null |
| 1690 | +++ b/src/realmachine/lib/cli11/CLI11.h |
| 1691 | @@ -0,0 +1,12216 @@ |
| 1692 | +// CLI11: Version 2.6.2 |
| 1693 | +// Originally designed by Henry Schreiner |
| 1694 | +// https://github.com/CLIUtils/CLI11 |
| 1695 | +// |
| 1696 | +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts |
| 1697 | +// from: v2.6.2 |
| 1698 | +// |
| 1699 | +// CLI11 2.6.2 Copyright (c) 2017-2026 University of Cincinnati, developed by Henry |
| 1700 | +// Schreiner under NSF AWARD 1414736. All rights reserved. |
| 1701 | +// |
| 1702 | +// Redistribution and use in source and binary forms of CLI11, with or without |
| 1703 | +// modification, are permitted provided that the following conditions are met: |
| 1704 | +// |
| 1705 | +// 1. Redistributions of source code must retain the above copyright notice, this |
| 1706 | +// list of conditions and the following disclaimer. |
| 1707 | +// 2. Redistributions in binary form must reproduce the above copyright notice, |
| 1708 | +// this list of conditions and the following disclaimer in the documentation |
| 1709 | +// and/or other materials provided with the distribution. |
| 1710 | +// 3. Neither the name of the copyright holder nor the names of its contributors |
| 1711 | +// may be used to endorse or promote products derived from this software without |
| 1712 | +// specific prior written permission. |
| 1713 | +// |
| 1714 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 1715 | +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 1716 | +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 1717 | +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR |
| 1718 | +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 1719 | +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 1720 | +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| 1721 | +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 1722 | +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 1723 | +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 1724 | + |
| 1725 | +#pragma once |
| 1726 | + |
| 1727 | +// Standard combined includes: |
| 1728 | +#include <algorithm> |
| 1729 | +#include <array> |
| 1730 | +#include <cctype> |
| 1731 | +#include <clocale> |
| 1732 | +#include <cmath> |
| 1733 | +#include <cstdint> |
| 1734 | +#include <cstdlib> |
| 1735 | +#include <cstring> |
| 1736 | +#include <cwchar> |
| 1737 | +#include <exception> |
| 1738 | +#include <fstream> |
| 1739 | +#include <functional> |
| 1740 | +#include <iomanip> |
| 1741 | +#include <iostream> |
| 1742 | +#include <iterator> |
| 1743 | +#include <limits> |
| 1744 | +#include <locale> |
| 1745 | +#include <map> |
| 1746 | +#include <memory> |
| 1747 | +#include <numeric> |
| 1748 | +#include <set> |
| 1749 | +#include <sstream> |
| 1750 | +#include <stdexcept> |
| 1751 | +#include <string> |
| 1752 | +#include <tuple> |
| 1753 | +#include <type_traits> |
| 1754 | +#include <utility> |
| 1755 | +#include <vector> |
| 1756 | + |
| 1757 | + |
| 1758 | +#define CLI11_VERSION_MAJOR 2 |
| 1759 | +#define CLI11_VERSION_MINOR 6 |
| 1760 | +#define CLI11_VERSION_PATCH 2 |
| 1761 | +#define CLI11_VERSION "2.6.2" |
| 1762 | + |
| 1763 | + |
| 1764 | + |
| 1765 | + |
| 1766 | +// The following version macro is very similar to the one in pybind11 |
| 1767 | +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) |
| 1768 | +#if __cplusplus >= 201402L |
| 1769 | +#define CLI11_CPP14 |
| 1770 | +#if __cplusplus >= 201703L |
| 1771 | +#define CLI11_CPP17 |
| 1772 | +#if __cplusplus > 201703L |
| 1773 | +#define CLI11_CPP20 |
| 1774 | +#if __cplusplus > 202002L |
| 1775 | +#define CLI11_CPP23 |
| 1776 | +#if __cplusplus > 202302L |
| 1777 | +#define CLI11_CPP26 |
| 1778 | +#endif |
| 1779 | +#endif |
| 1780 | +#endif |
| 1781 | +#endif |
| 1782 | +#endif |
| 1783 | +#elif defined(_MSC_VER) && __cplusplus == 199711L |
| 1784 | +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard was fully implemented) |
| 1785 | +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer |
| 1786 | +#if _MSVC_LANG >= 201402L |
| 1787 | +#define CLI11_CPP14 |
| 1788 | +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 |
| 1789 | +#define CLI11_CPP17 |
| 1790 | +#if _MSVC_LANG > 201703L && _MSC_VER >= 1910 |
| 1791 | +#define CLI11_CPP20 |
| 1792 | +#if _MSVC_LANG > 202002L && _MSC_VER >= 1922 |
| 1793 | +#define CLI11_CPP23 |
| 1794 | +#endif |
| 1795 | +#endif |
| 1796 | +#endif |
| 1797 | +#endif |
| 1798 | +#endif |
| 1799 | + |
| 1800 | +#if defined(CLI11_CPP14) |
| 1801 | +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] |
| 1802 | +#elif defined(_MSC_VER) |
| 1803 | +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) |
| 1804 | +#else |
| 1805 | +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) |
| 1806 | +#endif |
| 1807 | + |
| 1808 | +// GCC < 10 doesn't ignore this in unevaluated contexts |
| 1809 | +#if !defined(CLI11_CPP17) || \ |
| 1810 | + (defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 10 && __GNUC__ > 4) |
| 1811 | +#define CLI11_NODISCARD |
| 1812 | +#else |
| 1813 | +#define CLI11_NODISCARD [[nodiscard]] |
| 1814 | +#endif |
| 1815 | + |
| 1816 | +/** detection of rtti */ |
| 1817 | +#ifndef CLI11_USE_STATIC_RTTI |
| 1818 | +#if (defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) |
| 1819 | +#define CLI11_USE_STATIC_RTTI 1 |
| 1820 | +#elif defined(__cpp_rtti) |
| 1821 | +#if (defined(_CPPRTTI) && _CPPRTTI == 0) |
| 1822 | +#define CLI11_USE_STATIC_RTTI 1 |
| 1823 | +#else |
| 1824 | +#define CLI11_USE_STATIC_RTTI 0 |
| 1825 | +#endif |
| 1826 | +#elif (defined(__GCC_RTTI) && __GXX_RTTI) |
| 1827 | +#define CLI11_USE_STATIC_RTTI 0 |
| 1828 | +#else |
| 1829 | +#define CLI11_USE_STATIC_RTTI 1 |
| 1830 | +#endif |
| 1831 | +#endif |
| 1832 | + |
| 1833 | +/** <filesystem> availability */ |
| 1834 | +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM |
| 1835 | +#if __has_include(<filesystem>) |
| 1836 | +// Filesystem cannot be used if targeting macOS < 10.15 |
| 1837 | +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 |
| 1838 | +#define CLI11_HAS_FILESYSTEM 0 |
| 1839 | +#elif defined(__wasi__) |
| 1840 | +// As of wasi-sdk-14, filesystem is not implemented |
| 1841 | +#define CLI11_HAS_FILESYSTEM 0 |
| 1842 | +#else |
| 1843 | +#include <filesystem> |
| 1844 | +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 |
| 1845 | +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 |
| 1846 | +#define CLI11_HAS_FILESYSTEM 1 |
| 1847 | +#elif defined(__GLIBCXX__) |
| 1848 | +// if we are using gcc and Version <9 default to no filesystem |
| 1849 | +#define CLI11_HAS_FILESYSTEM 0 |
| 1850 | +#else |
| 1851 | +#define CLI11_HAS_FILESYSTEM 1 |
| 1852 | +#endif |
| 1853 | +#else |
| 1854 | +#define CLI11_HAS_FILESYSTEM 0 |
| 1855 | +#endif |
| 1856 | +#endif |
| 1857 | +#endif |
| 1858 | +#endif |
| 1859 | + |
| 1860 | +/** <codecvt> availability */ |
| 1861 | +#if !defined(CLI11_CPP26) && !defined(CLI11_HAS_CODECVT) |
| 1862 | +#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 |
| 1863 | +#define CLI11_HAS_CODECVT 0 |
| 1864 | +#else |
| 1865 | +#define CLI11_HAS_CODECVT 1 |
| 1866 | +#include <codecvt> |
| 1867 | +#endif |
| 1868 | +#else |
| 1869 | +#if defined(CLI11_HAS_CODECVT) |
| 1870 | +#if CLI11_HAS_CODECVT > 0 |
| 1871 | +#include <codecvt> |
| 1872 | +#endif |
| 1873 | +#else |
| 1874 | +#define CLI11_HAS_CODECVT 0 |
| 1875 | +#endif |
| 1876 | +#endif |
| 1877 | + |
| 1878 | +/** rtti enabled */ |
| 1879 | +#ifndef CLI11_HAS_RTTI |
| 1880 | +#if defined(__GXX_RTTI) && __GXX_RTTI == 1 |
| 1881 | +// gcc |
| 1882 | +#define CLI11_HAS_RTTI 1 |
| 1883 | +#elif defined(_CPPRTTI) && _CPPRTTI == 1 |
| 1884 | +// msvc |
| 1885 | +#define CLI11_HAS_RTTI 1 |
| 1886 | +#elif defined(__NO_RTTI__) && __NO_RTTI__ == 1 |
| 1887 | +// intel |
| 1888 | +#define CLI11_HAS_RTTI 0 |
| 1889 | +#elif defined(__has_feature) |
| 1890 | +// clang and other newer compilers |
| 1891 | +#if __has_feature(cxx_rtti) |
| 1892 | +#define CLI11_HAS_RTTI 1 |
| 1893 | +#else |
| 1894 | +#define CLI11_HAS_RTTI 0 |
| 1895 | +#endif |
| 1896 | +#elif defined(__RTTI) || defined(__INTEL_RTTI__) |
| 1897 | +// more intel and some other compilers |
| 1898 | +#define CLI11_HAS_RTTI 1 |
| 1899 | +#else |
| 1900 | +#define CLI11_HAS_RTTI 0 |
| 1901 | +#endif |
| 1902 | +#endif |
| 1903 | + |
| 1904 | +/** disable deprecations */ |
| 1905 | +#if defined(__GNUC__) // GCC or clang |
| 1906 | +#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") |
| 1907 | +#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") |
| 1908 | + |
| 1909 | +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") |
| 1910 | + |
| 1911 | +#elif defined(_MSC_VER) |
| 1912 | +#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push)) |
| 1913 | +#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop)) |
| 1914 | + |
| 1915 | +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) |
| 1916 | + |
| 1917 | +#else |
| 1918 | +#define CLI11_DIAGNOSTIC_PUSH |
| 1919 | +#define CLI11_DIAGNOSTIC_POP |
| 1920 | + |
| 1921 | +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED |
| 1922 | + |
| 1923 | +#endif |
| 1924 | + |
| 1925 | +/** Inline macro **/ |
| 1926 | +#ifdef CLI11_COMPILE |
| 1927 | +#define CLI11_INLINE |
| 1928 | +#else |
| 1929 | +#define CLI11_INLINE inline |
| 1930 | +#endif |
| 1931 | + |
| 1932 | +/** Module inline to support module operations**/ |
| 1933 | +#if defined CLI11_CPP17 |
| 1934 | +#define CLI11_MODULE_INLINE inline |
| 1935 | +#else |
| 1936 | +#define CLI11_MODULE_INLINE static |
| 1937 | +#endif |
| 1938 | + |
| 1939 | + |
| 1940 | + |
| 1941 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 |
| 1942 | +#include <filesystem> // NOLINT(build/include) |
| 1943 | +#else |
| 1944 | +#include <sys/stat.h> |
| 1945 | +#include <sys/types.h> |
| 1946 | +#endif |
| 1947 | + |
| 1948 | + |
| 1949 | + |
| 1950 | + |
| 1951 | +#ifdef CLI11_CPP17 |
| 1952 | +#include <string_view> |
| 1953 | +#endif // CLI11_CPP17 |
| 1954 | + |
| 1955 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 |
| 1956 | +#include <filesystem> |
| 1957 | +#include <string_view> // NOLINT(build/include) |
| 1958 | +#endif // CLI11_HAS_FILESYSTEM |
| 1959 | + |
| 1960 | + |
| 1961 | + |
| 1962 | +#if defined(_WIN32) |
| 1963 | +#if !(defined(_AMD64_) || defined(_X86_) || defined(_ARM_)) |
| 1964 | +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || \ |
| 1965 | + defined(_M_AMD64) |
| 1966 | +#define _AMD64_ |
| 1967 | +#elif defined(i386) || defined(__i386) || defined(__i386__) || defined(__i386__) || defined(_M_IX86) |
| 1968 | +#define _X86_ |
| 1969 | +#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARMT) |
| 1970 | +#define _ARM_ |
| 1971 | +#elif defined(__aarch64__) || defined(_M_ARM64) |
| 1972 | +#define _ARM64_ |
| 1973 | +#elif defined(_M_ARM64EC) |
| 1974 | +#define _ARM64EC_ |
| 1975 | +#endif |
| 1976 | +#endif |
| 1977 | + |
| 1978 | +// first |
| 1979 | +#ifndef NOMINMAX |
| 1980 | +// if NOMINMAX is already defined we don't want to mess with that either way |
| 1981 | +#define NOMINMAX |
| 1982 | +#include <windef.h> |
| 1983 | +#undef NOMINMAX |
| 1984 | +#else |
| 1985 | +#include <windef.h> |
| 1986 | +#endif |
| 1987 | + |
| 1988 | +// second |
| 1989 | +#include <winbase.h> |
| 1990 | +// third |
| 1991 | +#include <processthreadsapi.h> |
| 1992 | +#include <shellapi.h> |
| 1993 | +#endif |
| 1994 | + |
| 1995 | + |
| 1996 | +namespace CLI { |
| 1997 | + |
| 1998 | + |
| 1999 | +/// Convert a wide string to a narrow string. |
| 2000 | +CLI11_INLINE std::string narrow(const std::wstring &str); |
| 2001 | +CLI11_INLINE std::string narrow(const wchar_t *str); |
| 2002 | +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size); |
| 2003 | + |
| 2004 | +/// Convert a narrow string to a wide string. |
| 2005 | +CLI11_INLINE std::wstring widen(const std::string &str); |
| 2006 | +CLI11_INLINE std::wstring widen(const char *str); |
| 2007 | +CLI11_INLINE std::wstring widen(const char *str, std::size_t size); |
| 2008 | + |
| 2009 | +#ifdef CLI11_CPP17 |
| 2010 | +CLI11_INLINE std::string narrow(std::wstring_view str); |
| 2011 | +CLI11_INLINE std::wstring widen(std::string_view str); |
| 2012 | +#endif // CLI11_CPP17 |
| 2013 | + |
| 2014 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 |
| 2015 | +/// Convert a char-string to a native path correctly. |
| 2016 | +CLI11_INLINE std::filesystem::path to_path(std::string_view str); |
| 2017 | +#endif // CLI11_HAS_FILESYSTEM |
| 2018 | + |
| 2019 | + |
| 2020 | + |
| 2021 | + |
| 2022 | +namespace detail { |
| 2023 | + |
| 2024 | +#if !CLI11_HAS_CODECVT |
| 2025 | +/// Attempt to set one of the acceptable unicode locales for conversion |
| 2026 | +CLI11_INLINE void set_unicode_locale() { |
| 2027 | + static const std::array<const char *, 3> unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}}; |
| 2028 | + |
| 2029 | + for(const auto &locale_name : unicode_locales) { |
| 2030 | + if(std::setlocale(LC_ALL, locale_name) != nullptr) { |
| 2031 | + return; |
| 2032 | + } |
| 2033 | + } |
| 2034 | + throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8"); |
| 2035 | +} |
| 2036 | + |
| 2037 | +template <typename F> struct scope_guard_t { |
| 2038 | + F closure; |
| 2039 | + |
| 2040 | + explicit scope_guard_t(F closure_) : closure(closure_) {} |
| 2041 | + ~scope_guard_t() { closure(); } |
| 2042 | +}; |
| 2043 | + |
| 2044 | +template <typename F> CLI11_NODISCARD CLI11_INLINE scope_guard_t<F> scope_guard(F &&closure) { |
| 2045 | + return scope_guard_t<F>{std::forward<F>(closure)}; |
| 2046 | +} |
| 2047 | + |
| 2048 | +#endif // !CLI11_HAS_CODECVT |
| 2049 | + |
| 2050 | +CLI11_DIAGNOSTIC_PUSH |
| 2051 | +CLI11_DIAGNOSTIC_IGNORE_DEPRECATED |
| 2052 | + |
| 2053 | +CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) { |
| 2054 | +#if CLI11_HAS_CODECVT |
| 2055 | +#ifdef _WIN32 |
| 2056 | + return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(str, str + str_size); |
| 2057 | + |
| 2058 | +#else |
| 2059 | + return std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(str, str + str_size); |
| 2060 | + |
| 2061 | +#endif // _WIN32 |
| 2062 | +#else // CLI11_HAS_CODECVT |
| 2063 | + (void)str_size; |
| 2064 | + std::mbstate_t state = std::mbstate_t(); |
| 2065 | + const wchar_t *it = str; |
| 2066 | + |
| 2067 | + std::string old_locale = std::setlocale(LC_ALL, nullptr); |
| 2068 | + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); |
| 2069 | + set_unicode_locale(); |
| 2070 | + |
| 2071 | + std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state); |
| 2072 | + if(new_size == static_cast<std::size_t>(-1)) { |
| 2073 | + throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " + |
| 2074 | + std::to_string(it - str)); |
| 2075 | + } |
| 2076 | + std::string result(new_size, '\0'); |
| 2077 | + std::wcsrtombs(const_cast<char *>(result.data()), &str, new_size, &state); |
| 2078 | + |
| 2079 | + return result; |
| 2080 | + |
| 2081 | +#endif // CLI11_HAS_CODECVT |
| 2082 | +} |
| 2083 | + |
| 2084 | +CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) { |
| 2085 | +#if CLI11_HAS_CODECVT |
| 2086 | +#ifdef _WIN32 |
| 2087 | + return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(str, str + str_size); |
| 2088 | + |
| 2089 | +#else |
| 2090 | + return std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(str, str + str_size); |
| 2091 | + |
| 2092 | +#endif // _WIN32 |
| 2093 | +#else // CLI11_HAS_CODECVT |
| 2094 | + (void)str_size; |
| 2095 | + std::mbstate_t state = std::mbstate_t(); |
| 2096 | + const char *it = str; |
| 2097 | + |
| 2098 | + std::string old_locale = std::setlocale(LC_ALL, nullptr); |
| 2099 | + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); |
| 2100 | + set_unicode_locale(); |
| 2101 | + |
| 2102 | + std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state); |
| 2103 | + if(new_size == static_cast<std::size_t>(-1)) { |
| 2104 | + throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " + |
| 2105 | + std::to_string(it - str)); |
| 2106 | + } |
| 2107 | + std::wstring result(new_size, L'\0'); |
| 2108 | + std::mbsrtowcs(const_cast<wchar_t *>(result.data()), &str, new_size, &state); |
| 2109 | + |
| 2110 | + return result; |
| 2111 | + |
| 2112 | +#endif // CLI11_HAS_CODECVT |
| 2113 | +} |
| 2114 | + |
| 2115 | +CLI11_DIAGNOSTIC_POP |
| 2116 | + |
| 2117 | +} // namespace detail |
| 2118 | + |
| 2119 | +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); } |
| 2120 | +CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); } |
| 2121 | +// Flawfinder: ignore |
| 2122 | +CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); } |
| 2123 | + |
| 2124 | +CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); } |
| 2125 | +CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); } |
| 2126 | +// Flawfinder: ignore |
| 2127 | +CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); } |
| 2128 | + |
| 2129 | +#ifdef CLI11_CPP17 |
| 2130 | +CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); } |
| 2131 | +CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); } |
| 2132 | +#endif // CLI11_CPP17 |
| 2133 | + |
| 2134 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 |
| 2135 | +CLI11_INLINE std::filesystem::path to_path(std::string_view str) { |
| 2136 | + return std::filesystem::path{ |
| 2137 | +#ifdef _WIN32 |
| 2138 | + widen(str) |
| 2139 | +#else |
| 2140 | + str |
| 2141 | +#endif // _WIN32 |
| 2142 | + }; |
| 2143 | +} |
| 2144 | +#endif // CLI11_HAS_FILESYSTEM |
| 2145 | + |
| 2146 | + |
| 2147 | + |
| 2148 | + |
| 2149 | +namespace detail { |
| 2150 | +#ifdef _WIN32 |
| 2151 | +/// Decode and return UTF-8 argv from GetCommandLineW. |
| 2152 | +CLI11_INLINE std::vector<std::string> compute_win32_argv(); |
| 2153 | +#endif |
| 2154 | +} // namespace detail |
| 2155 | + |
| 2156 | + |
| 2157 | + |
| 2158 | +namespace detail { |
| 2159 | + |
| 2160 | +#ifdef _WIN32 |
| 2161 | +CLI11_INLINE std::vector<std::string> compute_win32_argv() { |
| 2162 | + std::vector<std::string> result; |
| 2163 | + int argc = 0; |
| 2164 | + |
| 2165 | + auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; |
| 2166 | + // NOLINTBEGIN(*-avoid-c-arrays) |
| 2167 | + auto wargv = std::unique_ptr<wchar_t *[], decltype(deleter)>(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); |
| 2168 | + // NOLINTEND(*-avoid-c-arrays) |
| 2169 | + |
| 2170 | + if(wargv == nullptr) { |
| 2171 | + throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); |
| 2172 | + } |
| 2173 | + |
| 2174 | + result.reserve(static_cast<size_t>(argc)); |
| 2175 | + for(size_t i = 0; i < static_cast<size_t>(argc); ++i) { |
| 2176 | + result.push_back(narrow(wargv[i])); |
| 2177 | + } |
| 2178 | + |
| 2179 | + return result; |
| 2180 | +} |
| 2181 | +#endif |
| 2182 | + |
| 2183 | +} // namespace detail |
| 2184 | + |
| 2185 | + |
| 2186 | + |
| 2187 | + |
| 2188 | +/// Include the items in this namespace to get free conversion of enums to/from streams. |
| 2189 | +/// (This is available inside CLI as well, so CLI11 will use this without a using statement). |
| 2190 | +namespace enums { |
| 2191 | + |
| 2192 | +/// output streaming for enumerations |
| 2193 | +template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type> |
| 2194 | +std::ostream &operator<<(std::ostream &in, const T &item) { |
| 2195 | + // make sure this is out of the detail namespace otherwise it won't be found when needed |
| 2196 | + // https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number |
| 2197 | + return in << +static_cast<typename std::underlying_type<T>::type>(item); |
| 2198 | +} |
| 2199 | + |
| 2200 | +} // namespace enums |
| 2201 | + |
| 2202 | +/// Export to CLI namespace |
| 2203 | +using enums::operator<<; |
| 2204 | + |
| 2205 | +namespace detail { |
| 2206 | +/// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not |
| 2207 | +/// produce overflow for some expected uses |
| 2208 | +CLI11_MODULE_INLINE constexpr int expected_max_vector_size{1 << 29}; |
| 2209 | +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c |
| 2210 | +/// Split a string by a delim |
| 2211 | +CLI11_INLINE std::vector<std::string> split(const std::string &s, char delim); |
| 2212 | + |
| 2213 | +/// Simple function to join a string |
| 2214 | +template <typename T> std::string join(const T &v, std::string delim = ",") { |
| 2215 | + std::ostringstream s; |
| 2216 | + auto beg = std::begin(v); |
| 2217 | + auto end = std::end(v); |
| 2218 | + if(beg != end) |
| 2219 | + s << *beg++; |
| 2220 | + while(beg != end) { |
| 2221 | + s << delim << *beg++; |
| 2222 | + } |
| 2223 | + auto rval = s.str(); |
| 2224 | + if(!rval.empty() && delim.size() == 1 && rval.back() == delim[0]) { |
| 2225 | + // remove trailing delimiter if the last entry was empty |
| 2226 | + rval.pop_back(); |
| 2227 | + } |
| 2228 | + return rval; |
| 2229 | +} |
| 2230 | + |
| 2231 | +/// Simple function to join a string from processed elements |
| 2232 | +template <typename T, |
| 2233 | + typename Callable, |
| 2234 | + typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type> |
| 2235 | +std::string join(const T &v, Callable func, std::string delim = ",") { |
| 2236 | + std::ostringstream s; |
| 2237 | + auto beg = std::begin(v); |
| 2238 | + auto end = std::end(v); |
| 2239 | + auto loc = s.tellp(); |
| 2240 | + while(beg != end) { |
| 2241 | + auto nloc = s.tellp(); |
| 2242 | + if(nloc > loc) { |
| 2243 | + s << delim; |
| 2244 | + loc = nloc; |
| 2245 | + } |
| 2246 | + s << func(*beg++); |
| 2247 | + } |
| 2248 | + return s.str(); |
| 2249 | +} |
| 2250 | + |
| 2251 | +/// Join a string in reverse order |
| 2252 | +template <typename T> std::string rjoin(const T &v, std::string delim = ",") { |
| 2253 | + std::ostringstream s; |
| 2254 | + for(std::size_t start = 0; start < v.size(); start++) { |
| 2255 | + if(start > 0) |
| 2256 | + s << delim; |
| 2257 | + s << v[v.size() - start - 1]; |
| 2258 | + } |
| 2259 | + return s.str(); |
| 2260 | +} |
| 2261 | + |
| 2262 | +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string |
| 2263 | + |
| 2264 | +/// Trim whitespace from left of string |
| 2265 | +CLI11_INLINE std::string <rim(std::string &str); |
| 2266 | + |
| 2267 | +/// Trim anything from left of string |
| 2268 | +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter); |
| 2269 | + |
| 2270 | +/// Trim whitespace from right of string |
| 2271 | +CLI11_INLINE std::string &rtrim(std::string &str); |
| 2272 | + |
| 2273 | +/// Trim anything from right of string |
| 2274 | +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter); |
| 2275 | + |
| 2276 | +/// Trim whitespace from string |
| 2277 | +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } |
| 2278 | + |
| 2279 | +/// Trim anything from string |
| 2280 | +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } |
| 2281 | + |
| 2282 | +/// Make a copy of the string and then trim it |
| 2283 | +inline std::string trim_copy(const std::string &str) { |
| 2284 | + std::string s = str; |
| 2285 | + return trim(s); |
| 2286 | +} |
| 2287 | + |
| 2288 | +/// remove quotes at the front and back of a string either '"' or '\'' |
| 2289 | +CLI11_INLINE std::string &remove_quotes(std::string &str); |
| 2290 | + |
| 2291 | +/// remove quotes from all elements of a string vector and process escaped components |
| 2292 | +CLI11_INLINE void remove_quotes(std::vector<std::string> &args); |
| 2293 | + |
| 2294 | +/// Add a leader to the beginning of all new lines (nothing is added |
| 2295 | +/// at the start of the first line). `"; "` would be for ini files |
| 2296 | +/// |
| 2297 | +/// Can't use Regex, or this would be a subs. |
| 2298 | +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input); |
| 2299 | + |
| 2300 | +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) |
| 2301 | +inline std::string trim_copy(const std::string &str, const std::string &filter) { |
| 2302 | + std::string s = str; |
| 2303 | + return trim(s, filter); |
| 2304 | +} |
| 2305 | + |
| 2306 | +/// Print subcommand aliases |
| 2307 | +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid); |
| 2308 | + |
| 2309 | +/// Verify the first character of an option |
| 2310 | +/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with |
| 2311 | +template <typename T> bool valid_first_char(T c) { |
| 2312 | + return ((c != '-') && (static_cast<unsigned char>(c) > 33)); // space and '!' not allowed |
| 2313 | +} |
| 2314 | + |
| 2315 | +/// Verify following characters of an option |
| 2316 | +template <typename T> bool valid_later_char(T c) { |
| 2317 | + // = and : are value separators, { has special meaning for option defaults, |
| 2318 | + // and control codes other than tab would just be annoying to deal with in many places allowing space here has too |
| 2319 | + // much potential for inadvertent entry errors and bugs |
| 2320 | + return ((c != '=') && (c != ':') && (c != '{') && ((static_cast<unsigned char>(c) > 32) || c == '\t')); |
| 2321 | +} |
| 2322 | + |
| 2323 | +/// Verify an option/subcommand name |
| 2324 | +CLI11_INLINE bool valid_name_string(const std::string &str); |
| 2325 | + |
| 2326 | +/// Verify an app name |
| 2327 | +inline bool valid_alias_name_string(const std::string &str) { |
| 2328 | + return ((str.find_first_of('\n') == std::string::npos) && (str.find_first_of('\0') == std::string::npos)); |
| 2329 | +} |
| 2330 | + |
| 2331 | +/// check if a string is a container segment separator (empty or "%%") |
| 2332 | +inline bool is_separator(const std::string &str) { |
| 2333 | + return (str.empty() || (str.size() == 2 && str[0] == '%' && str[1] == '%')); |
| 2334 | +} |
| 2335 | + |
| 2336 | +/// Verify that str consists of letters only |
| 2337 | +inline bool isalpha(const std::string &str) { |
| 2338 | + return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); |
| 2339 | +} |
| 2340 | + |
| 2341 | +/// Return a lower case version of a string |
| 2342 | +inline std::string to_lower(std::string str) { |
| 2343 | + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { |
| 2344 | + return std::tolower(x, std::locale()); |
| 2345 | + }); |
| 2346 | + return str; |
| 2347 | +} |
| 2348 | + |
| 2349 | +/// remove underscores from a string |
| 2350 | +inline std::string remove_underscore(std::string str) { |
| 2351 | + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); |
| 2352 | + return str; |
| 2353 | +} |
| 2354 | + |
| 2355 | +/// @brief get valid group separators _' + local separator if different |
| 2356 | +/// @return a string containing the group separators |
| 2357 | +CLI11_INLINE std::string get_group_separators(); |
| 2358 | + |
| 2359 | +/// Find and replace a substring with another substring |
| 2360 | +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to); |
| 2361 | + |
| 2362 | +/// check if the flag definitions has possible false flags |
| 2363 | +inline bool has_default_flag_values(const std::string &flags) { |
| 2364 | + return (flags.find_first_of("{!") != std::string::npos); |
| 2365 | +} |
| 2366 | + |
| 2367 | +CLI11_INLINE void remove_default_flag_values(std::string &flags); |
| 2368 | + |
| 2369 | +/// Check if a string is a member of a list of strings and optionally ignore case or ignore underscores |
| 2370 | +CLI11_INLINE std::ptrdiff_t find_member(std::string name, |
| 2371 | + const std::vector<std::string> names, |
| 2372 | + bool ignore_case = false, |
| 2373 | + bool ignore_underscore = false); |
| 2374 | + |
| 2375 | +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the |
| 2376 | +/// trigger and returns the position in the string to search for the next trigger string |
| 2377 | +template <typename Callable> inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { |
| 2378 | + std::size_t start_pos = 0; |
| 2379 | + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { |
| 2380 | + start_pos = modify(str, start_pos); |
| 2381 | + } |
| 2382 | + return str; |
| 2383 | +} |
| 2384 | + |
| 2385 | +/// close a sequence of characters indicated by a closure character. Brackets allows sub sequences |
| 2386 | +/// recognized bracket sequences include "'`[(<{ other closure characters are assumed to be literal strings |
| 2387 | +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char); |
| 2388 | + |
| 2389 | +/// Split a string '"one two" "three"' into 'one two', 'three' |
| 2390 | +/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket |
| 2391 | +CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter = '\0'); |
| 2392 | + |
| 2393 | +/// get the value of an environmental variable or empty string if empty |
| 2394 | +CLI11_INLINE std::string get_environment_value(const std::string &env_name); |
| 2395 | + |
| 2396 | +/// This function detects an equal or colon followed by an escaped quote after an argument |
| 2397 | +/// then modifies the string to replace the equality with a space. This is needed |
| 2398 | +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function |
| 2399 | +/// the return value is the offset+1 which is required by the find_and_modify function. |
| 2400 | +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset); |
| 2401 | + |
| 2402 | +/// @brief detect if a string has escapable characters |
| 2403 | +/// @param str the string to do the detection on |
| 2404 | +/// @return true if the string has escapable characters |
| 2405 | +CLI11_INLINE bool has_escapable_character(const std::string &str); |
| 2406 | + |
| 2407 | +/// @brief escape all escapable characters |
| 2408 | +/// @param str the string to escape |
| 2409 | +/// @return a string with the escapable characters escaped with '\' |
| 2410 | +CLI11_INLINE std::string add_escaped_characters(const std::string &str); |
| 2411 | + |
| 2412 | +/// @brief replace the escaped characters with their equivalent |
| 2413 | +CLI11_INLINE std::string remove_escaped_characters(const std::string &str); |
| 2414 | + |
| 2415 | +/// generate a string with all non printable characters escaped to hex codes |
| 2416 | +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape, bool force = false); |
| 2417 | + |
| 2418 | +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); |
| 2419 | + |
| 2420 | +/// extract an escaped binary_string |
| 2421 | +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string); |
| 2422 | + |
| 2423 | +/// process a quoted string, remove the quotes and if appropriate handle escaped characters |
| 2424 | +CLI11_INLINE bool process_quoted_string(std::string &str, |
| 2425 | + char string_char = '\"', |
| 2426 | + char literal_char = '\'', |
| 2427 | + bool disable_secondary_array_processing = false); |
| 2428 | + |
| 2429 | +/// This function formats the given text as a paragraph with fixed width and applies correct line wrapping |
| 2430 | +/// with a custom line prefix. The paragraph will get streamed to the given ostream. |
| 2431 | +CLI11_INLINE std::ostream &streamOutAsParagraph(std::ostream &out, |
| 2432 | + const std::string &text, |
| 2433 | + std::size_t paragraphWidth, |
| 2434 | + const std::string &linePrefix = "", |
| 2435 | + bool skipPrefixOnFirstLine = false); |
| 2436 | + |
| 2437 | +} // namespace detail |
| 2438 | + |
| 2439 | + |
| 2440 | + |
| 2441 | + |
| 2442 | +namespace detail { |
| 2443 | +CLI11_INLINE std::vector<std::string> split(const std::string &s, char delim) { |
| 2444 | + std::vector<std::string> elems; |
| 2445 | + // Check to see if empty string, give consistent result |
| 2446 | + if(s.empty()) { |
| 2447 | + elems.emplace_back(); |
| 2448 | + } else { |
| 2449 | + std::stringstream ss; |
| 2450 | + ss.str(s); |
| 2451 | + std::string item; |
| 2452 | + while(std::getline(ss, item, delim)) { |
| 2453 | + elems.push_back(item); |
| 2454 | + } |
| 2455 | + } |
| 2456 | + return elems; |
| 2457 | +} |
| 2458 | + |
| 2459 | +CLI11_INLINE std::string <rim(std::string &str) { |
| 2460 | + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); }); |
| 2461 | + str.erase(str.begin(), it); |
| 2462 | + return str; |
| 2463 | +} |
| 2464 | + |
| 2465 | +CLI11_INLINE std::string <rim(std::string &str, const std::string &filter) { |
| 2466 | + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); |
| 2467 | + str.erase(str.begin(), it); |
| 2468 | + return str; |
| 2469 | +} |
| 2470 | + |
| 2471 | +CLI11_INLINE std::string &rtrim(std::string &str) { |
| 2472 | + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<char>(ch, std::locale()); }); |
| 2473 | + str.erase(it.base(), str.end()); |
| 2474 | + return str; |
| 2475 | +} |
| 2476 | + |
| 2477 | +CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) { |
| 2478 | + auto it = |
| 2479 | + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); |
| 2480 | + str.erase(it.base(), str.end()); |
| 2481 | + return str; |
| 2482 | +} |
| 2483 | + |
| 2484 | +CLI11_INLINE std::string &remove_quotes(std::string &str) { |
| 2485 | + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'' || str.front() == '`')) { |
| 2486 | + if(str.front() == str.back()) { |
| 2487 | + str.pop_back(); |
| 2488 | + str.erase(str.begin(), str.begin() + 1); |
| 2489 | + } |
| 2490 | + } |
| 2491 | + return str; |
| 2492 | +} |
| 2493 | + |
| 2494 | +CLI11_INLINE std::string &remove_outer(std::string &str, char key) { |
| 2495 | + if(str.length() > 1 && (str.front() == key)) { |
| 2496 | + if(str.front() == str.back()) { |
| 2497 | + str.pop_back(); |
| 2498 | + str.erase(str.begin(), str.begin() + 1); |
| 2499 | + } |
| 2500 | + } |
| 2501 | + return str; |
| 2502 | +} |
| 2503 | + |
| 2504 | +CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string input) { |
| 2505 | + std::string::size_type n = 0; |
| 2506 | + while(n != std::string::npos && n < input.size()) { |
| 2507 | + n = input.find_first_of("\r\n", n); |
| 2508 | + if(n != std::string::npos) { |
| 2509 | + input = input.substr(0, n + 1) + leader + input.substr(n + 1); |
| 2510 | + n += leader.size(); |
| 2511 | + } |
| 2512 | + } |
| 2513 | + return input; |
| 2514 | +} |
| 2515 | + |
| 2516 | +CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid) { |
| 2517 | + if(!aliases.empty()) { |
| 2518 | + out << std::setw(static_cast<int>(wid)) << " aliases: "; |
| 2519 | + bool front = true; |
| 2520 | + for(const auto &alias : aliases) { |
| 2521 | + if(!front) { |
| 2522 | + out << ", "; |
| 2523 | + } else { |
| 2524 | + front = false; |
| 2525 | + } |
| 2526 | + out << detail::fix_newlines(" ", alias); |
| 2527 | + } |
| 2528 | + out << "\n"; |
| 2529 | + } |
| 2530 | + return out; |
| 2531 | +} |
| 2532 | + |
| 2533 | +CLI11_INLINE bool valid_name_string(const std::string &str) { |
| 2534 | + if(str.empty() || !valid_first_char(str[0])) { |
| 2535 | + return false; |
| 2536 | + } |
| 2537 | + auto e = str.end(); |
| 2538 | + for(auto c = str.begin() + 1; c != e; ++c) |
| 2539 | + if(!valid_later_char(*c)) |
| 2540 | + return false; |
| 2541 | + return true; |
| 2542 | +} |
| 2543 | + |
| 2544 | +CLI11_INLINE std::string get_group_separators() { |
| 2545 | + std::string separators{"_'"}; |
| 2546 | +#if CLI11_HAS_RTTI != 0 |
| 2547 | + char group_separator = std::use_facet<std::numpunct<char>>(std::locale()).thousands_sep(); |
| 2548 | + separators.push_back(group_separator); |
| 2549 | +#endif |
| 2550 | + return separators; |
| 2551 | +} |
| 2552 | + |
| 2553 | +CLI11_INLINE std::string find_and_replace(std::string str, std::string from, std::string to) { |
| 2554 | + |
| 2555 | + std::size_t start_pos = 0; |
| 2556 | + |
| 2557 | + while((start_pos = str.find(from, start_pos)) != std::string::npos) { |
| 2558 | + str.replace(start_pos, from.length(), to); |
| 2559 | + start_pos += to.length(); |
| 2560 | + } |
| 2561 | + |
| 2562 | + return str; |
| 2563 | +} |
| 2564 | + |
| 2565 | +CLI11_INLINE void remove_default_flag_values(std::string &flags) { |
| 2566 | + auto loc = flags.find_first_of('{', 2); |
| 2567 | + while(loc != std::string::npos) { |
| 2568 | + auto finish = flags.find_first_of("},", loc + 1); |
| 2569 | + if((finish != std::string::npos) && (flags[finish] == '}')) { |
| 2570 | + flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc), |
| 2571 | + flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1); |
| 2572 | + } |
| 2573 | + loc = flags.find_first_of('{', loc + 1); |
| 2574 | + } |
| 2575 | + flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end()); |
| 2576 | +} |
| 2577 | + |
| 2578 | +CLI11_INLINE std::ptrdiff_t |
| 2579 | +find_member(std::string name, const std::vector<std::string> names, bool ignore_case, bool ignore_underscore) { |
| 2580 | + auto it = std::end(names); |
| 2581 | + if(ignore_case) { |
| 2582 | + if(ignore_underscore) { |
| 2583 | + name = detail::to_lower(detail::remove_underscore(name)); |
| 2584 | + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { |
| 2585 | + return detail::to_lower(detail::remove_underscore(local_name)) == name; |
| 2586 | + }); |
| 2587 | + } else { |
| 2588 | + name = detail::to_lower(name); |
| 2589 | + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { |
| 2590 | + return detail::to_lower(local_name) == name; |
| 2591 | + }); |
| 2592 | + } |
| 2593 | + |
| 2594 | + } else if(ignore_underscore) { |
| 2595 | + name = detail::remove_underscore(name); |
| 2596 | + it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { |
| 2597 | + return detail::remove_underscore(local_name) == name; |
| 2598 | + }); |
| 2599 | + } else { |
| 2600 | + it = std::find(std::begin(names), std::end(names), name); |
| 2601 | + } |
| 2602 | + |
| 2603 | + return (it != std::end(names)) ? (it - std::begin(names)) : (-1); |
| 2604 | +} |
| 2605 | + |
| 2606 | +CLI11_MODULE_INLINE const std::string &escapedChars("\b\t\n\f\r\"\\"); |
| 2607 | +CLI11_MODULE_INLINE const std::string &escapedCharsCode("btnfr\"\\"); |
| 2608 | +CLI11_MODULE_INLINE const std::string &bracketChars("\"'`[(<{"); |
| 2609 | +CLI11_MODULE_INLINE const std::string &matchBracketChars("\"'`])>}"); |
| 2610 | + |
| 2611 | +CLI11_INLINE bool has_escapable_character(const std::string &str) { |
| 2612 | + return (str.find_first_of(escapedChars) != std::string::npos); |
| 2613 | +} |
| 2614 | + |
| 2615 | +CLI11_INLINE std::string add_escaped_characters(const std::string &str) { |
| 2616 | + std::string out; |
| 2617 | + out.reserve(str.size() + 4); |
| 2618 | + for(char s : str) { |
| 2619 | + auto sloc = escapedChars.find_first_of(s); |
| 2620 | + if(sloc != std::string::npos) { |
| 2621 | + out.push_back('\\'); |
| 2622 | + out.push_back(escapedCharsCode[sloc]); |
| 2623 | + } else { |
| 2624 | + out.push_back(s); |
| 2625 | + } |
| 2626 | + } |
| 2627 | + return out; |
| 2628 | +} |
| 2629 | + |
| 2630 | +CLI11_INLINE std::uint32_t hexConvert(char hc) { |
| 2631 | + int hcode{0}; |
| 2632 | + if(hc >= '0' && hc <= '9') { |
| 2633 | + hcode = (hc - '0'); |
| 2634 | + } else if(hc >= 'A' && hc <= 'F') { |
| 2635 | + hcode = (hc - 'A' + 10); |
| 2636 | + } else if(hc >= 'a' && hc <= 'f') { |
| 2637 | + hcode = (hc - 'a' + 10); |
| 2638 | + } else { |
| 2639 | + hcode = -1; |
| 2640 | + } |
| 2641 | + return static_cast<uint32_t>(hcode); |
| 2642 | +} |
| 2643 | + |
| 2644 | +CLI11_INLINE char make_char(std::uint32_t code) { return static_cast<char>(static_cast<unsigned char>(code)); } |
| 2645 | + |
| 2646 | +CLI11_INLINE void append_codepoint(std::string &str, std::uint32_t code) { |
| 2647 | + if(code < 0x80) { // ascii code equivalent |
| 2648 | + str.push_back(static_cast<char>(code)); |
| 2649 | + } else if(code < 0x800) { // \u0080 to \u07FF |
| 2650 | + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 |
| 2651 | + str.push_back(make_char(0xC0 | code >> 6)); |
| 2652 | + str.push_back(make_char(0x80 | (code & 0x3F))); |
| 2653 | + } else if(code < 0x10000) { // U+0800...U+FFFF |
| 2654 | + if(0xD800 <= code && code <= 0xDFFF) { |
| 2655 | + throw std::invalid_argument("[0xD800, 0xDFFF] are not valid UTF-8."); |
| 2656 | + } |
| 2657 | + // 1110yyyy 10yxxxxx 10xxxxxx |
| 2658 | + str.push_back(make_char(0xE0 | code >> 12)); |
| 2659 | + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); |
| 2660 | + str.push_back(make_char(0x80 | (code & 0x3F))); |
| 2661 | + } else if(code < 0x110000) { // U+010000 ... U+10FFFF |
| 2662 | + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx |
| 2663 | + str.push_back(make_char(0xF0 | code >> 18)); |
| 2664 | + str.push_back(make_char(0x80 | (code >> 12 & 0x3F))); |
| 2665 | + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); |
| 2666 | + str.push_back(make_char(0x80 | (code & 0x3F))); |
| 2667 | + } |
| 2668 | +} |
| 2669 | + |
| 2670 | +CLI11_INLINE std::string remove_escaped_characters(const std::string &str) { |
| 2671 | + |
| 2672 | + std::string out; |
| 2673 | + out.reserve(str.size()); |
| 2674 | + for(auto loc = str.begin(); loc < str.end(); ++loc) { |
| 2675 | + if(*loc == '\\') { |
| 2676 | + if(str.end() - loc < 2) { |
| 2677 | + throw std::invalid_argument("invalid escape sequence " + str); |
| 2678 | + } |
| 2679 | + auto ecloc = escapedCharsCode.find_first_of(*(loc + 1)); |
| 2680 | + if(ecloc != std::string::npos) { |
| 2681 | + out.push_back(escapedChars[ecloc]); |
| 2682 | + ++loc; |
| 2683 | + } else if(*(loc + 1) == 'u') { |
| 2684 | + // must have 4 hex characters |
| 2685 | + if(str.end() - loc < 6) { |
| 2686 | + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); |
| 2687 | + } |
| 2688 | + std::uint32_t code{0}; |
| 2689 | + std::uint32_t mplier{16 * 16 * 16}; |
| 2690 | + for(int ii = 2; ii < 6; ++ii) { |
| 2691 | + std::uint32_t res = hexConvert(*(loc + ii)); |
| 2692 | + if(res > 0x0F) { |
| 2693 | + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); |
| 2694 | + } |
| 2695 | + code += res * mplier; |
| 2696 | + mplier = mplier / 16; |
| 2697 | + } |
| 2698 | + append_codepoint(out, code); |
| 2699 | + loc += 5; |
| 2700 | + } else if(*(loc + 1) == 'U') { |
| 2701 | + // must have 8 hex characters |
| 2702 | + if(str.end() - loc < 10) { |
| 2703 | + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); |
| 2704 | + } |
| 2705 | + std::uint32_t code{0}; |
| 2706 | + std::uint32_t mplier{16 * 16 * 16 * 16 * 16 * 16 * 16}; |
| 2707 | + for(int ii = 2; ii < 10; ++ii) { |
| 2708 | + std::uint32_t res = hexConvert(*(loc + ii)); |
| 2709 | + if(res > 0x0F) { |
| 2710 | + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); |
| 2711 | + } |
| 2712 | + code += res * mplier; |
| 2713 | + mplier = mplier / 16; |
| 2714 | + } |
| 2715 | + append_codepoint(out, code); |
| 2716 | + loc += 9; |
| 2717 | + } else if(*(loc + 1) == '0') { |
| 2718 | + out.push_back('\0'); |
| 2719 | + ++loc; |
| 2720 | + } else { |
| 2721 | + throw std::invalid_argument(std::string("unrecognized escape sequence \\") + *(loc + 1) + " in " + str); |
| 2722 | + } |
| 2723 | + } else { |
| 2724 | + out.push_back(*loc); |
| 2725 | + } |
| 2726 | + } |
| 2727 | + return out; |
| 2728 | +} |
| 2729 | + |
| 2730 | +CLI11_INLINE std::size_t close_string_quote(const std::string &str, std::size_t start, char closure_char) { |
| 2731 | + std::size_t loc{0}; |
| 2732 | + for(loc = start + 1; loc < str.size(); ++loc) { |
| 2733 | + if(str[loc] == closure_char) { |
| 2734 | + break; |
| 2735 | + } |
| 2736 | + if(str[loc] == '\\') { |
| 2737 | + // skip the next character for escaped sequences |
| 2738 | + ++loc; |
| 2739 | + } |
| 2740 | + } |
| 2741 | + return loc; |
| 2742 | +} |
| 2743 | + |
| 2744 | +CLI11_INLINE std::size_t close_literal_quote(const std::string &str, std::size_t start, char closure_char) { |
| 2745 | + auto loc = str.find_first_of(closure_char, start + 1); |
| 2746 | + return (loc != std::string::npos ? loc : str.size()); |
| 2747 | +} |
| 2748 | + |
| 2749 | +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char) { |
| 2750 | + |
| 2751 | + auto bracket_loc = matchBracketChars.find(closure_char); |
| 2752 | + switch(bracket_loc) { |
| 2753 | + case 0: |
| 2754 | + return close_string_quote(str, start, closure_char); |
| 2755 | + case 1: |
| 2756 | + case 2: |
| 2757 | +#if defined(_MSC_VER) && _MSC_VER < 1920 |
| 2758 | + case(std::size_t)-1: |
| 2759 | +#else |
| 2760 | + case std::string::npos: |
| 2761 | +#endif |
| 2762 | + return close_literal_quote(str, start, closure_char); |
| 2763 | + default: |
| 2764 | + break; |
| 2765 | + } |
| 2766 | + |
| 2767 | + std::string closures(1, closure_char); |
| 2768 | + auto loc = start + 1; |
| 2769 | + |
| 2770 | + while(loc < str.size()) { |
| 2771 | + if(str[loc] == closures.back()) { |
| 2772 | + closures.pop_back(); |
| 2773 | + if(closures.empty()) { |
| 2774 | + return loc; |
| 2775 | + } |
| 2776 | + } |
| 2777 | + bracket_loc = bracketChars.find(str[loc]); |
| 2778 | + if(bracket_loc != std::string::npos) { |
| 2779 | + switch(bracket_loc) { |
| 2780 | + case 0: |
| 2781 | + loc = close_string_quote(str, loc, str[loc]); |
| 2782 | + break; |
| 2783 | + case 1: |
| 2784 | + case 2: |
| 2785 | + loc = close_literal_quote(str, loc, str[loc]); |
| 2786 | + break; |
| 2787 | + default: |
| 2788 | + closures.push_back(matchBracketChars[bracket_loc]); |
| 2789 | + break; |
| 2790 | + } |
| 2791 | + } |
| 2792 | + ++loc; |
| 2793 | + } |
| 2794 | + if(loc > str.size()) { |
| 2795 | + loc = str.size(); |
| 2796 | + } |
| 2797 | + return loc; |
| 2798 | +} |
| 2799 | + |
| 2800 | +CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter) { |
| 2801 | + |
| 2802 | + auto find_ws = [delimiter](char ch) { |
| 2803 | + return (delimiter == '\0') ? std::isspace<char>(ch, std::locale()) : (ch == delimiter); |
| 2804 | + }; |
| 2805 | + trim(str); |
| 2806 | + |
| 2807 | + std::vector<std::string> output; |
| 2808 | + while(!str.empty()) { |
| 2809 | + if(bracketChars.find_first_of(str[0]) != std::string::npos) { |
| 2810 | + auto bracketLoc = bracketChars.find_first_of(str[0]); |
| 2811 | + auto end = close_sequence(str, 0, matchBracketChars[bracketLoc]); |
| 2812 | + if(end >= str.size()) { |
| 2813 | + output.push_back(std::move(str)); |
| 2814 | + str.clear(); |
| 2815 | + } else { |
| 2816 | + output.push_back(str.substr(0, end + 1)); |
| 2817 | + if(end + 2 < str.size()) { |
| 2818 | + str = str.substr(end + 2); |
| 2819 | + } else { |
| 2820 | + str.clear(); |
| 2821 | + } |
| 2822 | + } |
| 2823 | + |
| 2824 | + } else { |
| 2825 | + auto it = std::find_if(std::begin(str), std::end(str), find_ws); |
| 2826 | + if(it != std::end(str)) { |
| 2827 | + std::string value = std::string(str.begin(), it); |
| 2828 | + output.push_back(value); |
| 2829 | + str = std::string(it + 1, str.end()); |
| 2830 | + } else { |
| 2831 | + output.push_back(str); |
| 2832 | + str.clear(); |
| 2833 | + } |
| 2834 | + } |
| 2835 | + trim(str); |
| 2836 | + } |
| 2837 | + return output; |
| 2838 | +} |
| 2839 | + |
| 2840 | +CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) { |
| 2841 | + auto next = str[offset + 1]; |
| 2842 | + if((next == '\"') || (next == '\'') || (next == '`')) { |
| 2843 | + auto astart = str.find_last_of("-/ \"\'`", offset - 1); |
| 2844 | + if(astart != std::string::npos) { |
| 2845 | + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) |
| 2846 | + str[offset] = ' '; // interpret this as a space so the split_up works properly |
| 2847 | + } |
| 2848 | + } |
| 2849 | + return offset + 1; |
| 2850 | +} |
| 2851 | + |
| 2852 | +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape, bool force) { |
| 2853 | + // s is our escaped output string |
| 2854 | + std::string escaped_string{}; |
| 2855 | + // loop through all characters |
| 2856 | + for(char c : string_to_escape) { |
| 2857 | + // check if a given character is printable |
| 2858 | + // the cast is necessary to avoid undefined behaviour |
| 2859 | + if(isprint(static_cast<unsigned char>(c)) == 0) { |
| 2860 | + std::stringstream stream; |
| 2861 | + // if the character is not printable |
| 2862 | + // we'll convert it to a hex string using a stringstream |
| 2863 | + // note that since char is signed we have to cast it to unsigned first |
| 2864 | + stream << std::hex << static_cast<unsigned int>(static_cast<unsigned char>(c)); |
| 2865 | + std::string code = stream.str(); |
| 2866 | + escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code; |
| 2867 | + } else if(c == 'x' || c == 'X') { |
| 2868 | + // need to check for inadvertent binary sequences |
| 2869 | + if(!escaped_string.empty() && escaped_string.back() == '\\') { |
| 2870 | + escaped_string += std::string("\\x") + (c == 'x' ? "78" : "58"); |
| 2871 | + } else { |
| 2872 | + escaped_string.push_back(c); |
| 2873 | + } |
| 2874 | + |
| 2875 | + } else { |
| 2876 | + escaped_string.push_back(c); |
| 2877 | + } |
| 2878 | + } |
| 2879 | + if(escaped_string != string_to_escape || force) { |
| 2880 | + auto sqLoc = escaped_string.find('\''); |
| 2881 | + while(sqLoc != std::string::npos) { |
| 2882 | + escaped_string[sqLoc] = '\\'; |
| 2883 | + escaped_string.insert(sqLoc + 1, "x27"); |
| 2884 | + sqLoc = escaped_string.find('\''); |
| 2885 | + } |
| 2886 | + escaped_string.insert(0, "'B\"("); |
| 2887 | + escaped_string.push_back(')'); |
| 2888 | + escaped_string.push_back('"'); |
| 2889 | + escaped_string.push_back('\''); |
| 2890 | + } |
| 2891 | + return escaped_string; |
| 2892 | +} |
| 2893 | + |
| 2894 | +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) { |
| 2895 | + size_t ssize = escaped_string.size(); |
| 2896 | + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { |
| 2897 | + return true; |
| 2898 | + } |
| 2899 | + return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0); |
| 2900 | +} |
| 2901 | + |
| 2902 | +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) { |
| 2903 | + std::size_t start{0}; |
| 2904 | + std::size_t tail{0}; |
| 2905 | + size_t ssize = escaped_string.size(); |
| 2906 | + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { |
| 2907 | + start = 3; |
| 2908 | + tail = 2; |
| 2909 | + } else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) { |
| 2910 | + start = 4; |
| 2911 | + tail = 3; |
| 2912 | + } |
| 2913 | + |
| 2914 | + if(start == 0) { |
| 2915 | + return escaped_string; |
| 2916 | + } |
| 2917 | + std::string outstring; |
| 2918 | + |
| 2919 | + outstring.reserve(ssize - start - tail); |
| 2920 | + std::size_t loc = start; |
| 2921 | + while(loc < ssize - tail) { |
| 2922 | + // ssize-2 to skip )" at the end |
| 2923 | + if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) { |
| 2924 | + auto c1 = escaped_string[loc + 2]; |
| 2925 | + auto c2 = escaped_string[loc + 3]; |
| 2926 | + |
| 2927 | + std::uint32_t res1 = hexConvert(c1); |
| 2928 | + std::uint32_t res2 = hexConvert(c2); |
| 2929 | + if(res1 <= 0x0F && res2 <= 0x0F) { |
| 2930 | + loc += 4; |
| 2931 | + outstring.push_back(static_cast<char>(res1 * 16 + res2)); |
| 2932 | + continue; |
| 2933 | + } |
| 2934 | + } |
| 2935 | + outstring.push_back(escaped_string[loc]); |
| 2936 | + ++loc; |
| 2937 | + } |
| 2938 | + return outstring; |
| 2939 | +} |
| 2940 | + |
| 2941 | +CLI11_INLINE void remove_quotes(std::vector<std::string> &args) { |
| 2942 | + for(auto &arg : args) { |
| 2943 | + if(arg.front() == '\"' && arg.back() == '\"') { |
| 2944 | + remove_quotes(arg); |
| 2945 | + // only remove escaped for string arguments not literal strings |
| 2946 | + arg = remove_escaped_characters(arg); |
| 2947 | + } else { |
| 2948 | + remove_quotes(arg); |
| 2949 | + } |
| 2950 | + } |
| 2951 | +} |
| 2952 | + |
| 2953 | +CLI11_INLINE void handle_secondary_array(std::string &str) { |
| 2954 | + if(str.size() >= 2 && str.front() == '[' && str.back() == ']') { |
| 2955 | + // handle some special array processing for arguments if it might be interpreted as a secondary array |
| 2956 | + std::string tstr{"[["}; |
| 2957 | + for(std::size_t ii = 1; ii < str.size(); ++ii) { |
| 2958 | + tstr.push_back(str[ii]); |
| 2959 | + tstr.push_back(str[ii]); |
| 2960 | + } |
| 2961 | + str = std::move(tstr); |
| 2962 | + } |
| 2963 | +} |
| 2964 | + |
| 2965 | +CLI11_INLINE bool |
| 2966 | +process_quoted_string(std::string &str, char string_char, char literal_char, bool disable_secondary_array_processing) { |
| 2967 | + if(str.size() <= 1) { |
| 2968 | + return false; |
| 2969 | + } |
| 2970 | + if(detail::is_binary_escaped_string(str)) { |
| 2971 | + str = detail::extract_binary_string(str); |
| 2972 | + if(!disable_secondary_array_processing) |
| 2973 | + handle_secondary_array(str); |
| 2974 | + return true; |
| 2975 | + } |
| 2976 | + if(str.front() == string_char && str.back() == string_char) { |
| 2977 | + detail::remove_outer(str, string_char); |
| 2978 | + if(str.find_first_of('\\') != std::string::npos) { |
| 2979 | + str = detail::remove_escaped_characters(str); |
| 2980 | + } |
| 2981 | + if(!disable_secondary_array_processing) |
| 2982 | + handle_secondary_array(str); |
| 2983 | + return true; |
| 2984 | + } |
| 2985 | + if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { |
| 2986 | + detail::remove_outer(str, str.front()); |
| 2987 | + if(!disable_secondary_array_processing) |
| 2988 | + handle_secondary_array(str); |
| 2989 | + return true; |
| 2990 | + } |
| 2991 | + return false; |
| 2992 | +} |
| 2993 | + |
| 2994 | +std::string get_environment_value(const std::string &env_name) { |
| 2995 | + std::string ename_string; |
| 2996 | + |
| 2997 | +#ifdef _MSC_VER |
| 2998 | + // Windows version |
| 2999 | + char *buffer = nullptr; |
| 3000 | + std::size_t sz = 0; |
| 3001 | + if(_dupenv_s(&buffer, &sz, env_name.c_str()) == 0 && buffer != nullptr) { |
| 3002 | + ename_string = std::string(buffer); |
| 3003 | + free(buffer); |
| 3004 | + } |
| 3005 | +#else |
| 3006 | + // This also works on Windows, but gives a warning |
| 3007 | + |
| 3008 | + // MISRA static analysis need. MISRACPP2023-25_5_2-a-1 |
| 3009 | + const char *buffer = nullptr; |
| 3010 | + buffer = std::getenv(env_name.c_str()); |
| 3011 | + if(buffer != nullptr) { |
| 3012 | + ename_string = std::string(buffer); |
| 3013 | + } |
| 3014 | +#endif |
| 3015 | + return ename_string; |
| 3016 | +} |
| 3017 | + |
| 3018 | +CLI11_INLINE std::ostream &streamOutAsParagraph(std::ostream &out, |
| 3019 | + const std::string &text, |
| 3020 | + std::size_t paragraphWidth, |
| 3021 | + const std::string &linePrefix, |
| 3022 | + bool skipPrefixOnFirstLine) { |
| 3023 | + if(!skipPrefixOnFirstLine) |
| 3024 | + out << linePrefix; // First line prefix |
| 3025 | + |
| 3026 | + std::istringstream lss(text); |
| 3027 | + std::string line = ""; |
| 3028 | + while(std::getline(lss, line)) { |
| 3029 | + std::istringstream iss(line); |
| 3030 | + std::string word = ""; |
| 3031 | + std::size_t charsWritten = 0; |
| 3032 | + |
| 3033 | + while(iss >> word) { |
| 3034 | + if(charsWritten > 0 && (word.length() + 1 + charsWritten > paragraphWidth)) { |
| 3035 | + out << '\n' << linePrefix; |
| 3036 | + charsWritten = 0; |
| 3037 | + } |
| 3038 | + if(charsWritten == 0) { |
| 3039 | + out << word; |
| 3040 | + charsWritten += word.length(); |
| 3041 | + } else { |
| 3042 | + out << ' ' << word; |
| 3043 | + charsWritten += word.length() + 1; |
| 3044 | + } |
| 3045 | + } |
| 3046 | + |
| 3047 | + if(!lss.eof()) |
| 3048 | + out << '\n' << linePrefix; |
| 3049 | + } |
| 3050 | + return out; |
| 3051 | +} |
| 3052 | + |
| 3053 | +} // namespace detail |
| 3054 | + |
| 3055 | + |
| 3056 | + |
| 3057 | +// Use one of these on all error classes. |
| 3058 | +// These are temporary and are undef'd at the end of this file. |
| 3059 | +#define CLI11_ERROR_DEF(parent, name) \ |
| 3060 | + protected: \ |
| 3061 | + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ |
| 3062 | + name(std::string ename, std::string msg, ExitCodes exit_code) \ |
| 3063 | + : parent(std::move(ename), std::move(msg), exit_code) {} \ |
| 3064 | + \ |
| 3065 | + public: \ |
| 3066 | + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ |
| 3067 | + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} |
| 3068 | + |
| 3069 | +// This is added after the one above if a class is used directly and builds its own message |
| 3070 | +#define CLI11_ERROR_SIMPLE(name) \ |
| 3071 | + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} |
| 3072 | + |
| 3073 | +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, |
| 3074 | +/// int values from e.get_error_code(). |
| 3075 | +enum class ExitCodes : int { |
| 3076 | + Success = 0, |
| 3077 | + IncorrectConstruction = 100, |
| 3078 | + BadNameString, |
| 3079 | + OptionAlreadyAdded, |
| 3080 | + FileError, |
| 3081 | + ConversionError, |
| 3082 | + ValidationError, |
| 3083 | + RequiredError, |
| 3084 | + RequiresError, |
| 3085 | + ExcludesError, |
| 3086 | + ExtrasError, |
| 3087 | + ConfigError, |
| 3088 | + InvalidError, |
| 3089 | + HorribleError, |
| 3090 | + OptionNotFound, |
| 3091 | + ArgumentMismatch, |
| 3092 | + BaseClass = 127 |
| 3093 | +}; |
| 3094 | + |
| 3095 | +// Error definitions |
| 3096 | + |
| 3097 | +/// @defgroup error_group Errors |
| 3098 | +/// @brief Errors thrown by CLI11 |
| 3099 | +/// |
| 3100 | +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. |
| 3101 | +/// @{ |
| 3102 | + |
| 3103 | +/// All errors derive from this one |
| 3104 | +class Error : public std::runtime_error { |
| 3105 | + int actual_exit_code; |
| 3106 | + std::string error_name{"Error"}; |
| 3107 | + |
| 3108 | + public: |
| 3109 | + CLI11_NODISCARD int get_exit_code() const { return actual_exit_code; } |
| 3110 | + |
| 3111 | + CLI11_NODISCARD std::string get_name() const { return error_name; } |
| 3112 | + |
| 3113 | + Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass)) |
| 3114 | + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} |
| 3115 | + |
| 3116 | + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast<int>(exit_code)) {} |
| 3117 | +}; |
| 3118 | + |
| 3119 | +// Note: Using Error::Error constructors does not work on GCC 4.7 |
| 3120 | + |
| 3121 | +/// Construction errors (not in parsing) |
| 3122 | +class ConstructionError : public Error { |
| 3123 | + CLI11_ERROR_DEF(Error, ConstructionError) |
| 3124 | +}; |
| 3125 | + |
| 3126 | +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) |
| 3127 | +class IncorrectConstruction : public ConstructionError { |
| 3128 | + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) |
| 3129 | + CLI11_ERROR_SIMPLE(IncorrectConstruction) |
| 3130 | + static IncorrectConstruction PositionalFlag(std::string name) { |
| 3131 | + return IncorrectConstruction(name + ": Flags cannot be positional"); |
| 3132 | + } |
| 3133 | + static IncorrectConstruction Set0Opt(std::string name) { |
| 3134 | + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); |
| 3135 | + } |
| 3136 | + static IncorrectConstruction SetFlag(std::string name) { |
| 3137 | + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); |
| 3138 | + } |
| 3139 | + static IncorrectConstruction ChangeNotVector(std::string name) { |
| 3140 | + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); |
| 3141 | + } |
| 3142 | + static IncorrectConstruction AfterMultiOpt(std::string name) { |
| 3143 | + return IncorrectConstruction( |
| 3144 | + name + ": You can't change expected arguments after you've changed the multi option policy!"); |
| 3145 | + } |
| 3146 | + static IncorrectConstruction MissingOption(std::string name) { |
| 3147 | + return IncorrectConstruction("Option " + name + " is not defined"); |
| 3148 | + } |
| 3149 | + static IncorrectConstruction MultiOptionPolicy(std::string name) { |
| 3150 | + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); |
| 3151 | + } |
| 3152 | +}; |
| 3153 | + |
| 3154 | +/// Thrown on construction of a bad name |
| 3155 | +class BadNameString : public ConstructionError { |
| 3156 | + CLI11_ERROR_DEF(ConstructionError, BadNameString) |
| 3157 | + CLI11_ERROR_SIMPLE(BadNameString) |
| 3158 | + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } |
| 3159 | + static BadNameString MissingDash(std::string name) { |
| 3160 | + return BadNameString("Long names strings require 2 dashes " + name); |
| 3161 | + } |
| 3162 | + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } |
| 3163 | + static BadNameString BadPositionalName(std::string name) { |
| 3164 | + return BadNameString("Invalid positional Name: " + name); |
| 3165 | + } |
| 3166 | + static BadNameString ReservedName(std::string name) { |
| 3167 | + return BadNameString("Names '-','--','++' are reserved and not allowed as option names " + name); |
| 3168 | + } |
| 3169 | + static BadNameString MultiPositionalNames(std::string name) { |
| 3170 | + return BadNameString("Only one positional name allowed, remove: " + name); |
| 3171 | + } |
| 3172 | +}; |
| 3173 | + |
| 3174 | +/// Thrown when an option already exists |
| 3175 | +class OptionAlreadyAdded : public ConstructionError { |
| 3176 | + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) |
| 3177 | + explicit OptionAlreadyAdded(std::string name) |
| 3178 | + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} |
| 3179 | + static OptionAlreadyAdded Requires(std::string name, std::string other) { |
| 3180 | + return {name + " requires " + other, ExitCodes::OptionAlreadyAdded}; |
| 3181 | + } |
| 3182 | + static OptionAlreadyAdded Excludes(std::string name, std::string other) { |
| 3183 | + return {name + " excludes " + other, ExitCodes::OptionAlreadyAdded}; |
| 3184 | + } |
| 3185 | +}; |
| 3186 | + |
| 3187 | +// Parsing errors |
| 3188 | + |
| 3189 | +/// Anything that can error in Parse |
| 3190 | +class ParseError : public Error { |
| 3191 | + CLI11_ERROR_DEF(Error, ParseError) |
| 3192 | +}; |
| 3193 | + |
| 3194 | +// Not really "errors" |
| 3195 | + |
| 3196 | +/// This is a successful completion on parsing, supposed to exit |
| 3197 | +class Success : public ParseError { |
| 3198 | + CLI11_ERROR_DEF(ParseError, Success) |
| 3199 | + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} |
| 3200 | +}; |
| 3201 | + |
| 3202 | +/// -h or --help on command line |
| 3203 | +class CallForHelp : public Success { |
| 3204 | + CLI11_ERROR_DEF(Success, CallForHelp) |
| 3205 | + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} |
| 3206 | +}; |
| 3207 | + |
| 3208 | +/// Usually something like --help-all on command line |
| 3209 | +class CallForAllHelp : public Success { |
| 3210 | + CLI11_ERROR_DEF(Success, CallForAllHelp) |
| 3211 | + CallForAllHelp() |
| 3212 | + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} |
| 3213 | +}; |
| 3214 | + |
| 3215 | +/// -v or --version on command line |
| 3216 | +class CallForVersion : public Success { |
| 3217 | + CLI11_ERROR_DEF(Success, CallForVersion) |
| 3218 | + CallForVersion() |
| 3219 | + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} |
| 3220 | +}; |
| 3221 | + |
| 3222 | +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. |
| 3223 | +class RuntimeError : public ParseError { |
| 3224 | + CLI11_ERROR_DEF(ParseError, RuntimeError) |
| 3225 | + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} |
| 3226 | +}; |
| 3227 | + |
| 3228 | +/// Thrown when parsing an INI file and it is missing |
| 3229 | +class FileError : public ParseError { |
| 3230 | + CLI11_ERROR_DEF(ParseError, FileError) |
| 3231 | + CLI11_ERROR_SIMPLE(FileError) |
| 3232 | + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } |
| 3233 | +}; |
| 3234 | + |
| 3235 | +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string |
| 3236 | +class ConversionError : public ParseError { |
| 3237 | + CLI11_ERROR_DEF(ParseError, ConversionError) |
| 3238 | + CLI11_ERROR_SIMPLE(ConversionError) |
| 3239 | + ConversionError(std::string member, std::string name) |
| 3240 | + : ConversionError("The value " + member + " is not an allowed value for " + name) {} |
| 3241 | + ConversionError(std::string name, std::vector<std::string> results) |
| 3242 | + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} |
| 3243 | + static ConversionError TooManyInputsFlag(std::string name) { |
| 3244 | + return ConversionError(name + ": too many inputs for a flag"); |
| 3245 | + } |
| 3246 | + static ConversionError TrueFalse(std::string name) { |
| 3247 | + return ConversionError(name + ": Should be true/false or a number"); |
| 3248 | + } |
| 3249 | +}; |
| 3250 | + |
| 3251 | +/// Thrown when validation of results fails |
| 3252 | +class ValidationError : public ParseError { |
| 3253 | + CLI11_ERROR_DEF(ParseError, ValidationError) |
| 3254 | + CLI11_ERROR_SIMPLE(ValidationError) |
| 3255 | + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} |
| 3256 | +}; |
| 3257 | + |
| 3258 | +/// Thrown when a required option is missing |
| 3259 | +class RequiredError : public ParseError { |
| 3260 | + CLI11_ERROR_DEF(ParseError, RequiredError) |
| 3261 | + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} |
| 3262 | + static RequiredError Subcommand(std::size_t min_subcom) { |
| 3263 | + if(min_subcom == 1) { |
| 3264 | + return RequiredError("A subcommand"); |
| 3265 | + } |
| 3266 | + return {"Requires at least " + std::to_string(min_subcom) + " subcommands", ExitCodes::RequiredError}; |
| 3267 | + } |
| 3268 | + static RequiredError |
| 3269 | + Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string &option_list) { |
| 3270 | + if((min_option == 1) && (max_option == 1) && (used == 0)) |
| 3271 | + return RequiredError("Exactly 1 option from [" + option_list + "]"); |
| 3272 | + if((min_option == 1) && (max_option == 1) && (used > 1)) { |
| 3273 | + return {"Exactly 1 option from [" + option_list + "] is required but " + std::to_string(used) + |
| 3274 | + " were given", |
| 3275 | + ExitCodes::RequiredError}; |
| 3276 | + } |
| 3277 | + if((min_option == 1) && (used == 0)) |
| 3278 | + return RequiredError("At least 1 option from [" + option_list + "]"); |
| 3279 | + if(used < min_option) { |
| 3280 | + return {"Requires at least " + std::to_string(min_option) + " options used but only " + |
| 3281 | + std::to_string(used) + " were given from [" + option_list + "]", |
| 3282 | + ExitCodes::RequiredError}; |
| 3283 | + } |
| 3284 | + if(max_option == 1) |
| 3285 | + return {"Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError}; |
| 3286 | + |
| 3287 | + return {"Requires at most " + std::to_string(max_option) + " options be used but " + std::to_string(used) + |
| 3288 | + " were given from [" + option_list + "]", |
| 3289 | + ExitCodes::RequiredError}; |
| 3290 | + } |
| 3291 | +}; |
| 3292 | + |
| 3293 | +/// Thrown when the wrong number of arguments has been received |
| 3294 | +class ArgumentMismatch : public ParseError { |
| 3295 | + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) |
| 3296 | + CLI11_ERROR_SIMPLE(ArgumentMismatch) |
| 3297 | + ArgumentMismatch(std::string name, int expected, std::size_t received) |
| 3298 | + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + |
| 3299 | + ", got " + std::to_string(received)) |
| 3300 | + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + |
| 3301 | + ", got " + std::to_string(received)), |
| 3302 | + ExitCodes::ArgumentMismatch) {} |
| 3303 | + |
| 3304 | + static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { |
| 3305 | + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " + |
| 3306 | + std::to_string(received)); |
| 3307 | + } |
| 3308 | + static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { |
| 3309 | + return ArgumentMismatch(name + ": At most " + std::to_string(num) + " required but received " + |
| 3310 | + std::to_string(received)); |
| 3311 | + } |
| 3312 | + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { |
| 3313 | + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); |
| 3314 | + } |
| 3315 | + static ArgumentMismatch FlagOverride(std::string name) { |
| 3316 | + return ArgumentMismatch(name + " was given a disallowed flag override"); |
| 3317 | + } |
| 3318 | + static ArgumentMismatch PartialType(std::string name, int num, std::string type) { |
| 3319 | + return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) + |
| 3320 | + " required for each element"); |
| 3321 | + } |
| 3322 | +}; |
| 3323 | + |
| 3324 | +/// Thrown when a requires option is missing |
| 3325 | +class RequiresError : public ParseError { |
| 3326 | + CLI11_ERROR_DEF(ParseError, RequiresError) |
| 3327 | + RequiresError(std::string curname, std::string subname) |
| 3328 | + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} |
| 3329 | +}; |
| 3330 | + |
| 3331 | +/// Thrown when an excludes option is present |
| 3332 | +class ExcludesError : public ParseError { |
| 3333 | + CLI11_ERROR_DEF(ParseError, ExcludesError) |
| 3334 | + ExcludesError(std::string curname, std::string subname) |
| 3335 | + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} |
| 3336 | +}; |
| 3337 | + |
| 3338 | +/// Thrown when too many positionals or options are found |
| 3339 | +class ExtrasError : public ParseError { |
| 3340 | + CLI11_ERROR_DEF(ParseError, ExtrasError) |
| 3341 | + explicit ExtrasError(std::vector<std::string> args) |
| 3342 | + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " |
| 3343 | + : "The following argument was not expected: ") + |
| 3344 | + detail::join(args, " "), |
| 3345 | + ExitCodes::ExtrasError) {} |
| 3346 | + ExtrasError(const std::string &name, std::vector<std::string> args) |
| 3347 | + : ExtrasError(name, |
| 3348 | + (args.size() > 1 ? "The following arguments were not expected: " |
| 3349 | + : "The following argument was not expected: ") + |
| 3350 | + detail::join(args, " "), |
| 3351 | + ExitCodes::ExtrasError) {} |
| 3352 | +}; |
| 3353 | + |
| 3354 | +/// Thrown when extra values are found in an INI file |
| 3355 | +class ConfigError : public ParseError { |
| 3356 | + CLI11_ERROR_DEF(ParseError, ConfigError) |
| 3357 | + CLI11_ERROR_SIMPLE(ConfigError) |
| 3358 | + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } |
| 3359 | + static ConfigError NotConfigurable(std::string item) { |
| 3360 | + return ConfigError(item + ": This option is not allowed in a configuration file"); |
| 3361 | + } |
| 3362 | +}; |
| 3363 | + |
| 3364 | +/// Thrown when validation fails before parsing |
| 3365 | +class InvalidError : public ParseError { |
| 3366 | + CLI11_ERROR_DEF(ParseError, InvalidError) |
| 3367 | + explicit InvalidError(std::string name) |
| 3368 | + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { |
| 3369 | + } |
| 3370 | +}; |
| 3371 | + |
| 3372 | +/// This is just a safety check to verify selection and parsing match - you should not ever see it |
| 3373 | +/// Strings are directly added to this error, but again, it should never be seen. |
| 3374 | +class HorribleError : public ParseError { |
| 3375 | + CLI11_ERROR_DEF(ParseError, HorribleError) |
| 3376 | + CLI11_ERROR_SIMPLE(HorribleError) |
| 3377 | +}; |
| 3378 | + |
| 3379 | +// After parsing |
| 3380 | + |
| 3381 | +/// Thrown when counting a nonexistent option |
| 3382 | +class OptionNotFound : public Error { |
| 3383 | + CLI11_ERROR_DEF(Error, OptionNotFound) |
| 3384 | + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} |
| 3385 | +}; |
| 3386 | + |
| 3387 | +#undef CLI11_ERROR_DEF |
| 3388 | +#undef CLI11_ERROR_SIMPLE |
| 3389 | + |
| 3390 | +/// @} |
| 3391 | + |
| 3392 | + |
| 3393 | + |
| 3394 | + |
| 3395 | +// Type tools |
| 3396 | + |
| 3397 | +// Utilities for type enabling |
| 3398 | +namespace detail { |
| 3399 | +// Based generally on https://rmf.io/cxx11/almost-static-if |
| 3400 | +/// Simple empty scoped class |
| 3401 | +enum class enabler : std::uint8_t {}; |
| 3402 | + |
| 3403 | +/// An instance to use in EnableIf |
| 3404 | +CLI11_MODULE_INLINE constexpr enabler dummy = {}; |
| 3405 | +} // namespace detail |
| 3406 | + |
| 3407 | +/// A copy of enable_if_t from C++14, compatible with C++11. |
| 3408 | +/// |
| 3409 | +/// We could check to see if C++14 is being used, but it does not hurt to redefine this |
| 3410 | +/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h) |
| 3411 | +/// It is not in the std namespace anyway, so no harm done. |
| 3412 | +template <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type; |
| 3413 | + |
| 3414 | +/// A copy of std::void_t from C++17 (helper for C++11 and C++14) |
| 3415 | +template <typename... Ts> struct make_void { |
| 3416 | + using type = void; |
| 3417 | +}; |
| 3418 | + |
| 3419 | +/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine |
| 3420 | +template <typename... Ts> using void_t = typename make_void<Ts...>::type; |
| 3421 | + |
| 3422 | +/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine |
| 3423 | +template <bool B, class T, class F> using conditional_t = typename std::conditional<B, T, F>::type; |
| 3424 | + |
| 3425 | +/// Check to see if something is bool (fail check by default) |
| 3426 | +template <typename T> struct is_bool : std::false_type {}; |
| 3427 | + |
| 3428 | +/// Check to see if something is bool (true if actually a bool) |
| 3429 | +template <> struct is_bool<bool> : std::true_type {}; |
| 3430 | + |
| 3431 | +/// Check to see if something is a shared pointer |
| 3432 | +template <typename T> struct is_shared_ptr : std::false_type {}; |
| 3433 | + |
| 3434 | +/// Check to see if something is a shared pointer (True if really a shared pointer) |
| 3435 | +template <typename T> struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {}; |
| 3436 | + |
| 3437 | +/// Check to see if something is a shared pointer (True if really a shared pointer) |
| 3438 | +template <typename T> struct is_shared_ptr<const std::shared_ptr<T>> : std::true_type {}; |
| 3439 | + |
| 3440 | +/// Check to see if something is copyable pointer |
| 3441 | +template <typename T> struct is_copyable_ptr { |
| 3442 | + static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value; |
| 3443 | +}; |
| 3444 | + |
| 3445 | +/// This can be specialized to override the type deduction for IsMember. |
| 3446 | +template <typename T> struct IsMemberType { |
| 3447 | + using type = T; |
| 3448 | +}; |
| 3449 | + |
| 3450 | +/// The main custom type needed here is const char * should be a string. |
| 3451 | +template <> struct IsMemberType<const char *> { |
| 3452 | + using type = std::string; |
| 3453 | +}; |
| 3454 | + |
| 3455 | +namespace adl_detail { |
| 3456 | +/// Check for existence of user-supplied lexical_cast. |
| 3457 | +/// |
| 3458 | +/// This struct has to be in a separate namespace so that it doesn't see our lexical_cast overloads in CLI::detail. |
| 3459 | +/// Standard says it shouldn't see them if it's defined before the corresponding lexical_cast declarations, but this |
| 3460 | +/// requires a working implementation of two-phase lookup, and not all compilers can boast that (msvc, ahem). |
| 3461 | +template <typename T, typename S = std::string> class is_lexical_castable { |
| 3462 | + template <typename TT, typename SS> |
| 3463 | + static auto test(int) -> decltype(lexical_cast(std::declval<const SS &>(), std::declval<TT &>()), std::true_type()); |
| 3464 | + |
| 3465 | + template <typename, typename> static auto test(...) -> std::false_type; |
| 3466 | + |
| 3467 | + public: |
| 3468 | + static constexpr bool value = decltype(test<T, S>(0))::value; |
| 3469 | +}; |
| 3470 | +} // namespace adl_detail |
| 3471 | + |
| 3472 | +namespace detail { |
| 3473 | + |
| 3474 | +// These are utilities for IsMember and other transforming objects |
| 3475 | + |
| 3476 | +/// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that |
| 3477 | +/// pointer_traits<T> be valid. |
| 3478 | + |
| 3479 | +/// not a pointer |
| 3480 | +template <typename T, typename Enable = void> struct element_type { |
| 3481 | + using type = T; |
| 3482 | +}; |
| 3483 | + |
| 3484 | +template <typename T> struct element_type<T, typename std::enable_if<is_copyable_ptr<T>::value>::type> { |
| 3485 | + using type = typename std::pointer_traits<T>::element_type; |
| 3486 | +}; |
| 3487 | + |
| 3488 | +/// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of |
| 3489 | +/// the container |
| 3490 | +template <typename T> struct element_value_type { |
| 3491 | + using type = typename element_type<T>::type::value_type; |
| 3492 | +}; |
| 3493 | + |
| 3494 | +/// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. |
| 3495 | +template <typename T, typename _ = void> struct pair_adaptor : std::false_type { |
| 3496 | + using value_type = typename T::value_type; |
| 3497 | + using first_type = typename std::remove_const<value_type>::type; |
| 3498 | + using second_type = typename std::remove_const<value_type>::type; |
| 3499 | + |
| 3500 | + /// Get the first value (really just the underlying value) |
| 3501 | + template <typename Q> static auto first(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) { |
| 3502 | + return std::forward<Q>(pair_value); |
| 3503 | + } |
| 3504 | + /// Get the second value (really just the underlying value) |
| 3505 | + template <typename Q> static auto second(Q &&pair_value) -> decltype(std::forward<Q>(pair_value)) { |
| 3506 | + return std::forward<Q>(pair_value); |
| 3507 | + } |
| 3508 | +}; |
| 3509 | + |
| 3510 | +/// Adaptor for map-like structure (true version, must have key_type and mapped_type). |
| 3511 | +/// This wraps a mapped container in a few utilities access it in a general way. |
| 3512 | +template <typename T> |
| 3513 | +struct pair_adaptor< |
| 3514 | + T, |
| 3515 | + conditional_t<false, void_t<typename T::value_type::first_type, typename T::value_type::second_type>, void>> |
| 3516 | + : std::true_type { |
| 3517 | + using value_type = typename T::value_type; |
| 3518 | + using first_type = typename std::remove_const<typename value_type::first_type>::type; |
| 3519 | + using second_type = typename std::remove_const<typename value_type::second_type>::type; |
| 3520 | + |
| 3521 | + /// Get the first value (really just the underlying value) |
| 3522 | + template <typename Q> static auto first(Q &&pair_value) -> decltype(std::get<0>(std::forward<Q>(pair_value))) { |
| 3523 | + return std::get<0>(std::forward<Q>(pair_value)); |
| 3524 | + } |
| 3525 | + /// Get the second value (really just the underlying value) |
| 3526 | + template <typename Q> static auto second(Q &&pair_value) -> decltype(std::get<1>(std::forward<Q>(pair_value))) { |
| 3527 | + return std::get<1>(std::forward<Q>(pair_value)); |
| 3528 | + } |
| 3529 | +}; |
| 3530 | + |
| 3531 | +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a -Wnarrowing warning |
| 3532 | +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in |
| 3533 | +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a |
| 3534 | +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. |
| 3535 | +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be |
| 3536 | +// suppressed |
| 3537 | +#ifdef __GNUC__ |
| 3538 | +#pragma GCC diagnostic push |
| 3539 | +#pragma GCC diagnostic ignored "-Wnarrowing" |
| 3540 | +#endif |
| 3541 | +// check for constructibility from a specific type and copy assignable used in the parse detection |
| 3542 | +template <typename T, typename C> class is_direct_constructible { |
| 3543 | + template <typename TT, typename CC> |
| 3544 | + static auto test(int, std::true_type) -> decltype( |
| 3545 | +// NVCC warns about narrowing conversions here |
| 3546 | +#ifdef __CUDACC__ |
| 3547 | +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ |
| 3548 | +#pragma nv_diag_suppress 2361 |
| 3549 | +#else |
| 3550 | +#pragma diag_suppress 2361 |
| 3551 | +#endif |
| 3552 | +#endif |
| 3553 | + TT{std::declval<CC>()} |
| 3554 | +#ifdef __CUDACC__ |
| 3555 | +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ |
| 3556 | +#pragma nv_diag_default 2361 |
| 3557 | +#else |
| 3558 | +#pragma diag_default 2361 |
| 3559 | +#endif |
| 3560 | +#endif |
| 3561 | + , |
| 3562 | + std::is_move_assignable<TT>()); |
| 3563 | + |
| 3564 | + template <typename TT, typename CC> static auto test(int, std::false_type) -> std::false_type; |
| 3565 | + |
| 3566 | + template <typename, typename> static auto test(...) -> std::false_type; |
| 3567 | + |
| 3568 | + public: |
| 3569 | + static constexpr bool value = decltype(test<T, C>(0, typename std::is_constructible<T, C>::type()))::value; |
| 3570 | +}; |
| 3571 | +#ifdef __GNUC__ |
| 3572 | +#pragma GCC diagnostic pop |
| 3573 | +#endif |
| 3574 | + |
| 3575 | +// Check for output streamability |
| 3576 | +// Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream |
| 3577 | + |
| 3578 | +template <typename T, typename S = std::ostringstream> class is_ostreamable { |
| 3579 | + template <typename TT, typename SS> |
| 3580 | + static auto test(int) -> decltype(std::declval<SS &>() << std::declval<TT>(), std::true_type()); |
| 3581 | + |
| 3582 | + template <typename, typename> static auto test(...) -> std::false_type; |
| 3583 | + |
| 3584 | + public: |
| 3585 | + static constexpr bool value = decltype(test<T, S>(0))::value; |
| 3586 | +}; |
| 3587 | + |
| 3588 | +/// Check for input streamability |
| 3589 | +template <typename T, typename S = std::istringstream> class is_istreamable { |
| 3590 | + template <typename TT, typename SS> |
| 3591 | + static auto test(int) -> decltype(std::declval<SS &>() >> std::declval<TT &>(), std::true_type()); |
| 3592 | + |
| 3593 | + template <typename, typename> static auto test(...) -> std::false_type; |
| 3594 | + |
| 3595 | + public: |
| 3596 | + static constexpr bool value = decltype(test<T, S>(0))::value; |
| 3597 | +}; |
| 3598 | + |
| 3599 | +/// Check for complex |
| 3600 | +template <typename T> class is_complex { |
| 3601 | + template <typename TT> |
| 3602 | + static auto test(int) -> decltype(std::declval<TT>().real(), std::declval<TT>().imag(), std::true_type()); |
| 3603 | + |
| 3604 | + template <typename> static auto test(...) -> std::false_type; |
| 3605 | + |
| 3606 | + public: |
| 3607 | + static constexpr bool value = decltype(test<T>(0))::value; |
| 3608 | +}; |
| 3609 | + |
| 3610 | +/// Templated operation to get a value from a stream |
| 3611 | +template <typename T, enable_if_t<is_istreamable<T>::value, detail::enabler> = detail::dummy> |
| 3612 | +bool from_stream(const std::string &istring, T &obj) { |
| 3613 | + std::istringstream is; |
| 3614 | + is.str(istring); |
| 3615 | + is >> obj; |
| 3616 | + return !is.fail() && !is.rdbuf()->in_avail(); |
| 3617 | +} |
| 3618 | + |
| 3619 | +template <typename T, enable_if_t<!is_istreamable<T>::value, detail::enabler> = detail::dummy> |
| 3620 | +bool from_stream(const std::string & /*istring*/, T & /*obj*/) { |
| 3621 | + return false; |
| 3622 | +} |
| 3623 | + |
| 3624 | +// check to see if an object is a mutable container (fail by default) |
| 3625 | +template <typename T, typename _ = void> struct is_mutable_container : std::false_type {}; |
| 3626 | + |
| 3627 | +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and |
| 3628 | +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed |
| 3629 | +/// from a std::string |
| 3630 | +template <typename T> |
| 3631 | +struct is_mutable_container< |
| 3632 | + T, |
| 3633 | + conditional_t<false, |
| 3634 | + void_t<typename T::value_type, |
| 3635 | + decltype(std::declval<T>().end()), |
| 3636 | + decltype(std::declval<T>().clear()), |
| 3637 | + decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(), |
| 3638 | + std::declval<const typename T::value_type &>()))>, |
| 3639 | + void>> : public conditional_t<std::is_constructible<T, std::string>::value || |
| 3640 | + std::is_constructible<T, std::wstring>::value, |
| 3641 | + std::false_type, |
| 3642 | + std::true_type> {}; |
| 3643 | + |
| 3644 | +// check to see if an object is a mutable container (fail by default) |
| 3645 | +template <typename T, typename _ = void> struct is_readable_container : std::false_type {}; |
| 3646 | + |
| 3647 | +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, and an end |
| 3648 | +/// method. |
| 3649 | +template <typename T> |
| 3650 | +struct is_readable_container< |
| 3651 | + T, |
| 3652 | + conditional_t<false, void_t<decltype(std::declval<T>().end()), decltype(std::declval<T>().begin())>, void>> |
| 3653 | + : public std::true_type {}; |
| 3654 | + |
| 3655 | +// check to see if an object is a wrapper (fail by default) |
| 3656 | +template <typename T, typename _ = void> struct is_wrapper : std::false_type {}; |
| 3657 | + |
| 3658 | +// check if an object is a wrapper (it has a value_type defined) |
| 3659 | +template <typename T> |
| 3660 | +struct is_wrapper<T, conditional_t<false, void_t<typename T::value_type>, void>> : public std::true_type {}; |
| 3661 | + |
| 3662 | +// Check for tuple like types, as in classes with a tuple_size type trait |
| 3663 | +// Even though in C++26 std::complex gains a std::tuple interface, for our purposes we treat is as NOT a tuple |
| 3664 | +template <typename S> class is_tuple_like { |
| 3665 | + template <typename SS, enable_if_t<!is_complex<SS>::value, detail::enabler> = detail::dummy> |
| 3666 | + // static auto test(int) |
| 3667 | + // -> decltype(std::conditional<(std::tuple_size<SS>::value > 0), std::true_type, std::false_type>::type()); |
| 3668 | + static auto test(int) -> decltype(std::tuple_size<typename std::decay<SS>::type>::value, std::true_type{}); |
| 3669 | + template <typename> static auto test(...) -> std::false_type; |
| 3670 | + |
| 3671 | + public: |
| 3672 | + static constexpr bool value = decltype(test<S>(0))::value; |
| 3673 | +}; |
| 3674 | + |
| 3675 | +/// This will only trigger for actual void type |
| 3676 | +template <typename T, typename Enable = void> struct type_count_base { |
| 3677 | + static const int value{0}; |
| 3678 | +}; |
| 3679 | + |
| 3680 | +/// Type size for regular object types that do not look like a tuple |
| 3681 | +template <typename T> |
| 3682 | +struct type_count_base<T, |
| 3683 | + typename std::enable_if<!is_tuple_like<T>::value && !is_mutable_container<T>::value && |
| 3684 | + !std::is_void<T>::value>::type> { |
| 3685 | + static constexpr int value{1}; |
| 3686 | +}; |
| 3687 | + |
| 3688 | +/// the base tuple size |
| 3689 | +template <typename T> |
| 3690 | +struct type_count_base<T, typename std::enable_if<is_tuple_like<T>::value && !is_mutable_container<T>::value>::type> { |
| 3691 | + static constexpr int value{// cppcheck-suppress unusedStructMember |
| 3692 | + std::tuple_size<typename std::decay<T>::type>::value}; |
| 3693 | +}; |
| 3694 | + |
| 3695 | +/// Type count base for containers is the type_count_base of the individual element |
| 3696 | +template <typename T> struct type_count_base<T, typename std::enable_if<is_mutable_container<T>::value>::type> { |
| 3697 | + static constexpr int value{type_count_base<typename T::value_type>::value}; |
| 3698 | +}; |
| 3699 | + |
| 3700 | +/// Convert an object to a string (directly forward if this can become a string) |
| 3701 | +template <typename T, enable_if_t<std::is_convertible<T, std::string>::value, detail::enabler> = detail::dummy> |
| 3702 | +auto to_string(T &&value) -> decltype(std::forward<T>(value)) { |
| 3703 | + return std::forward<T>(value); |
| 3704 | +} |
| 3705 | + |
| 3706 | +/// Construct a string from the object |
| 3707 | +template <typename T, |
| 3708 | + enable_if_t<std::is_constructible<std::string, T>::value && !std::is_convertible<T, std::string>::value, |
| 3709 | + detail::enabler> = detail::dummy> |
| 3710 | +std::string to_string(T &&value) { |
| 3711 | + return std::string(value); // NOLINT(google-readability-casting) |
| 3712 | +} |
| 3713 | + |
| 3714 | +/// Convert an object to a string (streaming must be supported for that type) |
| 3715 | +template <typename T, |
| 3716 | + enable_if_t<!std::is_convertible<T, std::string>::value && !std::is_constructible<std::string, T>::value && |
| 3717 | + is_ostreamable<T>::value, |
| 3718 | + detail::enabler> = detail::dummy> |
| 3719 | +std::string to_string(T &&value) { |
| 3720 | + std::stringstream stream; |
| 3721 | + stream << value; |
| 3722 | + return stream.str(); |
| 3723 | +} |
| 3724 | + |
| 3725 | +// additional forward declarations |
| 3726 | + |
| 3727 | +/// Print tuple value string for tuples of size ==1 |
| 3728 | +template <typename T, |
| 3729 | + enable_if_t<!std::is_convertible<T, std::string>::value && !std::is_constructible<std::string, T>::value && |
| 3730 | + !is_ostreamable<T>::value && is_tuple_like<T>::value && type_count_base<T>::value == 1, |
| 3731 | + detail::enabler> = detail::dummy> |
| 3732 | +inline std::string to_string(T &&value); |
| 3733 | + |
| 3734 | +/// Print tuple value string for tuples of size > 1 |
| 3735 | +template <typename T, |
| 3736 | + enable_if_t<!std::is_convertible<T, std::string>::value && !std::is_constructible<std::string, T>::value && |
| 3737 | + !is_ostreamable<T>::value && is_tuple_like<T>::value && type_count_base<T>::value >= 2, |
| 3738 | + detail::enabler> = detail::dummy> |
| 3739 | +inline std::string to_string(T &&value); |
| 3740 | + |
| 3741 | +/// If conversion is not supported, return an empty string (streaming is not supported for that type) |
| 3742 | +template < |
| 3743 | + typename T, |
| 3744 | + enable_if_t<!std::is_convertible<T, std::string>::value && !std::is_constructible<std::string, T>::value && |
| 3745 | + !is_ostreamable<T>::value && !is_readable_container<typename std::remove_const<T>::type>::value && |
| 3746 | + !is_tuple_like<T>::value, |
| 3747 | + detail::enabler> = detail::dummy> |
| 3748 | +inline std::string to_string(T &&) { |
| 3749 | + return {}; |
| 3750 | +} |
| 3751 | + |
| 3752 | +/// convert a readable container to a string |
| 3753 | +template <typename T, |
| 3754 | + enable_if_t<!std::is_convertible<T, std::string>::value && !std::is_constructible<std::string, T>::value && |
| 3755 | + !is_ostreamable<T>::value && is_readable_container<T>::value && !is_tuple_like<T>::value, |
| 3756 | + detail::enabler> = detail::dummy> |
| 3757 | +inline std::string to_string(T &&variable) { |
| 3758 | + auto cval = variable.begin(); |
| 3759 | + auto end = variable.end(); |
| 3760 | + if(cval == end) { |
| 3761 | + return {"{}"}; |
| 3762 | + } |
| 3763 | + std::vector<std::string> defaults; |
| 3764 | + while(cval != end) { |
| 3765 | + defaults.emplace_back(CLI::detail::to_string(*cval)); |
| 3766 | + ++cval; |
| 3767 | + } |
| 3768 | + return {"[" + detail::join(defaults) + "]"}; |
| 3769 | +} |
| 3770 | + |
| 3771 | +/// Convert a tuple like object to a string |
| 3772 | + |
| 3773 | +/// forward declarations for tuple_value_strings |
| 3774 | +template <typename T, std::size_t I> |
| 3775 | +inline typename std::enable_if<I == type_count_base<T>::value, std::string>::type tuple_value_string(T && /*value*/); |
| 3776 | + |
| 3777 | +/// Recursively generate the tuple value string |
| 3778 | +template <typename T, std::size_t I> |
| 3779 | +inline typename std::enable_if<(I < type_count_base<T>::value), std::string>::type tuple_value_string(T &&value); |
| 3780 | + |
| 3781 | +/// Print tuple value string for tuples of size ==1 |
| 3782 | +template <typename T, |
| 3783 | + enable_if_t<!std::is_convertible<T, std::string>::value && !std::is_constructible<std::string, T>::value && |
| 3784 | + !is_ostreamable<T>::value && is_tuple_like<T>::value && type_count_base<T>::value == 1, |
| 3785 | + detail::enabler>> |
| 3786 | +inline std::string to_string(T &&value) { |
| 3787 | + return to_string(std::get<0>(value)); |
| 3788 | +} |
| 3789 | + |
| 3790 | +/// Print tuple value string for tuples of size > 1 |
| 3791 | +template <typename T, |
| 3792 | + enable_if_t<!std::is_convertible<T, std::string>::value && !std::is_constructible<std::string, T>::value && |
| 3793 | + !is_ostreamable<T>::value && is_tuple_like<T>::value && type_count_base<T>::value >= 2, |
| 3794 | + detail::enabler>> |
| 3795 | +inline std::string to_string(T &&value) { |
| 3796 | + auto tname = std::string(1, '[') + tuple_value_string<T, 0>(value); |
| 3797 | + tname.push_back(']'); |
| 3798 | + return tname; |
| 3799 | +} |
| 3800 | + |
| 3801 | +/// Empty string if the index > tuple size |
| 3802 | +template <typename T, std::size_t I> |
| 3803 | +inline typename std::enable_if<I == type_count_base<T>::value, std::string>::type tuple_value_string(T && /*value*/) { |
| 3804 | + return std::string{}; |
| 3805 | +} |
| 3806 | + |
| 3807 | +/// Recursively generate the tuple value string |
| 3808 | +template <typename T, std::size_t I> |
| 3809 | +inline typename std::enable_if<(I < type_count_base<T>::value), std::string>::type tuple_value_string(T &&value) { |
| 3810 | + auto str = std::string{to_string(std::get<I>(value))} + ',' + tuple_value_string<T, I + 1>(value); |
| 3811 | + if(str.back() == ',') |
| 3812 | + str.pop_back(); |
| 3813 | + return str; |
| 3814 | +} |
| 3815 | + |
| 3816 | +/// special template overload |
| 3817 | +template <typename T1, |
| 3818 | + typename T2, |
| 3819 | + typename T, |
| 3820 | + enable_if_t<std::is_same<T1, T2>::value, detail::enabler> = detail::dummy> |
| 3821 | +auto checked_to_string(T &&value) -> decltype(to_string(std::forward<T>(value))) { |
| 3822 | + return to_string(std::forward<T>(value)); |
| 3823 | +} |
| 3824 | + |
| 3825 | +/// special template overload |
| 3826 | +template <typename T1, |
| 3827 | + typename T2, |
| 3828 | + typename T, |
| 3829 | + enable_if_t<!std::is_same<T1, T2>::value, detail::enabler> = detail::dummy> |
| 3830 | +std::string checked_to_string(T &&) { |
| 3831 | + return std::string{}; |
| 3832 | +} |
| 3833 | +/// get a string as a convertible value for arithmetic types |
| 3834 | +template <typename T, enable_if_t<std::is_arithmetic<T>::value, detail::enabler> = detail::dummy> |
| 3835 | +std::string value_string(const T &value) { |
| 3836 | + return std::to_string(value); |
| 3837 | +} |
| 3838 | +/// get a string as a convertible value for enumerations |
| 3839 | +template <typename T, enable_if_t<std::is_enum<T>::value, detail::enabler> = detail::dummy> |
| 3840 | +std::string value_string(const T &value) { |
| 3841 | + return std::to_string(static_cast<typename std::underlying_type<T>::type>(value)); |
| 3842 | +} |
| 3843 | +/// for other types just use the regular to_string function |
| 3844 | +template <typename T, |
| 3845 | + enable_if_t<!std::is_enum<T>::value && !std::is_arithmetic<T>::value, detail::enabler> = detail::dummy> |
| 3846 | +auto value_string(const T &value) -> decltype(to_string(value)) { |
| 3847 | + return to_string(value); |
| 3848 | +} |
| 3849 | + |
| 3850 | +/// template to get the underlying value type if it exists or use a default |
| 3851 | +template <typename T, typename def, typename Enable = void> struct wrapped_type { |
| 3852 | + using type = def; |
| 3853 | +}; |
| 3854 | + |
| 3855 | +/// Type size for regular object types that do not look like a tuple |
| 3856 | +template <typename T, typename def> struct wrapped_type<T, def, typename std::enable_if<is_wrapper<T>::value>::type> { |
| 3857 | + using type = typename T::value_type; |
| 3858 | +}; |
| 3859 | + |
| 3860 | +/// Set of overloads to get the type size of an object |
| 3861 | + |
| 3862 | +/// forward declare the subtype_count structure |
| 3863 | +template <typename T> struct subtype_count; |
| 3864 | + |
| 3865 | +/// forward declare the subtype_count_min structure |
| 3866 | +template <typename T> struct subtype_count_min; |
| 3867 | + |
| 3868 | +/// This will only trigger for actual void type |
| 3869 | +template <typename T, typename Enable = void> struct type_count { |
| 3870 | + static const int value{0}; |
| 3871 | +}; |
| 3872 | + |
| 3873 | +/// Type size for regular object types that do not look like a tuple |
| 3874 | +template <typename T> |
| 3875 | +struct type_count<T, |
| 3876 | + typename std::enable_if<!is_wrapper<T>::value && !is_tuple_like<T>::value && !is_complex<T>::value && |
| 3877 | + !std::is_void<T>::value>::type> { |
| 3878 | + static constexpr int value{1}; |
| 3879 | +}; |
| 3880 | + |
| 3881 | +/// Type size for complex since it sometimes looks like a wrapper |
| 3882 | +template <typename T> struct type_count<T, typename std::enable_if<is_complex<T>::value>::type> { |
| 3883 | + static constexpr int value{2}; |
| 3884 | +}; |
| 3885 | + |
| 3886 | +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) |
| 3887 | +template <typename T> struct type_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> { |
| 3888 | + static constexpr int value{subtype_count<typename T::value_type>::value}; |
| 3889 | +}; |
| 3890 | + |
| 3891 | +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) |
| 3892 | +template <typename T> |
| 3893 | +struct type_count<T, |
| 3894 | + typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value && |
| 3895 | + !is_mutable_container<T>::value>::type> { |
| 3896 | + static constexpr int value{type_count<typename T::value_type>::value}; |
| 3897 | +}; |
| 3898 | + |
| 3899 | +/// 0 if the index > tuple size |
| 3900 | +template <typename T, std::size_t I> |
| 3901 | +constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size() { |
| 3902 | + return 0; |
| 3903 | +} |
| 3904 | + |
| 3905 | +/// Recursively generate the tuple type name |
| 3906 | +template <typename T, std::size_t I> |
| 3907 | + constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size() { |
| 3908 | + return subtype_count<typename std::tuple_element<I, T>::type>::value + tuple_type_size<T, I + 1>(); |
| 3909 | +} |
| 3910 | + |
| 3911 | +/// Get the type size of the sum of type sizes for all the individual tuple types |
| 3912 | +template <typename T> |
| 3913 | +struct type_count<T, typename std::enable_if<is_tuple_like<T>::value && !is_complex<T>::value>::type> { |
| 3914 | + static constexpr int value{tuple_type_size<T, 0>()}; |
| 3915 | +}; |
| 3916 | + |
| 3917 | +/// definition of subtype count |
| 3918 | +template <typename T> struct subtype_count { |
| 3919 | + static constexpr int value{is_mutable_container<T>::value ? expected_max_vector_size : type_count<T>::value}; |
| 3920 | +}; |
| 3921 | + |
| 3922 | +/// This will only trigger for actual void type |
| 3923 | +template <typename T, typename Enable = void> struct type_count_min { |
| 3924 | + static const int value{0}; |
| 3925 | +}; |
| 3926 | + |
| 3927 | +/// Type size for regular object types that do not look like a tuple |
| 3928 | +template <typename T> |
| 3929 | +struct type_count_min< |
| 3930 | + T, |
| 3931 | + typename std::enable_if<!is_mutable_container<T>::value && !is_tuple_like<T>::value && !is_wrapper<T>::value && |
| 3932 | + !is_complex<T>::value && !std::is_void<T>::value>::type> { |
| 3933 | + static constexpr int value{type_count<T>::value}; |
| 3934 | +}; |
| 3935 | + |
| 3936 | +/// Type size for complex since it sometimes looks like a wrapper |
| 3937 | +template <typename T> struct type_count_min<T, typename std::enable_if<is_complex<T>::value>::type> { |
| 3938 | + static constexpr int value{1}; |
| 3939 | +}; |
| 3940 | + |
| 3941 | +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) |
| 3942 | +template <typename T> |
| 3943 | +struct type_count_min< |
| 3944 | + T, |
| 3945 | + typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value>::type> { |
| 3946 | + static constexpr int value{subtype_count_min<typename T::value_type>::value}; |
| 3947 | +}; |
| 3948 | + |
| 3949 | +/// 0 if the index > tuple size |
| 3950 | +template <typename T, std::size_t I> |
| 3951 | +constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size_min() { |
| 3952 | + return 0; |
| 3953 | +} |
| 3954 | + |
| 3955 | +/// Recursively generate the tuple type name |
| 3956 | +template <typename T, std::size_t I> |
| 3957 | + constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size_min() { |
| 3958 | + return subtype_count_min<typename std::tuple_element<I, T>::type>::value + tuple_type_size_min<T, I + 1>(); |
| 3959 | +} |
| 3960 | + |
| 3961 | +/// Get the type size of the sum of type sizes for all the individual tuple types |
| 3962 | +template <typename T> |
| 3963 | +struct type_count_min<T, typename std::enable_if<is_tuple_like<T>::value && !is_complex<T>::value>::type> { |
| 3964 | + static constexpr int value{tuple_type_size_min<T, 0>()}; |
| 3965 | +}; |
| 3966 | + |
| 3967 | +/// definition of subtype count |
| 3968 | +template <typename T> struct subtype_count_min { |
| 3969 | + static constexpr int value{is_mutable_container<T>::value |
| 3970 | + ? ((type_count<T>::value < expected_max_vector_size) ? type_count<T>::value : 0) |
| 3971 | + : type_count_min<T>::value}; |
| 3972 | +}; |
| 3973 | + |
| 3974 | +/// This will only trigger for actual void type |
| 3975 | +template <typename T, typename Enable = void> struct expected_count { |
| 3976 | + static const int value{0}; |
| 3977 | +}; |
| 3978 | + |
| 3979 | +/// For most types the number of expected items is 1 |
| 3980 | +template <typename T> |
| 3981 | +struct expected_count<T, |
| 3982 | + typename std::enable_if<!is_mutable_container<T>::value && !is_wrapper<T>::value && |
| 3983 | + !std::is_void<T>::value>::type> { |
| 3984 | + static constexpr int value{1}; |
| 3985 | +}; |
| 3986 | +/// number of expected items in a vector |
| 3987 | +template <typename T> struct expected_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> { |
| 3988 | + static constexpr int value{expected_max_vector_size}; |
| 3989 | +}; |
| 3990 | + |
| 3991 | +/// number of expected items in a vector |
| 3992 | +template <typename T> |
| 3993 | +struct expected_count<T, typename std::enable_if<!is_mutable_container<T>::value && is_wrapper<T>::value>::type> { |
| 3994 | + static constexpr int value{expected_count<typename T::value_type>::value}; |
| 3995 | +}; |
| 3996 | + |
| 3997 | +// Enumeration of the different supported categorizations of objects |
| 3998 | +enum class object_category : std::uint8_t { |
| 3999 | + char_value = 1, |
| 4000 | + integral_value = 2, |
| 4001 | + unsigned_integral = 4, |
| 4002 | + enumeration = 6, |
| 4003 | + boolean_value = 8, |
| 4004 | + floating_point = 10, |
| 4005 | + number_constructible = 12, |
| 4006 | + double_constructible = 14, |
| 4007 | + integer_constructible = 16, |
| 4008 | + // string like types |
| 4009 | + string_assignable = 23, |
| 4010 | + string_constructible = 24, |
| 4011 | + wstring_assignable = 25, |
| 4012 | + wstring_constructible = 26, |
| 4013 | + other = 45, |
| 4014 | + // special wrapper or container types |
| 4015 | + wrapper_value = 50, |
| 4016 | + complex_number = 60, |
| 4017 | + tuple_value = 70, |
| 4018 | + container_value = 80, |
| 4019 | + |
| 4020 | +}; |
| 4021 | + |
| 4022 | +/// Set of overloads to classify an object according to type |
| 4023 | + |
| 4024 | +/// some type that is not otherwise recognized |
| 4025 | +template <typename T, typename Enable = void> struct classify_object { |
| 4026 | + static constexpr object_category value{object_category::other}; |
| 4027 | +}; |
| 4028 | + |
| 4029 | +/// Signed integers |
| 4030 | +template <typename T> |
| 4031 | +struct classify_object< |
| 4032 | + T, |
| 4033 | + typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, char>::value && std::is_signed<T>::value && |
| 4034 | + !is_bool<T>::value && !std::is_enum<T>::value>::type> { |
| 4035 | + static constexpr object_category value{object_category::integral_value}; |
| 4036 | +}; |
| 4037 | + |
| 4038 | +/// Unsigned integers |
| 4039 | +template <typename T> |
| 4040 | +struct classify_object<T, |
| 4041 | + typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value && |
| 4042 | + !std::is_same<T, char>::value && !is_bool<T>::value>::type> { |
| 4043 | + static constexpr object_category value{object_category::unsigned_integral}; |
| 4044 | +}; |
| 4045 | + |
| 4046 | +/// single character values |
| 4047 | +template <typename T> |
| 4048 | +struct classify_object<T, typename std::enable_if<std::is_same<T, char>::value && !std::is_enum<T>::value>::type> { |
| 4049 | + static constexpr object_category value{object_category::char_value}; |
| 4050 | +}; |
| 4051 | + |
| 4052 | +/// Boolean values |
| 4053 | +template <typename T> struct classify_object<T, typename std::enable_if<is_bool<T>::value>::type> { |
| 4054 | + static constexpr object_category value{object_category::boolean_value}; |
| 4055 | +}; |
| 4056 | + |
| 4057 | +/// Floats |
| 4058 | +template <typename T> struct classify_object<T, typename std::enable_if<std::is_floating_point<T>::value>::type> { |
| 4059 | + static constexpr object_category value{object_category::floating_point}; |
| 4060 | +}; |
| 4061 | +#if defined _MSC_VER |
| 4062 | +// in MSVC wstring should take precedence if available this isn't as useful on other compilers due to the broader use of |
| 4063 | +// utf-8 encoding |
| 4064 | +#define WIDE_STRING_CHECK \ |
| 4065 | + !std::is_assignable<T &, std::wstring>::value && !std::is_constructible<T, std::wstring>::value |
| 4066 | +#define STRING_CHECK true |
| 4067 | +#else |
| 4068 | +#define WIDE_STRING_CHECK true |
| 4069 | +#define STRING_CHECK !std::is_assignable<T &, std::string>::value && !std::is_constructible<T, std::string>::value |
| 4070 | +#endif |
| 4071 | + |
| 4072 | +/// String and similar direct assignment |
| 4073 | +template <typename T> |
| 4074 | +struct classify_object< |
| 4075 | + T, |
| 4076 | + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && WIDE_STRING_CHECK && |
| 4077 | + std::is_assignable<T &, std::string>::value>::type> { |
| 4078 | + static constexpr object_category value{object_category::string_assignable}; |
| 4079 | +}; |
| 4080 | + |
| 4081 | +/// String and similar constructible and copy assignment |
| 4082 | +template <typename T> |
| 4083 | +struct classify_object< |
| 4084 | + T, |
| 4085 | + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && |
| 4086 | + !std::is_assignable<T &, std::string>::value && (type_count<T>::value == 1) && |
| 4087 | + WIDE_STRING_CHECK && std::is_constructible<T, std::string>::value>::type> { |
| 4088 | + static constexpr object_category value{object_category::string_constructible}; |
| 4089 | +}; |
| 4090 | + |
| 4091 | +/// Wide strings |
| 4092 | +template <typename T> |
| 4093 | +struct classify_object<T, |
| 4094 | + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && |
| 4095 | + STRING_CHECK && std::is_assignable<T &, std::wstring>::value>::type> { |
| 4096 | + static constexpr object_category value{object_category::wstring_assignable}; |
| 4097 | +}; |
| 4098 | + |
| 4099 | +template <typename T> |
| 4100 | +struct classify_object< |
| 4101 | + T, |
| 4102 | + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && |
| 4103 | + !std::is_assignable<T &, std::wstring>::value && (type_count<T>::value == 1) && |
| 4104 | + STRING_CHECK && std::is_constructible<T, std::wstring>::value>::type> { |
| 4105 | + static constexpr object_category value{object_category::wstring_constructible}; |
| 4106 | +}; |
| 4107 | + |
| 4108 | +/// Enumerations |
| 4109 | +template <typename T> struct classify_object<T, typename std::enable_if<std::is_enum<T>::value>::type> { |
| 4110 | + static constexpr object_category value{object_category::enumeration}; |
| 4111 | +}; |
| 4112 | + |
| 4113 | +template <typename T> struct classify_object<T, typename std::enable_if<is_complex<T>::value>::type> { |
| 4114 | + static constexpr object_category value{object_category::complex_number}; |
| 4115 | +}; |
| 4116 | + |
| 4117 | +/// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, |
| 4118 | +/// vectors, and enumerations |
| 4119 | +template <typename T> struct uncommon_type { |
| 4120 | + using type = typename std::conditional< |
| 4121 | + !std::is_floating_point<T>::value && !std::is_integral<T>::value && |
| 4122 | + !std::is_assignable<T &, std::string>::value && !std::is_constructible<T, std::string>::value && |
| 4123 | + !std::is_assignable<T &, std::wstring>::value && !std::is_constructible<T, std::wstring>::value && |
| 4124 | + !is_complex<T>::value && !is_mutable_container<T>::value && !std::is_enum<T>::value, |
| 4125 | + std::true_type, |
| 4126 | + std::false_type>::type; |
| 4127 | + static constexpr bool value = type::value; |
| 4128 | +}; |
| 4129 | + |
| 4130 | +/// wrapper type |
| 4131 | +template <typename T> |
| 4132 | +struct classify_object<T, |
| 4133 | + typename std::enable_if<(!is_mutable_container<T>::value && is_wrapper<T>::value && |
| 4134 | + !is_tuple_like<T>::value && uncommon_type<T>::value)>::type> { |
| 4135 | + static constexpr object_category value{object_category::wrapper_value}; |
| 4136 | +}; |
| 4137 | + |
| 4138 | +/// Assignable from double or int |
| 4139 | +template <typename T> |
| 4140 | +struct classify_object<T, |
| 4141 | + typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 && |
| 4142 | + !is_wrapper<T>::value && is_direct_constructible<T, double>::value && |
| 4143 | + is_direct_constructible<T, int>::value>::type> { |
| 4144 | + static constexpr object_category value{object_category::number_constructible}; |
| 4145 | +}; |
| 4146 | + |
| 4147 | +/// Assignable from int |
| 4148 | +template <typename T> |
| 4149 | +struct classify_object<T, |
| 4150 | + typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 && |
| 4151 | + !is_wrapper<T>::value && !is_direct_constructible<T, double>::value && |
| 4152 | + is_direct_constructible<T, int>::value>::type> { |
| 4153 | + static constexpr object_category value{object_category::integer_constructible}; |
| 4154 | +}; |
| 4155 | + |
| 4156 | +/// Assignable from double |
| 4157 | +template <typename T> |
| 4158 | +struct classify_object<T, |
| 4159 | + typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 && |
| 4160 | + !is_wrapper<T>::value && is_direct_constructible<T, double>::value && |
| 4161 | + !is_direct_constructible<T, int>::value>::type> { |
| 4162 | + static constexpr object_category value{object_category::double_constructible}; |
| 4163 | +}; |
| 4164 | + |
| 4165 | +/// Tuple type |
| 4166 | +template <typename T> |
| 4167 | +struct classify_object< |
| 4168 | + T, |
| 4169 | + typename std::enable_if<is_tuple_like<T>::value && |
| 4170 | + ((type_count<T>::value >= 2 && !is_wrapper<T>::value) || |
| 4171 | + (uncommon_type<T>::value && !is_direct_constructible<T, double>::value && |
| 4172 | + !is_direct_constructible<T, int>::value) || |
| 4173 | + (uncommon_type<T>::value && type_count<T>::value >= 2))>::type> { |
| 4174 | + static constexpr object_category value{object_category::tuple_value}; |
| 4175 | + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be |
| 4176 | + // constructed from just the first element so tuples of <string, int,int> can be constructed from a string, which |
| 4177 | + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 |
| 4178 | + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out |
| 4179 | + // those cases that are caught by other object classifications |
| 4180 | +}; |
| 4181 | + |
| 4182 | +/// container type |
| 4183 | +template <typename T> struct classify_object<T, typename std::enable_if<is_mutable_container<T>::value>::type> { |
| 4184 | + static constexpr object_category value{object_category::container_value}; |
| 4185 | +}; |
| 4186 | + |
| 4187 | +// Type name print |
| 4188 | + |
| 4189 | +/// Was going to be based on |
| 4190 | +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template |
| 4191 | +/// But this is cleaner and works better in this case |
| 4192 | + |
| 4193 | +template <typename T, |
| 4194 | + enable_if_t<classify_object<T>::value == object_category::char_value, detail::enabler> = detail::dummy> |
| 4195 | +constexpr const char *type_name() { |
| 4196 | + return "CHAR"; |
| 4197 | +} |
| 4198 | + |
| 4199 | +template <typename T, |
| 4200 | + enable_if_t<classify_object<T>::value == object_category::integral_value || |
| 4201 | + classify_object<T>::value == object_category::integer_constructible, |
| 4202 | + detail::enabler> = detail::dummy> |
| 4203 | +constexpr const char *type_name() { |
| 4204 | + return "INT"; |
| 4205 | +} |
| 4206 | + |
| 4207 | +template <typename T, |
| 4208 | + enable_if_t<classify_object<T>::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> |
| 4209 | +constexpr const char *type_name() { |
| 4210 | + return "UINT"; |
| 4211 | +} |
| 4212 | + |
| 4213 | +template <typename T, |
| 4214 | + enable_if_t<classify_object<T>::value == object_category::floating_point || |
| 4215 | + classify_object<T>::value == object_category::number_constructible || |
| 4216 | + classify_object<T>::value == object_category::double_constructible, |
| 4217 | + detail::enabler> = detail::dummy> |
| 4218 | +constexpr const char *type_name() { |
| 4219 | + return "FLOAT"; |
| 4220 | +} |
| 4221 | + |
| 4222 | +/// Print name for enumeration types |
| 4223 | +template <typename T, |
| 4224 | + enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy> |
| 4225 | +constexpr const char *type_name() { |
| 4226 | + return "ENUM"; |
| 4227 | +} |
| 4228 | + |
| 4229 | +/// Print name for enumeration types |
| 4230 | +template <typename T, |
| 4231 | + enable_if_t<classify_object<T>::value == object_category::boolean_value, detail::enabler> = detail::dummy> |
| 4232 | +constexpr const char *type_name() { |
| 4233 | + return "BOOLEAN"; |
| 4234 | +} |
| 4235 | + |
| 4236 | +/// Print name for enumeration types |
| 4237 | +template <typename T, |
| 4238 | + enable_if_t<classify_object<T>::value == object_category::complex_number, detail::enabler> = detail::dummy> |
| 4239 | +constexpr const char *type_name() { |
| 4240 | + return "COMPLEX"; |
| 4241 | +} |
| 4242 | + |
| 4243 | +/// Print for all other types |
| 4244 | +template <typename T, |
| 4245 | + enable_if_t<classify_object<T>::value >= object_category::string_assignable && |
| 4246 | + classify_object<T>::value <= object_category::other, |
| 4247 | + detail::enabler> = detail::dummy> |
| 4248 | +constexpr const char *type_name() { |
| 4249 | + return "TEXT"; |
| 4250 | +} |
| 4251 | +/// typename for tuple value |
| 4252 | +template <typename T, |
| 4253 | + enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value >= 2, |
| 4254 | + detail::enabler> = detail::dummy> |
| 4255 | +std::string type_name(); // forward declaration |
| 4256 | + |
| 4257 | +/// Generate type name for a wrapper or container value |
| 4258 | +template <typename T, |
| 4259 | + enable_if_t<classify_object<T>::value == object_category::container_value || |
| 4260 | + classify_object<T>::value == object_category::wrapper_value, |
| 4261 | + detail::enabler> = detail::dummy> |
| 4262 | +std::string type_name(); // forward declaration |
| 4263 | + |
| 4264 | +/// Print name for single element tuple types |
| 4265 | +template <typename T, |
| 4266 | + enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value == 1, |
| 4267 | + detail::enabler> = detail::dummy> |
| 4268 | +inline std::string type_name() { |
| 4269 | + return type_name<typename std::decay<typename std::tuple_element<0, T>::type>::type>(); |
| 4270 | +} |
| 4271 | + |
| 4272 | +/// Empty string if the index > tuple size |
| 4273 | +template <typename T, std::size_t I> |
| 4274 | +inline typename std::enable_if<I == type_count_base<T>::value, std::string>::type tuple_name() { |
| 4275 | + return std::string{}; |
| 4276 | +} |
| 4277 | + |
| 4278 | +/// Recursively generate the tuple type name |
| 4279 | +template <typename T, std::size_t I> |
| 4280 | +inline typename std::enable_if<(I < type_count_base<T>::value), std::string>::type tuple_name() { |
| 4281 | + auto str = std::string{type_name<typename std::decay<typename std::tuple_element<I, T>::type>::type>()} + ',' + |
| 4282 | + tuple_name<T, I + 1>(); |
| 4283 | + if(str.back() == ',') |
| 4284 | + str.pop_back(); |
| 4285 | + return str; |
| 4286 | +} |
| 4287 | + |
| 4288 | +/// Print type name for tuples with 2 or more elements |
| 4289 | +template <typename T, |
| 4290 | + enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value >= 2, |
| 4291 | + detail::enabler>> |
| 4292 | +inline std::string type_name() { |
| 4293 | + auto tname = std::string(1, '[') + tuple_name<T, 0>(); |
| 4294 | + tname.push_back(']'); |
| 4295 | + return tname; |
| 4296 | +} |
| 4297 | + |
| 4298 | +/// get the type name for a type that has a value_type member |
| 4299 | +template <typename T, |
| 4300 | + enable_if_t<classify_object<T>::value == object_category::container_value || |
| 4301 | + classify_object<T>::value == object_category::wrapper_value, |
| 4302 | + detail::enabler>> |
| 4303 | +inline std::string type_name() { |
| 4304 | + return type_name<typename T::value_type>(); |
| 4305 | +} |
| 4306 | + |
| 4307 | +// Lexical cast |
| 4308 | + |
| 4309 | +/// Convert to an unsigned integral |
| 4310 | +template <typename T, enable_if_t<std::is_unsigned<T>::value, detail::enabler> = detail::dummy> |
| 4311 | +bool integral_conversion(const std::string &input, T &output) noexcept { |
| 4312 | + if(input.empty() || input.front() == '-') { |
| 4313 | + return false; |
| 4314 | + } |
| 4315 | + char *val{nullptr}; |
| 4316 | + errno = 0; |
| 4317 | + std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); |
| 4318 | + if(errno == ERANGE) { |
| 4319 | + return false; |
| 4320 | + } |
| 4321 | + output = static_cast<T>(output_ll); |
| 4322 | + if(val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll) { |
| 4323 | + return true; |
| 4324 | + } |
| 4325 | + val = nullptr; |
| 4326 | + std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0); |
| 4327 | + if(val == (input.c_str() + input.size())) { |
| 4328 | + output = (output_sll < 0) ? static_cast<T>(0) : static_cast<T>(output_sll); |
| 4329 | + return (static_cast<std::int64_t>(output) == output_sll); |
| 4330 | + } |
| 4331 | + // remove separators if present |
| 4332 | + auto group_separators = get_group_separators(); |
| 4333 | + if(input.find_first_of(group_separators) != std::string::npos) { |
| 4334 | + std::string nstring = input; |
| 4335 | + for(auto &separator : group_separators) { |
| 4336 | + if(input.find_first_of(separator) != std::string::npos) { |
| 4337 | + nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end()); |
| 4338 | + } |
| 4339 | + } |
| 4340 | + return integral_conversion(nstring, output); |
| 4341 | + } |
| 4342 | + |
| 4343 | + if(std::isspace(static_cast<unsigned char>(input.back()))) { |
| 4344 | + return integral_conversion(trim_copy(input), output); |
| 4345 | + } |
| 4346 | + if(input.compare(0, 2, "0o") == 0 || input.compare(0, 2, "0O") == 0) { |
| 4347 | + val = nullptr; |
| 4348 | + errno = 0; |
| 4349 | + output_ll = std::strtoull(input.c_str() + 2, &val, 8); |
| 4350 | + if(errno == ERANGE) { |
| 4351 | + return false; |
| 4352 | + } |
| 4353 | + output = static_cast<T>(output_ll); |
| 4354 | + return (val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll); |
| 4355 | + } |
| 4356 | + if(input.compare(0, 2, "0b") == 0 || input.compare(0, 2, "0B") == 0) { |
| 4357 | + // LCOV_EXCL_START |
| 4358 | + // In some new compilers including the coverage testing one binary strings are handled properly in strtoull |
| 4359 | + // automatically so this coverage is missing but is well tested in other compilers |
| 4360 | + val = nullptr; |
| 4361 | + errno = 0; |
| 4362 | + output_ll = std::strtoull(input.c_str() + 2, &val, 2); |
| 4363 | + if(errno == ERANGE) { |
| 4364 | + return false; |
| 4365 | + } |
| 4366 | + output = static_cast<T>(output_ll); |
| 4367 | + return (val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll); |
| 4368 | + // LCOV_EXCL_STOP |
| 4369 | + } |
| 4370 | + return false; |
| 4371 | +} |
| 4372 | + |
| 4373 | +/// Convert to a signed integral |
| 4374 | +template <typename T, enable_if_t<std::is_signed<T>::value, detail::enabler> = detail::dummy> |
| 4375 | +bool integral_conversion(const std::string &input, T &output) noexcept { |
| 4376 | + if(input.empty()) { |
| 4377 | + return false; |
| 4378 | + } |
| 4379 | + char *val = nullptr; |
| 4380 | + errno = 0; |
| 4381 | + std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); |
| 4382 | + if(errno == ERANGE) { |
| 4383 | + return false; |
| 4384 | + } |
| 4385 | + output = static_cast<T>(output_ll); |
| 4386 | + if(val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll) { |
| 4387 | + return true; |
| 4388 | + } |
| 4389 | + if(input == "true") { |
| 4390 | + // this is to deal with a few oddities with flags and wrapper int types |
| 4391 | + output = static_cast<T>(1); |
| 4392 | + return true; |
| 4393 | + } |
| 4394 | + // remove separators if present |
| 4395 | + auto group_separators = get_group_separators(); |
| 4396 | + if(input.find_first_of(group_separators) != std::string::npos) { |
| 4397 | + for(auto &separator : group_separators) { |
| 4398 | + if(input.find_first_of(separator) != std::string::npos) { |
| 4399 | + std::string nstring = input; |
| 4400 | + nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end()); |
| 4401 | + return integral_conversion(nstring, output); |
| 4402 | + } |
| 4403 | + } |
| 4404 | + } |
| 4405 | + if(std::isspace(static_cast<unsigned char>(input.back()))) { |
| 4406 | + return integral_conversion(trim_copy(input), output); |
| 4407 | + } |
| 4408 | + if(input.compare(0, 2, "0o") == 0 || input.compare(0, 2, "0O") == 0) { |
| 4409 | + val = nullptr; |
| 4410 | + errno = 0; |
| 4411 | + output_ll = std::strtoll(input.c_str() + 2, &val, 8); |
| 4412 | + if(errno == ERANGE) { |
| 4413 | + return false; |
| 4414 | + } |
| 4415 | + output = static_cast<T>(output_ll); |
| 4416 | + return (val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll); |
| 4417 | + } |
| 4418 | + if(input.compare(0, 2, "0b") == 0 || input.compare(0, 2, "0B") == 0) { |
| 4419 | + // LCOV_EXCL_START |
| 4420 | + // In some new compilers including the coverage testing one binary strings are handled properly in strtoll |
| 4421 | + // automatically so this coverage is missing but is well tested in other compilers |
| 4422 | + val = nullptr; |
| 4423 | + errno = 0; |
| 4424 | + output_ll = std::strtoll(input.c_str() + 2, &val, 2); |
| 4425 | + if(errno == ERANGE) { |
| 4426 | + return false; |
| 4427 | + } |
| 4428 | + output = static_cast<T>(output_ll); |
| 4429 | + return (val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll); |
| 4430 | + // LCOV_EXCL_STOP |
| 4431 | + } |
| 4432 | + return false; |
| 4433 | +} |
| 4434 | + |
| 4435 | +/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed |
| 4436 | +inline std::int64_t to_flag_value(std::string val) noexcept { |
| 4437 | + static const std::string trueString("true"); |
| 4438 | + static const std::string falseString("false"); |
| 4439 | + if(val == trueString) { |
| 4440 | + return 1; |
| 4441 | + } |
| 4442 | + if(val == falseString) { |
| 4443 | + return -1; |
| 4444 | + } |
| 4445 | + val = detail::to_lower(val); |
| 4446 | + std::int64_t ret = 0; |
| 4447 | + if(val.size() == 1) { |
| 4448 | + if(val[0] >= '1' && val[0] <= '9') { |
| 4449 | + return (static_cast<std::int64_t>(val[0]) - '0'); |
| 4450 | + } |
| 4451 | + switch(val[0]) { |
| 4452 | + case '0': |
| 4453 | + case 'f': |
| 4454 | + case 'n': |
| 4455 | + case '-': |
| 4456 | + ret = -1; |
| 4457 | + break; |
| 4458 | + case 't': |
| 4459 | + case 'y': |
| 4460 | + case '+': |
| 4461 | + ret = 1; |
| 4462 | + break; |
| 4463 | + default: |
| 4464 | + errno = EINVAL; |
| 4465 | + return -1; |
| 4466 | + } |
| 4467 | + return ret; |
| 4468 | + } |
| 4469 | + if(val == trueString || val == "on" || val == "yes" || val == "enable") { |
| 4470 | + ret = 1; |
| 4471 | + } else if(val == falseString || val == "off" || val == "no" || val == "disable") { |
| 4472 | + ret = -1; |
| 4473 | + } else { |
| 4474 | + char *loc_ptr{nullptr}; |
| 4475 | + ret = std::strtoll(val.c_str(), &loc_ptr, 0); |
| 4476 | + if(loc_ptr != (val.c_str() + val.size()) && errno == 0) { |
| 4477 | + errno = EINVAL; |
| 4478 | + } |
| 4479 | + } |
| 4480 | + return ret; |
| 4481 | +} |
| 4482 | + |
| 4483 | +/// Integer conversion |
| 4484 | +template <typename T, |
| 4485 | + enable_if_t<classify_object<T>::value == object_category::integral_value || |
| 4486 | + classify_object<T>::value == object_category::unsigned_integral, |
| 4487 | + detail::enabler> = detail::dummy> |
| 4488 | +bool lexical_cast(const std::string &input, T &output) { |
| 4489 | + return integral_conversion(input, output); |
| 4490 | +} |
| 4491 | + |
| 4492 | +/// char values |
| 4493 | +template <typename T, |
| 4494 | + enable_if_t<classify_object<T>::value == object_category::char_value, detail::enabler> = detail::dummy> |
| 4495 | +bool lexical_cast(const std::string &input, T &output) { |
| 4496 | + if(input.size() == 1) { |
| 4497 | + output = static_cast<T>(input[0]); |
| 4498 | + return true; |
| 4499 | + } |
| 4500 | + std::int8_t res{0}; |
| 4501 | + // we do it this way as some systems have char as signed and not, this ensures consistency in the way things are |
| 4502 | + // handled |
| 4503 | + bool result = integral_conversion(input, res); |
| 4504 | + if(result) { |
| 4505 | + output = static_cast<T>(res); |
| 4506 | + } |
| 4507 | + return result; |
| 4508 | +} |
| 4509 | + |
| 4510 | +/// Boolean values |
| 4511 | +template <typename T, |
| 4512 | + enable_if_t<classify_object<T>::value == object_category::boolean_value, detail::enabler> = detail::dummy> |
| 4513 | +bool lexical_cast(const std::string &input, T &output) { |
| 4514 | + errno = 0; |
| 4515 | + auto out = to_flag_value(input); |
| 4516 | + if(errno == 0) { |
| 4517 | + output = (out > 0); |
| 4518 | + } else if(errno == ERANGE) { |
| 4519 | + output = (input[0] != '-'); |
| 4520 | + } else { |
| 4521 | + return false; |
| 4522 | + } |
| 4523 | + return true; |
| 4524 | +} |
| 4525 | + |
| 4526 | +/// Floats |
| 4527 | +template <typename T, |
| 4528 | + enable_if_t<classify_object<T>::value == object_category::floating_point, detail::enabler> = detail::dummy> |
| 4529 | +bool lexical_cast(const std::string &input, T &output) { |
| 4530 | + if(input.empty()) { |
| 4531 | + return false; |
| 4532 | + } |
| 4533 | + char *val = nullptr; |
| 4534 | + auto output_ld = std::strtold(input.c_str(), &val); |
| 4535 | + output = static_cast<T>(output_ld); |
| 4536 | + if(val == (input.c_str() + input.size())) { |
| 4537 | + return true; |
| 4538 | + } |
| 4539 | + while(std::isspace(static_cast<unsigned char>(*val))) { |
| 4540 | + ++val; |
| 4541 | + if(val == (input.c_str() + input.size())) { |
| 4542 | + return true; |
| 4543 | + } |
| 4544 | + } |
| 4545 | + |
| 4546 | + // remove separators if present |
| 4547 | + auto group_separators = get_group_separators(); |
| 4548 | + if(input.find_first_of(group_separators) != std::string::npos) { |
| 4549 | + for(auto &separator : group_separators) { |
| 4550 | + if(input.find_first_of(separator) != std::string::npos) { |
| 4551 | + std::string nstring = input; |
| 4552 | + nstring.erase(std::remove(nstring.begin(), nstring.end(), separator), nstring.end()); |
| 4553 | + return lexical_cast(nstring, output); |
| 4554 | + } |
| 4555 | + } |
| 4556 | + } |
| 4557 | + return false; |
| 4558 | +} |
| 4559 | + |
| 4560 | +/// complex |
| 4561 | +template <typename T, |
| 4562 | + enable_if_t<classify_object<T>::value == object_category::complex_number, detail::enabler> = detail::dummy> |
| 4563 | +bool lexical_cast(const std::string &input, T &output) { |
| 4564 | + using XC = typename wrapped_type<T, double>::type; |
| 4565 | + XC x{0.0}, y{0.0}; |
| 4566 | + auto str1 = input; |
| 4567 | + bool worked = false; |
| 4568 | + auto nloc = str1.find_last_of("+-"); |
| 4569 | + if(nloc != std::string::npos && nloc > 0) { |
| 4570 | + worked = lexical_cast(str1.substr(0, nloc), x); |
| 4571 | + str1 = str1.substr(nloc); |
| 4572 | + if(str1.back() == 'i' || str1.back() == 'j') |
| 4573 | + str1.pop_back(); |
| 4574 | + worked = worked && lexical_cast(str1, y); |
| 4575 | + } else { |
| 4576 | + if(str1.back() == 'i' || str1.back() == 'j') { |
| 4577 | + str1.pop_back(); |
| 4578 | + worked = lexical_cast(str1, y); |
| 4579 | + x = XC{0}; |
| 4580 | + } else { |
| 4581 | + worked = lexical_cast(str1, x); |
| 4582 | + y = XC{0}; |
| 4583 | + } |
| 4584 | + } |
| 4585 | + if(worked) { |
| 4586 | + output = T{x, y}; |
| 4587 | + return worked; |
| 4588 | + } |
| 4589 | + return from_stream(input, output); |
| 4590 | +} |
| 4591 | + |
| 4592 | +/// String and similar direct assignment |
| 4593 | +template <typename T, |
| 4594 | + enable_if_t<classify_object<T>::value == object_category::string_assignable, detail::enabler> = detail::dummy> |
| 4595 | +bool lexical_cast(const std::string &input, T &output) { |
| 4596 | + output = input; |
| 4597 | + return true; |
| 4598 | +} |
| 4599 | + |
| 4600 | +/// String and similar constructible and copy assignment |
| 4601 | +template < |
| 4602 | + typename T, |
| 4603 | + enable_if_t<classify_object<T>::value == object_category::string_constructible, detail::enabler> = detail::dummy> |
| 4604 | +bool lexical_cast(const std::string &input, T &output) { |
| 4605 | + output = T(input); |
| 4606 | + return true; |
| 4607 | +} |
| 4608 | + |
| 4609 | +/// Wide strings |
| 4610 | +template < |
| 4611 | + typename T, |
| 4612 | + enable_if_t<classify_object<T>::value == object_category::wstring_assignable, detail::enabler> = detail::dummy> |
| 4613 | +bool lexical_cast(const std::string &input, T &output) { |
| 4614 | + output = widen(input); |
| 4615 | + return true; |
| 4616 | +} |
| 4617 | + |
| 4618 | +template < |
| 4619 | + typename T, |
| 4620 | + enable_if_t<classify_object<T>::value == object_category::wstring_constructible, detail::enabler> = detail::dummy> |
| 4621 | +bool lexical_cast(const std::string &input, T &output) { |
| 4622 | + output = T{widen(input)}; |
| 4623 | + return true; |
| 4624 | +} |
| 4625 | + |
| 4626 | +/// Enumerations |
| 4627 | +template <typename T, |
| 4628 | + enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy> |
| 4629 | +bool lexical_cast(const std::string &input, T &output) { |
| 4630 | + typename std::underlying_type<T>::type val; |
| 4631 | + if(!integral_conversion(input, val)) { |
| 4632 | + return false; |
| 4633 | + } |
| 4634 | + output = static_cast<T>(val); |
| 4635 | + return true; |
| 4636 | +} |
| 4637 | + |
| 4638 | +/// wrapper types |
| 4639 | +template <typename T, |
| 4640 | + enable_if_t<classify_object<T>::value == object_category::wrapper_value && |
| 4641 | + std::is_assignable<T &, typename T::value_type>::value, |
| 4642 | + detail::enabler> = detail::dummy> |
| 4643 | +bool lexical_cast(const std::string &input, T &output) { |
| 4644 | + typename T::value_type val; |
| 4645 | + if(lexical_cast(input, val)) { |
| 4646 | + output = val; |
| 4647 | + return true; |
| 4648 | + } |
| 4649 | + return from_stream(input, output); |
| 4650 | +} |
| 4651 | + |
| 4652 | +template <typename T, |
| 4653 | + enable_if_t<classify_object<T>::value == object_category::wrapper_value && |
| 4654 | + !std::is_assignable<T &, typename T::value_type>::value && std::is_assignable<T &, T>::value, |
| 4655 | + detail::enabler> = detail::dummy> |
| 4656 | +bool lexical_cast(const std::string &input, T &output) { |
| 4657 | + typename T::value_type val; |
| 4658 | + if(lexical_cast(input, val)) { |
| 4659 | + output = T{val}; |
| 4660 | + return true; |
| 4661 | + } |
| 4662 | + return from_stream(input, output); |
| 4663 | +} |
| 4664 | + |
| 4665 | +/// Assignable from double or int |
| 4666 | +template < |
| 4667 | + typename T, |
| 4668 | + enable_if_t<classify_object<T>::value == object_category::number_constructible, detail::enabler> = detail::dummy> |
| 4669 | +bool lexical_cast(const std::string &input, T &output) { |
| 4670 | + int val = 0; |
| 4671 | + if(integral_conversion(input, val)) { |
| 4672 | + output = T(val); |
| 4673 | + return true; |
| 4674 | + } |
| 4675 | + |
| 4676 | + double dval = 0.0; |
| 4677 | + if(lexical_cast(input, dval)) { |
| 4678 | + output = T{dval}; |
| 4679 | + return true; |
| 4680 | + } |
| 4681 | + |
| 4682 | + return from_stream(input, output); |
| 4683 | +} |
| 4684 | + |
| 4685 | +/// Assignable from int |
| 4686 | +template < |
| 4687 | + typename T, |
| 4688 | + enable_if_t<classify_object<T>::value == object_category::integer_constructible, detail::enabler> = detail::dummy> |
| 4689 | +bool lexical_cast(const std::string &input, T &output) { |
| 4690 | + int val = 0; |
| 4691 | + if(integral_conversion(input, val)) { |
| 4692 | + output = T(val); |
| 4693 | + return true; |
| 4694 | + } |
| 4695 | + return from_stream(input, output); |
| 4696 | +} |
| 4697 | + |
| 4698 | +/// Assignable from double |
| 4699 | +template < |
| 4700 | + typename T, |
| 4701 | + enable_if_t<classify_object<T>::value == object_category::double_constructible, detail::enabler> = detail::dummy> |
| 4702 | +bool lexical_cast(const std::string &input, T &output) { |
| 4703 | + double val = 0.0; |
| 4704 | + if(lexical_cast(input, val)) { |
| 4705 | + output = T{val}; |
| 4706 | + return true; |
| 4707 | + } |
| 4708 | + return from_stream(input, output); |
| 4709 | +} |
| 4710 | + |
| 4711 | +/// Non-string convertible from an int |
| 4712 | +template <typename T, |
| 4713 | + enable_if_t<classify_object<T>::value == object_category::other && std::is_assignable<T &, int>::value, |
| 4714 | + detail::enabler> = detail::dummy> |
| 4715 | +bool lexical_cast(const std::string &input, T &output) { |
| 4716 | + int val = 0; |
| 4717 | + if(integral_conversion(input, val)) { |
| 4718 | +#ifdef _MSC_VER |
| 4719 | +#pragma warning(push) |
| 4720 | +#pragma warning(disable : 4800) |
| 4721 | +#endif |
| 4722 | + // with Atomic<XX> this could produce a warning due to the conversion but if atomic gets here it is an old style |
| 4723 | + // so will most likely still work |
| 4724 | + output = val; |
| 4725 | +#ifdef _MSC_VER |
| 4726 | +#pragma warning(pop) |
| 4727 | +#endif |
| 4728 | + return true; |
| 4729 | + } |
| 4730 | + // LCOV_EXCL_START |
| 4731 | + // This version of cast is only used for odd cases in an older compilers the fail over |
| 4732 | + // from_stream is tested elsewhere an not relevant for coverage here |
| 4733 | + return from_stream(input, output); |
| 4734 | + // LCOV_EXCL_STOP |
| 4735 | +} |
| 4736 | + |
| 4737 | +/// Non-string parsable by a stream |
| 4738 | +template <typename T, |
| 4739 | + enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value && |
| 4740 | + is_istreamable<T>::value, |
| 4741 | + detail::enabler> = detail::dummy> |
| 4742 | +bool lexical_cast(const std::string &input, T &output) { |
| 4743 | + return from_stream(input, output); |
| 4744 | +} |
| 4745 | + |
| 4746 | +/// Fallback overload that prints a human-readable error for types that we don't recognize and that don't have a |
| 4747 | +/// user-supplied lexical_cast overload. |
| 4748 | +template <typename T, |
| 4749 | + enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value && |
| 4750 | + !is_istreamable<T>::value && !adl_detail::is_lexical_castable<T>::value, |
| 4751 | + detail::enabler> = detail::dummy> |
| 4752 | +bool lexical_cast(const std::string & /*input*/, T & /*output*/) { |
| 4753 | + static_assert(!std::is_same<T, T>::value, // Can't just write false here. |
| 4754 | + "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " |
| 4755 | + "is convertible from another type use the add_option<T, XC>(...) with XC being the known type"); |
| 4756 | + return false; |
| 4757 | +} |
| 4758 | + |
| 4759 | +/// Assign a value through lexical cast operations |
| 4760 | +/// Strings can be empty so we need to do a little different |
| 4761 | +template <typename AssignTo, |
| 4762 | + typename ConvertTo, |
| 4763 | + enable_if_t<std::is_same<AssignTo, ConvertTo>::value && |
| 4764 | + (classify_object<AssignTo>::value == object_category::string_assignable || |
| 4765 | + classify_object<AssignTo>::value == object_category::string_constructible || |
| 4766 | + classify_object<AssignTo>::value == object_category::wstring_assignable || |
| 4767 | + classify_object<AssignTo>::value == object_category::wstring_constructible), |
| 4768 | + detail::enabler> = detail::dummy> |
| 4769 | +bool lexical_assign(const std::string &input, AssignTo &output) { |
| 4770 | + return lexical_cast(input, output); |
| 4771 | +} |
| 4772 | + |
| 4773 | +/// Assign a value through lexical cast operations |
| 4774 | +template <typename AssignTo, |
| 4775 | + typename ConvertTo, |
| 4776 | + enable_if_t<std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, AssignTo>::value && |
| 4777 | + classify_object<AssignTo>::value != object_category::string_assignable && |
| 4778 | + classify_object<AssignTo>::value != object_category::string_constructible && |
| 4779 | + classify_object<AssignTo>::value != object_category::wstring_assignable && |
| 4780 | + classify_object<AssignTo>::value != object_category::wstring_constructible, |
| 4781 | + detail::enabler> = detail::dummy> |
| 4782 | +bool lexical_assign(const std::string &input, AssignTo &output) { |
| 4783 | + if(input.empty()) { |
| 4784 | + output = AssignTo{}; |
| 4785 | + return true; |
| 4786 | + } |
| 4787 | + |
| 4788 | + return lexical_cast(input, output); |
| 4789 | +} // LCOV_EXCL_LINE |
| 4790 | + |
| 4791 | +/// Assign a value through lexical cast operations |
| 4792 | +template <typename AssignTo, |
| 4793 | + typename ConvertTo, |
| 4794 | + enable_if_t<std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, AssignTo>::value && |
| 4795 | + classify_object<AssignTo>::value == object_category::wrapper_value, |
| 4796 | + detail::enabler> = detail::dummy> |
| 4797 | +bool lexical_assign(const std::string &input, AssignTo &output) { |
| 4798 | + if(input.empty()) { |
| 4799 | + typename AssignTo::value_type emptyVal{}; |
| 4800 | + output = emptyVal; |
| 4801 | + return true; |
| 4802 | + } |
| 4803 | + return lexical_cast(input, output); |
| 4804 | +} |
| 4805 | + |
| 4806 | +/// Assign a value through lexical cast operations for int compatible values |
| 4807 | +/// mainly for atomic operations on some compilers |
| 4808 | +template <typename AssignTo, |
| 4809 | + typename ConvertTo, |
| 4810 | + enable_if_t<std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, AssignTo>::value && |
| 4811 | + classify_object<AssignTo>::value != object_category::wrapper_value && |
| 4812 | + std::is_assignable<AssignTo &, int>::value, |
| 4813 | + detail::enabler> = detail::dummy> |
| 4814 | +bool lexical_assign(const std::string &input, AssignTo &output) { |
| 4815 | + if(input.empty()) { |
| 4816 | + output = 0; |
| 4817 | + return true; |
| 4818 | + } |
| 4819 | + int val{0}; |
| 4820 | + if(lexical_cast(input, val)) { |
| 4821 | +#if defined(__clang__) |
| 4822 | +/* on some older clang compilers */ |
| 4823 | +#pragma clang diagnostic push |
| 4824 | +#pragma clang diagnostic ignored "-Wsign-conversion" |
| 4825 | +#endif |
| 4826 | + output = val; |
| 4827 | +#if defined(__clang__) |
| 4828 | +#pragma clang diagnostic pop |
| 4829 | +#endif |
| 4830 | + return true; |
| 4831 | + } |
| 4832 | + return false; |
| 4833 | +} |
| 4834 | + |
| 4835 | +/// Assign a value converted from a string in lexical cast to the output value directly |
| 4836 | +template <typename AssignTo, |
| 4837 | + typename ConvertTo, |
| 4838 | + enable_if_t<!std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, ConvertTo &>::value, |
| 4839 | + detail::enabler> = detail::dummy> |
| 4840 | +bool lexical_assign(const std::string &input, AssignTo &output) { |
| 4841 | + ConvertTo val{}; |
| 4842 | + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; |
| 4843 | + if(parse_result) { |
| 4844 | + output = val; |
| 4845 | + } |
| 4846 | + return parse_result; |
| 4847 | +} |
| 4848 | + |
| 4849 | +/// Assign a value from a lexical cast through constructing a value and move assigning it |
| 4850 | +template < |
| 4851 | + typename AssignTo, |
| 4852 | + typename ConvertTo, |
| 4853 | + enable_if_t<!std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, ConvertTo &>::value && |
| 4854 | + std::is_move_assignable<AssignTo>::value, |
| 4855 | + detail::enabler> = detail::dummy> |
| 4856 | +bool lexical_assign(const std::string &input, AssignTo &output) { |
| 4857 | + ConvertTo val{}; |
| 4858 | + bool parse_result = input.empty() ? true : lexical_cast(input, val); |
| 4859 | + if(parse_result) { |
| 4860 | + output = AssignTo(val); // use () form of constructor to allow some implicit conversions |
| 4861 | + } |
| 4862 | + return parse_result; |
| 4863 | +} |
| 4864 | + |
| 4865 | +/// primary lexical conversion operation, 1 string to 1 type of some kind |
| 4866 | +template <typename AssignTo, |
| 4867 | + typename ConvertTo, |
| 4868 | + enable_if_t<classify_object<ConvertTo>::value <= object_category::other && |
| 4869 | + classify_object<AssignTo>::value <= object_category::wrapper_value, |
| 4870 | + detail::enabler> = detail::dummy> |
| 4871 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { |
| 4872 | + return lexical_assign<AssignTo, ConvertTo>(strings[0], output); |
| 4873 | +} |
| 4874 | + |
| 4875 | +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element |
| 4876 | +/// constructor |
| 4877 | +template <typename AssignTo, |
| 4878 | + typename ConvertTo, |
| 4879 | + enable_if_t<(type_count<AssignTo>::value <= 2) && expected_count<AssignTo>::value == 1 && |
| 4880 | + is_tuple_like<ConvertTo>::value && type_count_base<ConvertTo>::value == 2, |
| 4881 | + detail::enabler> = detail::dummy> |
| 4882 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { |
| 4883 | + // the remove const is to handle pair types coming from a container |
| 4884 | + using FirstType = typename std::remove_const<typename std::tuple_element<0, ConvertTo>::type>::type; |
| 4885 | + using SecondType = typename std::tuple_element<1, ConvertTo>::type; |
| 4886 | + FirstType v1; |
| 4887 | + SecondType v2{}; |
| 4888 | + bool retval = lexical_assign<FirstType, FirstType>(strings[0], v1); |
| 4889 | + retval = retval && lexical_assign<SecondType, SecondType>((strings.size() > 1) ? strings[1] : std::string{}, v2); |
| 4890 | + if(retval) { |
| 4891 | + output = AssignTo{v1, v2}; |
| 4892 | + } |
| 4893 | + return retval; |
| 4894 | +} |
| 4895 | + |
| 4896 | +/// Lexical conversion of a container types of single elements |
| 4897 | +template <class AssignTo, |
| 4898 | + class ConvertTo, |
| 4899 | + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && |
| 4900 | + type_count<ConvertTo>::value == 1, |
| 4901 | + detail::enabler> = detail::dummy> |
| 4902 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { |
| 4903 | + output.erase(output.begin(), output.end()); |
| 4904 | + if(strings.empty()) { |
| 4905 | + return true; |
| 4906 | + } |
| 4907 | + if(strings.size() == 1 && strings[0] == "{}") { |
| 4908 | + return true; |
| 4909 | + } |
| 4910 | + bool skip_remaining = false; |
| 4911 | + if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) { |
| 4912 | + skip_remaining = true; |
| 4913 | + } |
| 4914 | + for(const auto &elem : strings) { |
| 4915 | + typename AssignTo::value_type out; |
| 4916 | + bool retval = lexical_assign<typename AssignTo::value_type, typename ConvertTo::value_type>(elem, out); |
| 4917 | + if(!retval) { |
| 4918 | + return false; |
| 4919 | + } |
| 4920 | + output.insert(output.end(), std::move(out)); |
| 4921 | + if(skip_remaining) { |
| 4922 | + break; |
| 4923 | + } |
| 4924 | + } |
| 4925 | + return (!output.empty()); |
| 4926 | +} |
| 4927 | + |
| 4928 | +/// Lexical conversion for complex types |
| 4929 | +template <class AssignTo, class ConvertTo, enable_if_t<is_complex<ConvertTo>::value, detail::enabler> = detail::dummy> |
| 4930 | +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) { |
| 4931 | + |
| 4932 | + if(strings.size() >= 2 && !strings[1].empty()) { |
| 4933 | + using XC2 = typename wrapped_type<ConvertTo, double>::type; |
| 4934 | + XC2 x{0.0}, y{0.0}; |
| 4935 | + auto str1 = strings[1]; |
| 4936 | + if(str1.back() == 'i' || str1.back() == 'j') { |
| 4937 | + str1.pop_back(); |
| 4938 | + } |
| 4939 | + auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y); |
| 4940 | + if(worked) { |
| 4941 | + output = ConvertTo{x, y}; |
| 4942 | + } |
| 4943 | + return worked; |
| 4944 | + } |
| 4945 | + return lexical_assign<AssignTo, ConvertTo>(strings[0], output); |
| 4946 | +} |
| 4947 | + |
| 4948 | +/// Conversion to a vector type using a particular single type as the conversion type |
| 4949 | +template <class AssignTo, |
| 4950 | + class ConvertTo, |
| 4951 | + enable_if_t<is_mutable_container<AssignTo>::value && (expected_count<ConvertTo>::value == 1) && |
| 4952 | + (type_count<ConvertTo>::value == 1), |
| 4953 | + detail::enabler> = detail::dummy> |
| 4954 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { |
| 4955 | + bool retval = true; |
| 4956 | + output.clear(); |
| 4957 | + output.reserve(strings.size()); |
| 4958 | + for(const auto &elem : strings) { |
| 4959 | + |
| 4960 | + output.emplace_back(); |
| 4961 | + retval = retval && lexical_assign<typename AssignTo::value_type, ConvertTo>(elem, output.back()); |
| 4962 | + } |
| 4963 | + return (!output.empty()) && retval; |
| 4964 | +} |
| 4965 | + |
| 4966 | +// forward declaration |
| 4967 | + |
| 4968 | +/// Lexical conversion of a container types with conversion type of two elements |
| 4969 | +template <class AssignTo, |
| 4970 | + class ConvertTo, |
| 4971 | + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && |
| 4972 | + type_count_base<ConvertTo>::value == 2, |
| 4973 | + detail::enabler> = detail::dummy> |
| 4974 | +bool lexical_conversion(std::vector<std::string> strings, AssignTo &output); |
| 4975 | + |
| 4976 | +/// Lexical conversion of a vector types with type_size >2 forward declaration |
| 4977 | +template <class AssignTo, |
| 4978 | + class ConvertTo, |
| 4979 | + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && |
| 4980 | + type_count_base<ConvertTo>::value != 2 && |
| 4981 | + ((type_count<ConvertTo>::value > 2) || |
| 4982 | + (type_count<ConvertTo>::value > type_count_base<ConvertTo>::value)), |
| 4983 | + detail::enabler> = detail::dummy> |
| 4984 | +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output); |
| 4985 | + |
| 4986 | +/// Conversion for tuples |
| 4987 | +template <class AssignTo, |
| 4988 | + class ConvertTo, |
| 4989 | + enable_if_t<is_tuple_like<AssignTo>::value && is_tuple_like<ConvertTo>::value && |
| 4990 | + (type_count_base<ConvertTo>::value != type_count<ConvertTo>::value || |
| 4991 | + type_count<ConvertTo>::value > 2), |
| 4992 | + detail::enabler> = detail::dummy> |
| 4993 | +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output); // forward declaration |
| 4994 | + |
| 4995 | +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large |
| 4996 | +/// tuple |
| 4997 | +template <typename AssignTo, |
| 4998 | + typename ConvertTo, |
| 4999 | + enable_if_t<!is_tuple_like<AssignTo>::value && !is_mutable_container<AssignTo>::value && |
| 5000 | + classify_object<ConvertTo>::value != object_category::wrapper_value && |
| 5001 | + (is_mutable_container<ConvertTo>::value || type_count<ConvertTo>::value > 2), |
| 5002 | + detail::enabler> = detail::dummy> |
| 5003 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { |
| 5004 | + |
| 5005 | + if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { |
| 5006 | + ConvertTo val; |
| 5007 | + auto retval = lexical_conversion<ConvertTo, ConvertTo>(strings, val); |
| 5008 | + output = AssignTo{val}; |
| 5009 | + return retval; |
| 5010 | + } |
| 5011 | + output = AssignTo{}; |
| 5012 | + return true; |
| 5013 | +} |
| 5014 | + |
| 5015 | +/// function template for converting tuples if the static Index is greater than the tuple size |
| 5016 | +template <class AssignTo, class ConvertTo, std::size_t I> |
| 5017 | +inline typename std::enable_if<(I >= type_count_base<AssignTo>::value), bool>::type |
| 5018 | +tuple_conversion(const std::vector<std::string> &, AssignTo &) { |
| 5019 | + return true; |
| 5020 | +} |
| 5021 | + |
| 5022 | +/// Conversion of a tuple element where the type size ==1 and not a mutable container |
| 5023 | +template <class AssignTo, class ConvertTo> |
| 5024 | +inline typename std::enable_if<!is_mutable_container<ConvertTo>::value && type_count<ConvertTo>::value == 1, bool>::type |
| 5025 | +tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) { |
| 5026 | + auto retval = lexical_assign<AssignTo, ConvertTo>(strings[0], output); |
| 5027 | + strings.erase(strings.begin()); |
| 5028 | + return retval; |
| 5029 | +} |
| 5030 | + |
| 5031 | +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container |
| 5032 | +template <class AssignTo, class ConvertTo> |
| 5033 | +inline typename std::enable_if<!is_mutable_container<ConvertTo>::value && (type_count<ConvertTo>::value > 1) && |
| 5034 | + type_count<ConvertTo>::value == type_count_min<ConvertTo>::value, |
| 5035 | + bool>::type |
| 5036 | +tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) { |
| 5037 | + auto retval = lexical_conversion<AssignTo, ConvertTo>(strings, output); |
| 5038 | + strings.erase(strings.begin(), strings.begin() + type_count<ConvertTo>::value); |
| 5039 | + return retval; |
| 5040 | +} |
| 5041 | + |
| 5042 | +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes |
| 5043 | +template <class AssignTo, class ConvertTo> |
| 5044 | +inline typename std::enable_if<is_mutable_container<ConvertTo>::value || |
| 5045 | + type_count<ConvertTo>::value != type_count_min<ConvertTo>::value, |
| 5046 | + bool>::type |
| 5047 | +tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) { |
| 5048 | + |
| 5049 | + std::size_t index{subtype_count_min<ConvertTo>::value}; |
| 5050 | + const std::size_t mx_count{subtype_count<ConvertTo>::value}; |
| 5051 | + const std::size_t mx{(std::min)(mx_count, strings.size() - 1)}; |
| 5052 | + |
| 5053 | + while(index < mx) { |
| 5054 | + if(is_separator(strings[index])) { |
| 5055 | + break; |
| 5056 | + } |
| 5057 | + ++index; |
| 5058 | + } |
| 5059 | + bool retval = lexical_conversion<AssignTo, ConvertTo>( |
| 5060 | + std::vector<std::string>(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index)), output); |
| 5061 | + if(strings.size() > index) { |
| 5062 | + strings.erase(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index) + 1); |
| 5063 | + } else { |
| 5064 | + strings.clear(); |
| 5065 | + } |
| 5066 | + return retval; |
| 5067 | +} |
| 5068 | + |
| 5069 | +/// Tuple conversion operation |
| 5070 | +template <class AssignTo, class ConvertTo, std::size_t I> |
| 5071 | +inline typename std::enable_if<(I < type_count_base<AssignTo>::value), bool>::type |
| 5072 | +tuple_conversion(std::vector<std::string> strings, AssignTo &output) { |
| 5073 | + bool retval = true; |
| 5074 | + using ConvertToElement = typename std:: |
| 5075 | + conditional<is_tuple_like<ConvertTo>::value, typename std::tuple_element<I, ConvertTo>::type, ConvertTo>::type; |
| 5076 | + if(!strings.empty()) { |
| 5077 | + retval = retval && tuple_type_conversion<typename std::tuple_element<I, AssignTo>::type, ConvertToElement>( |
| 5078 | + strings, std::get<I>(output)); |
| 5079 | + } |
| 5080 | + retval = retval && tuple_conversion<AssignTo, ConvertTo, I + 1>(std::move(strings), output); |
| 5081 | + return retval; |
| 5082 | +} |
| 5083 | + |
| 5084 | +/// Lexical conversion of a container types with tuple elements of size 2 |
| 5085 | +template <class AssignTo, |
| 5086 | + class ConvertTo, |
| 5087 | + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && |
| 5088 | + type_count_base<ConvertTo>::value == 2, |
| 5089 | + detail::enabler>> |
| 5090 | +bool lexical_conversion(std::vector<std::string> strings, AssignTo &output) { |
| 5091 | + output.clear(); |
| 5092 | + while(!strings.empty()) { |
| 5093 | + |
| 5094 | + typename std::remove_const<typename std::tuple_element<0, typename ConvertTo::value_type>::type>::type v1; |
| 5095 | + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; |
| 5096 | + bool retval = tuple_type_conversion<decltype(v1), decltype(v1)>(strings, v1); |
| 5097 | + if(!strings.empty()) { |
| 5098 | + retval = retval && tuple_type_conversion<decltype(v2), decltype(v2)>(strings, v2); |
| 5099 | + } |
| 5100 | + if(retval) { |
| 5101 | + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); |
| 5102 | + } else { |
| 5103 | + return false; |
| 5104 | + } |
| 5105 | + } |
| 5106 | + return (!output.empty()); |
| 5107 | +} |
| 5108 | + |
| 5109 | +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 |
| 5110 | +template <class AssignTo, |
| 5111 | + class ConvertTo, |
| 5112 | + enable_if_t<is_tuple_like<AssignTo>::value && is_tuple_like<ConvertTo>::value && |
| 5113 | + (type_count_base<ConvertTo>::value != type_count<ConvertTo>::value || |
| 5114 | + type_count<ConvertTo>::value > 2), |
| 5115 | + detail::enabler>> |
| 5116 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { |
| 5117 | + static_assert( |
| 5118 | + !is_tuple_like<ConvertTo>::value || type_count_base<AssignTo>::value == type_count_base<ConvertTo>::value, |
| 5119 | + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); |
| 5120 | + return tuple_conversion<AssignTo, ConvertTo, 0>(strings, output); |
| 5121 | +} |
| 5122 | + |
| 5123 | +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1 |
| 5124 | +template <class AssignTo, |
| 5125 | + class ConvertTo, |
| 5126 | + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && |
| 5127 | + type_count_base<ConvertTo>::value != 2 && |
| 5128 | + ((type_count<ConvertTo>::value > 2) || |
| 5129 | + (type_count<ConvertTo>::value > type_count_base<ConvertTo>::value)), |
| 5130 | + detail::enabler>> |
| 5131 | +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { |
| 5132 | + bool retval = true; |
| 5133 | + output.clear(); |
| 5134 | + std::vector<std::string> temp; |
| 5135 | + std::size_t ii{0}; |
| 5136 | + std::size_t icount{0}; |
| 5137 | + std::size_t xcm{type_count<ConvertTo>::value}; |
| 5138 | + auto ii_max = strings.size(); |
| 5139 | + while(ii < ii_max) { |
| 5140 | + temp.push_back(strings[ii]); |
| 5141 | + ++ii; |
| 5142 | + ++icount; |
| 5143 | + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { |
| 5144 | + if(static_cast<int>(xcm) > type_count_min<ConvertTo>::value && is_separator(temp.back())) { |
| 5145 | + temp.pop_back(); |
| 5146 | + } |
| 5147 | + typename AssignTo::value_type temp_out; |
| 5148 | + retval = retval && |
| 5149 | + lexical_conversion<typename AssignTo::value_type, typename ConvertTo::value_type>(temp, temp_out); |
| 5150 | + temp.clear(); |
| 5151 | + if(!retval) { |
| 5152 | + return false; |
| 5153 | + } |
| 5154 | + output.insert(output.end(), std::move(temp_out)); |
| 5155 | + icount = 0; |
| 5156 | + } |
| 5157 | + } |
| 5158 | + return retval; |
| 5159 | +} |
| 5160 | + |
| 5161 | +/// conversion for wrapper types |
| 5162 | +template <typename AssignTo, |
| 5163 | + class ConvertTo, |
| 5164 | + enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value && |
| 5165 | + std::is_assignable<ConvertTo &, ConvertTo>::value, |
| 5166 | + detail::enabler> = detail::dummy> |
| 5167 | +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) { |
| 5168 | + if(strings.empty() || strings.front().empty()) { |
| 5169 | + output = ConvertTo{}; |
| 5170 | + return true; |
| 5171 | + } |
| 5172 | + typename ConvertTo::value_type val; |
| 5173 | + if(lexical_conversion<typename ConvertTo::value_type, typename ConvertTo::value_type>(strings, val)) { |
| 5174 | + output = ConvertTo{val}; |
| 5175 | + return true; |
| 5176 | + } |
| 5177 | + return false; |
| 5178 | +} |
| 5179 | + |
| 5180 | +/// conversion for wrapper types |
| 5181 | +template <typename AssignTo, |
| 5182 | + class ConvertTo, |
| 5183 | + enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value && |
| 5184 | + !std::is_assignable<AssignTo &, ConvertTo>::value, |
| 5185 | + detail::enabler> = detail::dummy> |
| 5186 | +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) { |
| 5187 | + using ConvertType = typename ConvertTo::value_type; |
| 5188 | + if(strings.empty() || strings.front().empty()) { |
| 5189 | + output = ConvertType{}; |
| 5190 | + return true; |
| 5191 | + } |
| 5192 | + ConvertType val; |
| 5193 | + if(lexical_conversion<typename ConvertTo::value_type, typename ConvertTo::value_type>(strings, val)) { |
| 5194 | + output = val; |
| 5195 | + return true; |
| 5196 | + } |
| 5197 | + return false; |
| 5198 | +} |
| 5199 | + |
| 5200 | +/// Sum a vector of strings |
| 5201 | +inline std::string sum_string_vector(const std::vector<std::string> &values) { |
| 5202 | + double val{0.0}; |
| 5203 | + bool fail{false}; |
| 5204 | + std::string output; |
| 5205 | + for(const auto &arg : values) { |
| 5206 | + double tv{0.0}; |
| 5207 | + auto comp = lexical_cast(arg, tv); |
| 5208 | + if(!comp) { |
| 5209 | + errno = 0; |
| 5210 | + auto fv = detail::to_flag_value(arg); |
| 5211 | + fail = (errno != 0); |
| 5212 | + if(fail) { |
| 5213 | + break; |
| 5214 | + } |
| 5215 | + tv = static_cast<double>(fv); |
| 5216 | + } |
| 5217 | + val += tv; |
| 5218 | + } |
| 5219 | + if(fail) { |
| 5220 | + for(const auto &arg : values) { |
| 5221 | + output.append(arg); |
| 5222 | + } |
| 5223 | + } else { |
| 5224 | + std::ostringstream out; |
| 5225 | + out.precision(16); |
| 5226 | + out << val; |
| 5227 | + output = out.str(); |
| 5228 | + } |
| 5229 | + return output; |
| 5230 | +} |
| 5231 | + |
| 5232 | +} // namespace detail |
| 5233 | + |
| 5234 | + |
| 5235 | + |
| 5236 | +namespace detail { |
| 5237 | + |
| 5238 | +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true |
| 5239 | +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest); |
| 5240 | + |
| 5241 | +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true |
| 5242 | +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value); |
| 5243 | + |
| 5244 | +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true |
| 5245 | +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value); |
| 5246 | + |
| 5247 | +// Splits a string into multiple long and short names |
| 5248 | +CLI11_INLINE std::vector<std::string> split_names(std::string current); |
| 5249 | + |
| 5250 | +/// extract default flag values either {def} or starting with a ! |
| 5251 | +CLI11_INLINE std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str); |
| 5252 | + |
| 5253 | +/// Get a vector of short names, one of long names, and a single name |
| 5254 | +CLI11_INLINE std::tuple<std::vector<std::string>, std::vector<std::string>, std::string> |
| 5255 | +get_names(const std::vector<std::string> &input, bool allow_non_standard = false); |
| 5256 | + |
| 5257 | +} // namespace detail |
| 5258 | + |
| 5259 | + |
| 5260 | + |
| 5261 | +namespace detail { |
| 5262 | + |
| 5263 | +CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std::string &rest) { |
| 5264 | + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { |
| 5265 | + name = current.substr(1, 1); |
| 5266 | + rest = current.substr(2); |
| 5267 | + return true; |
| 5268 | + } |
| 5269 | + return false; |
| 5270 | +} |
| 5271 | + |
| 5272 | +CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value) { |
| 5273 | + if(current.size() > 2 && current.compare(0, 2, "--") == 0 && valid_first_char(current[2])) { |
| 5274 | + auto loc = current.find_first_of('='); |
| 5275 | + if(loc != std::string::npos) { |
| 5276 | + name = current.substr(2, loc - 2); |
| 5277 | + value = current.substr(loc + 1); |
| 5278 | + } else { |
| 5279 | + name = current.substr(2); |
| 5280 | + value = ""; |
| 5281 | + } |
| 5282 | + return true; |
| 5283 | + } |
| 5284 | + return false; |
| 5285 | +} |
| 5286 | + |
| 5287 | +CLI11_INLINE bool split_windows_style(const std::string ¤t, std::string &name, std::string &value) { |
| 5288 | + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { |
| 5289 | + auto loc = current.find_first_of(':'); |
| 5290 | + if(loc != std::string::npos) { |
| 5291 | + name = current.substr(1, loc - 1); |
| 5292 | + value = current.substr(loc + 1); |
| 5293 | + } else { |
| 5294 | + name = current.substr(1); |
| 5295 | + value = ""; |
| 5296 | + } |
| 5297 | + return true; |
| 5298 | + } |
| 5299 | + return false; |
| 5300 | +} |
| 5301 | + |
| 5302 | +CLI11_INLINE std::vector<std::string> split_names(std::string current) { |
| 5303 | + std::vector<std::string> output; |
| 5304 | + std::size_t val = 0; |
| 5305 | + while((val = current.find(',')) != std::string::npos) { |
| 5306 | + output.push_back(trim_copy(current.substr(0, val))); |
| 5307 | + current = current.substr(val + 1); |
| 5308 | + } |
| 5309 | + output.push_back(trim_copy(current)); |
| 5310 | + return output; |
| 5311 | +} |
| 5312 | + |
| 5313 | +CLI11_INLINE std::vector<std::pair<std::string, std::string>> get_default_flag_values(const std::string &str) { |
| 5314 | + std::vector<std::string> flags = split_names(str); |
| 5315 | + flags.erase(std::remove_if(flags.begin(), |
| 5316 | + flags.end(), |
| 5317 | + [](const std::string &name) { |
| 5318 | + return ((name.empty()) || (!(((name.find_first_of('{') != std::string::npos) && |
| 5319 | + (name.back() == '}')) || |
| 5320 | + (name[0] == '!')))); |
| 5321 | + }), |
| 5322 | + flags.end()); |
| 5323 | + std::vector<std::pair<std::string, std::string>> output; |
| 5324 | + output.reserve(flags.size()); |
| 5325 | + for(auto &flag : flags) { |
| 5326 | + auto def_start = flag.find_first_of('{'); |
| 5327 | + std::string defval = "false"; |
| 5328 | + if((def_start != std::string::npos) && (flag.back() == '}')) { |
| 5329 | + defval = flag.substr(def_start + 1); |
| 5330 | + defval.pop_back(); |
| 5331 | + flag.erase(def_start, std::string::npos); // NOLINT(readability-suspicious-call-argument) |
| 5332 | + } |
| 5333 | + flag.erase(0, flag.find_first_not_of("-!")); |
| 5334 | + output.emplace_back(flag, defval); |
| 5335 | + } |
| 5336 | + return output; |
| 5337 | +} |
| 5338 | + |
| 5339 | +CLI11_INLINE std::tuple<std::vector<std::string>, std::vector<std::string>, std::string> |
| 5340 | +get_names(const std::vector<std::string> &input, bool allow_non_standard) { |
| 5341 | + |
| 5342 | + std::vector<std::string> short_names; |
| 5343 | + std::vector<std::string> long_names; |
| 5344 | + std::string pos_name; |
| 5345 | + for(std::string name : input) { |
| 5346 | + if(name.empty()) { |
| 5347 | + continue; |
| 5348 | + } |
| 5349 | + if(name.length() > 1 && name[0] == '-' && name[1] != '-') { |
| 5350 | + if(name.length() == 2 && valid_first_char(name[1])) { |
| 5351 | + short_names.emplace_back(1, name[1]); |
| 5352 | + } else if(name.length() > 2) { |
| 5353 | + if(allow_non_standard) { |
| 5354 | + name = name.substr(1); |
| 5355 | + if(valid_name_string(name)) { |
| 5356 | + short_names.push_back(name); |
| 5357 | + } else { |
| 5358 | + throw BadNameString::BadLongName(name); |
| 5359 | + } |
| 5360 | + } else { |
| 5361 | + throw BadNameString::MissingDash(name); |
| 5362 | + } |
| 5363 | + } else { |
| 5364 | + throw BadNameString::OneCharName(name); |
| 5365 | + } |
| 5366 | + } else if(name.length() > 2 && name.substr(0, 2) == "--") { |
| 5367 | + name = name.substr(2); |
| 5368 | + if(valid_name_string(name)) { |
| 5369 | + long_names.push_back(name); |
| 5370 | + } else { |
| 5371 | + throw BadNameString::BadLongName(name); |
| 5372 | + } |
| 5373 | + } else if(name == "-" || name == "--" || name == "++") { |
| 5374 | + throw BadNameString::ReservedName(name); |
| 5375 | + } else { |
| 5376 | + if(!pos_name.empty()) { |
| 5377 | + throw BadNameString::MultiPositionalNames(name); |
| 5378 | + } |
| 5379 | + if(valid_name_string(name)) { |
| 5380 | + pos_name = name; |
| 5381 | + } else { |
| 5382 | + throw BadNameString::BadPositionalName(name); |
| 5383 | + } |
| 5384 | + } |
| 5385 | + } |
| 5386 | + return std::make_tuple(short_names, long_names, pos_name); |
| 5387 | +} |
| 5388 | + |
| 5389 | +} // namespace detail |
| 5390 | + |
| 5391 | + |
| 5392 | + |
| 5393 | +class App; |
| 5394 | + |
| 5395 | +/// Holds values to load into Options |
| 5396 | +struct ConfigItem { |
| 5397 | + /// This is the list of parents |
| 5398 | + std::vector<std::string> parents{}; |
| 5399 | + |
| 5400 | + /// This is the name |
| 5401 | + std::string name{}; |
| 5402 | + /// Listing of inputs |
| 5403 | + std::vector<std::string> inputs{}; |
| 5404 | + /// @brief indicator if a multiline vector separator was inserted |
| 5405 | + bool multiline{false}; |
| 5406 | + /// The list of parents and name joined by "." |
| 5407 | + CLI11_NODISCARD std::string fullname() const { |
| 5408 | + std::vector<std::string> tmp = parents; |
| 5409 | + tmp.emplace_back(name); |
| 5410 | + return detail::join(tmp, "."); |
| 5411 | + (void)multiline; // suppression for cppcheck false positive |
| 5412 | + } |
| 5413 | +}; |
| 5414 | + |
| 5415 | +/// This class provides a converter for configuration files. |
| 5416 | +class Config { |
| 5417 | + protected: |
| 5418 | + std::vector<ConfigItem> items{}; |
| 5419 | + |
| 5420 | + public: |
| 5421 | + /// Convert an app into a configuration |
| 5422 | + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; |
| 5423 | + |
| 5424 | + /// Convert a configuration into an app |
| 5425 | + virtual std::vector<ConfigItem> from_config(std::istream &) const = 0; |
| 5426 | + |
| 5427 | + /// Get a flag value |
| 5428 | + CLI11_NODISCARD virtual std::string to_flag(const ConfigItem &item) const { |
| 5429 | + if(item.inputs.size() == 1) { |
| 5430 | + return item.inputs.at(0); |
| 5431 | + } |
| 5432 | + if(item.inputs.empty()) { |
| 5433 | + return "{}"; |
| 5434 | + } |
| 5435 | + throw ConversionError::TooManyInputsFlag(item.fullname()); // LCOV_EXCL_LINE |
| 5436 | + } |
| 5437 | + |
| 5438 | + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure |
| 5439 | + CLI11_NODISCARD std::vector<ConfigItem> from_file(const std::string &name) const { |
| 5440 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 |
| 5441 | + std::ifstream input{to_path(name)}; |
| 5442 | +#else |
| 5443 | + std::ifstream input{name}; |
| 5444 | +#endif |
| 5445 | + |
| 5446 | + if(!input.good()) |
| 5447 | + throw FileError::Missing(name); |
| 5448 | + |
| 5449 | + return from_config(input); |
| 5450 | + } |
| 5451 | + |
| 5452 | + /// Virtual destructor |
| 5453 | + virtual ~Config() = default; |
| 5454 | +}; |
| 5455 | + |
| 5456 | +/// This converter works with INI/TOML files; to write INI files use ConfigINI |
| 5457 | +class ConfigBase : public Config { |
| 5458 | + protected: |
| 5459 | + /// the character used for comments |
| 5460 | + char commentChar = '#'; |
| 5461 | + /// the character used to start an array '\0' is a default to not use |
| 5462 | + char arrayStart = '['; |
| 5463 | + /// the character used to end an array '\0' is a default to not use |
| 5464 | + char arrayEnd = ']'; |
| 5465 | + /// the character used to separate elements in an array |
| 5466 | + char arraySeparator = ','; |
| 5467 | + /// the character used separate the name from the value |
| 5468 | + char valueDelimiter = '='; |
| 5469 | + /// the character to use around strings |
| 5470 | + char stringQuote = '"'; |
| 5471 | + /// the character to use around single characters and literal strings |
| 5472 | + char literalQuote = '\''; |
| 5473 | + /// the maximum number of layers to allow |
| 5474 | + uint8_t maximumLayers{255}; |
| 5475 | + /// the separator used to separator parent layers |
| 5476 | + char parentSeparatorChar{'.'}; |
| 5477 | + /// comment default values |
| 5478 | + bool commentDefaultsBool = false; |
| 5479 | + /// specify the config reader should collapse repeated field names to a single vector |
| 5480 | + bool allowMultipleDuplicateFields{false}; |
| 5481 | + /// Specify the configuration index to use for arrayed sections |
| 5482 | + int16_t configIndex{-1}; |
| 5483 | + /// Specify the configuration section that should be used |
| 5484 | + std::string configSection{}; |
| 5485 | + |
| 5486 | + public: |
| 5487 | + std::string |
| 5488 | + to_config(const App * /*app*/, bool default_also, bool write_description, std::string prefix) const override; |
| 5489 | + |
| 5490 | + std::vector<ConfigItem> from_config(std::istream &input) const override; |
| 5491 | + /// Specify the configuration for comment characters |
| 5492 | + ConfigBase *comment(char cchar) { |
| 5493 | + commentChar = cchar; |
| 5494 | + return this; |
| 5495 | + } |
| 5496 | + /// Specify the start and end characters for an array |
| 5497 | + ConfigBase *arrayBounds(char aStart, char aEnd) { |
| 5498 | + arrayStart = aStart; |
| 5499 | + arrayEnd = aEnd; |
| 5500 | + return this; |
| 5501 | + } |
| 5502 | + /// Specify the delimiter character for an array |
| 5503 | + ConfigBase *arrayDelimiter(char aSep) { |
| 5504 | + arraySeparator = aSep; |
| 5505 | + return this; |
| 5506 | + } |
| 5507 | + /// Specify the delimiter between a name and value |
| 5508 | + ConfigBase *valueSeparator(char vSep) { |
| 5509 | + valueDelimiter = vSep; |
| 5510 | + return this; |
| 5511 | + } |
| 5512 | + /// Specify the quote characters used around strings and literal strings |
| 5513 | + ConfigBase *quoteCharacter(char qString, char literalChar) { |
| 5514 | + stringQuote = qString; |
| 5515 | + literalQuote = literalChar; |
| 5516 | + return this; |
| 5517 | + } |
| 5518 | + /// Specify the maximum number of parents |
| 5519 | + ConfigBase *maxLayers(uint8_t layers) { |
| 5520 | + maximumLayers = layers; |
| 5521 | + return this; |
| 5522 | + } |
| 5523 | + /// Specify the separator to use for parent layers |
| 5524 | + ConfigBase *parentSeparator(char sep) { |
| 5525 | + parentSeparatorChar = sep; |
| 5526 | + return this; |
| 5527 | + } |
| 5528 | + /// comment default value options |
| 5529 | + ConfigBase *commentDefaults(bool comDef = true) { |
| 5530 | + commentDefaultsBool = comDef; |
| 5531 | + return this; |
| 5532 | + } |
| 5533 | + /// get a reference to the configuration section |
| 5534 | + std::string §ionRef() { return configSection; } |
| 5535 | + /// get the section |
| 5536 | + CLI11_NODISCARD const std::string §ion() const { return configSection; } |
| 5537 | + /// specify a particular section of the configuration file to use |
| 5538 | + ConfigBase *section(const std::string §ionName) { |
| 5539 | + configSection = sectionName; |
| 5540 | + return this; |
| 5541 | + } |
| 5542 | + |
| 5543 | + /// get a reference to the configuration index |
| 5544 | + int16_t &indexRef() { return configIndex; } |
| 5545 | + /// get the section index |
| 5546 | + CLI11_NODISCARD int16_t index() const { return configIndex; } |
| 5547 | + /// specify a particular index in the section to use (-1) for all sections to use |
| 5548 | + ConfigBase *index(int16_t sectionIndex) { |
| 5549 | + configIndex = sectionIndex; |
| 5550 | + return this; |
| 5551 | + } |
| 5552 | + /// specify that multiple duplicate arguments should be merged even if not sequential |
| 5553 | + ConfigBase *allowDuplicateFields(bool value = true) { |
| 5554 | + allowMultipleDuplicateFields = value; |
| 5555 | + return this; |
| 5556 | + } |
| 5557 | +}; |
| 5558 | + |
| 5559 | +/// the default Config is the TOML file format |
| 5560 | +using ConfigTOML = ConfigBase; |
| 5561 | + |
| 5562 | +/// ConfigINI generates a "standard" INI compliant output |
| 5563 | +class ConfigINI : public ConfigTOML { |
| 5564 | + |
| 5565 | + public: |
| 5566 | + ConfigINI() { |
| 5567 | + commentChar = ';'; |
| 5568 | + arrayStart = '\0'; |
| 5569 | + arrayEnd = '\0'; |
| 5570 | + arraySeparator = ' '; |
| 5571 | + valueDelimiter = '='; |
| 5572 | + } |
| 5573 | +}; |
| 5574 | + |
| 5575 | + |
| 5576 | + |
| 5577 | +class Option; |
| 5578 | + |
| 5579 | +/// @defgroup validator_group Validators |
| 5580 | + |
| 5581 | +/// @brief Some validators that are provided |
| 5582 | +/// |
| 5583 | +/// These are simple `std::string(const std::string&)` validators that are useful. They return |
| 5584 | +/// a string if the validation fails. A custom struct is provided, as well, with the same user |
| 5585 | +/// semantics, but with the ability to provide a new type name. |
| 5586 | +/// @{ |
| 5587 | + |
| 5588 | +/// |
| 5589 | +class Validator { |
| 5590 | + protected: |
| 5591 | + /// This is the description function, if empty the description_ will be used |
| 5592 | + std::function<std::string()> desc_function_{[]() { return std::string{}; }}; |
| 5593 | + |
| 5594 | + /// This is the base function that is to be called. |
| 5595 | + /// Returns a string error message if validation fails. |
| 5596 | + std::function<std::string(std::string &)> func_{[](std::string &) { return std::string{}; }}; |
| 5597 | + /// The name for search purposes of the Validator |
| 5598 | + std::string name_{}; |
| 5599 | + /// A Validator will only apply to an indexed value (-1 is all elements) |
| 5600 | + int application_index_ = -1; |
| 5601 | + /// Enable for Validator to allow it to be disabled if need be |
| 5602 | + bool active_{true}; |
| 5603 | + /// specify that a validator should not modify the input |
| 5604 | + bool non_modifying_{false}; |
| 5605 | + |
| 5606 | + Validator(std::string validator_desc, std::function<std::string(std::string &)> func) |
| 5607 | + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(func)) {} |
| 5608 | + |
| 5609 | + public: |
| 5610 | + Validator() = default; |
| 5611 | + /// Construct a Validator with just the description string |
| 5612 | + explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {} |
| 5613 | + /// Construct Validator from basic information |
| 5614 | + Validator(std::function<std::string(std::string &)> op, std::string validator_desc, std::string validator_name = "") |
| 5615 | + : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)), |
| 5616 | + name_(std::move(validator_name)) {} |
| 5617 | + /// Set the Validator operation function |
| 5618 | + Validator &operation(std::function<std::string(std::string &)> op) { |
| 5619 | + func_ = std::move(op); |
| 5620 | + return *this; |
| 5621 | + } |
| 5622 | + /// This is the required operator for a Validator - provided to help |
| 5623 | + /// users (CLI11 uses the member `func` directly) |
| 5624 | + std::string operator()(std::string &str) const; |
| 5625 | + |
| 5626 | + /// This is the required operator for a Validator - provided to help |
| 5627 | + /// users (CLI11 uses the member `func` directly) |
| 5628 | + std::string operator()(const std::string &str) const { |
| 5629 | + std::string value = str; |
| 5630 | + return (active_) ? func_(value) : std::string{}; |
| 5631 | + } |
| 5632 | + |
| 5633 | + /// Specify the type string |
| 5634 | + Validator &description(std::string validator_desc) { |
| 5635 | + desc_function_ = [validator_desc]() { return validator_desc; }; |
| 5636 | + return *this; |
| 5637 | + } |
| 5638 | + /// Specify the type string |
| 5639 | + CLI11_NODISCARD Validator description(std::string validator_desc) const; |
| 5640 | + |
| 5641 | + /// Generate type description information for the Validator |
| 5642 | + CLI11_NODISCARD std::string get_description() const { |
| 5643 | + if(active_) { |
| 5644 | + return desc_function_(); |
| 5645 | + } |
| 5646 | + return std::string{}; |
| 5647 | + } |
| 5648 | + /// Specify the type string |
| 5649 | + Validator &name(std::string validator_name) { |
| 5650 | + name_ = std::move(validator_name); |
| 5651 | + return *this; |
| 5652 | + } |
| 5653 | + /// Specify the type string |
| 5654 | + CLI11_NODISCARD Validator name(std::string validator_name) const { |
| 5655 | + Validator newval(*this); |
| 5656 | + newval.name_ = std::move(validator_name); |
| 5657 | + return newval; |
| 5658 | + } |
| 5659 | + /// Get the name of the Validator |
| 5660 | + CLI11_NODISCARD const std::string &get_name() const { return name_; } |
| 5661 | + /// Specify whether the Validator is active or not |
| 5662 | + Validator &active(bool active_val = true) { |
| 5663 | + active_ = active_val; |
| 5664 | + return *this; |
| 5665 | + } |
| 5666 | + /// Specify whether the Validator is active or not |
| 5667 | + CLI11_NODISCARD Validator active(bool active_val = true) const { |
| 5668 | + Validator newval(*this); |
| 5669 | + newval.active_ = active_val; |
| 5670 | + return newval; |
| 5671 | + } |
| 5672 | + |
| 5673 | + /// Specify whether the Validator can be modifying or not |
| 5674 | + Validator &non_modifying(bool no_modify = true) { |
| 5675 | + non_modifying_ = no_modify; |
| 5676 | + return *this; |
| 5677 | + } |
| 5678 | + /// Specify the application index of a validator |
| 5679 | + Validator &application_index(int app_index) { |
| 5680 | + application_index_ = app_index; |
| 5681 | + return *this; |
| 5682 | + } |
| 5683 | + /// Specify the application index of a validator |
| 5684 | + CLI11_NODISCARD Validator application_index(int app_index) const { |
| 5685 | + Validator newval(*this); |
| 5686 | + newval.application_index_ = app_index; |
| 5687 | + return newval; |
| 5688 | + } |
| 5689 | + /// Get the current value of the application index |
| 5690 | + CLI11_NODISCARD int get_application_index() const { return application_index_; } |
| 5691 | + /// Get a boolean if the validator is active |
| 5692 | + CLI11_NODISCARD bool get_active() const { return active_; } |
| 5693 | + |
| 5694 | + /// Get a boolean if the validator is allowed to modify the input returns true if it can modify the input |
| 5695 | + CLI11_NODISCARD bool get_modifying() const { return !non_modifying_; } |
| 5696 | + |
| 5697 | + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the |
| 5698 | + /// same. |
| 5699 | + Validator operator&(const Validator &other) const; |
| 5700 | + |
| 5701 | + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the |
| 5702 | + /// same. |
| 5703 | + Validator operator|(const Validator &other) const; |
| 5704 | + |
| 5705 | + /// Create a validator that fails when a given validator succeeds |
| 5706 | + Validator operator!() const; |
| 5707 | + |
| 5708 | + private: |
| 5709 | + void _merge_description(const Validator &val1, const Validator &val2, const std::string &merger); |
| 5710 | +}; |
| 5711 | + |
| 5712 | +/// Alias for Validator for custom Validator for clarity |
| 5713 | +using CustomValidator = Validator; |
| 5714 | + |
| 5715 | +// The implementation of the built in validators is using the Validator class; |
| 5716 | +// the user is only expected to use the const (static) versions (since there's no setup). |
| 5717 | +// Therefore, this is in detail. |
| 5718 | +namespace detail { |
| 5719 | + |
| 5720 | +/// CLI enumeration of different file types |
| 5721 | +enum class path_type : std::uint8_t { nonexistent, file, directory }; |
| 5722 | + |
| 5723 | +/// get the type of the path from a file name |
| 5724 | +CLI11_INLINE path_type check_path(const char *file) noexcept; |
| 5725 | + |
| 5726 | +// Static is not needed here, because global const implies static. |
| 5727 | + |
| 5728 | +/// Check for an existing file (returns error message if check fails) |
| 5729 | +class ExistingFileValidator : public Validator { |
| 5730 | + public: |
| 5731 | + ExistingFileValidator(); |
| 5732 | +}; |
| 5733 | + |
| 5734 | +/// Check for an existing directory (returns error message if check fails) |
| 5735 | +class ExistingDirectoryValidator : public Validator { |
| 5736 | + public: |
| 5737 | + ExistingDirectoryValidator(); |
| 5738 | +}; |
| 5739 | + |
| 5740 | +/// Check for an existing path |
| 5741 | +class ExistingPathValidator : public Validator { |
| 5742 | + public: |
| 5743 | + ExistingPathValidator(); |
| 5744 | +}; |
| 5745 | + |
| 5746 | +/// Check for an non-existing path |
| 5747 | +class NonexistentPathValidator : public Validator { |
| 5748 | + public: |
| 5749 | + NonexistentPathValidator(); |
| 5750 | +}; |
| 5751 | + |
| 5752 | +class EscapedStringTransformer : public Validator { |
| 5753 | + public: |
| 5754 | + EscapedStringTransformer(); |
| 5755 | +}; |
| 5756 | + |
| 5757 | +} // namespace detail |
| 5758 | + |
| 5759 | +/// Check for existing file (returns error message if check fails) |
| 5760 | +CLI11_MODULE_INLINE const detail::ExistingFileValidator ExistingFile; |
| 5761 | + |
| 5762 | +/// Check for an existing directory (returns error message if check fails) |
| 5763 | +CLI11_MODULE_INLINE const detail::ExistingDirectoryValidator ExistingDirectory; |
| 5764 | + |
| 5765 | +/// Check for an existing path |
| 5766 | +CLI11_MODULE_INLINE const detail::ExistingPathValidator ExistingPath; |
| 5767 | + |
| 5768 | +/// Check for an non-existing path |
| 5769 | +CLI11_MODULE_INLINE const detail::NonexistentPathValidator NonexistentPath; |
| 5770 | + |
| 5771 | +/// convert escaped characters into their associated values |
| 5772 | +CLI11_MODULE_INLINE const detail::EscapedStringTransformer EscapedString; |
| 5773 | + |
| 5774 | +/// Modify a path if the file is a particular default location, can be used as Check or transform |
| 5775 | +/// with the error return optionally disabled |
| 5776 | +class FileOnDefaultPath : public Validator { |
| 5777 | + public: |
| 5778 | + explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true); |
| 5779 | +}; |
| 5780 | + |
| 5781 | +/// Produce a range (factory). Min and max are inclusive. |
| 5782 | +class Range : public Validator { |
| 5783 | + public: |
| 5784 | + /// This produces a range with min and max inclusive. |
| 5785 | + /// |
| 5786 | + /// Note that the constructor is templated, but the struct is not, so C++17 is not |
| 5787 | + /// needed to provide nice syntax for Range(a,b). |
| 5788 | + template <typename T> |
| 5789 | + Range(T min_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) { |
| 5790 | + if(validator_name.empty()) { |
| 5791 | + std::stringstream out; |
| 5792 | + out << detail::type_name<T>() << " in [" << min_val << " - " << max_val << "]"; |
| 5793 | + description(out.str()); |
| 5794 | + } |
| 5795 | + |
| 5796 | + func_ = [min_val, max_val](std::string &input) { |
| 5797 | + using CLI::detail::lexical_cast; |
| 5798 | + T val; |
| 5799 | + bool converted = lexical_cast(input, val); |
| 5800 | + if((!converted) || (val < min_val || val > max_val)) { |
| 5801 | + std::stringstream out; |
| 5802 | + out << "Value " << input << " not in range ["; |
| 5803 | + out << min_val << " - " << max_val << "]"; |
| 5804 | + return out.str(); |
| 5805 | + } |
| 5806 | + return std::string{}; |
| 5807 | + }; |
| 5808 | + } |
| 5809 | + |
| 5810 | + /// Range of one value is 0 to value |
| 5811 | + template <typename T> |
| 5812 | + explicit Range(T max_val, const std::string &validator_name = std::string{}) |
| 5813 | + : Range(static_cast<T>(0), max_val, validator_name) {} |
| 5814 | +}; |
| 5815 | + |
| 5816 | +/// Check for a non negative number |
| 5817 | +CLI11_MODULE_INLINE const Range NonNegativeNumber((std::numeric_limits<double>::max)(), "NONNEGATIVE"); |
| 5818 | + |
| 5819 | +/// Check for a positive valued number (val>0.0), <double>::min here is the smallest positive number |
| 5820 | +CLI11_MODULE_INLINE const |
| 5821 | + Range PositiveNumber((std::numeric_limits<double>::min)(), (std::numeric_limits<double>::max)(), "POSITIVE"); |
| 5822 | + |
| 5823 | +namespace detail { |
| 5824 | +// the following suggestion was made by Nikita Ofitserov(@himikof) |
| 5825 | +// done in templates to prevent compiler warnings on negation of unsigned numbers |
| 5826 | + |
| 5827 | +/// Do a check for overflow on signed numbers |
| 5828 | +template <typename T> |
| 5829 | +inline typename std::enable_if<std::is_signed<T>::value, T>::type overflowCheck(const T &a, const T &b) { |
| 5830 | + if((a > 0) == (b > 0)) { |
| 5831 | + return ((std::numeric_limits<T>::max)() / (std::abs)(a) < (std::abs)(b)); |
| 5832 | + } |
| 5833 | + return ((std::numeric_limits<T>::min)() / (std::abs)(a) > -(std::abs)(b)); |
| 5834 | +} |
| 5835 | +/// Do a check for overflow on unsigned numbers |
| 5836 | +template <typename T> |
| 5837 | +inline typename std::enable_if<!std::is_signed<T>::value, T>::type overflowCheck(const T &a, const T &b) { |
| 5838 | + return ((std::numeric_limits<T>::max)() / a < b); |
| 5839 | +} |
| 5840 | + |
| 5841 | +/// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. |
| 5842 | +template <typename T> typename std::enable_if<std::is_integral<T>::value, bool>::type checked_multiply(T &a, T b) { |
| 5843 | + if(a == 0 || b == 0 || a == 1 || b == 1) { |
| 5844 | + a *= b; |
| 5845 | + return true; |
| 5846 | + } |
| 5847 | + if(a == (std::numeric_limits<T>::min)() || b == (std::numeric_limits<T>::min)()) { |
| 5848 | + return false; |
| 5849 | + } |
| 5850 | + if(overflowCheck(a, b)) { |
| 5851 | + return false; |
| 5852 | + } |
| 5853 | + a *= b; |
| 5854 | + return true; |
| 5855 | +} |
| 5856 | + |
| 5857 | +/// Performs a *= b; if it doesn't equal infinity. Returns false otherwise. |
| 5858 | +template <typename T> |
| 5859 | +typename std::enable_if<std::is_floating_point<T>::value, bool>::type checked_multiply(T &a, T b) { |
| 5860 | + T c = a * b; |
| 5861 | + if(std::isinf(c) && !std::isinf(a) && !std::isinf(b)) { |
| 5862 | + return false; |
| 5863 | + } |
| 5864 | + a = c; |
| 5865 | + return true; |
| 5866 | +} |
| 5867 | +/// Split a string into a program name and command line arguments |
| 5868 | +/// the string is assumed to contain a file name followed by other arguments |
| 5869 | +/// the return value contains is a pair with the first argument containing the program name and the second |
| 5870 | +/// everything else. |
| 5871 | +CLI11_INLINE std::pair<std::string, std::string> split_program_name(std::string commandline); |
| 5872 | + |
| 5873 | +} // namespace detail |
| 5874 | +/// @} |
| 5875 | + |
| 5876 | + |
| 5877 | + |
| 5878 | + |
| 5879 | +CLI11_INLINE std::string Validator::operator()(std::string &str) const { |
| 5880 | + std::string retstring; |
| 5881 | + if(active_) { |
| 5882 | + if(non_modifying_) { |
| 5883 | + std::string value = str; |
| 5884 | + retstring = func_(value); |
| 5885 | + } else { |
| 5886 | + retstring = func_(str); |
| 5887 | + } |
| 5888 | + } |
| 5889 | + return retstring; |
| 5890 | +} |
| 5891 | + |
| 5892 | +CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const { |
| 5893 | + Validator newval(*this); |
| 5894 | + newval.desc_function_ = [validator_desc]() { return validator_desc; }; |
| 5895 | + return newval; |
| 5896 | +} |
| 5897 | + |
| 5898 | +CLI11_INLINE Validator Validator::operator&(const Validator &other) const { |
| 5899 | + Validator newval; |
| 5900 | + |
| 5901 | + newval._merge_description(*this, other, " AND "); |
| 5902 | + |
| 5903 | + // Give references (will make a copy in lambda function) |
| 5904 | + const std::function<std::string(std::string & filename)> &f1 = func_; |
| 5905 | + const std::function<std::string(std::string & filename)> &f2 = other.func_; |
| 5906 | + |
| 5907 | + newval.func_ = [f1, f2](std::string &input) { |
| 5908 | + std::string s1 = f1(input); |
| 5909 | + std::string s2 = f2(input); |
| 5910 | + if(!s1.empty() && !s2.empty()) |
| 5911 | + return std::string("(") + s1 + ") AND (" + s2 + ")"; |
| 5912 | + return s1 + s2; |
| 5913 | + }; |
| 5914 | + |
| 5915 | + newval.active_ = active_ && other.active_; |
| 5916 | + newval.application_index_ = application_index_; |
| 5917 | + return newval; |
| 5918 | +} |
| 5919 | + |
| 5920 | +CLI11_INLINE Validator Validator::operator|(const Validator &other) const { |
| 5921 | + Validator newval; |
| 5922 | + |
| 5923 | + newval._merge_description(*this, other, " OR "); |
| 5924 | + |
| 5925 | + // Give references (will make a copy in lambda function) |
| 5926 | + const std::function<std::string(std::string &)> &f1 = func_; |
| 5927 | + const std::function<std::string(std::string &)> &f2 = other.func_; |
| 5928 | + |
| 5929 | + newval.func_ = [f1, f2](std::string &input) { |
| 5930 | + std::string s1 = f1(input); |
| 5931 | + std::string s2 = f2(input); |
| 5932 | + if(s1.empty() || s2.empty()) |
| 5933 | + return std::string(); |
| 5934 | + |
| 5935 | + return std::string("(") + s1 + ") OR (" + s2 + ")"; |
| 5936 | + }; |
| 5937 | + newval.active_ = active_ && other.active_; |
| 5938 | + newval.application_index_ = application_index_; |
| 5939 | + return newval; |
| 5940 | +} |
| 5941 | + |
| 5942 | +CLI11_INLINE Validator Validator::operator!() const { |
| 5943 | + Validator newval; |
| 5944 | + const std::function<std::string()> &dfunc1 = desc_function_; |
| 5945 | + newval.desc_function_ = [dfunc1]() { |
| 5946 | + auto str = dfunc1(); |
| 5947 | + return (!str.empty()) ? std::string("NOT ") + str : std::string{}; |
| 5948 | + }; |
| 5949 | + // Give references (will make a copy in lambda function) |
| 5950 | + const std::function<std::string(std::string & res)> &f1 = func_; |
| 5951 | + |
| 5952 | + newval.func_ = [f1, dfunc1](std::string &test) -> std::string { |
| 5953 | + std::string s1 = f1(test); |
| 5954 | + if(s1.empty()) { |
| 5955 | + return std::string("check ") + dfunc1() + " succeeded improperly"; |
| 5956 | + } |
| 5957 | + return std::string{}; |
| 5958 | + }; |
| 5959 | + newval.active_ = active_; |
| 5960 | + newval.application_index_ = application_index_; |
| 5961 | + return newval; |
| 5962 | +} |
| 5963 | + |
| 5964 | +CLI11_INLINE void |
| 5965 | +Validator::_merge_description(const Validator &val1, const Validator &val2, const std::string &merger) { |
| 5966 | + |
| 5967 | + const std::function<std::string()> &dfunc1 = val1.desc_function_; |
| 5968 | + const std::function<std::string()> &dfunc2 = val2.desc_function_; |
| 5969 | + |
| 5970 | + desc_function_ = [=]() { |
| 5971 | + std::string f1 = dfunc1(); |
| 5972 | + std::string f2 = dfunc2(); |
| 5973 | + if((f1.empty()) || (f2.empty())) { |
| 5974 | + return f1 + f2; |
| 5975 | + } |
| 5976 | + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; |
| 5977 | + }; |
| 5978 | +} |
| 5979 | + |
| 5980 | +namespace detail { |
| 5981 | + |
| 5982 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 |
| 5983 | +CLI11_INLINE path_type check_path(const char *file) noexcept { |
| 5984 | + std::error_code ec; |
| 5985 | + auto stat = std::filesystem::status(to_path(file), ec); |
| 5986 | + if(ec) { |
| 5987 | + return path_type::nonexistent; |
| 5988 | + } |
| 5989 | + switch(stat.type()) { |
| 5990 | + case std::filesystem::file_type::none: // LCOV_EXCL_LINE |
| 5991 | + case std::filesystem::file_type::not_found: |
| 5992 | + return path_type::nonexistent; // LCOV_EXCL_LINE |
| 5993 | + case std::filesystem::file_type::directory: |
| 5994 | + return path_type::directory; |
| 5995 | + case std::filesystem::file_type::symlink: |
| 5996 | + case std::filesystem::file_type::block: |
| 5997 | + case std::filesystem::file_type::character: |
| 5998 | + case std::filesystem::file_type::fifo: |
| 5999 | + case std::filesystem::file_type::socket: |
| 6000 | + case std::filesystem::file_type::regular: |
| 6001 | + case std::filesystem::file_type::unknown: |
| 6002 | + default: |
| 6003 | + return path_type::file; |
| 6004 | + } |
| 6005 | +} |
| 6006 | +#else |
| 6007 | +CLI11_INLINE path_type check_path(const char *file) noexcept { |
| 6008 | +#if defined(_MSC_VER) |
| 6009 | + struct __stat64 buffer; |
| 6010 | + if(_stat64(file, &buffer) == 0) { |
| 6011 | + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; |
| 6012 | + } |
| 6013 | +#else |
| 6014 | + struct stat buffer; |
| 6015 | + if(stat(file, &buffer) == 0) { |
| 6016 | + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; |
| 6017 | + } |
| 6018 | +#endif |
| 6019 | + return path_type::nonexistent; |
| 6020 | +} |
| 6021 | +#endif |
| 6022 | + |
| 6023 | +CLI11_INLINE ExistingFileValidator::ExistingFileValidator() : Validator("FILE") { |
| 6024 | + func_ = [](std::string &filename) { |
| 6025 | + auto path_result = check_path(filename.c_str()); |
| 6026 | + if(path_result == path_type::nonexistent) { |
| 6027 | + return "File does not exist: " + filename; |
| 6028 | + } |
| 6029 | + if(path_result == path_type::directory) { |
| 6030 | + return "File is actually a directory: " + filename; |
| 6031 | + } |
| 6032 | + return std::string(); |
| 6033 | + }; |
| 6034 | +} |
| 6035 | + |
| 6036 | +CLI11_INLINE ExistingDirectoryValidator::ExistingDirectoryValidator() : Validator("DIR") { |
| 6037 | + func_ = [](std::string &filename) { |
| 6038 | + auto path_result = check_path(filename.c_str()); |
| 6039 | + if(path_result == path_type::nonexistent) { |
| 6040 | + return "Directory does not exist: " + filename; |
| 6041 | + } |
| 6042 | + if(path_result == path_type::file) { |
| 6043 | + return "Directory is actually a file: " + filename; |
| 6044 | + } |
| 6045 | + return std::string(); |
| 6046 | + }; |
| 6047 | +} |
| 6048 | + |
| 6049 | +CLI11_INLINE ExistingPathValidator::ExistingPathValidator() : Validator("PATH(existing)") { |
| 6050 | + func_ = [](std::string &filename) { |
| 6051 | + auto path_result = check_path(filename.c_str()); |
| 6052 | + if(path_result == path_type::nonexistent) { |
| 6053 | + return "Path does not exist: " + filename; |
| 6054 | + } |
| 6055 | + return std::string(); |
| 6056 | + }; |
| 6057 | +} |
| 6058 | + |
| 6059 | +CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("PATH(non-existing)") { |
| 6060 | + func_ = [](std::string &filename) { |
| 6061 | + auto path_result = check_path(filename.c_str()); |
| 6062 | + if(path_result != path_type::nonexistent) { |
| 6063 | + return "Path already exists: " + filename; |
| 6064 | + } |
| 6065 | + return std::string(); |
| 6066 | + }; |
| 6067 | +} |
| 6068 | + |
| 6069 | +CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { |
| 6070 | + func_ = [](std::string &str) { |
| 6071 | + try { |
| 6072 | + if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') && |
| 6073 | + str.front() == str.back()) { |
| 6074 | + process_quoted_string(str); |
| 6075 | + } else if(str.find_first_of('\\') != std::string::npos) { |
| 6076 | + if(detail::is_binary_escaped_string(str)) { |
| 6077 | + str = detail::extract_binary_string(str); |
| 6078 | + } else { |
| 6079 | + str = remove_escaped_characters(str); |
| 6080 | + } |
| 6081 | + } |
| 6082 | + return std::string{}; |
| 6083 | + } catch(const std::invalid_argument &ia) { |
| 6084 | + return std::string(ia.what()); |
| 6085 | + } |
| 6086 | + }; |
| 6087 | +} |
| 6088 | +} // namespace detail |
| 6089 | + |
| 6090 | +CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn) |
| 6091 | + : Validator("FILE") { |
| 6092 | + func_ = [default_path, enableErrorReturn](std::string &filename) { |
| 6093 | + auto path_result = detail::check_path(filename.c_str()); |
| 6094 | + if(path_result == detail::path_type::nonexistent) { |
| 6095 | + std::string test_file_path = default_path; |
| 6096 | + if(default_path.back() != '/' && default_path.back() != '\\') { |
| 6097 | + // Add folder separator |
| 6098 | + test_file_path += '/'; |
| 6099 | + } |
| 6100 | + test_file_path.append(filename); |
| 6101 | + path_result = detail::check_path(test_file_path.c_str()); |
| 6102 | + if(path_result == detail::path_type::file) { |
| 6103 | + filename = test_file_path; |
| 6104 | + } else { |
| 6105 | + if(enableErrorReturn) { |
| 6106 | + return "File does not exist: " + filename; |
| 6107 | + } |
| 6108 | + } |
| 6109 | + } |
| 6110 | + return std::string{}; |
| 6111 | + }; |
| 6112 | +} |
| 6113 | + |
| 6114 | +namespace detail { |
| 6115 | + |
| 6116 | +CLI11_INLINE std::pair<std::string, std::string> split_program_name(std::string commandline) { |
| 6117 | + // try to determine the programName |
| 6118 | + std::pair<std::string, std::string> vals; |
| 6119 | + trim(commandline); |
| 6120 | + auto esp = commandline.find_first_of(' ', 1); |
| 6121 | + while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { |
| 6122 | + esp = commandline.find_first_of(' ', esp + 1); |
| 6123 | + if(esp == std::string::npos) { |
| 6124 | + // if we have reached the end and haven't found a valid file just assume the first argument is the |
| 6125 | + // program name |
| 6126 | + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { |
| 6127 | + bool embeddedQuote = false; |
| 6128 | + auto keyChar = commandline[0]; |
| 6129 | + auto end = commandline.find_first_of(keyChar, 1); |
| 6130 | + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes |
| 6131 | + end = commandline.find_first_of(keyChar, end + 1); |
| 6132 | + embeddedQuote = true; |
| 6133 | + } |
| 6134 | + if(end != std::string::npos) { |
| 6135 | + vals.first = commandline.substr(1, end - 1); |
| 6136 | + esp = end + 1; |
| 6137 | + if(embeddedQuote) { |
| 6138 | + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); |
| 6139 | + } |
| 6140 | + } else { |
| 6141 | + esp = commandline.find_first_of(' ', 1); |
| 6142 | + } |
| 6143 | + } else { |
| 6144 | + esp = commandline.find_first_of(' ', 1); |
| 6145 | + } |
| 6146 | + |
| 6147 | + break; |
| 6148 | + } |
| 6149 | + } |
| 6150 | + if(vals.first.empty()) { |
| 6151 | + vals.first = commandline.substr(0, esp); |
| 6152 | + rtrim(vals.first); |
| 6153 | + } |
| 6154 | + |
| 6155 | + // strip the program name |
| 6156 | + vals.second = (esp < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{}; |
| 6157 | + ltrim(vals.second); |
| 6158 | + return vals; |
| 6159 | +} |
| 6160 | + |
| 6161 | +} // namespace detail |
| 6162 | +/// @} |
| 6163 | + |
| 6164 | + |
| 6165 | + |
| 6166 | + |
| 6167 | +// The implementation of the extra validators is using the Validator class; |
| 6168 | +// the user is only expected to use the const (static) versions (since there's no setup). |
| 6169 | +// Therefore, this is in detail. |
| 6170 | +namespace detail { |
| 6171 | + |
| 6172 | +/// Validate the given string is a legal ipv4 address |
| 6173 | +class IPV4Validator : public Validator { |
| 6174 | + public: |
| 6175 | + IPV4Validator(); |
| 6176 | +}; |
| 6177 | + |
| 6178 | +} // namespace detail |
| 6179 | + |
| 6180 | +/// Validate the input as a particular type |
| 6181 | +template <typename DesiredType> class TypeValidator : public Validator { |
| 6182 | + public: |
| 6183 | + explicit TypeValidator(const std::string &validator_name) |
| 6184 | + : Validator(validator_name, [](std::string &input_string) { |
| 6185 | + using CLI::detail::lexical_cast; |
| 6186 | + auto val = DesiredType(); |
| 6187 | + if(!lexical_cast(input_string, val)) { |
| 6188 | + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>(); |
| 6189 | + } |
| 6190 | + return std::string{}; |
| 6191 | + }) {} |
| 6192 | + TypeValidator() : TypeValidator(detail::type_name<DesiredType>()) {} |
| 6193 | +}; |
| 6194 | + |
| 6195 | +/// Check for a number |
| 6196 | +const TypeValidator<double> Number("NUMBER"); |
| 6197 | + |
| 6198 | +/// Produce a bounded range (factory). Min and max are inclusive. |
| 6199 | +class Bound : public Validator { |
| 6200 | + public: |
| 6201 | + /// This bounds a value with min and max inclusive. |
| 6202 | + /// |
| 6203 | + /// Note that the constructor is templated, but the struct is not, so C++17 is not |
| 6204 | + /// needed to provide nice syntax for Range(a,b). |
| 6205 | + template <typename T> Bound(T min_val, T max_val) { |
| 6206 | + std::stringstream out; |
| 6207 | + out << detail::type_name<T>() << " bounded to [" << min_val << " - " << max_val << "]"; |
| 6208 | + description(out.str()); |
| 6209 | + |
| 6210 | + func_ = [min_val, max_val](std::string &input) { |
| 6211 | + using CLI::detail::lexical_cast; |
| 6212 | + T val; |
| 6213 | + bool converted = lexical_cast(input, val); |
| 6214 | + if(!converted) { |
| 6215 | + return std::string("Value ") + input + " could not be converted"; |
| 6216 | + } |
| 6217 | + if(val < min_val) |
| 6218 | + input = detail::to_string(min_val); |
| 6219 | + else if(val > max_val) |
| 6220 | + input = detail::to_string(max_val); |
| 6221 | + |
| 6222 | + return std::string{}; |
| 6223 | + }; |
| 6224 | + } |
| 6225 | + |
| 6226 | + /// Range of one value is 0 to value |
| 6227 | + template <typename T> explicit Bound(T max_val) : Bound(static_cast<T>(0), max_val) {} |
| 6228 | +}; |
| 6229 | + |
| 6230 | +// Static is not needed here, because global const implies static. |
| 6231 | + |
| 6232 | +/// Check for an IP4 address |
| 6233 | +CLI11_MODULE_INLINE const detail::IPV4Validator ValidIPV4; |
| 6234 | + |
| 6235 | +namespace detail { |
| 6236 | +template <typename T, |
| 6237 | + enable_if_t<is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy> |
| 6238 | +auto smart_deref(T value) -> decltype(*value) { |
| 6239 | + return *value; |
| 6240 | +} |
| 6241 | + |
| 6242 | +template < |
| 6243 | + typename T, |
| 6244 | + enable_if_t<!is_copyable_ptr<typename std::remove_reference<T>::type>::value, detail::enabler> = detail::dummy> |
| 6245 | +typename std::remove_reference<T>::type &smart_deref(T &value) { |
| 6246 | + // NOLINTNEXTLINE |
| 6247 | + return value; |
| 6248 | +} |
| 6249 | +/// Generate a string representation of a set |
| 6250 | +template <typename T> std::string generate_set(const T &set) { |
| 6251 | + using element_t = typename detail::element_type<T>::type; |
| 6252 | + using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair |
| 6253 | + std::string out(1, '{'); |
| 6254 | + out.append(detail::join( |
| 6255 | + detail::smart_deref(set), |
| 6256 | + [](const iteration_type_t &v) { return detail::pair_adaptor<element_t>::first(v); }, |
| 6257 | + ",")); |
| 6258 | + out.push_back('}'); |
| 6259 | + return out; |
| 6260 | +} |
| 6261 | + |
| 6262 | +/// Generate a string representation of a map |
| 6263 | +template <typename T> std::string generate_map(const T &map, bool key_only = false) { |
| 6264 | + using element_t = typename detail::element_type<T>::type; |
| 6265 | + using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair |
| 6266 | + std::string out(1, '{'); |
| 6267 | + out.append(detail::join( |
| 6268 | + detail::smart_deref(map), |
| 6269 | + [key_only](const iteration_type_t &v) { |
| 6270 | + std::string res{detail::to_string(detail::pair_adaptor<element_t>::first(v))}; |
| 6271 | + |
| 6272 | + if(!key_only) { |
| 6273 | + res.append("->"); |
| 6274 | + res += detail::to_string(detail::pair_adaptor<element_t>::second(v)); |
| 6275 | + } |
| 6276 | + return res; |
| 6277 | + }, |
| 6278 | + ",")); |
| 6279 | + out.push_back('}'); |
| 6280 | + return out; |
| 6281 | +} |
| 6282 | + |
| 6283 | +template <typename C, typename V> struct has_find { |
| 6284 | + template <typename CC, typename VV> |
| 6285 | + static auto test(int) -> decltype(std::declval<CC>().find(std::declval<VV>()), std::true_type()); |
| 6286 | + template <typename, typename> static auto test(...) -> decltype(std::false_type()); |
| 6287 | + |
| 6288 | + static const auto value = decltype(test<C, V>(0))::value; |
| 6289 | + using type = std::integral_constant<bool, value>; |
| 6290 | +}; |
| 6291 | + |
| 6292 | +/// A search function |
| 6293 | +template <typename T, typename V, enable_if_t<!has_find<T, V>::value, detail::enabler> = detail::dummy> |
| 6294 | +auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> { |
| 6295 | + using element_t = typename detail::element_type<T>::type; |
| 6296 | + auto &setref = detail::smart_deref(set); |
| 6297 | + auto it = std::find_if(std::begin(setref), std::end(setref), [&val](decltype(*std::begin(setref)) v) { |
| 6298 | + return (detail::pair_adaptor<element_t>::first(v) == val); |
| 6299 | + }); |
| 6300 | + return {(it != std::end(setref)), it}; |
| 6301 | +} |
| 6302 | + |
| 6303 | +/// A search function that uses the built in find function |
| 6304 | +template <typename T, typename V, enable_if_t<has_find<T, V>::value, detail::enabler> = detail::dummy> |
| 6305 | +auto search(const T &set, const V &val) -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> { |
| 6306 | + auto &setref = detail::smart_deref(set); |
| 6307 | + auto it = setref.find(val); |
| 6308 | + return {(it != std::end(setref)), it}; |
| 6309 | +} |
| 6310 | + |
| 6311 | +/// A search function with a filter function |
| 6312 | +template <typename T, typename V> |
| 6313 | +auto search(const T &set, const V &val, const std::function<V(V)> &filter_function) |
| 6314 | + -> std::pair<bool, decltype(std::begin(detail::smart_deref(set)))> { |
| 6315 | + using element_t = typename detail::element_type<T>::type; |
| 6316 | + // do the potentially faster first search |
| 6317 | + auto res = search(set, val); |
| 6318 | + if((res.first) || (!(filter_function))) { |
| 6319 | + return res; |
| 6320 | + } |
| 6321 | + // if we haven't found it do the longer linear search with all the element translations |
| 6322 | + auto &setref = detail::smart_deref(set); |
| 6323 | + auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { |
| 6324 | + V a{detail::pair_adaptor<element_t>::first(v)}; |
| 6325 | + a = filter_function(a); |
| 6326 | + return (a == val); |
| 6327 | + }); |
| 6328 | + return {(it != std::end(setref)), it}; |
| 6329 | +} |
| 6330 | + |
| 6331 | +} // namespace detail |
| 6332 | + /// Verify items are in a set |
| 6333 | +class IsMember : public Validator { |
| 6334 | + public: |
| 6335 | + using filter_fn_t = std::function<std::string(std::string)>; |
| 6336 | + |
| 6337 | + /// This allows in-place construction using an initializer list |
| 6338 | + template <typename T, typename... Args> |
| 6339 | + IsMember(std::initializer_list<T> values, Args &&...args) |
| 6340 | + : IsMember(std::vector<T>(values), std::forward<Args>(args)...) {} |
| 6341 | + |
| 6342 | + /// This checks to see if an item is in a set (empty function) |
| 6343 | + template <typename T> explicit IsMember(T &&set) : IsMember(std::forward<T>(set), nullptr) {} |
| 6344 | + |
| 6345 | + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter |
| 6346 | + /// both sides of the comparison before computing the comparison. |
| 6347 | + template <typename T, typename F> explicit IsMember(T set, F filter_function) { |
| 6348 | + |
| 6349 | + // Get the type of the contained item - requires a container have ::value_type |
| 6350 | + // if the type does not have first_type and second_type, these are both value_type |
| 6351 | + using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed |
| 6352 | + using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map |
| 6353 | + |
| 6354 | + using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones |
| 6355 | + // (const char * to std::string) |
| 6356 | + |
| 6357 | + // Make a local copy of the filter function, using a std::function if not one already |
| 6358 | + std::function<local_item_t(local_item_t)> filter_fn = filter_function; |
| 6359 | + |
| 6360 | + // This is the type name for help, it will take the current version of the set contents |
| 6361 | + desc_function_ = [set]() { return detail::generate_set(detail::smart_deref(set)); }; |
| 6362 | + |
| 6363 | + // This is the function that validates |
| 6364 | + // It stores a copy of the set pointer-like, so shared_ptr will stay alive |
| 6365 | + func_ = [set, filter_fn](std::string &input) { |
| 6366 | + using CLI::detail::lexical_cast; |
| 6367 | + local_item_t b; |
| 6368 | + if(!lexical_cast(input, b)) { |
| 6369 | + throw ValidationError(input); // name is added later |
| 6370 | + } |
| 6371 | + if(filter_fn) { |
| 6372 | + b = filter_fn(b); |
| 6373 | + } |
| 6374 | + auto res = detail::search(set, b, filter_fn); |
| 6375 | + if(res.first) { |
| 6376 | + // Make sure the version in the input string is identical to the one in the set |
| 6377 | + if(filter_fn) { |
| 6378 | + input = detail::value_string(detail::pair_adaptor<element_t>::first(*(res.second))); |
| 6379 | + } |
| 6380 | + |
| 6381 | + // Return empty error string (success) |
| 6382 | + return std::string{}; |
| 6383 | + } |
| 6384 | + |
| 6385 | + // If you reach this point, the result was not found |
| 6386 | + return input + " not in " + detail::generate_set(detail::smart_deref(set)); |
| 6387 | + }; |
| 6388 | + } |
| 6389 | + |
| 6390 | + /// You can pass in as many filter functions as you like, they nest (string only currently) |
| 6391 | + template <typename T, typename... Args> |
| 6392 | + IsMember(T &&set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) |
| 6393 | + : IsMember( |
| 6394 | + std::forward<T>(set), |
| 6395 | + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, |
| 6396 | + other...) {} |
| 6397 | +}; |
| 6398 | + |
| 6399 | +/// definition of the default transformation object |
| 6400 | +template <typename T> using TransformPairs = std::vector<std::pair<std::string, T>>; |
| 6401 | + |
| 6402 | +/// Translate named items to other or a value set |
| 6403 | +class Transformer : public Validator { |
| 6404 | + public: |
| 6405 | + using filter_fn_t = std::function<std::string(std::string)>; |
| 6406 | + |
| 6407 | + /// This allows in-place construction |
| 6408 | + template <typename... Args> |
| 6409 | + Transformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args) |
| 6410 | + : Transformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {} |
| 6411 | + |
| 6412 | + /// direct map of std::string to std::string |
| 6413 | + template <typename T> explicit Transformer(T &&mapping) : Transformer(std::forward<T>(mapping), nullptr) {} |
| 6414 | + |
| 6415 | + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter |
| 6416 | + /// both sides of the comparison before computing the comparison. |
| 6417 | + template <typename T, typename F> explicit Transformer(T mapping, F filter_function) { |
| 6418 | + |
| 6419 | + static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value, |
| 6420 | + "mapping must produce value pairs"); |
| 6421 | + // Get the type of the contained item - requires a container have ::value_type |
| 6422 | + // if the type does not have first_type and second_type, these are both value_type |
| 6423 | + using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed |
| 6424 | + using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map |
| 6425 | + using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones |
| 6426 | + // (const char * to std::string) |
| 6427 | + |
| 6428 | + // Make a local copy of the filter function, using a std::function if not one already |
| 6429 | + std::function<local_item_t(local_item_t)> filter_fn = filter_function; |
| 6430 | + |
| 6431 | + // This is the type name for help, it will take the current version of the set contents |
| 6432 | + desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; |
| 6433 | + |
| 6434 | + func_ = [mapping, filter_fn](std::string &input) { |
| 6435 | + using CLI::detail::lexical_cast; |
| 6436 | + local_item_t b; |
| 6437 | + if(!lexical_cast(input, b)) { |
| 6438 | + return std::string(); |
| 6439 | + // there is no possible way we can match anything in the mapping if we can't convert so just return |
| 6440 | + } |
| 6441 | + if(filter_fn) { |
| 6442 | + b = filter_fn(b); |
| 6443 | + } |
| 6444 | + auto res = detail::search(mapping, b, filter_fn); |
| 6445 | + if(res.first) { |
| 6446 | + input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second)); |
| 6447 | + } |
| 6448 | + return std::string{}; |
| 6449 | + }; |
| 6450 | + } |
| 6451 | + |
| 6452 | + /// You can pass in as many filter functions as you like, they nest |
| 6453 | + template <typename T, typename... Args> |
| 6454 | + Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) |
| 6455 | + : Transformer( |
| 6456 | + std::forward<T>(mapping), |
| 6457 | + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, |
| 6458 | + other...) {} |
| 6459 | +}; |
| 6460 | + |
| 6461 | +/// translate named items to other or a value set |
| 6462 | +class CheckedTransformer : public Validator { |
| 6463 | + public: |
| 6464 | + using filter_fn_t = std::function<std::string(std::string)>; |
| 6465 | + |
| 6466 | + /// This allows in-place construction |
| 6467 | + template <typename... Args> |
| 6468 | + CheckedTransformer(std::initializer_list<std::pair<std::string, std::string>> values, Args &&...args) |
| 6469 | + : CheckedTransformer(TransformPairs<std::string>(values), std::forward<Args>(args)...) {} |
| 6470 | + |
| 6471 | + /// direct map of std::string to std::string |
| 6472 | + template <typename T> explicit CheckedTransformer(T mapping) : CheckedTransformer(std::move(mapping), nullptr) {} |
| 6473 | + |
| 6474 | + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter |
| 6475 | + /// both sides of the comparison before computing the comparison. |
| 6476 | + template <typename T, typename F> explicit CheckedTransformer(T mapping, F filter_function) { |
| 6477 | + |
| 6478 | + static_assert(detail::pair_adaptor<typename detail::element_type<T>::type>::value, |
| 6479 | + "mapping must produce value pairs"); |
| 6480 | + // Get the type of the contained item - requires a container have ::value_type |
| 6481 | + // if the type does not have first_type and second_type, these are both value_type |
| 6482 | + using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed |
| 6483 | + using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map |
| 6484 | + using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones |
| 6485 | + // (const char * to std::string) |
| 6486 | + using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair |
| 6487 | + |
| 6488 | + // Make a local copy of the filter function, using a std::function if not one already |
| 6489 | + std::function<local_item_t(local_item_t)> filter_fn = filter_function; |
| 6490 | + |
| 6491 | + auto tfunc = [mapping]() { |
| 6492 | + std::string out("value in "); |
| 6493 | + out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; |
| 6494 | + out += detail::join( |
| 6495 | + detail::smart_deref(mapping), |
| 6496 | + [](const iteration_type_t &v) { |
| 6497 | + return detail::value_string(detail::pair_adaptor<element_t>::second(v)); |
| 6498 | + }, |
| 6499 | + ","); |
| 6500 | + out.push_back('}'); |
| 6501 | + return out; |
| 6502 | + }; |
| 6503 | + |
| 6504 | + desc_function_ = tfunc; |
| 6505 | + |
| 6506 | + func_ = [mapping, tfunc, filter_fn](std::string &input) { |
| 6507 | + using CLI::detail::lexical_cast; |
| 6508 | + local_item_t b; |
| 6509 | + bool converted = lexical_cast(input, b); |
| 6510 | + if(converted) { |
| 6511 | + if(filter_fn) { |
| 6512 | + b = filter_fn(b); |
| 6513 | + } |
| 6514 | + auto res = detail::search(mapping, b, filter_fn); |
| 6515 | + if(res.first) { |
| 6516 | + input = detail::value_string(detail::pair_adaptor<element_t>::second(*res.second)); |
| 6517 | + return std::string{}; |
| 6518 | + } |
| 6519 | + } |
| 6520 | + for(const auto &v : detail::smart_deref(mapping)) { |
| 6521 | + auto output_string = detail::value_string(detail::pair_adaptor<element_t>::second(v)); |
| 6522 | + if(output_string == input) { |
| 6523 | + return std::string(); |
| 6524 | + } |
| 6525 | + } |
| 6526 | + |
| 6527 | + return "Check " + input + " " + tfunc() + " FAILED"; |
| 6528 | + }; |
| 6529 | + } |
| 6530 | + |
| 6531 | + /// You can pass in as many filter functions as you like, they nest |
| 6532 | + template <typename T, typename... Args> |
| 6533 | + CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&...other) |
| 6534 | + : CheckedTransformer( |
| 6535 | + std::forward<T>(mapping), |
| 6536 | + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, |
| 6537 | + other...) {} |
| 6538 | +}; |
| 6539 | + |
| 6540 | +/// Helper function to allow ignore_case to be passed to IsMember or Transform |
| 6541 | +inline std::string ignore_case(std::string item) { return detail::to_lower(item); } |
| 6542 | + |
| 6543 | +/// Helper function to allow ignore_underscore to be passed to IsMember or Transform |
| 6544 | +inline std::string ignore_underscore(std::string item) { return detail::remove_underscore(item); } |
| 6545 | + |
| 6546 | +/// Helper function to allow checks to ignore spaces to be passed to IsMember or Transform |
| 6547 | +inline std::string ignore_space(std::string item) { |
| 6548 | + item.erase(std::remove(std::begin(item), std::end(item), ' '), std::end(item)); |
| 6549 | + item.erase(std::remove(std::begin(item), std::end(item), '\t'), std::end(item)); |
| 6550 | + return item; |
| 6551 | +} |
| 6552 | + |
| 6553 | +/// Multiply a number by a factor using given mapping. |
| 6554 | +/// Can be used to write transforms for SIZE or DURATION inputs. |
| 6555 | +/// |
| 6556 | +/// Example: |
| 6557 | +/// With mapping = `{"b"->1, "kb"->1024, "mb"->1024*1024}` |
| 6558 | +/// one can recognize inputs like "100", "12kb", "100 MB", |
| 6559 | +/// that will be automatically transformed to 100, 14448, 104857600. |
| 6560 | +/// |
| 6561 | +/// Output number type matches the type in the provided mapping. |
| 6562 | +/// Therefore, if it is required to interpret real inputs like "0.42 s", |
| 6563 | +/// the mapping should be of a type <string, float> or <string, double>. |
| 6564 | +class AsNumberWithUnit : public Validator { |
| 6565 | + public: |
| 6566 | + /// Adjust AsNumberWithUnit behavior. |
| 6567 | + /// CASE_SENSITIVE/CASE_INSENSITIVE controls how units are matched. |
| 6568 | + /// UNIT_OPTIONAL/UNIT_REQUIRED throws ValidationError |
| 6569 | + /// if UNIT_REQUIRED is set and unit literal is not found. |
| 6570 | + enum Options : std::uint8_t { |
| 6571 | + CASE_SENSITIVE = 0, |
| 6572 | + CASE_INSENSITIVE = 1, |
| 6573 | + UNIT_OPTIONAL = 0, |
| 6574 | + UNIT_REQUIRED = 2, |
| 6575 | + DEFAULT = CASE_INSENSITIVE | UNIT_OPTIONAL |
| 6576 | + }; |
| 6577 | + |
| 6578 | + template <typename Number> |
| 6579 | + explicit AsNumberWithUnit(std::map<std::string, Number> mapping, |
| 6580 | + Options opts = DEFAULT, |
| 6581 | + const std::string &unit_name = "UNIT") { |
| 6582 | + description(generate_description<Number>(unit_name, opts)); |
| 6583 | + validate_mapping(mapping, opts); |
| 6584 | + |
| 6585 | + // transform function |
| 6586 | + func_ = [mapping, opts](std::string &input) -> std::string { |
| 6587 | + Number num{}; |
| 6588 | + |
| 6589 | + detail::rtrim(input); |
| 6590 | + if(input.empty()) { |
| 6591 | + throw ValidationError("Input is empty"); |
| 6592 | + } |
| 6593 | + |
| 6594 | + // Find split position between number and prefix |
| 6595 | + auto unit_begin = input.end(); |
| 6596 | + while(unit_begin > input.begin() && std::isalpha(*(unit_begin - 1), std::locale())) { |
| 6597 | + --unit_begin; |
| 6598 | + } |
| 6599 | + |
| 6600 | + std::string unit{unit_begin, input.end()}; |
| 6601 | + input.resize(static_cast<std::size_t>(std::distance(input.begin(), unit_begin))); |
| 6602 | + detail::trim(input); |
| 6603 | + |
| 6604 | + if(opts & UNIT_REQUIRED && unit.empty()) { |
| 6605 | + throw ValidationError("Missing mandatory unit"); |
| 6606 | + } |
| 6607 | + if(opts & CASE_INSENSITIVE) { |
| 6608 | + unit = detail::to_lower(unit); |
| 6609 | + } |
| 6610 | + if(unit.empty()) { |
| 6611 | + using CLI::detail::lexical_cast; |
| 6612 | + if(!lexical_cast(input, num)) { |
| 6613 | + throw ValidationError(std::string("Value ") + input + " could not be converted to " + |
| 6614 | + detail::type_name<Number>()); |
| 6615 | + } |
| 6616 | + // No need to modify input if no unit passed |
| 6617 | + return {}; |
| 6618 | + } |
| 6619 | + |
| 6620 | + // find corresponding factor |
| 6621 | + auto it = mapping.find(unit); |
| 6622 | + if(it == mapping.end()) { |
| 6623 | + throw ValidationError(unit + |
| 6624 | + " unit not recognized. " |
| 6625 | + "Allowed values: " + |
| 6626 | + detail::generate_map(mapping, true)); |
| 6627 | + } |
| 6628 | + |
| 6629 | + if(!input.empty()) { |
| 6630 | + using CLI::detail::lexical_cast; |
| 6631 | + bool converted = lexical_cast(input, num); |
| 6632 | + if(!converted) { |
| 6633 | + throw ValidationError(std::string("Value ") + input + " could not be converted to " + |
| 6634 | + detail::type_name<Number>()); |
| 6635 | + } |
| 6636 | + // perform safe multiplication |
| 6637 | + bool ok = detail::checked_multiply(num, it->second); |
| 6638 | + if(!ok) { |
| 6639 | + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + |
| 6640 | + " factor would cause number overflow. Use smaller value."); |
| 6641 | + } |
| 6642 | + } else { |
| 6643 | + num = static_cast<Number>(it->second); |
| 6644 | + } |
| 6645 | + |
| 6646 | + input = detail::to_string(num); |
| 6647 | + |
| 6648 | + return {}; |
| 6649 | + }; |
| 6650 | + } |
| 6651 | + |
| 6652 | + private: |
| 6653 | + /// Check that mapping contains valid units. |
| 6654 | + /// Update mapping for CASE_INSENSITIVE mode. |
| 6655 | + template <typename Number> static void validate_mapping(std::map<std::string, Number> &mapping, Options opts) { |
| 6656 | + for(auto &kv : mapping) { |
| 6657 | + if(kv.first.empty()) { |
| 6658 | + throw ValidationError("Unit must not be empty."); |
| 6659 | + } |
| 6660 | + if(!detail::isalpha(kv.first)) { |
| 6661 | + throw ValidationError("Unit must contain only letters."); |
| 6662 | + } |
| 6663 | + } |
| 6664 | + |
| 6665 | + // make all units lowercase if CASE_INSENSITIVE |
| 6666 | + if(opts & CASE_INSENSITIVE) { |
| 6667 | + std::map<std::string, Number> lower_mapping; |
| 6668 | + for(auto &kv : mapping) { |
| 6669 | + auto s = detail::to_lower(kv.first); |
| 6670 | + if(lower_mapping.count(s)) { |
| 6671 | + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + |
| 6672 | + s); |
| 6673 | + } |
| 6674 | + lower_mapping[detail::to_lower(kv.first)] = kv.second; |
| 6675 | + } |
| 6676 | + mapping = std::move(lower_mapping); |
| 6677 | + } |
| 6678 | + } |
| 6679 | + |
| 6680 | + /// Generate description like this: NUMBER [UNIT] |
| 6681 | + template <typename Number> static std::string generate_description(const std::string &name, Options opts) { |
| 6682 | + std::stringstream out; |
| 6683 | + out << detail::type_name<Number>() << ' '; |
| 6684 | + if(opts & UNIT_REQUIRED) { |
| 6685 | + out << name; |
| 6686 | + } else { |
| 6687 | + out << '[' << name << ']'; |
| 6688 | + } |
| 6689 | + return out.str(); |
| 6690 | + } |
| 6691 | +}; |
| 6692 | + |
| 6693 | +inline AsNumberWithUnit::Options operator|(const AsNumberWithUnit::Options &a, const AsNumberWithUnit::Options &b) { |
| 6694 | + return static_cast<AsNumberWithUnit::Options>(static_cast<int>(a) | static_cast<int>(b)); |
| 6695 | +} |
| 6696 | + |
| 6697 | +/// Converts a human-readable size string (with unit literal) to uin64_t size. |
| 6698 | +/// Example: |
| 6699 | +/// "100" => 100 |
| 6700 | +/// "1 b" => 100 |
| 6701 | +/// "10Kb" => 10240 // you can configure this to be interpreted as kilobyte (*1000) or kibibyte (*1024) |
| 6702 | +/// "10 KB" => 10240 |
| 6703 | +/// "10 kb" => 10240 |
| 6704 | +/// "10 kib" => 10240 // *i, *ib are always interpreted as *bibyte (*1024) |
| 6705 | +/// "10kb" => 10240 |
| 6706 | +/// "2 MB" => 2097152 |
| 6707 | +/// "2 EiB" => 2^61 // Units up to exibyte are supported |
| 6708 | +class AsSizeValue : public AsNumberWithUnit { |
| 6709 | + public: |
| 6710 | + using result_t = std::uint64_t; |
| 6711 | + |
| 6712 | + /// If kb_is_1000 is true, |
| 6713 | + /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 |
| 6714 | + /// (same applies to higher order units as well). |
| 6715 | + /// Otherwise, interpret all literals as factors of 1024. |
| 6716 | + /// The first option is formally correct, but |
| 6717 | + /// the second interpretation is more wide-spread |
| 6718 | + /// (see https://en.wikipedia.org/wiki/Binary_prefix). |
| 6719 | + explicit AsSizeValue(bool kb_is_1000); |
| 6720 | + |
| 6721 | + private: |
| 6722 | + /// Get <size unit, factor> mapping |
| 6723 | + static std::map<std::string, result_t> init_mapping(bool kb_is_1000); |
| 6724 | + |
| 6725 | + /// Cache calculated mapping |
| 6726 | + static std::map<std::string, result_t> get_mapping(bool kb_is_1000); |
| 6727 | +}; |
| 6728 | + |
| 6729 | +#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0 |
| 6730 | +// new extra validators |
| 6731 | +#if CLI11_HAS_FILESYSTEM |
| 6732 | +namespace detail { |
| 6733 | +enum class Permission : std::uint8_t { none = 0, read = 1, write = 2, exec = 4 }; |
| 6734 | +class PermissionValidator : public Validator { |
| 6735 | + public: |
| 6736 | + explicit PermissionValidator(Permission permission); |
| 6737 | +}; |
| 6738 | +} // namespace detail |
| 6739 | + |
| 6740 | +/// Check that the file exist and available for read |
| 6741 | +const detail::PermissionValidator ReadPermissions(detail::Permission::read); |
| 6742 | + |
| 6743 | +/// Check that the file exist and available for write |
| 6744 | +const detail::PermissionValidator WritePermissions(detail::Permission::write); |
| 6745 | + |
| 6746 | +/// Check that the file exist and available for write |
| 6747 | +const detail::PermissionValidator ExecPermissions(detail::Permission::exec); |
| 6748 | +#endif |
| 6749 | + |
| 6750 | +#endif |
| 6751 | + |
| 6752 | + |
| 6753 | + |
| 6754 | +namespace detail { |
| 6755 | + |
| 6756 | +CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { |
| 6757 | + func_ = [](std::string &ip_addr) { |
| 6758 | + auto cdot = std::count(ip_addr.begin(), ip_addr.end(), '.'); |
| 6759 | + if(cdot != 3u) { |
| 6760 | + return std::string("Invalid IPV4 address: must have 3 separators"); |
| 6761 | + } |
| 6762 | + auto result = CLI::detail::split(ip_addr, '.'); |
| 6763 | + if(result.size() != 4) { |
| 6764 | + return std::string("Invalid IPV4 address: must have four parts (") + ip_addr + ')'; |
| 6765 | + } |
| 6766 | + int num = 0; |
| 6767 | + for(const auto &var : result) { |
| 6768 | + using CLI::detail::lexical_cast; |
| 6769 | + bool retval = lexical_cast(var, num); |
| 6770 | + if(!retval) { |
| 6771 | + return std::string("Failed parsing number (") + var + ')'; |
| 6772 | + } |
| 6773 | + if(num < 0 || num > 255) { |
| 6774 | + return std::string("Each IP number must be between 0 and 255 ") + var; |
| 6775 | + } |
| 6776 | + } |
| 6777 | + return std::string{}; |
| 6778 | + }; |
| 6779 | +} |
| 6780 | + |
| 6781 | +} // namespace detail |
| 6782 | + |
| 6783 | +CLI11_INLINE AsSizeValue::AsSizeValue(bool kb_is_1000) : AsNumberWithUnit(get_mapping(kb_is_1000)) { |
| 6784 | + if(kb_is_1000) { |
| 6785 | + description("SIZE [b, kb(=1000b), kib(=1024b), ...]"); |
| 6786 | + } else { |
| 6787 | + description("SIZE [b, kb(=1024b), ...]"); |
| 6788 | + } |
| 6789 | +} |
| 6790 | + |
| 6791 | +CLI11_INLINE std::map<std::string, AsSizeValue::result_t> AsSizeValue::init_mapping(bool kb_is_1000) { |
| 6792 | + std::map<std::string, result_t> m; |
| 6793 | + result_t k_factor = kb_is_1000 ? 1000 : 1024; |
| 6794 | + result_t ki_factor = 1024; |
| 6795 | + result_t k = 1; |
| 6796 | + result_t ki = 1; |
| 6797 | + m["b"] = 1; |
| 6798 | + for(std::string p : {"k", "m", "g", "t", "p", "e"}) { |
| 6799 | + k *= k_factor; |
| 6800 | + ki *= ki_factor; |
| 6801 | + m[p] = k; |
| 6802 | + m[p + "b"] = k; |
| 6803 | + m[p + "i"] = ki; |
| 6804 | + m[p + "ib"] = ki; |
| 6805 | + } |
| 6806 | + return m; |
| 6807 | +} |
| 6808 | + |
| 6809 | +CLI11_INLINE std::map<std::string, AsSizeValue::result_t> AsSizeValue::get_mapping(bool kb_is_1000) { |
| 6810 | + if(kb_is_1000) { |
| 6811 | + static auto m = init_mapping(true); |
| 6812 | + return m; |
| 6813 | + } |
| 6814 | + static auto m = init_mapping(false); |
| 6815 | + return m; |
| 6816 | +} |
| 6817 | + |
| 6818 | +namespace detail {} // namespace detail |
| 6819 | +/// @} |
| 6820 | + |
| 6821 | +#if defined(CLI11_ENABLE_EXTRA_VALIDATORS) && CLI11_ENABLE_EXTRA_VALIDATORS != 0 |
| 6822 | +// new extra validators |
| 6823 | +namespace detail { |
| 6824 | + |
| 6825 | +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 |
| 6826 | +CLI11_INLINE PermissionValidator::PermissionValidator(Permission permission) { |
| 6827 | + std::filesystem::perms permission_code = std::filesystem::perms::none; |
| 6828 | + std::string permission_name; |
| 6829 | + switch(permission) { |
| 6830 | + case Permission::read: |
| 6831 | + permission_code = std::filesystem::perms::owner_read | std::filesystem::perms::group_read | |
| 6832 | + std::filesystem::perms::others_read; |
| 6833 | + permission_name = "read"; |
| 6834 | + break; |
| 6835 | + case Permission::write: |
| 6836 | + permission_code = std::filesystem::perms::owner_write | std::filesystem::perms::group_write | |
| 6837 | + std::filesystem::perms::others_write; |
| 6838 | + permission_name = "write"; |
| 6839 | + break; |
| 6840 | + case Permission::exec: |
| 6841 | + permission_code = std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec | |
| 6842 | + std::filesystem::perms::others_exec; |
| 6843 | + permission_name = "exec"; |
| 6844 | + break; |
| 6845 | + case Permission::none: |
| 6846 | + default: |
| 6847 | + permission_code = std::filesystem::perms::none; |
| 6848 | + break; |
| 6849 | + } |
| 6850 | + func_ = [permission_code](std::string &path) { |
| 6851 | + std::error_code ec; |
| 6852 | + auto p = std::filesystem::path(path); |
| 6853 | + if(!std::filesystem::exists(p, ec)) { |
| 6854 | + return std::string("Path does not exist: ") + path; |
| 6855 | + } |
| 6856 | + if(ec) { |
| 6857 | + return std::string("Error checking path: ") + ec.message(); // LCOV_EXCL_LINE |
| 6858 | + } |
| 6859 | + if(permission_code == std::filesystem::perms::none) { |
| 6860 | + return std::string{}; |
| 6861 | + } |
| 6862 | + auto perms = std::filesystem::status(p, ec).permissions(); |
| 6863 | + if(ec) { |
| 6864 | + return std::string("Error checking path status: ") + ec.message(); // LCOV_EXCL_LINE |
| 6865 | + } |
| 6866 | + if((perms & permission_code) == std::filesystem::perms::none) { |
| 6867 | + return std::string("Path does not have required permissions: ") + path; |
| 6868 | + } |
| 6869 | + return std::string{}; |
| 6870 | + }; |
| 6871 | + description("Path with " + permission_name + " permission"); |
| 6872 | +} |
| 6873 | +#endif |
| 6874 | + |
| 6875 | +} // namespace detail |
| 6876 | +#endif |
| 6877 | + |
| 6878 | + |
| 6879 | + |
| 6880 | +class Option; |
| 6881 | +class App; |
| 6882 | + |
| 6883 | +/// This enum signifies the type of help requested |
| 6884 | +/// |
| 6885 | +/// This is passed in by App; all user classes must accept this as |
| 6886 | +/// the second argument. |
| 6887 | + |
| 6888 | +enum class AppFormatMode : std::uint8_t { |
| 6889 | + Normal, ///< The normal, detailed help |
| 6890 | + All, ///< A fully expanded help |
| 6891 | + Sub, ///< Used when printed as part of expanded subcommand |
| 6892 | +}; |
| 6893 | + |
| 6894 | +/// This is the minimum requirements to run a formatter. |
| 6895 | +/// |
| 6896 | +/// A user can subclass this is if they do not care at all |
| 6897 | +/// about the structure in CLI::Formatter. |
| 6898 | +class FormatterBase { |
| 6899 | + protected: |
| 6900 | + /// @name Options |
| 6901 | + ///@{ |
| 6902 | + |
| 6903 | + /// The width of the left column (options/flags/subcommands) |
| 6904 | + std::size_t column_width_{30}; |
| 6905 | + |
| 6906 | + /// The alignment ratio for long options within the left column |
| 6907 | + float long_option_alignment_ratio_{1 / 3.f}; |
| 6908 | + |
| 6909 | + /// The width of the right column (description of options/flags/subcommands) |
| 6910 | + std::size_t right_column_width_{65}; |
| 6911 | + |
| 6912 | + /// The width of the description paragraph at the top of help |
| 6913 | + std::size_t description_paragraph_width_{80}; |
| 6914 | + |
| 6915 | + /// The width of the footer paragraph |
| 6916 | + std::size_t footer_paragraph_width_{80}; |
| 6917 | + |
| 6918 | + /// options controlling formatting for footer and descriptions |
| 6919 | + bool enable_description_formatting_{true}; |
| 6920 | + bool enable_footer_formatting_{true}; |
| 6921 | + |
| 6922 | + /// options controlling formatting of options |
| 6923 | + bool enable_option_defaults_{true}; |
| 6924 | + bool enable_option_type_names_{true}; |
| 6925 | + bool enable_default_flag_values_{true}; |
| 6926 | + /// @brief The required help printout labels (user changeable) |
| 6927 | + /// Values are Needs, Excludes, etc. |
| 6928 | + std::map<std::string, std::string> labels_{}; |
| 6929 | + |
| 6930 | + ///@} |
| 6931 | + /// @name Basic |
| 6932 | + ///@{ |
| 6933 | + |
| 6934 | + public: |
| 6935 | + FormatterBase() = default; |
| 6936 | + FormatterBase(const FormatterBase &) = default; |
| 6937 | + FormatterBase(FormatterBase &&) = default; |
| 6938 | + FormatterBase &operator=(const FormatterBase &) = default; |
| 6939 | + FormatterBase &operator=(FormatterBase &&) = default; |
| 6940 | + |
| 6941 | + /// Adding a destructor in this form to work around bug in GCC 4.7 |
| 6942 | + virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) |
| 6943 | + |
| 6944 | + /// This is the key method that puts together help |
| 6945 | + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; |
| 6946 | + |
| 6947 | + ///@} |
| 6948 | + /// @name Setters |
| 6949 | + ///@{ |
| 6950 | + |
| 6951 | + /// Set the "REQUIRED" or other labels |
| 6952 | + void label(std::string key, std::string val) { labels_[key] = val; } |
| 6953 | + |
| 6954 | + /// Set the left column width (options/flags/subcommands) |
| 6955 | + void column_width(std::size_t val) { column_width_ = val; } |
| 6956 | + |
| 6957 | + /// Set the alignment ratio for long options within the left column |
| 6958 | + /// The ratio is in [0;1] range (e.g. 0.2 = 20% of column width, 6.f/column_width = 6th character) |
| 6959 | + void long_option_alignment_ratio(float ratio) { |
| 6960 | + long_option_alignment_ratio_ = |
| 6961 | + (ratio >= 0.0f) ? ((ratio <= 1.0f) ? ratio : 1.0f / ratio) : ((ratio < -1.0f) ? 1.0f / (-ratio) : -ratio); |
| 6962 | + } |
| 6963 | + |
| 6964 | + /// Set the right column width (description of options/flags/subcommands) |
| 6965 | + void right_column_width(std::size_t val) { right_column_width_ = val; } |
| 6966 | + |
| 6967 | + /// Set the description paragraph width at the top of help |
| 6968 | + void description_paragraph_width(std::size_t val) { description_paragraph_width_ = val; } |
| 6969 | + |
| 6970 | + /// Set the footer paragraph width |
| 6971 | + void footer_paragraph_width(std::size_t val) { footer_paragraph_width_ = val; } |
| 6972 | + /// enable formatting for description paragraph |
| 6973 | + void enable_description_formatting(bool value = true) { enable_description_formatting_ = value; } |
| 6974 | + /// disable formatting for footer paragraph |
| 6975 | + void enable_footer_formatting(bool value = true) { enable_footer_formatting_ = value; } |
| 6976 | + |
| 6977 | + /// enable option defaults to be printed |
| 6978 | + void enable_option_defaults(bool value = true) { enable_option_defaults_ = value; } |
| 6979 | + /// enable option type names to be printed |
| 6980 | + void enable_option_type_names(bool value = true) { enable_option_type_names_ = value; } |
| 6981 | + /// enable default flag values to be printed |
| 6982 | + void enable_default_flag_values(bool value = true) { enable_default_flag_values_ = value; } |
| 6983 | + ///@} |
| 6984 | + /// @name Getters |
| 6985 | + ///@{ |
| 6986 | + |
| 6987 | + /// Get the current value of a name (REQUIRED, etc.) |
| 6988 | + CLI11_NODISCARD std::string get_label(std::string key) const { |
| 6989 | + if(labels_.find(key) == labels_.end()) |
| 6990 | + return key; |
| 6991 | + return labels_.at(key); |
| 6992 | + } |
| 6993 | + |
| 6994 | + /// Get the current left column width (options/flags/subcommands) |
| 6995 | + CLI11_NODISCARD std::size_t get_column_width() const { return column_width_; } |
| 6996 | + |
| 6997 | + /// Get the current right column width (description of options/flags/subcommands) |
| 6998 | + CLI11_NODISCARD std::size_t get_right_column_width() const { return right_column_width_; } |
| 6999 | + |
| 7000 | + /// Get the current description paragraph width at the top of help |
| 7001 | + CLI11_NODISCARD std::size_t get_description_paragraph_width() const { return description_paragraph_width_; } |
| 7002 | + |
| 7003 | + /// Get the current footer paragraph width |
| 7004 | + CLI11_NODISCARD std::size_t get_footer_paragraph_width() const { return footer_paragraph_width_; } |
| 7005 | + |
| 7006 | + /// @brief Get the current alignment ratio for long options within the left column |
| 7007 | + /// @return |
| 7008 | + CLI11_NODISCARD float get_long_option_alignment_ratio() const { return long_option_alignment_ratio_; } |
| 7009 | + |
| 7010 | + /// Get the current status of description paragraph formatting |
| 7011 | + CLI11_NODISCARD bool is_description_paragraph_formatting_enabled() const { return enable_description_formatting_; } |
| 7012 | + |
| 7013 | + /// Get the current status of whether footer paragraph formatting is enabled |
| 7014 | + CLI11_NODISCARD bool is_footer_paragraph_formatting_enabled() const { return enable_footer_formatting_; } |
| 7015 | + |
| 7016 | + /// Get the current status of whether option defaults are printed |
| 7017 | + CLI11_NODISCARD bool is_option_defaults_enabled() const { return enable_option_defaults_; } |
| 7018 | + |
| 7019 | + /// Get the current status of whether option type names are printed |
| 7020 | + CLI11_NODISCARD bool is_option_type_names_enabled() const { return enable_option_type_names_; } |
| 7021 | + |
| 7022 | + /// Get the current status of whether default flag values are printed |
| 7023 | + CLI11_NODISCARD bool is_default_flag_values_enabled() const { return enable_default_flag_values_; } |
| 7024 | + |
| 7025 | + ///@} |
| 7026 | +}; |
| 7027 | + |
| 7028 | +/// This is a specialty override for lambda functions |
| 7029 | +class FormatterLambda final : public FormatterBase { |
| 7030 | + using funct_t = std::function<std::string(const App *, std::string, AppFormatMode)>; |
| 7031 | + |
| 7032 | + /// The lambda to hold and run |
| 7033 | + funct_t lambda_; |
| 7034 | + |
| 7035 | + public: |
| 7036 | + /// Create a FormatterLambda with a lambda function |
| 7037 | + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} |
| 7038 | + |
| 7039 | + /// Adding a destructor (mostly to make GCC 4.7 happy) |
| 7040 | + ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) |
| 7041 | + |
| 7042 | + /// This will simply call the lambda function |
| 7043 | + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { |
| 7044 | + return lambda_(app, name, mode); |
| 7045 | + } |
| 7046 | +}; |
| 7047 | + |
| 7048 | +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few |
| 7049 | +/// overridable methods, to be highly customizable with minimal effort. |
| 7050 | +class Formatter : public FormatterBase { |
| 7051 | + public: |
| 7052 | + Formatter() = default; |
| 7053 | + Formatter(const Formatter &) = default; |
| 7054 | + Formatter(Formatter &&) = default; |
| 7055 | + Formatter &operator=(const Formatter &) = default; |
| 7056 | + Formatter &operator=(Formatter &&) = default; |
| 7057 | + |
| 7058 | + /// @name Overridables |
| 7059 | + ///@{ |
| 7060 | + |
| 7061 | + /// This prints out a group of options with title |
| 7062 | + /// |
| 7063 | + CLI11_NODISCARD virtual std::string |
| 7064 | + make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const; |
| 7065 | + |
| 7066 | + /// This prints out just the positionals "group" |
| 7067 | + virtual std::string make_positionals(const App *app) const; |
| 7068 | + |
| 7069 | + /// This prints out all the groups of options |
| 7070 | + std::string make_groups(const App *app, AppFormatMode mode) const; |
| 7071 | + |
| 7072 | + /// This prints out all the subcommands |
| 7073 | + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; |
| 7074 | + |
| 7075 | + /// This prints out a subcommand |
| 7076 | + virtual std::string make_subcommand(const App *sub) const; |
| 7077 | + |
| 7078 | + /// This prints out a subcommand in help-all |
| 7079 | + virtual std::string make_expanded(const App *sub, AppFormatMode mode) const; |
| 7080 | + |
| 7081 | + /// This prints out all the groups of options |
| 7082 | + virtual std::string make_footer(const App *app) const; |
| 7083 | + |
| 7084 | + /// This displays the description line |
| 7085 | + virtual std::string make_description(const App *app) const; |
| 7086 | + |
| 7087 | + /// This displays the usage line |
| 7088 | + virtual std::string make_usage(const App *app, std::string name) const; |
| 7089 | + |
| 7090 | + /// This puts everything together |
| 7091 | + std::string make_help(const App *app, std::string, AppFormatMode mode) const override; |
| 7092 | + |
| 7093 | + ///@} |
| 7094 | + /// @name Options |
| 7095 | + ///@{ |
| 7096 | + |
| 7097 | + /// This prints out an option help line, either positional or optional form |
| 7098 | + virtual std::string make_option(const Option *, bool) const; |
| 7099 | + |
| 7100 | + /// @brief This is the name part of an option, Default: left column |
| 7101 | + virtual std::string make_option_name(const Option *, bool) const; |
| 7102 | + |
| 7103 | + /// @brief This is the options part of the name, Default: combined into left column |
| 7104 | + virtual std::string make_option_opts(const Option *) const; |
| 7105 | + |
| 7106 | + /// @brief This is the description. Default: Right column, on new line if left column too large |
| 7107 | + virtual std::string make_option_desc(const Option *) const; |
| 7108 | + |
| 7109 | + /// @brief This is used to print the name on the USAGE line |
| 7110 | + virtual std::string make_option_usage(const Option *opt) const; |
| 7111 | + |
| 7112 | + ///@} |
| 7113 | +}; |
| 7114 | + |
| 7115 | + |
| 7116 | + |
| 7117 | + |
| 7118 | +using results_t = std::vector<std::string>; |
| 7119 | +/// callback function definition |
| 7120 | +using callback_t = std::function<bool(const results_t &)>; |
| 7121 | + |
| 7122 | +class Option; |
| 7123 | +class App; |
| 7124 | +class ConfigBase; |
| 7125 | + |
| 7126 | +using Option_p = std::unique_ptr<Option>; |
| 7127 | +using Validator_p = std::shared_ptr<Validator>; |
| 7128 | + |
| 7129 | +/// Enumeration of the multiOption Policy selection |
| 7130 | +enum class MultiOptionPolicy : char { |
| 7131 | + Throw, //!< Throw an error if any extra arguments were given |
| 7132 | + TakeLast, //!< take only the last Expected number of arguments |
| 7133 | + TakeFirst, //!< take only the first Expected number of arguments |
| 7134 | + Join, //!< merge all the arguments together into a single string via the delimiter character default('\n') |
| 7135 | + TakeAll, //!< just get all the passed argument regardless |
| 7136 | + Sum, //!< sum all the arguments together if numerical or concatenate directly without delimiter |
| 7137 | + Reverse, //!< take only the last Expected number of arguments in reverse order |
| 7138 | +}; |
| 7139 | + |
| 7140 | +/// @brief enumeration for the callback priority |
| 7141 | +enum class CallbackPriority : std::uint8_t { |
| 7142 | + FirstPreHelp = 0, |
| 7143 | + First = 1, |
| 7144 | + PreRequirementsCheckPreHelp = 2, |
| 7145 | + PreRequirementsCheck = 3, |
| 7146 | + NormalPreHelp = 4, |
| 7147 | + Normal = 5, |
| 7148 | + LastPreHelp = 6, |
| 7149 | + Last = 7 |
| 7150 | +}; // namespace CLI |
| 7151 | + |
| 7152 | +/// This is the CRTP base class for Option and OptionDefaults. It was designed this way |
| 7153 | +/// to share parts of the class; an OptionDefaults can copy to an Option. |
| 7154 | +template <typename CRTP> class OptionBase { |
| 7155 | + friend App; |
| 7156 | + friend ConfigBase; |
| 7157 | + |
| 7158 | + protected: |
| 7159 | + /// The group membership |
| 7160 | + std::string group_ = std::string("OPTIONS"); |
| 7161 | + |
| 7162 | + /// True if this is a required option |
| 7163 | + bool required_{false}; |
| 7164 | + |
| 7165 | + /// Ignore the case when matching (option, not value) |
| 7166 | + bool ignore_case_{false}; |
| 7167 | + |
| 7168 | + /// Ignore underscores when matching (option, not value) |
| 7169 | + bool ignore_underscore_{false}; |
| 7170 | + |
| 7171 | + /// Allow this option to be given in a configuration file |
| 7172 | + bool configurable_{true}; |
| 7173 | + |
| 7174 | + /// Disable overriding flag values with '=value' |
| 7175 | + bool disable_flag_override_{false}; |
| 7176 | + |
| 7177 | + /// Specify a delimiter character for vector arguments |
| 7178 | + char delimiter_{'\0'}; |
| 7179 | + |
| 7180 | + /// Automatically capture default value |
| 7181 | + bool always_capture_default_{false}; |
| 7182 | + |
| 7183 | + /// Policy for handling multiple arguments beyond the expected Max |
| 7184 | + MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; |
| 7185 | + |
| 7186 | + /// Priority of callback |
| 7187 | + CallbackPriority callback_priority_{CallbackPriority::Normal}; |
| 7188 | + |
| 7189 | + /// Copy the contents to another similar class (one based on OptionBase) |
| 7190 | + template <typename T> void copy_to(T *other) const; |
| 7191 | + |
| 7192 | + public: |
| 7193 | + // setters |
| 7194 | + |
| 7195 | + /// Changes the group membership |
| 7196 | + CRTP *group(const std::string &name) { |
| 7197 | + if(!detail::valid_alias_name_string(name)) { |
| 7198 | + throw IncorrectConstruction("Group names may not contain newlines or null characters"); |
| 7199 | + } |
| 7200 | + group_ = name; |
| 7201 | + return static_cast<CRTP *>(this); |
| 7202 | + } |
| 7203 | + |
| 7204 | + /// Set the option as required |
| 7205 | + CRTP *required(bool value = true) { |
| 7206 | + required_ = value; |
| 7207 | + return static_cast<CRTP *>(this); |
| 7208 | + } |
| 7209 | + |
| 7210 | + /// Support Plumbum term |
| 7211 | + CRTP *mandatory(bool value = true) { return required(value); } |
| 7212 | + |
| 7213 | + CRTP *always_capture_default(bool value = true) { |
| 7214 | + always_capture_default_ = value; |
| 7215 | + return static_cast<CRTP *>(this); |
| 7216 | + } |
| 7217 | + |
| 7218 | + // Getters |
| 7219 | + |
| 7220 | + /// Get the group of this option |
| 7221 | + CLI11_NODISCARD const std::string &get_group() const { return group_; } |
| 7222 | + |
| 7223 | + /// True if this is a required option |
| 7224 | + CLI11_NODISCARD bool get_required() const { return required_; } |
| 7225 | + |
| 7226 | + /// The status of ignore case |
| 7227 | + CLI11_NODISCARD bool get_ignore_case() const { return ignore_case_; } |
| 7228 | + |
| 7229 | + /// The status of ignore_underscore |
| 7230 | + CLI11_NODISCARD bool get_ignore_underscore() const { return ignore_underscore_; } |
| 7231 | + |
| 7232 | + /// The status of configurable |
| 7233 | + CLI11_NODISCARD bool get_configurable() const { return configurable_; } |
| 7234 | + |
| 7235 | + /// The status of configurable |
| 7236 | + CLI11_NODISCARD bool get_disable_flag_override() const { return disable_flag_override_; } |
| 7237 | + |
| 7238 | + /// Get the current delimiter char |
| 7239 | + CLI11_NODISCARD char get_delimiter() const { return delimiter_; } |
| 7240 | + |
| 7241 | + /// Return true if this will automatically capture the default value for help printing |
| 7242 | + CLI11_NODISCARD bool get_always_capture_default() const { return always_capture_default_; } |
| 7243 | + |
| 7244 | + /// The status of the multi option policy |
| 7245 | + CLI11_NODISCARD MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; } |
| 7246 | + |
| 7247 | + /// The priority of callback |
| 7248 | + CLI11_NODISCARD CallbackPriority get_callback_priority() const { return callback_priority_; } |
| 7249 | + |
| 7250 | + // Shortcuts for multi option policy |
| 7251 | + |
| 7252 | + /// Set the multi option policy to take last |
| 7253 | + CRTP *take_last() { |
| 7254 | + auto *self = static_cast<CRTP *>(this); |
| 7255 | + self->multi_option_policy(MultiOptionPolicy::TakeLast); |
| 7256 | + return self; |
| 7257 | + } |
| 7258 | + |
| 7259 | + /// Set the multi option policy to take last |
| 7260 | + CRTP *take_first() { |
| 7261 | + auto *self = static_cast<CRTP *>(this); |
| 7262 | + self->multi_option_policy(MultiOptionPolicy::TakeFirst); |
| 7263 | + return self; |
| 7264 | + } |
| 7265 | + |
| 7266 | + /// Set the multi option policy to take all arguments |
| 7267 | + CRTP *take_all() { |
| 7268 | + auto self = static_cast<CRTP *>(this); |
| 7269 | + self->multi_option_policy(MultiOptionPolicy::TakeAll); |
| 7270 | + return self; |
| 7271 | + } |
| 7272 | + |
| 7273 | + /// Set the multi option policy to join |
| 7274 | + CRTP *join() { |
| 7275 | + auto *self = static_cast<CRTP *>(this); |
| 7276 | + self->multi_option_policy(MultiOptionPolicy::Join); |
| 7277 | + return self; |
| 7278 | + } |
| 7279 | + |
| 7280 | + /// Set the multi option policy to join with a specific delimiter |
| 7281 | + CRTP *join(char delim) { |
| 7282 | + auto self = static_cast<CRTP *>(this); |
| 7283 | + self->delimiter_ = delim; |
| 7284 | + self->multi_option_policy(MultiOptionPolicy::Join); |
| 7285 | + return self; |
| 7286 | + } |
| 7287 | + |
| 7288 | + /// Allow in a configuration file |
| 7289 | + CRTP *configurable(bool value = true) { |
| 7290 | + configurable_ = value; |
| 7291 | + return static_cast<CRTP *>(this); |
| 7292 | + } |
| 7293 | + |
| 7294 | + /// Allow in a configuration file |
| 7295 | + CRTP *delimiter(char value = '\0') { |
| 7296 | + delimiter_ = value; |
| 7297 | + return static_cast<CRTP *>(this); |
| 7298 | + } |
| 7299 | +}; |
| 7300 | + |
| 7301 | +/// This is a version of OptionBase that only supports setting values, |
| 7302 | +/// for defaults. It is stored as the default option in an App. |
| 7303 | +class OptionDefaults : public OptionBase<OptionDefaults> { |
| 7304 | + public: |
| 7305 | + OptionDefaults() = default; |
| 7306 | + |
| 7307 | + // Methods here need a different implementation if they are Option vs. OptionDefault |
| 7308 | + |
| 7309 | + /// Set the callback priority |
| 7310 | + OptionDefaults *callback_priority(CallbackPriority value = CallbackPriority::Normal) { |
| 7311 | + callback_priority_ = value; |
| 7312 | + return this; |
| 7313 | + } |
| 7314 | + |
| 7315 | + /// Take the last argument if given multiple times |
| 7316 | + OptionDefaults *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { |
| 7317 | + multi_option_policy_ = value; |
| 7318 | + return this; |
| 7319 | + } |
| 7320 | + |
| 7321 | + /// Ignore the case of the option name |
| 7322 | + OptionDefaults *ignore_case(bool value = true) { |
| 7323 | + ignore_case_ = value; |
| 7324 | + return this; |
| 7325 | + } |
| 7326 | + |
| 7327 | + /// Ignore underscores in the option name |
| 7328 | + OptionDefaults *ignore_underscore(bool value = true) { |
| 7329 | + ignore_underscore_ = value; |
| 7330 | + return this; |
| 7331 | + } |
| 7332 | + |
| 7333 | + /// Disable overriding flag values with an '=<value>' segment |
| 7334 | + OptionDefaults *disable_flag_override(bool value = true) { |
| 7335 | + disable_flag_override_ = value; |
| 7336 | + return this; |
| 7337 | + } |
| 7338 | + |
| 7339 | + /// set a delimiter character to split up single arguments to treat as multiple inputs |
| 7340 | + OptionDefaults *delimiter(char value = '\0') { |
| 7341 | + delimiter_ = value; |
| 7342 | + return this; |
| 7343 | + } |
| 7344 | +}; |
| 7345 | + |
| 7346 | +class Option : public OptionBase<Option> { |
| 7347 | + friend App; |
| 7348 | + friend ConfigBase; |
| 7349 | + |
| 7350 | + protected: |
| 7351 | + /// @name Names |
| 7352 | + ///@{ |
| 7353 | + |
| 7354 | + /// A list of the short names (`-a`) without the leading dashes |
| 7355 | + std::vector<std::string> snames_{}; |
| 7356 | + |
| 7357 | + /// A list of the long names (`--long`) without the leading dashes |
| 7358 | + std::vector<std::string> lnames_{}; |
| 7359 | + |
| 7360 | + /// A list of the flag names with the appropriate default value, the first part of the pair should be duplicates of |
| 7361 | + /// what is in snames or lnames but will trigger a particular response on a flag |
| 7362 | + std::vector<std::pair<std::string, std::string>> default_flag_values_{}; |
| 7363 | + |
| 7364 | + /// a list of flag names with specified default values; |
| 7365 | + std::vector<std::string> fnames_{}; |
| 7366 | + |
| 7367 | + /// A positional name |
| 7368 | + std::string pname_{}; |
| 7369 | + |
| 7370 | + /// If given, check the environment for this option |
| 7371 | + std::string envname_{}; |
| 7372 | + |
| 7373 | + ///@} |
| 7374 | + /// @name Help |
| 7375 | + ///@{ |
| 7376 | + |
| 7377 | + /// The description for help strings |
| 7378 | + std::string description_{}; |
| 7379 | + |
| 7380 | + /// A human readable default value, either manually set, captured, or captured by default |
| 7381 | + std::string default_str_{}; |
| 7382 | + |
| 7383 | + /// If given, replace the text that describes the option type and usage in the help text |
| 7384 | + std::string option_text_{}; |
| 7385 | + |
| 7386 | + /// A human readable type value, set when App creates this |
| 7387 | + /// |
| 7388 | + /// This is a lambda function so "types" can be dynamic, such as when a set prints its contents. |
| 7389 | + std::function<std::string()> type_name_{[]() { return std::string(); }}; |
| 7390 | + |
| 7391 | + /// Run this function to capture a default (ignore if empty) |
| 7392 | + std::function<std::string()> default_function_{}; |
| 7393 | + |
| 7394 | + ///@} |
| 7395 | + /// @name Configuration |
| 7396 | + ///@{ |
| 7397 | + |
| 7398 | + /// The number of arguments that make up one option. max is the nominal type size, min is the minimum number of |
| 7399 | + /// strings |
| 7400 | + int type_size_max_{1}; |
| 7401 | + /// The minimum number of arguments an option should be expecting |
| 7402 | + int type_size_min_{1}; |
| 7403 | + |
| 7404 | + /// The minimum number of expected values |
| 7405 | + int expected_min_{1}; |
| 7406 | + /// The maximum number of expected values |
| 7407 | + int expected_max_{1}; |
| 7408 | + |
| 7409 | + /// A list of Validators to run on each value parsed |
| 7410 | + std::vector<Validator_p> validators_{}; |
| 7411 | + |
| 7412 | + /// A list of options that are required with this option |
| 7413 | + std::set<Option *> needs_{}; |
| 7414 | + |
| 7415 | + /// A list of options that are excluded with this option |
| 7416 | + std::set<Option *> excludes_{}; |
| 7417 | + |
| 7418 | + ///@} |
| 7419 | + /// @name Other |
| 7420 | + ///@{ |
| 7421 | + |
| 7422 | + /// link back up to the parent App for fallthrough |
| 7423 | + App *parent_{nullptr}; |
| 7424 | + |
| 7425 | + /// Options store a callback to do all the work |
| 7426 | + callback_t callback_{}; |
| 7427 | + |
| 7428 | + ///@} |
| 7429 | + /// @name Parsing results |
| 7430 | + ///@{ |
| 7431 | + |
| 7432 | + /// complete Results of parsing |
| 7433 | + results_t results_{}; |
| 7434 | + /// results after reduction |
| 7435 | + mutable results_t proc_results_{}; |
| 7436 | + /// enumeration for the option state machine |
| 7437 | + enum class option_state : char { |
| 7438 | + parsing = 0, //!< The option is currently collecting parsed results |
| 7439 | + validated = 2, //!< the results have been validated |
| 7440 | + reduced = 4, //!< a subset of results has been generated |
| 7441 | + callback_run = 6, //!< the callback has been executed |
| 7442 | + }; |
| 7443 | + /// Whether the callback has run (needed for INI parsing) |
| 7444 | + option_state current_option_state_{option_state::parsing}; |
| 7445 | + /// Specify that extra args beyond type_size_max should be allowed |
| 7446 | + bool allow_extra_args_{false}; |
| 7447 | + /// Specify that the option should act like a flag vs regular option |
| 7448 | + bool flag_like_{false}; |
| 7449 | + /// Control option to run the callback to set the default |
| 7450 | + bool run_callback_for_default_{false}; |
| 7451 | + /// flag indicating a separator needs to be injected after each argument call |
| 7452 | + bool inject_separator_{false}; |
| 7453 | + /// flag indicating that the option should trigger the validation and callback chain on each result when loaded |
| 7454 | + bool trigger_on_result_{false}; |
| 7455 | + /// flag indicating that the option should force the callback regardless if any results present |
| 7456 | + bool force_callback_{false}; |
| 7457 | + |
| 7458 | + /// Making an option by hand is not defined, it must be made by the App class |
| 7459 | + Option(std::string option_name, |
| 7460 | + std::string option_description, |
| 7461 | + callback_t callback, |
| 7462 | + App *parent, |
| 7463 | + bool allow_non_standard = false) |
| 7464 | + : description_(std::move(option_description)), parent_(parent), callback_(std::move(callback)) { |
| 7465 | + std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(option_name), allow_non_standard); |
| 7466 | + } |
| 7467 | + |
| 7468 | + public: |
| 7469 | + /// @name Basic |
| 7470 | + ///@{ |
| 7471 | + |
| 7472 | + Option(const Option &) = delete; |
| 7473 | + Option &operator=(const Option &) = delete; |
| 7474 | + |
| 7475 | + /// Count the total number of times an option was passed |
| 7476 | + CLI11_NODISCARD std::size_t count() const { return results_.size(); } |
| 7477 | + |
| 7478 | + /// True if the option was not passed |
| 7479 | + CLI11_NODISCARD bool empty() const { return results_.empty(); } |
| 7480 | + |
| 7481 | + /// This bool operator returns true if any arguments were passed or the option callback is forced |
| 7482 | + explicit operator bool() const { return !empty() || force_callback_; } |
| 7483 | + |
| 7484 | + /// Clear the parsed results (mostly for testing) |
| 7485 | + void clear() { |
| 7486 | + results_.clear(); |
| 7487 | + current_option_state_ = option_state::parsing; |
| 7488 | + } |
| 7489 | + |
| 7490 | + ///@} |
| 7491 | + /// @name Setting options |
| 7492 | + ///@{ |
| 7493 | + |
| 7494 | + /// Set the number of expected arguments |
| 7495 | + Option *expected(int value); |
| 7496 | + |
| 7497 | + /// Set the range of expected arguments |
| 7498 | + Option *expected(int value_min, int value_max); |
| 7499 | + |
| 7500 | + /// Set the value of allow_extra_args which allows extra value arguments on the flag or option to be included |
| 7501 | + /// with each instance |
| 7502 | + Option *allow_extra_args(bool value = true) { |
| 7503 | + allow_extra_args_ = value; |
| 7504 | + return this; |
| 7505 | + } |
| 7506 | + /// Get the current value of allow extra args |
| 7507 | + CLI11_NODISCARD bool get_allow_extra_args() const { return allow_extra_args_; } |
| 7508 | + /// Set the value of trigger_on_parse which specifies that the option callback should be triggered on every parse |
| 7509 | + Option *trigger_on_parse(bool value = true) { |
| 7510 | + trigger_on_result_ = value; |
| 7511 | + return this; |
| 7512 | + } |
| 7513 | + /// The status of trigger on parse |
| 7514 | + CLI11_NODISCARD bool get_trigger_on_parse() const { return trigger_on_result_; } |
| 7515 | + |
| 7516 | + /// Set the value of force_callback |
| 7517 | + Option *force_callback(bool value = true) { |
| 7518 | + force_callback_ = value; |
| 7519 | + return this; |
| 7520 | + } |
| 7521 | + /// The status of force_callback |
| 7522 | + CLI11_NODISCARD bool get_force_callback() const { return force_callback_; } |
| 7523 | + |
| 7524 | + /// Set the value of run_callback_for_default which controls whether the callback function should be called to set |
| 7525 | + /// the default This is controlled automatically but could be manipulated by the user. |
| 7526 | + Option *run_callback_for_default(bool value = true) { |
| 7527 | + run_callback_for_default_ = value; |
| 7528 | + return this; |
| 7529 | + } |
| 7530 | + /// Get the current value of run_callback_for_default |
| 7531 | + CLI11_NODISCARD bool get_run_callback_for_default() const { return run_callback_for_default_; } |
| 7532 | + |
| 7533 | + /// Set the value of callback priority which controls when the callback function should be called relative to other |
| 7534 | + /// parsing operations the default This is controlled automatically but could be manipulated by the user. |
| 7535 | + Option *callback_priority(CallbackPriority value = CallbackPriority::Normal) { |
| 7536 | + callback_priority_ = value; |
| 7537 | + return this; |
| 7538 | + } |
| 7539 | + |
| 7540 | + /// Adds a shared validator |
| 7541 | + Option *check(Validator_p validator); |
| 7542 | + |
| 7543 | + /// Adds a Validator with a built in type name |
| 7544 | + Option *check(Validator validator, const std::string &validator_name = ""); |
| 7545 | + |
| 7546 | + /// Adds a Validator. Takes a const string& and returns an error message (empty if conversion/check is okay). |
| 7547 | + Option *check(std::function<std::string(const std::string &)> validator_func, |
| 7548 | + std::string validator_description = "", |
| 7549 | + std::string validator_name = ""); |
| 7550 | + |
| 7551 | + /// Adds a shared Validator |
| 7552 | + Option *transform(Validator_p validator); |
| 7553 | + |
| 7554 | + /// Adds a transforming Validator with a built in type name |
| 7555 | + Option *transform(Validator validator, const std::string &transform_name = ""); |
| 7556 | + |
| 7557 | + /// Adds a Validator-like function that can change result |
| 7558 | + Option *transform(const std::function<std::string(std::string)> &transform_func, |
| 7559 | + std::string transform_description = "", |
| 7560 | + std::string transform_name = ""); |
| 7561 | + |
| 7562 | + /// Adds a user supplied function to run on each item passed in (communicate though lambda capture) |
| 7563 | + Option *each(const std::function<void(std::string)> &func); |
| 7564 | + |
| 7565 | + /// Get a named Validator |
| 7566 | + Validator *get_validator(const std::string &validator_name = ""); |
| 7567 | + |
| 7568 | + /// Get a Validator by index NOTE: this may not be the order of definition |
| 7569 | + Validator *get_validator(int index); |
| 7570 | + |
| 7571 | + /// Sets required options |
| 7572 | + Option *needs(Option *opt) { |
| 7573 | + if(opt != this) { |
| 7574 | + needs_.insert(opt); |
| 7575 | + } |
| 7576 | + return this; |
| 7577 | + } |
| 7578 | + |
| 7579 | + /// Can find a string if needed |
| 7580 | + template <typename T = App> Option *needs(std::string opt_name) { |
| 7581 | + auto opt = static_cast<T *>(parent_)->get_option_no_throw(opt_name); |
| 7582 | + if(opt == nullptr) { |
| 7583 | + throw IncorrectConstruction::MissingOption(opt_name); |
| 7584 | + } |
| 7585 | + return needs(opt); |
| 7586 | + } |
| 7587 | + |
| 7588 | + /// Any number supported, any mix of string and Opt |
| 7589 | + template <typename A, typename B, typename... ARG> Option *needs(A opt, B opt1, ARG... args) { |
| 7590 | + needs(opt); |
| 7591 | + return needs(opt1, args...); // NOLINT(readability-suspicious-call-argument) |
| 7592 | + } |
| 7593 | + |
| 7594 | + /// Remove needs link from an option. Returns true if the option really was in the needs list. |
| 7595 | + bool remove_needs(Option *opt); |
| 7596 | + |
| 7597 | + /// Sets excluded options |
| 7598 | + Option *excludes(Option *opt); |
| 7599 | + |
| 7600 | + /// Can find a string if needed |
| 7601 | + template <typename T = App> Option *excludes(std::string opt_name) { |
| 7602 | + auto opt = static_cast<T *>(parent_)->get_option_no_throw(opt_name); |
| 7603 | + if(opt == nullptr) { |
| 7604 | + throw IncorrectConstruction::MissingOption(opt_name); |
| 7605 | + } |
| 7606 | + return excludes(opt); |
| 7607 | + } |
| 7608 | + |
| 7609 | + /// Any number supported, any mix of string and Opt |
| 7610 | + template <typename A, typename B, typename... ARG> Option *excludes(A opt, B opt1, ARG... args) { |
| 7611 | + excludes(opt); |
| 7612 | + return excludes(opt1, args...); |
| 7613 | + } |
| 7614 | + |
| 7615 | + /// Remove needs link from an option. Returns true if the option really was in the needs list. |
| 7616 | + bool remove_excludes(Option *opt); |
| 7617 | + |
| 7618 | + /// Sets environment variable to read if no option given |
| 7619 | + Option *envname(std::string name) { |
| 7620 | + envname_ = std::move(name); |
| 7621 | + return this; |
| 7622 | + } |
| 7623 | + |
| 7624 | + /// Ignore case |
| 7625 | + /// |
| 7626 | + /// The template hides the fact that we don't have the definition of App yet. |
| 7627 | + /// You are never expected to add an argument to the template here. |
| 7628 | + template <typename T = App> Option *ignore_case(bool value = true); |
| 7629 | + |
| 7630 | + /// Ignore underscores in the option names |
| 7631 | + /// |
| 7632 | + /// The template hides the fact that we don't have the definition of App yet. |
| 7633 | + /// You are never expected to add an argument to the template here. |
| 7634 | + template <typename T = App> Option *ignore_underscore(bool value = true); |
| 7635 | + |
| 7636 | + /// Take the last argument if given multiple times (or another policy) |
| 7637 | + Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw); |
| 7638 | + |
| 7639 | + /// Disable flag overrides values, e.g. --flag=<value> is not allowed |
| 7640 | + Option *disable_flag_override(bool value = true) { |
| 7641 | + disable_flag_override_ = value; |
| 7642 | + return this; |
| 7643 | + } |
| 7644 | + ///@} |
| 7645 | + /// @name Accessors |
| 7646 | + ///@{ |
| 7647 | + |
| 7648 | + /// The number of arguments the option expects |
| 7649 | + CLI11_NODISCARD int get_type_size() const { return type_size_min_; } |
| 7650 | + |
| 7651 | + /// The minimum number of arguments the option expects |
| 7652 | + CLI11_NODISCARD int get_type_size_min() const { return type_size_min_; } |
| 7653 | + /// The maximum number of arguments the option expects |
| 7654 | + CLI11_NODISCARD int get_type_size_max() const { return type_size_max_; } |
| 7655 | + |
| 7656 | + /// Return the inject_separator flag |
| 7657 | + CLI11_NODISCARD bool get_inject_separator() const { return inject_separator_; } |
| 7658 | + |
| 7659 | + /// The environment variable associated to this value |
| 7660 | + CLI11_NODISCARD std::string get_envname() const { return envname_; } |
| 7661 | + |
| 7662 | + /// The set of options needed |
| 7663 | + CLI11_NODISCARD std::set<Option *> get_needs() const { return needs_; } |
| 7664 | + |
| 7665 | + /// The set of options excluded |
| 7666 | + CLI11_NODISCARD std::set<Option *> get_excludes() const { return excludes_; } |
| 7667 | + |
| 7668 | + /// The default value (for help printing) |
| 7669 | + CLI11_NODISCARD std::string get_default_str() const { return default_str_; } |
| 7670 | + |
| 7671 | + /// Get the callback function |
| 7672 | + CLI11_NODISCARD callback_t get_callback() const { return callback_; } |
| 7673 | + |
| 7674 | + /// Get the long names |
| 7675 | + CLI11_NODISCARD const std::vector<std::string> &get_lnames() const { return lnames_; } |
| 7676 | + |
| 7677 | + /// Get the short names |
| 7678 | + CLI11_NODISCARD const std::vector<std::string> &get_snames() const { return snames_; } |
| 7679 | + |
| 7680 | + /// Get the flag names with specified default values |
| 7681 | + CLI11_NODISCARD const std::vector<std::string> &get_fnames() const { return fnames_; } |
| 7682 | + /// Get a single name for the option, first of lname, sname, pname, envname |
| 7683 | + CLI11_NODISCARD const std::string &get_single_name() const { |
| 7684 | + if(!lnames_.empty()) { |
| 7685 | + return lnames_[0]; |
| 7686 | + } |
| 7687 | + if(!snames_.empty()) { |
| 7688 | + return snames_[0]; |
| 7689 | + } |
| 7690 | + if(!pname_.empty()) { |
| 7691 | + return pname_; |
| 7692 | + } |
| 7693 | + return envname_; |
| 7694 | + } |
| 7695 | + /// The number of times the option expects to be included |
| 7696 | + CLI11_NODISCARD int get_expected() const { return expected_min_; } |
| 7697 | + |
| 7698 | + /// The number of times the option expects to be included |
| 7699 | + CLI11_NODISCARD int get_expected_min() const { return expected_min_; } |
| 7700 | + /// The max number of times the option expects to be included |
| 7701 | + CLI11_NODISCARD int get_expected_max() const { return expected_max_; } |
| 7702 | + |
| 7703 | + /// The total min number of expected string values to be used |
| 7704 | + CLI11_NODISCARD int get_items_expected_min() const { return type_size_min_ * expected_min_; } |
| 7705 | + |
| 7706 | + /// Get the maximum number of items expected to be returned and used for the callback |
| 7707 | + CLI11_NODISCARD int get_items_expected_max() const { |
| 7708 | + int t = type_size_max_; |
| 7709 | + return detail::checked_multiply(t, expected_max_) ? t : detail::expected_max_vector_size; |
| 7710 | + } |
| 7711 | + /// The total min number of expected string values to be used |
| 7712 | + CLI11_NODISCARD int get_items_expected() const { return get_items_expected_min(); } |
| 7713 | + |
| 7714 | + /// True if the argument can be given directly |
| 7715 | + CLI11_NODISCARD bool get_positional() const { return !pname_.empty(); } |
| 7716 | + |
| 7717 | + /// True if option has at least one non-positional name |
| 7718 | + CLI11_NODISCARD bool nonpositional() const { return (!lnames_.empty() || !snames_.empty()); } |
| 7719 | + |
| 7720 | + /// True if option has description |
| 7721 | + CLI11_NODISCARD bool has_description() const { return !description_.empty(); } |
| 7722 | + |
| 7723 | + /// Get the description |
| 7724 | + CLI11_NODISCARD const std::string &get_description() const { return description_; } |
| 7725 | + |
| 7726 | + /// Set the description |
| 7727 | + Option *description(std::string option_description) { |
| 7728 | + description_ = std::move(option_description); |
| 7729 | + return this; |
| 7730 | + } |
| 7731 | + |
| 7732 | + Option *option_text(std::string text) { |
| 7733 | + option_text_ = std::move(text); |
| 7734 | + return this; |
| 7735 | + } |
| 7736 | + |
| 7737 | + CLI11_NODISCARD const std::string &get_option_text() const { return option_text_; } |
| 7738 | + |
| 7739 | + ///@} |
| 7740 | + /// @name Help tools |
| 7741 | + ///@{ |
| 7742 | + |
| 7743 | + /// \brief Gets a comma separated list of names. |
| 7744 | + /// Will include / prefer the positional name if positional is true. |
| 7745 | + /// If all_options is false, pick just the most descriptive name to show. |
| 7746 | + /// Use `get_name(true)` to get the positional name (replaces `get_pname`) |
| 7747 | + /// if disable_default_flag_values is true, do not include the default values for flags such as `--no-flag{false}` |
| 7748 | + CLI11_NODISCARD std::string get_name(bool positional = false, ///< Show the positional name |
| 7749 | + bool all_options = false, ///< Show every option |
| 7750 | + bool disable_default_flag_values = false ///< Disable default values in name |
| 7751 | + ) const; |
| 7752 | + |
| 7753 | + ///@} |
| 7754 | + /// @name Parser tools |
| 7755 | + ///@{ |
| 7756 | + |
| 7757 | + /// Process the callback |
| 7758 | + void run_callback(); |
| 7759 | + |
| 7760 | + /// If options share any of the same names, find it |
| 7761 | + CLI11_NODISCARD const std::string &matching_name(const Option &other) const; |
| 7762 | + |
| 7763 | + /// If options share any of the same names, they are equal (not counting positional) |
| 7764 | + bool operator==(const Option &other) const { return !matching_name(other).empty(); } |
| 7765 | + |
| 7766 | + /// Check a name. Requires "-" or "--" for short / long, supports positional name |
| 7767 | + CLI11_NODISCARD bool check_name(const std::string &name) const; |
| 7768 | + |
| 7769 | + /// Requires "-" to be removed from string |
| 7770 | + CLI11_NODISCARD bool check_sname(std::string name) const { |
| 7771 | + return (detail::find_member(std::move(name), snames_, ignore_case_) >= 0); |
| 7772 | + } |
| 7773 | + |
| 7774 | + /// Requires "--" to be removed from string |
| 7775 | + CLI11_NODISCARD bool check_lname(std::string name) const { |
| 7776 | + return (detail::find_member(std::move(name), lnames_, ignore_case_, ignore_underscore_) >= 0); |
| 7777 | + } |
| 7778 | + |
| 7779 | + /// Requires "--" to be removed from string |
| 7780 | + CLI11_NODISCARD bool check_fname(std::string name) const { |
| 7781 | + if(fnames_.empty()) { |
| 7782 | + return false; |
| 7783 | + } |
| 7784 | + return (detail::find_member(std::move(name), fnames_, ignore_case_, ignore_underscore_) >= 0); |
| 7785 | + } |
| 7786 | + |
| 7787 | + /// Get the value that goes for a flag, nominally gets the default value but allows for overrides if not |
| 7788 | + /// disabled |
| 7789 | + CLI11_NODISCARD std::string get_flag_value(const std::string &name, std::string input_value) const; |
| 7790 | + |
| 7791 | + /// Puts a result at the end |
| 7792 | + Option *add_result(std::string s); |
| 7793 | + |
| 7794 | + /// Puts a result at the end and get a count of the number of arguments actually added |
| 7795 | + Option *add_result(std::string s, int &results_added); |
| 7796 | + |
| 7797 | + /// Puts a result at the end |
| 7798 | + Option *add_result(std::vector<std::string> s); |
| 7799 | + |
| 7800 | + /// Get the current complete results set |
| 7801 | + CLI11_NODISCARD const results_t &results() const { return results_; } |
| 7802 | + |
| 7803 | + /// Get a copy of the results |
| 7804 | + CLI11_NODISCARD results_t reduced_results() const; |
| 7805 | + |
| 7806 | + /// Get the results as a specified type |
| 7807 | + template <typename T> void results(T &output) const { |
| 7808 | + bool retval = false; |
| 7809 | + if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) { |
| 7810 | + const results_t &res = (proc_results_.empty()) ? results_ : proc_results_; |
| 7811 | + if(!res.empty()) { |
| 7812 | + retval = detail::lexical_conversion<T, T>(res, output); |
| 7813 | + } else { |
| 7814 | + results_t res2; |
| 7815 | + res2.emplace_back(); |
| 7816 | + proc_results_ = std::move(res2); |
| 7817 | + retval = detail::lexical_conversion<T, T>(proc_results_, output); |
| 7818 | + } |
| 7819 | + |
| 7820 | + } else { |
| 7821 | + results_t res; |
| 7822 | + if(results_.empty()) { |
| 7823 | + if(!default_str_.empty()) { |
| 7824 | + // _add_results takes an rvalue only |
| 7825 | + _add_result(std::string(default_str_), res); |
| 7826 | + _validate_results(res); |
| 7827 | + results_t extra; |
| 7828 | + _reduce_results(extra, res); |
| 7829 | + if(!extra.empty()) { |
| 7830 | + res = std::move(extra); |
| 7831 | + } |
| 7832 | + } else { |
| 7833 | + res.emplace_back(); |
| 7834 | + } |
| 7835 | + } else { |
| 7836 | + res = reduced_results(); |
| 7837 | + } |
| 7838 | + // store the results in a stable location if the output is a view |
| 7839 | + proc_results_ = std::move(res); |
| 7840 | + retval = detail::lexical_conversion<T, T>(proc_results_, output); |
| 7841 | + } |
| 7842 | + if(!retval) { |
| 7843 | + throw ConversionError(get_name(), results_); |
| 7844 | + } |
| 7845 | + } |
| 7846 | + |
| 7847 | + /// Return the results as the specified type |
| 7848 | + template <typename T> CLI11_NODISCARD T as() const { |
| 7849 | + T output; |
| 7850 | + results(output); |
| 7851 | + return output; |
| 7852 | + } |
| 7853 | + |
| 7854 | + /// See if the callback has been run already |
| 7855 | + CLI11_NODISCARD bool get_callback_run() const { return (current_option_state_ == option_state::callback_run); } |
| 7856 | + |
| 7857 | + ///@} |
| 7858 | + /// @name Custom options |
| 7859 | + ///@{ |
| 7860 | + |
| 7861 | + /// Set the type function to run when displayed on this option |
| 7862 | + Option *type_name_fn(std::function<std::string()> typefun) { |
| 7863 | + type_name_ = std::move(typefun); |
| 7864 | + return this; |
| 7865 | + } |
| 7866 | + |
| 7867 | + /// Set a custom option typestring |
| 7868 | + Option *type_name(std::string typeval) { |
| 7869 | + type_name_fn([typeval]() { return typeval; }); |
| 7870 | + return this; |
| 7871 | + } |
| 7872 | + |
| 7873 | + /// Set a custom option size |
| 7874 | + Option *type_size(int option_type_size); |
| 7875 | + |
| 7876 | + /// Set a custom option type size range |
| 7877 | + Option *type_size(int option_type_size_min, int option_type_size_max); |
| 7878 | + |
| 7879 | + /// Set the value of the separator injection flag |
| 7880 | + void inject_separator(bool value = true) { inject_separator_ = value; } |
| 7881 | + |
| 7882 | + /// Set a capture function for the default. Mostly used by App. |
| 7883 | + Option *default_function(const std::function<std::string()> &func) { |
| 7884 | + default_function_ = func; |
| 7885 | + return this; |
| 7886 | + } |
| 7887 | + |
| 7888 | + /// Capture the default value from the original value (if it can be captured) |
| 7889 | + Option *capture_default_str() { |
| 7890 | + if(default_function_) { |
| 7891 | + default_str_ = default_function_(); |
| 7892 | + } |
| 7893 | + return this; |
| 7894 | + } |
| 7895 | + |
| 7896 | + /// Set the default value string representation (does not change the contained value) |
| 7897 | + Option *default_str(std::string val) { |
| 7898 | + default_str_ = std::move(val); |
| 7899 | + return this; |
| 7900 | + } |
| 7901 | + |
| 7902 | + /// Set the default value and validate the results and run the callback if appropriate to set the value into the |
| 7903 | + /// bound value only available for types that can be converted to a string |
| 7904 | + template <typename X> Option *default_val(const X &val) { |
| 7905 | + std::string val_str = detail::value_string(val); |
| 7906 | + auto old_option_state = current_option_state_; |
| 7907 | + results_t old_results{std::move(results_)}; |
| 7908 | + results_.clear(); |
| 7909 | + try { |
| 7910 | + add_result(val_str); |
| 7911 | + // if trigger_on_result_ is set the callback already ran |
| 7912 | + if(run_callback_for_default_ && !trigger_on_result_) { |
| 7913 | + run_callback(); // run callback sets the state, we need to reset it again |
| 7914 | + current_option_state_ = option_state::parsing; |
| 7915 | + } else { |
| 7916 | + _validate_results(results_); |
| 7917 | + current_option_state_ = old_option_state; |
| 7918 | + } |
| 7919 | + } catch(const ConversionError &err) { |
| 7920 | + // this should be done |
| 7921 | + results_ = std::move(old_results); |
| 7922 | + current_option_state_ = old_option_state; |
| 7923 | + |
| 7924 | + throw ConversionError( |
| 7925 | + get_name(), std::string("given default value(\"") + val_str + "\") produces an error : " + err.what()); |
| 7926 | + } catch(const CLI::Error &) { |
| 7927 | + results_ = std::move(old_results); |
| 7928 | + current_option_state_ = old_option_state; |
| 7929 | + throw; |
| 7930 | + } |
| 7931 | + results_ = std::move(old_results); |
| 7932 | + default_str_ = std::move(val_str); |
| 7933 | + return this; |
| 7934 | + } |
| 7935 | + |
| 7936 | + /// Get the full typename for this option |
| 7937 | + CLI11_NODISCARD std::string get_type_name() const; |
| 7938 | + |
| 7939 | + private: |
| 7940 | + /// Run the results through the Validators |
| 7941 | + void _validate_results(results_t &res) const; |
| 7942 | + |
| 7943 | + /** reduce the results in accordance with the MultiOptionPolicy |
| 7944 | + @param[out] out results are assigned to res if there if they are different |
| 7945 | + */ |
| 7946 | + void _reduce_results(results_t &out, const results_t &original) const; |
| 7947 | + |
| 7948 | + // Run a result through the Validators |
| 7949 | + std::string _validate(std::string &result, int index) const; |
| 7950 | + |
| 7951 | + /// Add a single result to the result set, taking into account delimiters |
| 7952 | + int _add_result(std::string &&result, std::vector<std::string> &res) const; |
| 7953 | +}; |
| 7954 | + |
| 7955 | + |
| 7956 | + |
| 7957 | + |
| 7958 | +template <typename CRTP> template <typename T> void OptionBase<CRTP>::copy_to(T *other) const { |
| 7959 | + other->group(group_); |
| 7960 | + other->required(required_); |
| 7961 | + other->ignore_case(ignore_case_); |
| 7962 | + other->ignore_underscore(ignore_underscore_); |
| 7963 | + other->configurable(configurable_); |
| 7964 | + other->disable_flag_override(disable_flag_override_); |
| 7965 | + other->delimiter(delimiter_); |
| 7966 | + other->always_capture_default(always_capture_default_); |
| 7967 | + other->multi_option_policy(multi_option_policy_); |
| 7968 | + other->callback_priority(callback_priority_); |
| 7969 | +} |
| 7970 | + |
| 7971 | +CLI11_INLINE Option *Option::expected(int value) { |
| 7972 | + if(value < 0) { |
| 7973 | + expected_min_ = -value; |
| 7974 | + if(expected_max_ < expected_min_) { |
| 7975 | + expected_max_ = expected_min_; |
| 7976 | + } |
| 7977 | + allow_extra_args_ = true; |
| 7978 | + flag_like_ = false; |
| 7979 | + } else if(value == detail::expected_max_vector_size) { |
| 7980 | + expected_min_ = 1; |
| 7981 | + expected_max_ = detail::expected_max_vector_size; |
| 7982 | + allow_extra_args_ = true; |
| 7983 | + flag_like_ = false; |
| 7984 | + } else { |
| 7985 | + expected_min_ = value; |
| 7986 | + expected_max_ = value; |
| 7987 | + flag_like_ = (expected_min_ == 0); |
| 7988 | + } |
| 7989 | + return this; |
| 7990 | +} |
| 7991 | + |
| 7992 | +CLI11_INLINE Option *Option::expected(int value_min, int value_max) { |
| 7993 | + if(value_min < 0) { |
| 7994 | + value_min = -value_min; |
| 7995 | + } |
| 7996 | + |
| 7997 | + if(value_max < 0) { |
| 7998 | + value_max = detail::expected_max_vector_size; |
| 7999 | + } |
| 8000 | + if(value_max < value_min) { |
| 8001 | + expected_min_ = value_max; |
| 8002 | + expected_max_ = value_min; |
| 8003 | + } else { |
| 8004 | + expected_max_ = value_max; |
| 8005 | + expected_min_ = value_min; |
| 8006 | + } |
| 8007 | + |
| 8008 | + return this; |
| 8009 | +} |
| 8010 | + |
| 8011 | +CLI11_INLINE Option *Option::check(Validator_p validator) { |
| 8012 | + validator->non_modifying(); |
| 8013 | + validators_.push_back(std::move(validator)); |
| 8014 | + |
| 8015 | + return this; |
| 8016 | +} |
| 8017 | + |
| 8018 | +CLI11_INLINE Option *Option::check(Validator validator, const std::string &validator_name) { |
| 8019 | + validator.non_modifying(); |
| 8020 | + auto vp = std::make_shared<Validator>(std::move(validator)); |
| 8021 | + if(!validator_name.empty()) { |
| 8022 | + vp->name(validator_name); |
| 8023 | + } |
| 8024 | + validators_.push_back(std::move(vp)); |
| 8025 | + |
| 8026 | + return this; |
| 8027 | +} |
| 8028 | + |
| 8029 | +CLI11_INLINE Option *Option::check(std::function<std::string(const std::string &)> validator_func, |
| 8030 | + std::string validator_description, |
| 8031 | + std::string validator_name) { |
| 8032 | + |
| 8033 | + auto vp = std::make_shared<Validator>( |
| 8034 | + std::move(validator_func), std::move(validator_description), std::move(validator_name)); |
| 8035 | + vp->non_modifying(); |
| 8036 | + validators_.push_back(std::move(vp)); |
| 8037 | + return this; |
| 8038 | +} |
| 8039 | + |
| 8040 | +CLI11_INLINE Option *Option::transform(Validator_p validator) { |
| 8041 | + validators_.insert(validators_.begin(), std::move(validator)); |
| 8042 | + |
| 8043 | + return this; |
| 8044 | +} |
| 8045 | + |
| 8046 | +CLI11_INLINE Option *Option::transform(Validator validator, const std::string &transform_name) { |
| 8047 | + auto vp = std::make_shared<Validator>(std::move(validator)); |
| 8048 | + if(!transform_name.empty()) { |
| 8049 | + vp->name(transform_name); |
| 8050 | + } |
| 8051 | + validators_.insert(validators_.begin(), std::move(vp)); |
| 8052 | + return this; |
| 8053 | +} |
| 8054 | + |
| 8055 | +CLI11_INLINE Option *Option::transform(const std::function<std::string(std::string)> &transform_func, |
| 8056 | + std::string transform_description, |
| 8057 | + std::string transform_name) { |
| 8058 | + auto vp = std::make_shared<Validator>( |
| 8059 | + [transform_func](std::string &val) { |
| 8060 | + val = transform_func(val); |
| 8061 | + return std::string{}; |
| 8062 | + }, |
| 8063 | + std::move(transform_description), |
| 8064 | + std::move(transform_name)); |
| 8065 | + validators_.insert(validators_.begin(), std::move(vp)); |
| 8066 | + |
| 8067 | + return this; |
| 8068 | +} |
| 8069 | + |
| 8070 | +CLI11_INLINE Option *Option::each(const std::function<void(std::string)> &func) { |
| 8071 | + auto vp = std::make_shared<Validator>( |
| 8072 | + [func](std::string &inout) { |
| 8073 | + func(inout); |
| 8074 | + return std::string{}; |
| 8075 | + }, |
| 8076 | + std::string{}); |
| 8077 | + validators_.push_back(std::move(vp)); |
| 8078 | + return this; |
| 8079 | +} |
| 8080 | + |
| 8081 | +CLI11_INLINE Validator *Option::get_validator(const std::string &validator_name) { |
| 8082 | + for(auto &validator : validators_) { |
| 8083 | + if(validator_name == validator->get_name()) { |
| 8084 | + return validator.get(); |
| 8085 | + } |
| 8086 | + } |
| 8087 | + if((validator_name.empty()) && (!validators_.empty())) { |
| 8088 | + return validators_.front().get(); |
| 8089 | + } |
| 8090 | + throw OptionNotFound(std::string{"Validator "} + validator_name + " Not Found"); |
| 8091 | +} |
| 8092 | + |
| 8093 | +CLI11_INLINE Validator *Option::get_validator(int index) { |
| 8094 | + // This is a signed int so that it is not equivalent to a pointer. |
| 8095 | + if(index >= 0 && index < static_cast<int>(validators_.size())) { |
| 8096 | + return validators_[static_cast<decltype(validators_)::size_type>(index)].get(); |
| 8097 | + } |
| 8098 | + throw OptionNotFound("Validator index is not valid"); |
| 8099 | +} |
| 8100 | + |
| 8101 | +CLI11_INLINE bool Option::remove_needs(Option *opt) { |
| 8102 | + auto iterator = std::find(std::begin(needs_), std::end(needs_), opt); |
| 8103 | + |
| 8104 | + if(iterator == std::end(needs_)) { |
| 8105 | + return false; |
| 8106 | + } |
| 8107 | + needs_.erase(iterator); |
| 8108 | + return true; |
| 8109 | +} |
| 8110 | + |
| 8111 | +CLI11_INLINE Option *Option::excludes(Option *opt) { |
| 8112 | + if(opt == this) { |
| 8113 | + throw(IncorrectConstruction("and option cannot exclude itself")); |
| 8114 | + } |
| 8115 | + excludes_.insert(opt); |
| 8116 | + |
| 8117 | + // Help text should be symmetric - excluding a should exclude b |
| 8118 | + opt->excludes_.insert(this); |
| 8119 | + |
| 8120 | + // Ignoring the insert return value, excluding twice is now allowed. |
| 8121 | + // (Mostly to allow both directions to be excluded by user, even though the library does it for you.) |
| 8122 | + |
| 8123 | + return this; |
| 8124 | +} |
| 8125 | + |
| 8126 | +CLI11_INLINE bool Option::remove_excludes(Option *opt) { |
| 8127 | + auto iterator = std::find(std::begin(excludes_), std::end(excludes_), opt); |
| 8128 | + |
| 8129 | + if(iterator == std::end(excludes_)) { |
| 8130 | + return false; |
| 8131 | + } |
| 8132 | + excludes_.erase(iterator); |
| 8133 | + return true; |
| 8134 | +} |
| 8135 | + |
| 8136 | +template <typename T> Option *Option::ignore_case(bool value) { |
| 8137 | + if(!ignore_case_ && value) { |
| 8138 | + ignore_case_ = value; |
| 8139 | + auto *parent = static_cast<T *>(parent_); |
| 8140 | + for(const Option_p &opt : parent->options_) { |
| 8141 | + if(opt.get() == this) { |
| 8142 | + continue; |
| 8143 | + } |
| 8144 | + const auto &omatch = opt->matching_name(*this); |
| 8145 | + if(!omatch.empty()) { |
| 8146 | + ignore_case_ = false; |
| 8147 | + throw OptionAlreadyAdded("adding ignore case caused a name conflict with " + omatch); |
| 8148 | + } |
| 8149 | + } |
| 8150 | + } else { |
| 8151 | + ignore_case_ = value; |
| 8152 | + } |
| 8153 | + return this; |
| 8154 | +} |
| 8155 | + |
| 8156 | +template <typename T> Option *Option::ignore_underscore(bool value) { |
| 8157 | + |
| 8158 | + if(!ignore_underscore_ && value) { |
| 8159 | + ignore_underscore_ = value; |
| 8160 | + auto *parent = static_cast<T *>(parent_); |
| 8161 | + for(const Option_p &opt : parent->options_) { |
| 8162 | + if(opt.get() == this) { |
| 8163 | + continue; |
| 8164 | + } |
| 8165 | + const auto &omatch = opt->matching_name(*this); |
| 8166 | + if(!omatch.empty()) { |
| 8167 | + ignore_underscore_ = false; |
| 8168 | + throw OptionAlreadyAdded("adding ignore underscore caused a name conflict with " + omatch); |
| 8169 | + } |
| 8170 | + } |
| 8171 | + } else { |
| 8172 | + ignore_underscore_ = value; |
| 8173 | + } |
| 8174 | + return this; |
| 8175 | +} |
| 8176 | + |
| 8177 | +CLI11_INLINE Option *Option::multi_option_policy(MultiOptionPolicy value) { |
| 8178 | + if(value != multi_option_policy_) { |
| 8179 | + if(multi_option_policy_ == MultiOptionPolicy::Throw && expected_max_ == detail::expected_max_vector_size && |
| 8180 | + expected_min_ > 1) { // this bizarre condition is to maintain backwards compatibility |
| 8181 | + // with the previous behavior of expected_ with vectors |
| 8182 | + expected_max_ = expected_min_; |
| 8183 | + } |
| 8184 | + multi_option_policy_ = value; |
| 8185 | + current_option_state_ = option_state::parsing; |
| 8186 | + } |
| 8187 | + return this; |
| 8188 | +} |
| 8189 | + |
| 8190 | +CLI11_NODISCARD CLI11_INLINE std::string |
| 8191 | +Option::get_name(bool positional, bool all_options, bool disable_default_flag_values) const { |
| 8192 | + if(get_group().empty()) |
| 8193 | + return {}; // Hidden |
| 8194 | + |
| 8195 | + if(all_options) { |
| 8196 | + |
| 8197 | + std::vector<std::string> name_list; |
| 8198 | + |
| 8199 | + /// The all list will never include a positional unless asked or that's the only name. |
| 8200 | + if((positional && (!pname_.empty())) || (snames_.empty() && lnames_.empty())) { |
| 8201 | + name_list.push_back(pname_); |
| 8202 | + } |
| 8203 | + if((get_items_expected() == 0) && (!fnames_.empty())) { |
| 8204 | + for(const std::string &sname : snames_) { |
| 8205 | + name_list.push_back("-" + sname); |
| 8206 | + if(!disable_default_flag_values && check_fname(sname)) { |
| 8207 | + name_list.back() += "{" + get_flag_value(sname, "") + "}"; |
| 8208 | + } |
| 8209 | + } |
| 8210 | + |
| 8211 | + for(const std::string &lname : lnames_) { |
| 8212 | + name_list.push_back("--" + lname); |
| 8213 | + if(!disable_default_flag_values && check_fname(lname)) { |
| 8214 | + name_list.back() += "{" + get_flag_value(lname, "") + "}"; |
| 8215 | + } |
| 8216 | + } |
| 8217 | + } else { |
| 8218 | + for(const std::string &sname : snames_) |
| 8219 | + name_list.push_back("-" + sname); |
| 8220 | + |
| 8221 | + for(const std::string &lname : lnames_) |
| 8222 | + name_list.push_back("--" + lname); |
| 8223 | + } |
| 8224 | + |
| 8225 | + return detail::join(name_list); |
| 8226 | + } |
| 8227 | + |
| 8228 | + // This returns the positional name no matter what |
| 8229 | + if(positional) |
| 8230 | + return pname_; |
| 8231 | + |
| 8232 | + // Prefer long name |
| 8233 | + if(!lnames_.empty()) |
| 8234 | + return std::string(2, '-') + lnames_[0]; |
| 8235 | + |
| 8236 | + // Or short name if no long name |
| 8237 | + if(!snames_.empty()) |
| 8238 | + return std::string(1, '-') + snames_[0]; |
| 8239 | + |
| 8240 | + // If positional is the only name, it's okay to use that |
| 8241 | + return pname_; |
| 8242 | +} |
| 8243 | + |
| 8244 | +CLI11_INLINE void Option::run_callback() { |
| 8245 | + bool used_default_str = false; |
| 8246 | + if(force_callback_ && results_.empty()) { |
| 8247 | + used_default_str = true; |
| 8248 | + add_result(default_str_); |
| 8249 | + } |
| 8250 | + if(current_option_state_ == option_state::parsing) { |
| 8251 | + _validate_results(results_); |
| 8252 | + current_option_state_ = option_state::validated; |
| 8253 | + } |
| 8254 | + |
| 8255 | + if(current_option_state_ < option_state::reduced) { |
| 8256 | + _reduce_results(proc_results_, results_); |
| 8257 | + } |
| 8258 | + |
| 8259 | + current_option_state_ = option_state::callback_run; |
| 8260 | + if(callback_) { |
| 8261 | + const results_t &send_results = proc_results_.empty() ? results_ : proc_results_; |
| 8262 | + if(send_results.empty()) { |
| 8263 | + return; |
| 8264 | + } |
| 8265 | + bool local_result = callback_(send_results); |
| 8266 | + if(used_default_str) { |
| 8267 | + // we only clear the results if the callback was actually used |
| 8268 | + // otherwise the callback is the storage of the default |
| 8269 | + results_.clear(); |
| 8270 | + proc_results_.clear(); |
| 8271 | + } |
| 8272 | + if(!local_result) |
| 8273 | + throw ConversionError(get_name(), results_); |
| 8274 | + } |
| 8275 | +} |
| 8276 | + |
| 8277 | +CLI11_NODISCARD CLI11_INLINE const std::string &Option::matching_name(const Option &other) const { |
| 8278 | + static const std::string estring; |
| 8279 | + bool bothConfigurable = configurable_ && other.configurable_; |
| 8280 | + for(const std::string &sname : snames_) { |
| 8281 | + if(other.check_sname(sname)) |
| 8282 | + return sname; |
| 8283 | + if(bothConfigurable && other.check_lname(sname)) |
| 8284 | + return sname; |
| 8285 | + } |
| 8286 | + for(const std::string &lname : lnames_) { |
| 8287 | + if(other.check_lname(lname)) |
| 8288 | + return lname; |
| 8289 | + if(lname.size() == 1 && bothConfigurable) { |
| 8290 | + if(other.check_sname(lname)) { |
| 8291 | + return lname; |
| 8292 | + } |
| 8293 | + } |
| 8294 | + } |
| 8295 | + if(bothConfigurable && snames_.empty() && lnames_.empty() && !pname_.empty()) { |
| 8296 | + if(other.check_sname(pname_) || other.check_lname(pname_) || pname_ == other.pname_) |
| 8297 | + return pname_; |
| 8298 | + } |
| 8299 | + if(bothConfigurable && other.snames_.empty() && other.fnames_.empty() && !other.pname_.empty()) { |
| 8300 | + if(check_sname(other.pname_) || check_lname(other.pname_) || (pname_ == other.pname_)) |
| 8301 | + return other.pname_; |
| 8302 | + } |
| 8303 | + if(ignore_case_ || |
| 8304 | + ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore |
| 8305 | + for(const std::string &sname : other.snames_) |
| 8306 | + if(check_sname(sname)) |
| 8307 | + return sname; |
| 8308 | + for(const std::string &lname : other.lnames_) |
| 8309 | + if(check_lname(lname)) |
| 8310 | + return lname; |
| 8311 | + } |
| 8312 | + return estring; |
| 8313 | +} |
| 8314 | + |
| 8315 | +CLI11_NODISCARD CLI11_INLINE bool Option::check_name(const std::string &name) const { |
| 8316 | + |
| 8317 | + if(name.length() > 2 && name[0] == '-' && name[1] == '-') |
| 8318 | + return check_lname(name.substr(2)); |
| 8319 | + if(name.length() > 1 && name.front() == '-') |
| 8320 | + return check_sname(name.substr(1)); |
| 8321 | + if(!pname_.empty()) { |
| 8322 | + std::string local_pname = pname_; |
| 8323 | + std::string local_name = name; |
| 8324 | + if(ignore_underscore_) { |
| 8325 | + local_pname = detail::remove_underscore(local_pname); |
| 8326 | + local_name = detail::remove_underscore(local_name); |
| 8327 | + } |
| 8328 | + if(ignore_case_) { |
| 8329 | + local_pname = detail::to_lower(local_pname); |
| 8330 | + local_name = detail::to_lower(local_name); |
| 8331 | + } |
| 8332 | + if(local_name == local_pname) { |
| 8333 | + return true; |
| 8334 | + } |
| 8335 | + } |
| 8336 | + |
| 8337 | + if(!envname_.empty()) { |
| 8338 | + // this needs to be the original since envname_ shouldn't match on case insensitivity |
| 8339 | + return (name == envname_); |
| 8340 | + } |
| 8341 | + return false; |
| 8342 | +} |
| 8343 | + |
| 8344 | +CLI11_NODISCARD CLI11_INLINE std::string Option::get_flag_value(const std::string &name, |
| 8345 | + std::string input_value) const { |
| 8346 | + static const std::string trueString{"true"}; |
| 8347 | + static const std::string falseString{"false"}; |
| 8348 | + static const std::string emptyString{"{}"}; |
| 8349 | + // check for disable flag override_ |
| 8350 | + if(disable_flag_override_) { |
| 8351 | + if(!((input_value.empty()) || (input_value == emptyString))) { |
| 8352 | + auto default_ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_); |
| 8353 | + if(default_ind >= 0) { |
| 8354 | + // We can static cast this to std::size_t because it is more than 0 in this block |
| 8355 | + if(default_flag_values_[static_cast<std::size_t>(default_ind)].second != input_value) { |
| 8356 | + if(input_value == default_str_ && force_callback_) { |
| 8357 | + return input_value; |
| 8358 | + } |
| 8359 | + throw(ArgumentMismatch::FlagOverride(name)); |
| 8360 | + } |
| 8361 | + } else { |
| 8362 | + if(input_value != trueString) { |
| 8363 | + throw(ArgumentMismatch::FlagOverride(name)); |
| 8364 | + } |
| 8365 | + } |
| 8366 | + } |
| 8367 | + } |
| 8368 | + auto ind = detail::find_member(name, fnames_, ignore_case_, ignore_underscore_); |
| 8369 | + if((input_value.empty()) || (input_value == emptyString)) { |
| 8370 | + if(flag_like_) { |
| 8371 | + return (ind < 0) ? trueString : default_flag_values_[static_cast<std::size_t>(ind)].second; |
| 8372 | + } |
| 8373 | + return (ind < 0) ? default_str_ : default_flag_values_[static_cast<std::size_t>(ind)].second; |
| 8374 | + } |
| 8375 | + if(ind < 0) { |
| 8376 | + return input_value; |
| 8377 | + } |
| 8378 | + if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) { |
| 8379 | + errno = 0; |
| 8380 | + auto val = detail::to_flag_value(input_value); |
| 8381 | + if(errno != 0) { |
| 8382 | + errno = 0; |
| 8383 | + return input_value; |
| 8384 | + } |
| 8385 | + return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val)); |
| 8386 | + } |
| 8387 | + return input_value; |
| 8388 | +} |
| 8389 | + |
| 8390 | +CLI11_INLINE Option *Option::add_result(std::string s) { |
| 8391 | + _add_result(std::move(s), results_); |
| 8392 | + current_option_state_ = option_state::parsing; |
| 8393 | + return this; |
| 8394 | +} |
| 8395 | + |
| 8396 | +CLI11_INLINE Option *Option::add_result(std::string s, int &results_added) { |
| 8397 | + results_added = _add_result(std::move(s), results_); |
| 8398 | + current_option_state_ = option_state::parsing; |
| 8399 | + return this; |
| 8400 | +} |
| 8401 | + |
| 8402 | +CLI11_INLINE Option *Option::add_result(std::vector<std::string> s) { |
| 8403 | + current_option_state_ = option_state::parsing; |
| 8404 | + for(auto &str : s) { |
| 8405 | + _add_result(std::move(str), results_); |
| 8406 | + } |
| 8407 | + return this; |
| 8408 | +} |
| 8409 | + |
| 8410 | +CLI11_NODISCARD CLI11_INLINE results_t Option::reduced_results() const { |
| 8411 | + results_t res = proc_results_.empty() ? results_ : proc_results_; |
| 8412 | + if(current_option_state_ < option_state::reduced) { |
| 8413 | + if(current_option_state_ == option_state::parsing) { |
| 8414 | + res = results_; |
| 8415 | + _validate_results(res); |
| 8416 | + } |
| 8417 | + if(!res.empty()) { |
| 8418 | + results_t extra; |
| 8419 | + _reduce_results(extra, res); |
| 8420 | + if(!extra.empty()) { |
| 8421 | + res = std::move(extra); |
| 8422 | + } |
| 8423 | + } |
| 8424 | + } |
| 8425 | + return res; |
| 8426 | +} |
| 8427 | + |
| 8428 | +CLI11_INLINE Option *Option::type_size(int option_type_size) { |
| 8429 | + if(option_type_size < 0) { |
| 8430 | + // this section is included for backwards compatibility |
| 8431 | + type_size_max_ = -option_type_size; |
| 8432 | + type_size_min_ = -option_type_size; |
| 8433 | + expected_max_ = detail::expected_max_vector_size; |
| 8434 | + } else { |
| 8435 | + type_size_max_ = option_type_size; |
| 8436 | + if(type_size_max_ < detail::expected_max_vector_size) { |
| 8437 | + type_size_min_ = option_type_size; |
| 8438 | + } else { |
| 8439 | + inject_separator_ = true; |
| 8440 | + } |
| 8441 | + if(type_size_max_ == 0) |
| 8442 | + required_ = false; |
| 8443 | + } |
| 8444 | + return this; |
| 8445 | +} |
| 8446 | + |
| 8447 | +CLI11_INLINE Option *Option::type_size(int option_type_size_min, int option_type_size_max) { |
| 8448 | + if(option_type_size_min < 0 || option_type_size_max < 0) { |
| 8449 | + // this section is included for backwards compatibility |
| 8450 | + expected_max_ = detail::expected_max_vector_size; |
| 8451 | + option_type_size_min = (std::abs)(option_type_size_min); |
| 8452 | + option_type_size_max = (std::abs)(option_type_size_max); |
| 8453 | + } |
| 8454 | + |
| 8455 | + if(option_type_size_min > option_type_size_max) { |
| 8456 | + type_size_max_ = option_type_size_min; |
| 8457 | + type_size_min_ = option_type_size_max; |
| 8458 | + } else { |
| 8459 | + type_size_min_ = option_type_size_min; |
| 8460 | + type_size_max_ = option_type_size_max; |
| 8461 | + } |
| 8462 | + if(type_size_max_ == 0) { |
| 8463 | + required_ = false; |
| 8464 | + } |
| 8465 | + if(type_size_max_ >= detail::expected_max_vector_size) { |
| 8466 | + inject_separator_ = true; |
| 8467 | + } |
| 8468 | + return this; |
| 8469 | +} |
| 8470 | + |
| 8471 | +CLI11_NODISCARD CLI11_INLINE std::string Option::get_type_name() const { |
| 8472 | + std::string full_type_name = type_name_(); |
| 8473 | + if(!validators_.empty()) { |
| 8474 | + for(const auto &validator : validators_) { |
| 8475 | + std::string vtype = validator->get_description(); |
| 8476 | + if(!vtype.empty()) { |
| 8477 | + full_type_name += ":" + vtype; |
| 8478 | + } |
| 8479 | + } |
| 8480 | + } |
| 8481 | + return full_type_name; |
| 8482 | +} |
| 8483 | + |
| 8484 | +CLI11_INLINE void Option::_validate_results(results_t &res) const { |
| 8485 | + // Run the Validators (can change the string) |
| 8486 | + if(!validators_.empty()) { |
| 8487 | + if(type_size_max_ > 1) { // in this context index refers to the index in the type |
| 8488 | + int index = 0; |
| 8489 | + if(get_items_expected_max() < static_cast<int>(res.size()) && |
| 8490 | + (multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast || |
| 8491 | + multi_option_policy_ == CLI::MultiOptionPolicy::Reverse)) { |
| 8492 | + // create a negative index for the earliest ones |
| 8493 | + index = get_items_expected_max() - static_cast<int>(res.size()); |
| 8494 | + } |
| 8495 | + |
| 8496 | + for(std::string &result : res) { |
| 8497 | + if(detail::is_separator(result) && type_size_max_ != type_size_min_ && index >= 0) { |
| 8498 | + index = 0; // reset index for variable size chunks |
| 8499 | + continue; |
| 8500 | + } |
| 8501 | + auto err_msg = _validate(result, (index >= 0) ? (index % type_size_max_) : index); |
| 8502 | + if(!err_msg.empty()) |
| 8503 | + throw ValidationError(get_name(), err_msg); |
| 8504 | + ++index; |
| 8505 | + } |
| 8506 | + } else { |
| 8507 | + int index = 0; |
| 8508 | + if(expected_max_ < static_cast<int>(res.size()) && |
| 8509 | + (multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast || |
| 8510 | + multi_option_policy_ == CLI::MultiOptionPolicy::Reverse)) { |
| 8511 | + // create a negative index for the earliest ones |
| 8512 | + index = expected_max_ - static_cast<int>(res.size()); |
| 8513 | + } |
| 8514 | + for(std::string &result : res) { |
| 8515 | + auto err_msg = _validate(result, index); |
| 8516 | + ++index; |
| 8517 | + if(!err_msg.empty()) |
| 8518 | + throw ValidationError(get_name(), err_msg); |
| 8519 | + } |
| 8520 | + } |
| 8521 | + } |
| 8522 | +} |
| 8523 | + |
| 8524 | +CLI11_INLINE void Option::_reduce_results(results_t &out, const results_t &original) const { |
| 8525 | + |
| 8526 | + // max num items expected or length of vector, always at least 1 |
| 8527 | + // Only valid for a trimming policy |
| 8528 | + |
| 8529 | + out.clear(); |
| 8530 | + // Operation depends on the policy setting |
| 8531 | + switch(multi_option_policy_) { |
| 8532 | + case MultiOptionPolicy::TakeAll: |
| 8533 | + break; |
| 8534 | + case MultiOptionPolicy::TakeLast: { |
| 8535 | + // Allow multi-option sizes (including 0) |
| 8536 | + std::size_t trim_size = std::min<std::size_t>( |
| 8537 | + static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size()); |
| 8538 | + if(original.size() != trim_size) { |
| 8539 | + out.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end()); |
| 8540 | + } |
| 8541 | + } break; |
| 8542 | + case MultiOptionPolicy::Reverse: { |
| 8543 | + // Allow multi-option sizes (including 0) |
| 8544 | + std::size_t trim_size = std::min<std::size_t>( |
| 8545 | + static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size()); |
| 8546 | + if(original.size() != trim_size || trim_size > 1) { |
| 8547 | + out.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end()); |
| 8548 | + } |
| 8549 | + std::reverse(out.begin(), out.end()); |
| 8550 | + } break; |
| 8551 | + case MultiOptionPolicy::TakeFirst: { |
| 8552 | + std::size_t trim_size = std::min<std::size_t>( |
| 8553 | + static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size()); |
| 8554 | + if(original.size() != trim_size) { |
| 8555 | + out.assign(original.begin(), original.begin() + static_cast<results_t::difference_type>(trim_size)); |
| 8556 | + } |
| 8557 | + } break; |
| 8558 | + case MultiOptionPolicy::Join: |
| 8559 | + if(results_.size() > 1) { |
| 8560 | + out.push_back(detail::join(original, std::string(1, (delimiter_ == '\0') ? '\n' : delimiter_))); |
| 8561 | + } |
| 8562 | + break; |
| 8563 | + case MultiOptionPolicy::Sum: |
| 8564 | + out.push_back(detail::sum_string_vector(original)); |
| 8565 | + break; |
| 8566 | + case MultiOptionPolicy::Throw: |
| 8567 | + default: { |
| 8568 | + auto num_min = static_cast<std::size_t>(get_items_expected_min()); |
| 8569 | + auto num_max = static_cast<std::size_t>(get_items_expected_max()); |
| 8570 | + if(num_min == 0) { |
| 8571 | + num_min = 1; |
| 8572 | + } |
| 8573 | + if(num_max == 0) { |
| 8574 | + num_max = 1; |
| 8575 | + } |
| 8576 | + if(original.size() < num_min) { |
| 8577 | + throw ArgumentMismatch::AtLeast(get_name(), static_cast<int>(num_min), original.size()); |
| 8578 | + } |
| 8579 | + if(original.size() > num_max) { |
| 8580 | + if(original.size() == 2 && num_max == 1 && original[1] == "%%" && original[0] == "{}") { |
| 8581 | + // this condition is a trap for the following empty indicator check on config files, it may not be used |
| 8582 | + // anymore |
| 8583 | + out = original; // LCOV_EXCL_LINE |
| 8584 | + } else { |
| 8585 | + throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size()); |
| 8586 | + } |
| 8587 | + } |
| 8588 | + break; |
| 8589 | + } |
| 8590 | + } |
| 8591 | + // this check is to allow an empty vector in certain circumstances but not if expected is not zero. |
| 8592 | + // {} is the indicator for an empty container |
| 8593 | + if(out.empty()) { |
| 8594 | + if(original.size() == 1 && original[0] == "{}" && get_items_expected_min() > 0) { |
| 8595 | + out.emplace_back("{}"); |
| 8596 | + out.emplace_back("%%"); |
| 8597 | + } |
| 8598 | + } else if(out.size() == 1 && out[0] == "{}" && get_items_expected_min() > 0) { |
| 8599 | + out.emplace_back("%%"); |
| 8600 | + } |
| 8601 | +} |
| 8602 | + |
| 8603 | +CLI11_INLINE std::string Option::_validate(std::string &result, int index) const { |
| 8604 | + std::string err_msg; |
| 8605 | + if(result.empty() && expected_min_ == 0) { |
| 8606 | + // an empty with nothing expected is allowed |
| 8607 | + return err_msg; |
| 8608 | + } |
| 8609 | + for(const auto &vali : validators_) { |
| 8610 | + auto v = vali->get_application_index(); |
| 8611 | + if(v == -1 || v == index) { |
| 8612 | + try { |
| 8613 | + err_msg = (*vali)(result); |
| 8614 | + } catch(const ValidationError &err) { |
| 8615 | + err_msg = err.what(); |
| 8616 | + } |
| 8617 | + if(!err_msg.empty()) |
| 8618 | + break; |
| 8619 | + } |
| 8620 | + } |
| 8621 | + |
| 8622 | + return err_msg; |
| 8623 | +} |
| 8624 | + |
| 8625 | +CLI11_INLINE int Option::_add_result(std::string &&result, std::vector<std::string> &res) const { |
| 8626 | + int result_count = 0; |
| 8627 | + |
| 8628 | + // Handle the vector escape possibility all characters duplicated and starting with [[ ending with ]] |
| 8629 | + // this is always a single result |
| 8630 | + if(result.size() >= 4 && result[0] == '[' && result[1] == '[' && result.back() == ']' && |
| 8631 | + (*(result.end() - 2) == ']')) { |
| 8632 | + // this is an escape clause for odd strings |
| 8633 | + std::string nstrs{'['}; |
| 8634 | + bool duplicated{true}; |
| 8635 | + for(std::size_t ii = 2; ii < result.size() - 2; ii += 2) { |
| 8636 | + if(result[ii] == result[ii + 1]) { |
| 8637 | + nstrs.push_back(result[ii]); |
| 8638 | + } else { |
| 8639 | + duplicated = false; |
| 8640 | + break; |
| 8641 | + } |
| 8642 | + } |
| 8643 | + if(duplicated) { |
| 8644 | + nstrs.push_back(']'); |
| 8645 | + res.push_back(std::move(nstrs)); |
| 8646 | + ++result_count; |
| 8647 | + return result_count; |
| 8648 | + } |
| 8649 | + } |
| 8650 | + |
| 8651 | + if((allow_extra_args_ || get_expected_max() > 1 || get_type_size() > 1) && !result.empty() && |
| 8652 | + result.front() == '[' && |
| 8653 | + result.back() == ']') { // this is now a vector string likely from the default or user entry |
| 8654 | + |
| 8655 | + result.pop_back(); |
| 8656 | + result.erase(result.begin()); |
| 8657 | + bool skipSection{false}; |
| 8658 | + for(auto &var : CLI::detail::split_up(result, ',')) { |
| 8659 | + if(!var.empty()) { |
| 8660 | + result_count += _add_result(std::move(var), res); |
| 8661 | + } |
| 8662 | + } |
| 8663 | + if(!skipSection) { |
| 8664 | + return result_count; |
| 8665 | + } |
| 8666 | + } |
| 8667 | + if(delimiter_ == '\0') { |
| 8668 | + res.push_back(std::move(result)); |
| 8669 | + ++result_count; |
| 8670 | + } else { |
| 8671 | + if((result.find_first_of(delimiter_) != std::string::npos)) { |
| 8672 | + for(const auto &var : CLI::detail::split(result, delimiter_)) { |
| 8673 | + if(!var.empty()) { |
| 8674 | + res.push_back(var); |
| 8675 | + ++result_count; |
| 8676 | + } |
| 8677 | + } |
| 8678 | + } else { |
| 8679 | + res.push_back(std::move(result)); |
| 8680 | + ++result_count; |
| 8681 | + } |
| 8682 | + } |
| 8683 | + return result_count; |
| 8684 | +} |
| 8685 | + |
| 8686 | + |
| 8687 | + |
| 8688 | +#ifndef CLI11_PARSE |
| 8689 | +#define CLI11_PARSE(app, ...) \ |
| 8690 | + try { \ |
| 8691 | + (app).parse(__VA_ARGS__); \ |
| 8692 | + } catch(const CLI::ParseError &e) { \ |
| 8693 | + return (app).exit(e); \ |
| 8694 | + } |
| 8695 | +#endif |
| 8696 | + |
| 8697 | +namespace detail { |
| 8698 | +enum class Classifier : std::uint8_t { |
| 8699 | + NONE, |
| 8700 | + POSITIONAL_MARK, |
| 8701 | + SHORT, |
| 8702 | + LONG, |
| 8703 | + WINDOWS_STYLE, |
| 8704 | + SUBCOMMAND, |
| 8705 | + SUBCOMMAND_TERMINATOR |
| 8706 | +}; |
| 8707 | +struct AppFriend; |
| 8708 | +} // namespace detail |
| 8709 | + |
| 8710 | +namespace FailureMessage { |
| 8711 | +/// Printout a clean, simple message on error (the default in CLI11 1.5+) |
| 8712 | +CLI11_INLINE std::string simple(const App *app, const Error &e); |
| 8713 | + |
| 8714 | +/// Printout the full help string on error (if this fn is set, the old default for CLI11) |
| 8715 | +CLI11_INLINE std::string help(const App *app, const Error &e); |
| 8716 | +} // namespace FailureMessage |
| 8717 | + |
| 8718 | +/// enumeration of modes of how to deal with command line extras |
| 8719 | +enum class ExtrasMode : std::uint8_t { |
| 8720 | + Error = 0, |
| 8721 | + ErrorImmediately, |
| 8722 | + Ignore, |
| 8723 | + AssumeSingleArgument, |
| 8724 | + AssumeMultipleArguments, |
| 8725 | + Capture |
| 8726 | +}; |
| 8727 | + |
| 8728 | +/// enumeration of modes of how to deal with extras in config files |
| 8729 | +enum class ConfigExtrasMode : std::uint8_t { Error = 0, Ignore, IgnoreAll, Capture }; |
| 8730 | + |
| 8731 | +/// @brief enumeration of modes of how to deal with extras in config files |
| 8732 | +enum class config_extras_mode : std::uint8_t { error = 0, ignore, ignore_all, capture }; |
| 8733 | + |
| 8734 | +/// @brief enumeration of prefix command modes, separator requires that the first extra argument be a "--", other |
| 8735 | +/// unrecognized arguments will cause an error. on allows the first extra to trigger prefix mode regardless of other |
| 8736 | +/// recognized options |
| 8737 | +enum class PrefixCommandMode : std::uint8_t { Off = 0, SeparatorOnly = 1, On = 2 }; |
| 8738 | + |
| 8739 | +class App; |
| 8740 | + |
| 8741 | +using App_p = std::shared_ptr<App>; |
| 8742 | + |
| 8743 | +namespace detail { |
| 8744 | +/// helper functions for adding in appropriate flag modifiers for add_flag |
| 8745 | + |
| 8746 | +template <typename T, enable_if_t<!std::is_integral<T>::value || (sizeof(T) <= 1U), detail::enabler> = detail::dummy> |
| 8747 | +Option *default_flag_modifiers(Option *opt) { |
| 8748 | + return opt->always_capture_default(); |
| 8749 | +} |
| 8750 | + |
| 8751 | +/// summing modifiers |
| 8752 | +template <typename T, enable_if_t<std::is_integral<T>::value && (sizeof(T) > 1U), detail::enabler> = detail::dummy> |
| 8753 | +Option *default_flag_modifiers(Option *opt) { |
| 8754 | + return opt->multi_option_policy(MultiOptionPolicy::Sum)->default_str("0")->force_callback(); |
| 8755 | +} |
| 8756 | + |
| 8757 | +} // namespace detail |
| 8758 | + |
| 8759 | +class Option_group; |
| 8760 | +/// Creates a command line program, with very few defaults. |
| 8761 | +/** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated |
| 8762 | + * add_option methods make it easy to prepare options. Remember to call `.start` before starting your |
| 8763 | + * program, so that the options can be evaluated and the help option doesn't accidentally run your program. */ |
| 8764 | +class App { |
| 8765 | + friend Option; |
| 8766 | + friend detail::AppFriend; |
| 8767 | + |
| 8768 | + protected: |
| 8769 | + // This library follows the Google style guide for member names ending in underscores |
| 8770 | + |
| 8771 | + /// @name Basics |
| 8772 | + ///@{ |
| 8773 | + |
| 8774 | + /// Subcommand name or program name (from parser if name is empty) |
| 8775 | + std::string name_{}; |
| 8776 | + |
| 8777 | + /// Description of the current program/subcommand |
| 8778 | + std::string description_{}; |
| 8779 | + |
| 8780 | + /// If true, allow extra arguments (ie, don't throw an error). INHERITABLE |
| 8781 | + ExtrasMode allow_extras_{ExtrasMode::Error}; |
| 8782 | + |
| 8783 | + /// If ignore, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE |
| 8784 | + /// if error, error on an extra argument, and if capture feed it to the app |
| 8785 | + ConfigExtrasMode allow_config_extras_{ConfigExtrasMode::Ignore}; |
| 8786 | + |
| 8787 | + /// If true, cease processing on an unrecognized option (implies allow_extras) INHERITABLE |
| 8788 | + PrefixCommandMode prefix_command_{PrefixCommandMode::Off}; |
| 8789 | + |
| 8790 | + /// If set to true the name was automatically generated from the command line vs a user set name |
| 8791 | + bool has_automatic_name_{false}; |
| 8792 | + |
| 8793 | + /// If set to true the subcommand is required to be processed and used, ignored for main app |
| 8794 | + bool required_{false}; |
| 8795 | + |
| 8796 | + /// If set to true the subcommand is disabled and cannot be used, ignored for main app |
| 8797 | + bool disabled_{false}; |
| 8798 | + |
| 8799 | + /// Flag indicating that the pre_parse_callback has been triggered |
| 8800 | + bool pre_parse_called_{false}; |
| 8801 | + |
| 8802 | + /// Flag indicating that the callback for the subcommand should be executed immediately on parse completion which is |
| 8803 | + /// before help or ini files are processed. INHERITABLE |
| 8804 | + bool immediate_callback_{false}; |
| 8805 | + |
| 8806 | + /// This is a function that runs prior to the start of parsing |
| 8807 | + std::function<void(std::size_t)> pre_parse_callback_{}; |
| 8808 | + |
| 8809 | + /// This is a function that runs when parsing has finished. |
| 8810 | + std::function<void()> parse_complete_callback_{}; |
| 8811 | + |
| 8812 | + /// This is a function that runs when all processing has completed |
| 8813 | + std::function<void()> final_callback_{}; |
| 8814 | + |
| 8815 | + ///@} |
| 8816 | + /// @name Options |
| 8817 | + ///@{ |
| 8818 | + |
| 8819 | + /// The default values for options, customizable and changeable INHERITABLE |
| 8820 | + OptionDefaults option_defaults_{}; |
| 8821 | + |
| 8822 | + /// The list of options, stored locally |
| 8823 | + std::vector<Option_p> options_{}; |
| 8824 | + |
| 8825 | + ///@} |
| 8826 | + /// @name Help |
| 8827 | + ///@{ |
| 8828 | + |
| 8829 | + /// Usage to put after program/subcommand description in the help output INHERITABLE |
| 8830 | + std::string usage_{}; |
| 8831 | + |
| 8832 | + /// This is a function that generates a usage to put after program/subcommand description in help output |
| 8833 | + std::function<std::string()> usage_callback_{}; |
| 8834 | + |
| 8835 | + /// Footer to put after all options in the help output INHERITABLE |
| 8836 | + std::string footer_{}; |
| 8837 | + |
| 8838 | + /// This is a function that generates a footer to put after all other options in help output |
| 8839 | + std::function<std::string()> footer_callback_{}; |
| 8840 | + |
| 8841 | + /// A pointer to the help flag if there is one INHERITABLE |
| 8842 | + Option *help_ptr_{nullptr}; |
| 8843 | + |
| 8844 | + /// A pointer to the help all flag if there is one INHERITABLE |
| 8845 | + Option *help_all_ptr_{nullptr}; |
| 8846 | + |
| 8847 | + /// A pointer to a version flag if there is one |
| 8848 | + Option *version_ptr_{nullptr}; |
| 8849 | + |
| 8850 | + /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) |
| 8851 | + std::shared_ptr<FormatterBase> formatter_{new Formatter()}; |
| 8852 | + |
| 8853 | + /// The error message printing function INHERITABLE |
| 8854 | + std::function<std::string(const App *, const Error &e)> failure_message_{FailureMessage::simple}; |
| 8855 | + |
| 8856 | + ///@} |
| 8857 | + /// @name Parsing |
| 8858 | + ///@{ |
| 8859 | + |
| 8860 | + using missing_t = std::vector<std::pair<detail::Classifier, std::string>>; |
| 8861 | + |
| 8862 | + /// Pair of classifier, string for missing options. (extra detail is removed on returning from parse) |
| 8863 | + /// |
| 8864 | + /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator. |
| 8865 | + missing_t missing_{}; |
| 8866 | + |
| 8867 | + /// This is a list of pointers to options with the original parse order |
| 8868 | + std::vector<Option *> parse_order_{}; |
| 8869 | + |
| 8870 | + /// This is a list of the subcommands collected, in order |
| 8871 | + std::vector<App *> parsed_subcommands_{}; |
| 8872 | + |
| 8873 | + /// this is a list of subcommands that are exclusionary to this one |
| 8874 | + std::set<App *> exclude_subcommands_{}; |
| 8875 | + |
| 8876 | + /// This is a list of options which are exclusionary to this App, if the options were used this subcommand should |
| 8877 | + /// not be |
| 8878 | + std::set<Option *> exclude_options_{}; |
| 8879 | + |
| 8880 | + /// this is a list of subcommands or option groups that are required by this one, the list is not mutual, the |
| 8881 | + /// listed subcommands do not require this one |
| 8882 | + std::set<App *> need_subcommands_{}; |
| 8883 | + |
| 8884 | + /// This is a list of options which are required by this app, the list is not mutual, listed options do not need the |
| 8885 | + /// subcommand not be |
| 8886 | + std::set<Option *> need_options_{}; |
| 8887 | + |
| 8888 | + ///@} |
| 8889 | + /// @name Subcommands |
| 8890 | + ///@{ |
| 8891 | + |
| 8892 | + /// Storage for subcommand list |
| 8893 | + std::vector<App_p> subcommands_{}; |
| 8894 | + |
| 8895 | + /// If true, the program name is not case-sensitive INHERITABLE |
| 8896 | + bool ignore_case_{false}; |
| 8897 | + |
| 8898 | + /// If true, the program should ignore underscores INHERITABLE |
| 8899 | + bool ignore_underscore_{false}; |
| 8900 | + |
| 8901 | + /// Allow options or other arguments to fallthrough, so that parent commands can collect options after subcommand. |
| 8902 | + /// INHERITABLE |
| 8903 | + bool fallthrough_{false}; |
| 8904 | + |
| 8905 | + /// Allow subcommands to fallthrough, so that parent commands can trigger other subcommands after subcommand. |
| 8906 | + bool subcommand_fallthrough_{true}; |
| 8907 | + |
| 8908 | + /// Allow '/' for options for Windows like options. Defaults to true on Windows, false otherwise. INHERITABLE |
| 8909 | + bool allow_windows_style_options_{ |
| 8910 | +#ifdef _WIN32 |
| 8911 | + true |
| 8912 | +#else |
| 8913 | + false |
| 8914 | +#endif |
| 8915 | + }; |
| 8916 | + /// specify that positional arguments come at the end of the argument sequence not inheritable |
| 8917 | + bool positionals_at_end_{false}; |
| 8918 | + |
| 8919 | + enum class startup_mode : std::uint8_t { stable, enabled, disabled }; |
| 8920 | + /// specify the startup mode for the app |
| 8921 | + /// stable=no change, enabled= startup enabled, disabled=startup disabled |
| 8922 | + startup_mode default_startup{startup_mode::stable}; |
| 8923 | + |
| 8924 | + /// if set to true the subcommand can be triggered via configuration files INHERITABLE |
| 8925 | + bool configurable_{false}; |
| 8926 | + |
| 8927 | + /// If set to true positional options are validated before assigning INHERITABLE |
| 8928 | + bool validate_positionals_{false}; |
| 8929 | + |
| 8930 | + /// If set to true optional vector arguments are validated before assigning INHERITABLE |
| 8931 | + bool validate_optional_arguments_{false}; |
| 8932 | + |
| 8933 | + /// indicator that the subcommand is silent and won't show up in subcommands list |
| 8934 | + /// This is potentially useful as a modifier subcommand |
| 8935 | + bool silent_{false}; |
| 8936 | + |
| 8937 | + /// indicator that the subcommand should allow non-standard option arguments, such as -single_dash_flag |
| 8938 | + bool allow_non_standard_options_{false}; |
| 8939 | + |
| 8940 | + /// indicator to allow subcommands to match with prefix matching |
| 8941 | + bool allow_prefix_matching_{false}; |
| 8942 | + |
| 8943 | + /// Counts the number of times this command/subcommand was parsed |
| 8944 | + std::uint32_t parsed_{0U}; |
| 8945 | + |
| 8946 | + /// Minimum required subcommands (not inheritable!) |
| 8947 | + std::size_t require_subcommand_min_{0}; |
| 8948 | + |
| 8949 | + /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE |
| 8950 | + std::size_t require_subcommand_max_{0}; |
| 8951 | + |
| 8952 | + /// Minimum required options (not inheritable!) |
| 8953 | + std::size_t require_option_min_{0}; |
| 8954 | + |
| 8955 | + /// Max number of options allowed. 0 is unlimited (not inheritable) |
| 8956 | + std::size_t require_option_max_{0}; |
| 8957 | + |
| 8958 | + /// A pointer to the parent if this is a subcommand |
| 8959 | + App *parent_{nullptr}; |
| 8960 | + |
| 8961 | + /// The group membership INHERITABLE |
| 8962 | + std::string group_{"SUBCOMMANDS"}; |
| 8963 | + |
| 8964 | + /// Alias names for the subcommand |
| 8965 | + std::vector<std::string> aliases_{}; |
| 8966 | + |
| 8967 | + ///@} |
| 8968 | + /// @name Config |
| 8969 | + ///@{ |
| 8970 | + |
| 8971 | + /// Pointer to the config option |
| 8972 | + Option *config_ptr_{nullptr}; |
| 8973 | + |
| 8974 | + /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) |
| 8975 | + std::shared_ptr<Config> config_formatter_{new ConfigTOML()}; |
| 8976 | + |
| 8977 | + ///@} |
| 8978 | + |
| 8979 | +#ifdef _WIN32 |
| 8980 | + /// When normalizing argv to UTF-8 on Windows, this is the storage for normalized args. |
| 8981 | + std::vector<std::string> normalized_argv_{}; |
| 8982 | + |
| 8983 | + /// When normalizing argv to UTF-8 on Windows, this is the `char**` value returned to the user. |
| 8984 | + std::vector<char *> normalized_argv_view_{}; |
| 8985 | +#endif |
| 8986 | + |
| 8987 | + /// Special private constructor for subcommand |
| 8988 | + App(std::string app_description, std::string app_name, App *parent); |
| 8989 | + |
| 8990 | + public: |
| 8991 | + /// @name Basic |
| 8992 | + ///@{ |
| 8993 | + |
| 8994 | + /// Create a new program. Pass in the same arguments as main(), along with a help string. |
| 8995 | + explicit App(std::string app_description = "", std::string app_name = "") |
| 8996 | + : App(app_description, app_name, nullptr) { |
| 8997 | + set_help_flag("-h,--help", "Print this help message and exit"); |
| 8998 | + } |
| 8999 | + |
| 9000 | + App(const App &) = delete; |
| 9001 | + App &operator=(const App &) = delete; |
| 9002 | + |
| 9003 | + /// virtual destructor |
| 9004 | + virtual ~App() = default; |
| 9005 | + |
| 9006 | + /// Convert the contents of argv to UTF-8. Only does something on Windows, does nothing elsewhere. |
| 9007 | + CLI11_NODISCARD char **ensure_utf8(char **argv); |
| 9008 | + |
| 9009 | + /// Set a callback for execution when all parsing and processing has completed |
| 9010 | + /// |
| 9011 | + /// Due to a bug in c++11, |
| 9012 | + /// it is not possible to overload on std::function (fixed in c++14 |
| 9013 | + /// and backported to c++11 on newer compilers). Use capture by reference |
| 9014 | + /// to get a pointer to App if needed. |
| 9015 | + App *callback(std::function<void()> app_callback) { |
| 9016 | + if(immediate_callback_) { |
| 9017 | + parse_complete_callback_ = std::move(app_callback); |
| 9018 | + } else { |
| 9019 | + final_callback_ = std::move(app_callback); |
| 9020 | + } |
| 9021 | + return this; |
| 9022 | + } |
| 9023 | + |
| 9024 | + /// Set a callback for execution when all parsing and processing has completed |
| 9025 | + /// aliased as callback |
| 9026 | + App *final_callback(std::function<void()> app_callback) { |
| 9027 | + final_callback_ = std::move(app_callback); |
| 9028 | + return this; |
| 9029 | + } |
| 9030 | + |
| 9031 | + /// Set a callback to execute when parsing has completed for the app |
| 9032 | + /// |
| 9033 | + App *parse_complete_callback(std::function<void()> pc_callback) { |
| 9034 | + parse_complete_callback_ = std::move(pc_callback); |
| 9035 | + return this; |
| 9036 | + } |
| 9037 | + |
| 9038 | + /// Set a callback to execute prior to parsing. |
| 9039 | + /// |
| 9040 | + App *preparse_callback(std::function<void(std::size_t)> pp_callback) { |
| 9041 | + pre_parse_callback_ = std::move(pp_callback); |
| 9042 | + return this; |
| 9043 | + } |
| 9044 | + |
| 9045 | + /// Set a name for the app (empty will use parser to set the name) |
| 9046 | + App *name(std::string app_name = ""); |
| 9047 | + |
| 9048 | + /// Set an alias for the app |
| 9049 | + App *alias(std::string app_name); |
| 9050 | + |
| 9051 | + /// Remove the error when extras are left over on the command line. |
| 9052 | + App *allow_extras(bool allow = true) { |
| 9053 | + allow_extras_ = allow ? ExtrasMode::Capture : ExtrasMode::Error; |
| 9054 | + return this; |
| 9055 | + } |
| 9056 | + |
| 9057 | + /// Remove the error when extras are left over on the command line. |
| 9058 | + App *allow_extras(ExtrasMode allow) { |
| 9059 | + allow_extras_ = allow; |
| 9060 | + return this; |
| 9061 | + } |
| 9062 | + |
| 9063 | + /// Remove the error when extras are left over on the command line. |
| 9064 | + App *required(bool require = true) { |
| 9065 | + required_ = require; |
| 9066 | + return this; |
| 9067 | + } |
| 9068 | + |
| 9069 | + /// Disable the subcommand or option group |
| 9070 | + App *disabled(bool disable = true) { |
| 9071 | + disabled_ = disable; |
| 9072 | + return this; |
| 9073 | + } |
| 9074 | + |
| 9075 | + /// silence the subcommand from showing up in the processed list |
| 9076 | + App *silent(bool silence = true) { |
| 9077 | + silent_ = silence; |
| 9078 | + return this; |
| 9079 | + } |
| 9080 | + |
| 9081 | + /// allow non standard option names |
| 9082 | + App *allow_non_standard_option_names(bool allowed = true) { |
| 9083 | + allow_non_standard_options_ = allowed; |
| 9084 | + return this; |
| 9085 | + } |
| 9086 | + |
| 9087 | + /// allow prefix matching for subcommands |
| 9088 | + App *allow_subcommand_prefix_matching(bool allowed = true) { |
| 9089 | + allow_prefix_matching_ = allowed; |
| 9090 | + return this; |
| 9091 | + } |
| 9092 | + /// Set the subcommand to be disabled by default, so on clear(), at the start of each parse it is disabled |
| 9093 | + App *disabled_by_default(bool disable = true) { |
| 9094 | + if(disable) { |
| 9095 | + default_startup = startup_mode::disabled; |
| 9096 | + } else { |
| 9097 | + default_startup = (default_startup == startup_mode::enabled) ? startup_mode::enabled : startup_mode::stable; |
| 9098 | + } |
| 9099 | + return this; |
| 9100 | + } |
| 9101 | + |
| 9102 | + /// Set the subcommand to be enabled by default, so on clear(), at the start of each parse it is enabled (not |
| 9103 | + /// disabled) |
| 9104 | + App *enabled_by_default(bool enable = true) { |
| 9105 | + if(enable) { |
| 9106 | + default_startup = startup_mode::enabled; |
| 9107 | + } else { |
| 9108 | + default_startup = |
| 9109 | + (default_startup == startup_mode::disabled) ? startup_mode::disabled : startup_mode::stable; |
| 9110 | + } |
| 9111 | + return this; |
| 9112 | + } |
| 9113 | + |
| 9114 | + /// Set the subcommand callback to be executed immediately on subcommand completion |
| 9115 | + App *immediate_callback(bool immediate = true); |
| 9116 | + |
| 9117 | + /// Set the subcommand to validate positional arguments before assigning |
| 9118 | + App *validate_positionals(bool validate = true) { |
| 9119 | + validate_positionals_ = validate; |
| 9120 | + return this; |
| 9121 | + } |
| 9122 | + |
| 9123 | + /// Set the subcommand to validate optional vector arguments before assigning |
| 9124 | + App *validate_optional_arguments(bool validate = true) { |
| 9125 | + validate_optional_arguments_ = validate; |
| 9126 | + return this; |
| 9127 | + } |
| 9128 | + |
| 9129 | + /// ignore extras in config files |
| 9130 | + App *allow_config_extras(bool allow = true) { |
| 9131 | + if(allow) { |
| 9132 | + allow_config_extras_ = ConfigExtrasMode::Capture; |
| 9133 | + allow_extras_ = ExtrasMode::Capture; |
| 9134 | + } else { |
| 9135 | + allow_config_extras_ = ConfigExtrasMode::Error; |
| 9136 | + } |
| 9137 | + return this; |
| 9138 | + } |
| 9139 | + |
| 9140 | + /// ignore extras in config files |
| 9141 | + App *allow_config_extras(config_extras_mode mode) { |
| 9142 | + allow_config_extras_ = static_cast<ConfigExtrasMode>(mode); |
| 9143 | + return this; |
| 9144 | + } |
| 9145 | + |
| 9146 | + /// ignore extras in config files |
| 9147 | + App *allow_config_extras(ConfigExtrasMode mode) { |
| 9148 | + allow_config_extras_ = mode; |
| 9149 | + return this; |
| 9150 | + } |
| 9151 | + |
| 9152 | + /// Do not parse anything after the first unrecognized option (if true) all remaining arguments are stored in |
| 9153 | + /// remaining args |
| 9154 | + App *prefix_command(bool is_prefix = true) { |
| 9155 | + prefix_command_ = is_prefix ? PrefixCommandMode::On : PrefixCommandMode::Off; |
| 9156 | + return this; |
| 9157 | + } |
| 9158 | + |
| 9159 | + /// Do not parse anything after the first unrecognized option (if true) all remaining arguments are stored in |
| 9160 | + /// remaining args |
| 9161 | + App *prefix_command(PrefixCommandMode mode) { |
| 9162 | + prefix_command_ = mode; |
| 9163 | + return this; |
| 9164 | + } |
| 9165 | + |
| 9166 | + /// Ignore case. Subcommands inherit value. |
| 9167 | + App *ignore_case(bool value = true); |
| 9168 | + |
| 9169 | + /// Allow windows style options, such as `/opt`. First matching short or long name used. Subcommands inherit |
| 9170 | + /// value. |
| 9171 | + App *allow_windows_style_options(bool value = true) { |
| 9172 | + allow_windows_style_options_ = value; |
| 9173 | + return this; |
| 9174 | + } |
| 9175 | + |
| 9176 | + /// Specify that the positional arguments are only at the end of the sequence |
| 9177 | + App *positionals_at_end(bool value = true) { |
| 9178 | + positionals_at_end_ = value; |
| 9179 | + return this; |
| 9180 | + } |
| 9181 | + |
| 9182 | + /// Specify that the subcommand can be triggered by a config file |
| 9183 | + App *configurable(bool value = true) { |
| 9184 | + configurable_ = value; |
| 9185 | + return this; |
| 9186 | + } |
| 9187 | + |
| 9188 | + /// Ignore underscore. Subcommands inherit value. |
| 9189 | + App *ignore_underscore(bool value = true); |
| 9190 | + |
| 9191 | + /// Set the help formatter |
| 9192 | + App *formatter(std::shared_ptr<FormatterBase> fmt) { |
| 9193 | + formatter_ = fmt; |
| 9194 | + return this; |
| 9195 | + } |
| 9196 | + |
| 9197 | + /// Set the help formatter |
| 9198 | + App *formatter_fn(std::function<std::string(const App *, std::string, AppFormatMode)> fmt) { |
| 9199 | + formatter_ = std::make_shared<FormatterLambda>(fmt); |
| 9200 | + return this; |
| 9201 | + } |
| 9202 | + |
| 9203 | + /// Set the config formatter |
| 9204 | + App *config_formatter(std::shared_ptr<Config> fmt) { |
| 9205 | + config_formatter_ = fmt; |
| 9206 | + return this; |
| 9207 | + } |
| 9208 | + |
| 9209 | + /// Check to see if this subcommand was parsed, true only if received on command line. |
| 9210 | + CLI11_NODISCARD bool parsed() const { return parsed_ > 0; } |
| 9211 | + |
| 9212 | + /// Get the OptionDefault object, to set option defaults |
| 9213 | + OptionDefaults *option_defaults() { return &option_defaults_; } |
| 9214 | + |
| 9215 | + ///@} |
| 9216 | + /// @name Adding options |
| 9217 | + ///@{ |
| 9218 | + |
| 9219 | + /// Add an option, will automatically understand the type for common types. |
| 9220 | + /// |
| 9221 | + /// To use, create a variable with the expected type, and pass it in after the name. |
| 9222 | + /// After start is called, you can use count to see if the value was passed, and |
| 9223 | + /// the value will be initialized properly. Numbers, vectors, and strings are supported. |
| 9224 | + /// |
| 9225 | + /// ->required(), ->default, and the validators are options, |
| 9226 | + /// The positional options take an optional number of arguments. |
| 9227 | + /// |
| 9228 | + /// For example, |
| 9229 | + /// |
| 9230 | + /// std::string filename; |
| 9231 | + /// program.add_option("filename", filename, "description of filename"); |
| 9232 | + /// |
| 9233 | + Option *add_option(std::string option_name, |
| 9234 | + callback_t option_callback, |
| 9235 | + std::string option_description = "", |
| 9236 | + bool defaulted = false, |
| 9237 | + std::function<std::string()> func = {}); |
| 9238 | + |
| 9239 | + /// Add option for assigning to a variable |
| 9240 | + template <typename AssignTo, |
| 9241 | + typename ConvertTo = AssignTo, |
| 9242 | + enable_if_t<!std::is_const<ConvertTo>::value, detail::enabler> = detail::dummy> |
| 9243 | + Option *add_option(std::string option_name, |
| 9244 | + AssignTo &variable, ///< The variable to set |
| 9245 | + std::string option_description = "") { |
| 9246 | + |
| 9247 | + auto fun = [&variable](const CLI::results_t &res) { // comment for spacing |
| 9248 | + return detail::lexical_conversion<AssignTo, ConvertTo>(res, variable); |
| 9249 | + }; |
| 9250 | + |
| 9251 | + Option *opt = add_option(option_name, fun, option_description, false, [&variable]() { |
| 9252 | + return CLI::detail::checked_to_string<AssignTo, ConvertTo>(variable); |
| 9253 | + }); |
| 9254 | + opt->type_name(detail::type_name<ConvertTo>()); |
| 9255 | + // these must be actual lvalues since (std::max) sometimes is defined in terms of references and references |
| 9256 | + // to structs used in the evaluation can be temporary so that would cause issues. |
| 9257 | + auto Tcount = detail::type_count<AssignTo>::value; |
| 9258 | + auto XCcount = detail::type_count<ConvertTo>::value; |
| 9259 | + opt->type_size(detail::type_count_min<ConvertTo>::value, (std::max)(Tcount, XCcount)); |
| 9260 | + opt->expected(detail::expected_count<ConvertTo>::value); |
| 9261 | + opt->run_callback_for_default(); |
| 9262 | + return opt; |
| 9263 | + } |
| 9264 | + |
| 9265 | + /// Add option for assigning to a variable |
| 9266 | + template <typename AssignTo, enable_if_t<!std::is_const<AssignTo>::value, detail::enabler> = detail::dummy> |
| 9267 | + Option *add_option_no_stream(std::string option_name, |
| 9268 | + AssignTo &variable, ///< The variable to set |
| 9269 | + std::string option_description = "") { |
| 9270 | + |
| 9271 | + auto fun = [&variable](const CLI::results_t &res) { // comment for spacing |
| 9272 | + return detail::lexical_conversion<AssignTo, AssignTo>(res, variable); |
| 9273 | + }; |
| 9274 | + |
| 9275 | + Option *opt = add_option(option_name, fun, option_description, false, []() { return std::string{}; }); |
| 9276 | + opt->type_name(detail::type_name<AssignTo>()); |
| 9277 | + opt->type_size(detail::type_count_min<AssignTo>::value, detail::type_count<AssignTo>::value); |
| 9278 | + opt->expected(detail::expected_count<AssignTo>::value); |
| 9279 | + opt->run_callback_for_default(); |
| 9280 | + return opt; |
| 9281 | + } |
| 9282 | + |
| 9283 | + /// Add option for a callback of a specific type |
| 9284 | + template <typename ArgType> |
| 9285 | + Option *add_option_function(std::string option_name, |
| 9286 | + const std::function<void(const ArgType &)> &func, ///< the callback to execute |
| 9287 | + std::string option_description = "") { |
| 9288 | + |
| 9289 | + auto fun = [func](const CLI::results_t &res) { |
| 9290 | + ArgType variable; |
| 9291 | + bool result = detail::lexical_conversion<ArgType, ArgType>(res, variable); |
| 9292 | + if(result) { |
| 9293 | + func(variable); |
| 9294 | + } |
| 9295 | + return result; |
| 9296 | + }; |
| 9297 | + |
| 9298 | + Option *opt = add_option(option_name, std::move(fun), option_description, false); |
| 9299 | + opt->type_name(detail::type_name<ArgType>()); |
| 9300 | + opt->type_size(detail::type_count_min<ArgType>::value, detail::type_count<ArgType>::value); |
| 9301 | + opt->expected(detail::expected_count<ArgType>::value); |
| 9302 | + return opt; |
| 9303 | + } |
| 9304 | + |
| 9305 | + /// Add option with no description or variable assignment |
| 9306 | + Option *add_option(std::string option_name) { |
| 9307 | + return add_option(option_name, CLI::callback_t{}, std::string{}, false); |
| 9308 | + } |
| 9309 | + |
| 9310 | + /// Add option with description but with no variable assignment or callback |
| 9311 | + template <typename T, |
| 9312 | + enable_if_t<std::is_const<T>::value && std::is_constructible<std::string, T>::value, detail::enabler> = |
| 9313 | + detail::dummy> |
| 9314 | + Option *add_option(std::string option_name, T &option_description) { |
| 9315 | + return add_option(option_name, CLI::callback_t(), option_description, false); |
| 9316 | + } |
| 9317 | + |
| 9318 | + /// Set a help flag, replace the existing one if present |
| 9319 | + Option *set_help_flag(std::string flag_name = "", const std::string &help_description = ""); |
| 9320 | + |
| 9321 | + /// Set a help all flag, replaced the existing one if present |
| 9322 | + Option *set_help_all_flag(std::string help_name = "", const std::string &help_description = ""); |
| 9323 | + |
| 9324 | + /// Set a version flag and version display string, replace the existing one if present |
| 9325 | + Option *set_version_flag(std::string flag_name = "", |
| 9326 | + const std::string &versionString = "", |
| 9327 | + const std::string &version_help = "Display program version information and exit"); |
| 9328 | + |
| 9329 | + /// Generate the version string through a callback function |
| 9330 | + Option *set_version_flag(std::string flag_name, |
| 9331 | + std::function<std::string()> vfunc, |
| 9332 | + const std::string &version_help = "Display program version information and exit"); |
| 9333 | + |
| 9334 | + private: |
| 9335 | + /// Internal function for adding a flag |
| 9336 | + Option *_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description); |
| 9337 | + |
| 9338 | + public: |
| 9339 | + /// Add a flag with no description or variable assignment |
| 9340 | + Option *add_flag(std::string flag_name) { return _add_flag_internal(flag_name, CLI::callback_t(), std::string{}); } |
| 9341 | + |
| 9342 | + /// Add flag with description but with no variable assignment or callback |
| 9343 | + /// takes a constant string or a rvalue reference to a string, if a variable string is passed that variable will be |
| 9344 | + /// assigned the results from the flag |
| 9345 | + template <typename T, |
| 9346 | + enable_if_t<(std::is_const<typename std::remove_reference<T>::type>::value || |
| 9347 | + std::is_rvalue_reference<T &&>::value) && |
| 9348 | + std::is_constructible<std::string, typename std::remove_reference<T>::type>::value, |
| 9349 | + detail::enabler> = detail::dummy> |
| 9350 | + Option *add_flag(std::string flag_name, T &&flag_description) { |
| 9351 | + return _add_flag_internal(flag_name, CLI::callback_t(), std::forward<T>(flag_description)); |
| 9352 | + } |
| 9353 | + |
| 9354 | + /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes |
| 9355 | + /// that can be converted from a string |
| 9356 | + template <typename T, |
| 9357 | + enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value && |
| 9358 | + !std::is_constructible<std::function<void(int)>, T>::value, |
| 9359 | + detail::enabler> = detail::dummy> |
| 9360 | + Option *add_flag(std::string flag_name, |
| 9361 | + T &flag_result, ///< A variable holding the flag result |
| 9362 | + std::string flag_description = "") { |
| 9363 | + |
| 9364 | + CLI::callback_t fun = [&flag_result](const CLI::results_t &res) { |
| 9365 | + using CLI::detail::lexical_cast; |
| 9366 | + return lexical_cast(res[0], flag_result); |
| 9367 | + }; |
| 9368 | + auto *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); |
| 9369 | + return detail::default_flag_modifiers<T>(opt); |
| 9370 | + } |
| 9371 | + |
| 9372 | + /// Vector version to capture multiple flags. |
| 9373 | + template <typename T, |
| 9374 | + enable_if_t<!std::is_assignable<std::function<void(std::int64_t)> &, T>::value, detail::enabler> = |
| 9375 | + detail::dummy> |
| 9376 | + Option *add_flag(std::string flag_name, |
| 9377 | + std::vector<T> &flag_results, ///< A vector of values with the flag results |
| 9378 | + std::string flag_description = "") { |
| 9379 | + CLI::callback_t fun = [&flag_results](const CLI::results_t &res) { |
| 9380 | + bool retval = true; |
| 9381 | + for(const auto &elem : res) { |
| 9382 | + using CLI::detail::lexical_cast; |
| 9383 | + flag_results.emplace_back(); |
| 9384 | + retval &= lexical_cast(elem, flag_results.back()); |
| 9385 | + } |
| 9386 | + return retval; |
| 9387 | + }; |
| 9388 | + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)) |
| 9389 | + ->multi_option_policy(MultiOptionPolicy::TakeAll) |
| 9390 | + ->run_callback_for_default(); |
| 9391 | + } |
| 9392 | + |
| 9393 | + /// Add option for callback that is triggered with a true flag and takes no arguments |
| 9394 | + Option *add_flag_callback(std::string flag_name, |
| 9395 | + std::function<void(void)> function, ///< A function to call, void(void) |
| 9396 | + std::string flag_description = ""); |
| 9397 | + |
| 9398 | + /// Add option for callback with an integer value |
| 9399 | + Option *add_flag_function(std::string flag_name, |
| 9400 | + std::function<void(std::int64_t)> function, ///< A function to call, void(int) |
| 9401 | + std::string flag_description = ""); |
| 9402 | + |
| 9403 | +#ifdef CLI11_CPP14 |
| 9404 | + /// Add option for callback (C++14 or better only) |
| 9405 | + Option *add_flag(std::string flag_name, |
| 9406 | + std::function<void(std::int64_t)> function, ///< A function to call, void(std::int64_t) |
| 9407 | + std::string flag_description = "") { |
| 9408 | + return add_flag_function(std::move(flag_name), std::move(function), std::move(flag_description)); |
| 9409 | + } |
| 9410 | +#endif |
| 9411 | + |
| 9412 | + /// Set a configuration ini file option, or clear it if no name passed |
| 9413 | + Option *set_config(std::string option_name = "", |
| 9414 | + std::string default_filename = "", |
| 9415 | + const std::string &help_message = "Read an ini file", |
| 9416 | + bool config_required = false); |
| 9417 | + |
| 9418 | + /// Removes an option from the App. Takes an option pointer. Returns true if found and removed. |
| 9419 | + bool remove_option(Option *opt); |
| 9420 | + |
| 9421 | + /// creates an option group as part of the given app |
| 9422 | + template <typename T = Option_group> |
| 9423 | + T *add_option_group(std::string group_name, std::string group_description = "") { |
| 9424 | + if(!detail::valid_alias_name_string(group_name)) { |
| 9425 | + throw IncorrectConstruction("option group names may not contain newlines or null characters"); |
| 9426 | + } |
| 9427 | + auto option_group = std::make_shared<T>(std::move(group_description), group_name, this); |
| 9428 | + auto *ptr = option_group.get(); |
| 9429 | + // move to App_p for overload resolution on older gcc versions |
| 9430 | + App_p app_ptr = std::static_pointer_cast<App>(option_group); |
| 9431 | + // don't inherit the footer in option groups and clear the help flag by default |
| 9432 | + app_ptr->footer_ = ""; |
| 9433 | + app_ptr->set_help_flag(); |
| 9434 | + add_subcommand(std::move(app_ptr)); |
| 9435 | + return ptr; |
| 9436 | + } |
| 9437 | + |
| 9438 | + ///@} |
| 9439 | + /// @name Subcommands |
| 9440 | + ///@{ |
| 9441 | + |
| 9442 | + /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag |
| 9443 | + App *add_subcommand(std::string subcommand_name = "", std::string subcommand_description = ""); |
| 9444 | + |
| 9445 | + /// Add a previously created app as a subcommand |
| 9446 | + App *add_subcommand(CLI::App_p subcom); |
| 9447 | + |
| 9448 | + /// Removes a subcommand from the App. Takes a subcommand pointer. Returns true if found and removed. |
| 9449 | + bool remove_subcommand(App *subcom); |
| 9450 | + |
| 9451 | + /// Check to see if a subcommand is part of this command (doesn't have to be in command line) |
| 9452 | + /// returns the first subcommand if passed a nullptr |
| 9453 | + App *get_subcommand(const App *subcom) const; |
| 9454 | + |
| 9455 | + /// Check to see if a subcommand is part of this command (text version) |
| 9456 | + CLI11_NODISCARD App *get_subcommand(std::string subcom) const; |
| 9457 | + |
| 9458 | + /// Get a subcommand by name (noexcept non-const version) |
| 9459 | + /// returns null if subcommand doesn't exist |
| 9460 | + CLI11_NODISCARD App *get_subcommand_no_throw(std::string subcom) const noexcept; |
| 9461 | + |
| 9462 | + /// Get a pointer to subcommand by index |
| 9463 | + CLI11_NODISCARD App *get_subcommand(int index = 0) const; |
| 9464 | + |
| 9465 | + /// Check to see if a subcommand is part of this command and get a shared_ptr to it |
| 9466 | + CLI::App_p get_subcommand_ptr(App *subcom) const; |
| 9467 | + |
| 9468 | + /// Check to see if a subcommand is part of this command (text version) |
| 9469 | + CLI11_NODISCARD CLI::App_p get_subcommand_ptr(std::string subcom) const; |
| 9470 | + |
| 9471 | + /// Get an owning pointer to subcommand by index |
| 9472 | + CLI11_NODISCARD CLI::App_p get_subcommand_ptr(int index = 0) const; |
| 9473 | + |
| 9474 | + /// Check to see if an option group is part of this App |
| 9475 | + CLI11_NODISCARD App *get_option_group(std::string group_name) const; |
| 9476 | + |
| 9477 | + /// No argument version of count counts the number of times this subcommand was |
| 9478 | + /// passed in. The main app will return 1. Unnamed subcommands will also return 1 unless |
| 9479 | + /// otherwise modified in a callback |
| 9480 | + CLI11_NODISCARD std::size_t count() const { return parsed_; } |
| 9481 | + |
| 9482 | + /// Get a count of all the arguments processed in options and subcommands, this excludes arguments which were |
| 9483 | + /// treated as extras. |
| 9484 | + CLI11_NODISCARD std::size_t count_all() const; |
| 9485 | + |
| 9486 | + /// Changes the group membership |
| 9487 | + App *group(std::string group_name) { |
| 9488 | + group_ = group_name; |
| 9489 | + return this; |
| 9490 | + } |
| 9491 | + |
| 9492 | + /// The argumentless form of require subcommand requires 1 or more subcommands |
| 9493 | + App *require_subcommand() { |
| 9494 | + require_subcommand_min_ = 1; |
| 9495 | + require_subcommand_max_ = 0; |
| 9496 | + return this; |
| 9497 | + } |
| 9498 | + |
| 9499 | + /// Require a subcommand to be given (does not affect help call) |
| 9500 | + /// The number required can be given. Negative values indicate maximum |
| 9501 | + /// number allowed (0 for any number). Max number inheritable. |
| 9502 | + App *require_subcommand(int value) { |
| 9503 | + if(value < 0) { |
| 9504 | + require_subcommand_min_ = 0; |
| 9505 | + require_subcommand_max_ = static_cast<std::size_t>(-value); |
| 9506 | + } else { |
| 9507 | + require_subcommand_min_ = static_cast<std::size_t>(value); |
| 9508 | + require_subcommand_max_ = static_cast<std::size_t>(value); |
| 9509 | + } |
| 9510 | + return this; |
| 9511 | + } |
| 9512 | + |
| 9513 | + /// Explicitly control the number of subcommands required. Setting 0 |
| 9514 | + /// for the max means unlimited number allowed. Max number inheritable. |
| 9515 | + App *require_subcommand(std::size_t min, std::size_t max) { |
| 9516 | + require_subcommand_min_ = min; |
| 9517 | + require_subcommand_max_ = max; |
| 9518 | + return this; |
| 9519 | + } |
| 9520 | + |
| 9521 | + /// The argumentless form of require option requires 1 or more options be used |
| 9522 | + App *require_option() { |
| 9523 | + require_option_min_ = 1; |
| 9524 | + require_option_max_ = 0; |
| 9525 | + return this; |
| 9526 | + } |
| 9527 | + |
| 9528 | + /// Require an option to be given (does not affect help call) |
| 9529 | + /// The number required can be given. Negative values indicate maximum |
| 9530 | + /// number allowed (0 for any number). |
| 9531 | + App *require_option(int value) { |
| 9532 | + if(value < 0) { |
| 9533 | + require_option_min_ = 0; |
| 9534 | + require_option_max_ = static_cast<std::size_t>(-value); |
| 9535 | + } else { |
| 9536 | + require_option_min_ = static_cast<std::size_t>(value); |
| 9537 | + require_option_max_ = static_cast<std::size_t>(value); |
| 9538 | + } |
| 9539 | + return this; |
| 9540 | + } |
| 9541 | + |
| 9542 | + /// Explicitly control the number of options required. Setting 0 |
| 9543 | + /// for the max means unlimited number allowed. Max number inheritable. |
| 9544 | + App *require_option(std::size_t min, std::size_t max) { |
| 9545 | + require_option_min_ = min; |
| 9546 | + require_option_max_ = max; |
| 9547 | + return this; |
| 9548 | + } |
| 9549 | + |
| 9550 | + /// Set fallthrough, set to true so that options will fallthrough to parent if not recognized in a subcommand |
| 9551 | + /// Default from parent, usually set on parent. |
| 9552 | + App *fallthrough(bool value = true) { |
| 9553 | + fallthrough_ = value; |
| 9554 | + return this; |
| 9555 | + } |
| 9556 | + |
| 9557 | + /// Set subcommand fallthrough, set to true so that subcommands on parents are recognized |
| 9558 | + App *subcommand_fallthrough(bool value = true) { |
| 9559 | + subcommand_fallthrough_ = value; |
| 9560 | + return this; |
| 9561 | + } |
| 9562 | + |
| 9563 | + /// Check to see if this subcommand was parsed, true only if received on command line. |
| 9564 | + /// This allows the subcommand to be directly checked. |
| 9565 | + explicit operator bool() const { return parsed_ > 0; } |
| 9566 | + |
| 9567 | + ///@} |
| 9568 | + /// @name Extras for subclassing |
| 9569 | + ///@{ |
| 9570 | + |
| 9571 | + /// This allows subclasses to inject code before callbacks but after parse. |
| 9572 | + /// |
| 9573 | + /// This does not run if any errors or help is thrown. |
| 9574 | + virtual void pre_callback() {} |
| 9575 | + |
| 9576 | + ///@} |
| 9577 | + /// @name Parsing |
| 9578 | + ///@{ |
| 9579 | + // |
| 9580 | + /// Reset the parsed data |
| 9581 | + void clear(); |
| 9582 | + |
| 9583 | + /// Parses the command line - throws errors. |
| 9584 | + /// This must be called after the options are in but before the rest of the program. |
| 9585 | + void parse(int argc, const char *const *argv); |
| 9586 | + void parse(int argc, const wchar_t *const *argv); |
| 9587 | + |
| 9588 | + private: |
| 9589 | + template <class CharT> void parse_char_t(int argc, const CharT *const *argv); |
| 9590 | + |
| 9591 | + public: |
| 9592 | + /// Parse a single string as if it contained command line arguments. |
| 9593 | + /// This function splits the string into arguments then calls parse(std::vector<std::string> &) |
| 9594 | + /// the function takes an optional boolean argument specifying if the programName is included in the string to |
| 9595 | + /// process |
| 9596 | + void parse(std::string commandline, bool program_name_included = false); |
| 9597 | + void parse(std::wstring commandline, bool program_name_included = false); |
| 9598 | + |
| 9599 | + /// The real work is done here. Expects a reversed vector. |
| 9600 | + /// Changes the vector to the remaining options. |
| 9601 | + void parse(std::vector<std::string> &args); |
| 9602 | + |
| 9603 | + /// The real work is done here. Expects a reversed vector. |
| 9604 | + void parse(std::vector<std::string> &&args); |
| 9605 | + |
| 9606 | + void parse_from_stream(std::istream &input); |
| 9607 | + |
| 9608 | + /// Provide a function to print a help message. The function gets access to the App pointer and error. |
| 9609 | + void failure_message(std::function<std::string(const App *, const Error &e)> function) { |
| 9610 | + failure_message_ = function; |
| 9611 | + } |
| 9612 | + |
| 9613 | + /// Print a nice error message and return the exit code |
| 9614 | + int exit(const Error &e, std::ostream &out = std::cout, std::ostream &err = std::cerr) const; |
| 9615 | + |
| 9616 | + ///@} |
| 9617 | + /// @name Post parsing |
| 9618 | + ///@{ |
| 9619 | + |
| 9620 | + /// Counts the number of times the given option was passed. |
| 9621 | + CLI11_NODISCARD std::size_t count(std::string option_name) const { return get_option(option_name)->count(); } |
| 9622 | + |
| 9623 | + /// Get a subcommand pointer list to the currently selected subcommands (after parsing by default, in command |
| 9624 | + /// line order; use parsed = false to get the original definition list.) |
| 9625 | + CLI11_NODISCARD std::vector<App *> get_subcommands() const { return parsed_subcommands_; } |
| 9626 | + |
| 9627 | + /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all |
| 9628 | + /// subcommands (const) |
| 9629 | + std::vector<const App *> get_subcommands(const std::function<bool(const App *)> &filter) const; |
| 9630 | + |
| 9631 | + /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all |
| 9632 | + /// subcommands |
| 9633 | + std::vector<App *> get_subcommands(const std::function<bool(App *)> &filter); |
| 9634 | + |
| 9635 | + /// Check to see if given subcommand was selected |
| 9636 | + bool got_subcommand(const App *subcom) const { |
| 9637 | + // get subcom needed to verify that this was a real subcommand |
| 9638 | + return get_subcommand(subcom)->parsed_ > 0; |
| 9639 | + } |
| 9640 | + |
| 9641 | + /// Check with name instead of pointer to see if subcommand was selected |
| 9642 | + CLI11_NODISCARD bool got_subcommand(std::string subcommand_name) const noexcept { |
| 9643 | + App *sub = get_subcommand_no_throw(subcommand_name); |
| 9644 | + return (sub != nullptr) ? (sub->parsed_ > 0) : false; |
| 9645 | + } |
| 9646 | + |
| 9647 | + /// Sets excluded options for the subcommand |
| 9648 | + App *excludes(Option *opt) { |
| 9649 | + if(opt == nullptr) { |
| 9650 | + throw OptionNotFound("nullptr passed"); |
| 9651 | + } |
| 9652 | + exclude_options_.insert(opt); |
| 9653 | + return this; |
| 9654 | + } |
| 9655 | + |
| 9656 | + /// Sets excluded subcommands for the subcommand |
| 9657 | + App *excludes(App *app) { |
| 9658 | + if(app == nullptr) { |
| 9659 | + throw OptionNotFound("nullptr passed"); |
| 9660 | + } |
| 9661 | + if(app == this) { |
| 9662 | + throw OptionNotFound("cannot self reference in needs"); |
| 9663 | + } |
| 9664 | + auto res = exclude_subcommands_.insert(app); |
| 9665 | + // subcommand exclusion should be symmetric |
| 9666 | + if(res.second) { |
| 9667 | + app->exclude_subcommands_.insert(this); |
| 9668 | + } |
| 9669 | + return this; |
| 9670 | + } |
| 9671 | + |
| 9672 | + App *needs(Option *opt) { |
| 9673 | + if(opt == nullptr) { |
| 9674 | + throw OptionNotFound("nullptr passed"); |
| 9675 | + } |
| 9676 | + need_options_.insert(opt); |
| 9677 | + return this; |
| 9678 | + } |
| 9679 | + |
| 9680 | + App *needs(App *app) { |
| 9681 | + if(app == nullptr) { |
| 9682 | + throw OptionNotFound("nullptr passed"); |
| 9683 | + } |
| 9684 | + if(app == this) { |
| 9685 | + throw OptionNotFound("cannot self reference in needs"); |
| 9686 | + } |
| 9687 | + need_subcommands_.insert(app); |
| 9688 | + return this; |
| 9689 | + } |
| 9690 | + |
| 9691 | + /// Removes an option from the excludes list of this subcommand |
| 9692 | + bool remove_excludes(Option *opt); |
| 9693 | + |
| 9694 | + /// Removes a subcommand from the excludes list of this subcommand |
| 9695 | + bool remove_excludes(App *app); |
| 9696 | + |
| 9697 | + /// Removes an option from the needs list of this subcommand |
| 9698 | + bool remove_needs(Option *opt); |
| 9699 | + |
| 9700 | + /// Removes a subcommand from the needs list of this subcommand |
| 9701 | + bool remove_needs(App *app); |
| 9702 | + ///@} |
| 9703 | + /// @name Help |
| 9704 | + ///@{ |
| 9705 | + |
| 9706 | + /// Set usage. |
| 9707 | + App *usage(std::string usage_string) { |
| 9708 | + usage_ = std::move(usage_string); |
| 9709 | + return this; |
| 9710 | + } |
| 9711 | + /// Set usage. |
| 9712 | + App *usage(std::function<std::string()> usage_function) { |
| 9713 | + usage_callback_ = std::move(usage_function); |
| 9714 | + return this; |
| 9715 | + } |
| 9716 | + /// Set footer. |
| 9717 | + App *footer(std::string footer_string) { |
| 9718 | + footer_ = std::move(footer_string); |
| 9719 | + return this; |
| 9720 | + } |
| 9721 | + /// Set footer. |
| 9722 | + App *footer(std::function<std::string()> footer_function) { |
| 9723 | + footer_callback_ = std::move(footer_function); |
| 9724 | + return this; |
| 9725 | + } |
| 9726 | + /// Produce a string that could be read in as a config of the current values of the App. Set default_also to |
| 9727 | + /// include default arguments. write_descriptions will print a description for the App and for each option. |
| 9728 | + CLI11_NODISCARD std::string config_to_str(bool default_also = false, bool write_description = false) const { |
| 9729 | + return config_formatter_->to_config(this, default_also, write_description, ""); |
| 9730 | + } |
| 9731 | + |
| 9732 | + /// Makes a help message, using the currently configured formatter |
| 9733 | + /// Will only do one subcommand at a time |
| 9734 | + CLI11_NODISCARD std::string help(std::string prev = "", AppFormatMode mode = AppFormatMode::Normal) const; |
| 9735 | + |
| 9736 | + /// Displays a version string |
| 9737 | + CLI11_NODISCARD std::string version() const; |
| 9738 | + ///@} |
| 9739 | + /// @name Getters |
| 9740 | + ///@{ |
| 9741 | + |
| 9742 | + /// Access the formatter |
| 9743 | + CLI11_NODISCARD std::shared_ptr<FormatterBase> get_formatter() const { return formatter_; } |
| 9744 | + |
| 9745 | + /// Access the config formatter |
| 9746 | + CLI11_NODISCARD std::shared_ptr<Config> get_config_formatter() const { return config_formatter_; } |
| 9747 | + |
| 9748 | + /// Access the config formatter as a configBase pointer |
| 9749 | + CLI11_NODISCARD std::shared_ptr<ConfigBase> get_config_formatter_base() const { |
| 9750 | + // This is safer as a dynamic_cast if we have RTTI, as Config -> ConfigBase |
| 9751 | +#if CLI11_USE_STATIC_RTTI == 0 |
| 9752 | + return std::dynamic_pointer_cast<ConfigBase>(config_formatter_); |
| 9753 | +#else |
| 9754 | + return std::static_pointer_cast<ConfigBase>(config_formatter_); |
| 9755 | +#endif |
| 9756 | + } |
| 9757 | + |
| 9758 | + /// Get the app or subcommand description |
| 9759 | + CLI11_NODISCARD std::string get_description() const { return description_; } |
| 9760 | + |
| 9761 | + /// Set the description of the app |
| 9762 | + App *description(std::string app_description) { |
| 9763 | + description_ = std::move(app_description); |
| 9764 | + return this; |
| 9765 | + } |
| 9766 | + |
| 9767 | + /// Get the list of options (user facing function, so returns raw pointers), has optional filter function |
| 9768 | + std::vector<const Option *> get_options(const std::function<bool(const Option *)> filter = {}) const; |
| 9769 | + |
| 9770 | + /// Non-const version of the above |
| 9771 | + std::vector<Option *> get_options(const std::function<bool(Option *)> filter = {}); |
| 9772 | + |
| 9773 | + /// Get an option by name (noexcept non-const version) |
| 9774 | + CLI11_NODISCARD Option *get_option_no_throw(std::string option_name) noexcept; |
| 9775 | + |
| 9776 | + /// Get an option by name (noexcept const version) |
| 9777 | + CLI11_NODISCARD const Option *get_option_no_throw(std::string option_name) const noexcept; |
| 9778 | + |
| 9779 | + /// Get an option by name |
| 9780 | + CLI11_NODISCARD const Option *get_option(std::string option_name) const { |
| 9781 | + const auto *opt = get_option_no_throw(option_name); |
| 9782 | + if(opt == nullptr) { |
| 9783 | + throw OptionNotFound(option_name); |
| 9784 | + } |
| 9785 | + return opt; |
| 9786 | + } |
| 9787 | + |
| 9788 | + /// Get an option by name (non-const version) |
| 9789 | + CLI11_NODISCARD Option *get_option(std::string option_name) { |
| 9790 | + auto *opt = get_option_no_throw(option_name); |
| 9791 | + if(opt == nullptr) { |
| 9792 | + throw OptionNotFound(option_name); |
| 9793 | + } |
| 9794 | + return opt; |
| 9795 | + } |
| 9796 | + |
| 9797 | + /// Shortcut bracket operator for getting a pointer to an option |
| 9798 | + const Option *operator[](const std::string &option_name) const { return get_option(option_name); } |
| 9799 | + |
| 9800 | + /// Shortcut bracket operator for getting a pointer to an option |
| 9801 | + const Option *operator[](const char *option_name) const { return get_option(option_name); } |
| 9802 | + |
| 9803 | + /// Check the status of ignore_case |
| 9804 | + CLI11_NODISCARD bool get_ignore_case() const { return ignore_case_; } |
| 9805 | + |
| 9806 | + /// Check the status of ignore_underscore |
| 9807 | + CLI11_NODISCARD bool get_ignore_underscore() const { return ignore_underscore_; } |
| 9808 | + |
| 9809 | + /// Check the status of fallthrough |
| 9810 | + CLI11_NODISCARD bool get_fallthrough() const { return fallthrough_; } |
| 9811 | + |
| 9812 | + /// Check the status of subcommand fallthrough |
| 9813 | + CLI11_NODISCARD bool get_subcommand_fallthrough() const { return subcommand_fallthrough_; } |
| 9814 | + |
| 9815 | + /// Check the status of the allow windows style options |
| 9816 | + CLI11_NODISCARD bool get_allow_windows_style_options() const { return allow_windows_style_options_; } |
| 9817 | + |
| 9818 | + /// Check the status of the allow windows style options |
| 9819 | + CLI11_NODISCARD bool get_positionals_at_end() const { return positionals_at_end_; } |
| 9820 | + |
| 9821 | + /// Check the status of the allow windows style options |
| 9822 | + CLI11_NODISCARD bool get_configurable() const { return configurable_; } |
| 9823 | + |
| 9824 | + /// Get the group of this subcommand |
| 9825 | + CLI11_NODISCARD const std::string &get_group() const { return group_; } |
| 9826 | + |
| 9827 | + /// Generate and return the usage. |
| 9828 | + CLI11_NODISCARD std::string get_usage() const { |
| 9829 | + return (usage_callback_) ? usage_callback_() + '\n' + usage_ : usage_; |
| 9830 | + } |
| 9831 | + |
| 9832 | + /// Generate and return the footer. |
| 9833 | + CLI11_NODISCARD std::string get_footer() const { |
| 9834 | + return (footer_callback_) ? footer_callback_() + '\n' + footer_ : footer_; |
| 9835 | + } |
| 9836 | + |
| 9837 | + /// Get the required min subcommand value |
| 9838 | + CLI11_NODISCARD std::size_t get_require_subcommand_min() const { return require_subcommand_min_; } |
| 9839 | + |
| 9840 | + /// Get the required max subcommand value |
| 9841 | + CLI11_NODISCARD std::size_t get_require_subcommand_max() const { return require_subcommand_max_; } |
| 9842 | + |
| 9843 | + /// Get the required min option value |
| 9844 | + CLI11_NODISCARD std::size_t get_require_option_min() const { return require_option_min_; } |
| 9845 | + |
| 9846 | + /// Get the required max option value |
| 9847 | + CLI11_NODISCARD std::size_t get_require_option_max() const { return require_option_max_; } |
| 9848 | + |
| 9849 | + /// Get the prefix command status |
| 9850 | + CLI11_NODISCARD bool get_prefix_command() const { return static_cast<bool>(prefix_command_); } |
| 9851 | + |
| 9852 | + /// Get the prefix command status |
| 9853 | + CLI11_NODISCARD PrefixCommandMode get_prefix_command_mode() const { return prefix_command_; } |
| 9854 | + |
| 9855 | + /// Get the status of allow extras |
| 9856 | + CLI11_NODISCARD bool get_allow_extras() const { return allow_extras_ > ExtrasMode::Ignore; } |
| 9857 | + |
| 9858 | + /// Get the mode of allow_extras |
| 9859 | + CLI11_NODISCARD ExtrasMode get_allow_extras_mode() const { return allow_extras_; } |
| 9860 | + |
| 9861 | + /// Get the status of required |
| 9862 | + CLI11_NODISCARD bool get_required() const { return required_; } |
| 9863 | + |
| 9864 | + /// Get the status of disabled |
| 9865 | + CLI11_NODISCARD bool get_disabled() const { return disabled_; } |
| 9866 | + |
| 9867 | + /// Get the status of silence |
| 9868 | + CLI11_NODISCARD bool get_silent() const { return silent_; } |
| 9869 | + |
| 9870 | + /// Get the status of allowing non standard option names |
| 9871 | + CLI11_NODISCARD bool get_allow_non_standard_option_names() const { return allow_non_standard_options_; } |
| 9872 | + |
| 9873 | + /// Get the status of allowing prefix matching for subcommands |
| 9874 | + CLI11_NODISCARD bool get_allow_subcommand_prefix_matching() const { return allow_prefix_matching_; } |
| 9875 | + |
| 9876 | + /// Get the status of disabled |
| 9877 | + CLI11_NODISCARD bool get_immediate_callback() const { return immediate_callback_; } |
| 9878 | + |
| 9879 | + /// Get the status of disabled by default |
| 9880 | + CLI11_NODISCARD bool get_disabled_by_default() const { return (default_startup == startup_mode::disabled); } |
| 9881 | + |
| 9882 | + /// Get the status of disabled by default |
| 9883 | + CLI11_NODISCARD bool get_enabled_by_default() const { return (default_startup == startup_mode::enabled); } |
| 9884 | + /// Get the status of validating positionals |
| 9885 | + CLI11_NODISCARD bool get_validate_positionals() const { return validate_positionals_; } |
| 9886 | + /// Get the status of validating optional vector arguments |
| 9887 | + CLI11_NODISCARD bool get_validate_optional_arguments() const { return validate_optional_arguments_; } |
| 9888 | + |
| 9889 | + /// Get the status of allow extras |
| 9890 | + CLI11_NODISCARD config_extras_mode get_allow_config_extras() const { |
| 9891 | + return static_cast<config_extras_mode>(allow_config_extras_); |
| 9892 | + } |
| 9893 | + |
| 9894 | + /// Get a pointer to the help flag. |
| 9895 | + Option *get_help_ptr() { return help_ptr_; } |
| 9896 | + |
| 9897 | + /// Get a pointer to the help flag. (const) |
| 9898 | + CLI11_NODISCARD const Option *get_help_ptr() const { return help_ptr_; } |
| 9899 | + |
| 9900 | + /// Get a pointer to the help all flag. (const) |
| 9901 | + CLI11_NODISCARD const Option *get_help_all_ptr() const { return help_all_ptr_; } |
| 9902 | + |
| 9903 | + /// Get a pointer to the config option. |
| 9904 | + Option *get_config_ptr() { return config_ptr_; } |
| 9905 | + |
| 9906 | + /// Get a pointer to the config option. (const) |
| 9907 | + CLI11_NODISCARD const Option *get_config_ptr() const { return config_ptr_; } |
| 9908 | + |
| 9909 | + /// Get a pointer to the version option. |
| 9910 | + Option *get_version_ptr() { return version_ptr_; } |
| 9911 | + |
| 9912 | + /// Get a pointer to the version option. (const) |
| 9913 | + CLI11_NODISCARD const Option *get_version_ptr() const { return version_ptr_; } |
| 9914 | + |
| 9915 | + /// Get the parent of this subcommand (or nullptr if main app) |
| 9916 | + App *get_parent() { return parent_; } |
| 9917 | + |
| 9918 | + /// Get the parent of this subcommand (or nullptr if main app) (const version) |
| 9919 | + CLI11_NODISCARD const App *get_parent() const { return parent_; } |
| 9920 | + |
| 9921 | + /// Get the name of the current app |
| 9922 | + CLI11_NODISCARD const std::string &get_name() const { return name_; } |
| 9923 | + |
| 9924 | + /// Get the aliases of the current app |
| 9925 | + CLI11_NODISCARD const std::vector<std::string> &get_aliases() const { return aliases_; } |
| 9926 | + |
| 9927 | + /// clear all the aliases of the current App |
| 9928 | + App *clear_aliases() { |
| 9929 | + aliases_.clear(); |
| 9930 | + return this; |
| 9931 | + } |
| 9932 | + |
| 9933 | + /// Get a display name for an app |
| 9934 | + CLI11_NODISCARD std::string get_display_name(bool with_aliases = false) const; |
| 9935 | + |
| 9936 | + /// Check the name, case-insensitive and underscore insensitive, and prefix matching if set |
| 9937 | + /// @return true if matched |
| 9938 | + CLI11_NODISCARD bool check_name(std::string name_to_check) const; |
| 9939 | + |
| 9940 | + /// @brief enumeration of matching possibilities |
| 9941 | + enum class NameMatch : std::uint8_t { none = 0, exact = 1, prefix = 2 }; |
| 9942 | + |
| 9943 | + /// Check the name, case-insensitive and underscore insensitive if set |
| 9944 | + /// @return NameMatch::none if no match, NameMatch::exact if the match is exact NameMatch::prefix if prefix is |
| 9945 | + /// enabled and a prefix matches |
| 9946 | + CLI11_NODISCARD NameMatch check_name_detail(std::string name_to_check) const; |
| 9947 | + |
| 9948 | + /// Get the groups available directly from this option (in order) |
| 9949 | + CLI11_NODISCARD std::vector<std::string> get_groups() const; |
| 9950 | + |
| 9951 | + /// This gets a vector of pointers with the original parse order |
| 9952 | + CLI11_NODISCARD const std::vector<Option *> &parse_order() const { return parse_order_; } |
| 9953 | + |
| 9954 | + /// This returns the missing options from the current subcommand |
| 9955 | + CLI11_NODISCARD std::vector<std::string> remaining(bool recurse = false) const; |
| 9956 | + |
| 9957 | + /// This returns the missing options in a form ready for processing by another command line program |
| 9958 | + CLI11_NODISCARD std::vector<std::string> remaining_for_passthrough(bool recurse = false) const; |
| 9959 | + |
| 9960 | + /// This returns the number of remaining options, minus the -- separator |
| 9961 | + CLI11_NODISCARD std::size_t remaining_size(bool recurse = false) const; |
| 9962 | + |
| 9963 | + ///@} |
| 9964 | + |
| 9965 | + protected: |
| 9966 | + /// Check the options to make sure there are no conflicts. |
| 9967 | + /// |
| 9968 | + /// Currently checks to see if multiple positionals exist with unlimited args and checks if the min and max options |
| 9969 | + /// are feasible |
| 9970 | + void _validate() const; |
| 9971 | + |
| 9972 | + /// configure subcommands to enable parsing through the current object |
| 9973 | + /// set the correct fallthrough and prefix for nameless subcommands and manage the automatic enable or disable |
| 9974 | + /// makes sure parent is set correctly |
| 9975 | + void _configure(); |
| 9976 | + |
| 9977 | + /// Internal function to run (App) callback, bottom up |
| 9978 | + void run_callback(bool final_mode = false, bool suppress_final_callback = false); |
| 9979 | + |
| 9980 | + /// Check to see if a subcommand is valid. Give up immediately if subcommand max has been reached. |
| 9981 | + CLI11_NODISCARD bool _valid_subcommand(const std::string ¤t, bool ignore_used = true) const; |
| 9982 | + |
| 9983 | + /// Selects a Classifier enum based on the type of the current argument |
| 9984 | + CLI11_NODISCARD detail::Classifier _recognize(const std::string ¤t, |
| 9985 | + bool ignore_used_subcommands = true) const; |
| 9986 | + |
| 9987 | + // The parse function is now broken into several parts, and part of process |
| 9988 | + |
| 9989 | + /// Read and process a configuration file (main app only) |
| 9990 | + void _process_config_file(); |
| 9991 | + |
| 9992 | + /// Read and process a particular configuration file |
| 9993 | + bool _process_config_file(const std::string &config_file, bool throw_error); |
| 9994 | + |
| 9995 | + /// Get envname options if not yet passed. Runs on *all* subcommands. |
| 9996 | + void _process_env(); |
| 9997 | + |
| 9998 | + /// Process callbacks. Runs on *all* subcommands. |
| 9999 | + void _process_callbacks(CallbackPriority priority); |
| 10000 | + |
| 10001 | + /// Run help flag processing if any are found. |
| 10002 | + /// |
| 10003 | + /// The flags allow recursive calls to remember if there was a help flag on a parent. |
| 10004 | + void _process_help_flags(CallbackPriority priority, bool trigger_help = false, bool trigger_all_help = false) const; |
| 10005 | + |
| 10006 | + /// Verify required options and cross requirements. Subcommands too (only if selected). |
| 10007 | + void _process_requirements(); |
| 10008 | + |
| 10009 | + /// Process callbacks and such. |
| 10010 | + void _process(); |
| 10011 | + |
| 10012 | + /// Throw an error if anything is left over and should not be. |
| 10013 | + void _process_extras(); |
| 10014 | + |
| 10015 | + /// Internal function to recursively increment the parsed counter on the current app as well unnamed subcommands |
| 10016 | + void increment_parsed(); |
| 10017 | + |
| 10018 | + /// Internal parse function |
| 10019 | + void _parse(std::vector<std::string> &args); |
| 10020 | + |
| 10021 | + /// Internal parse function |
| 10022 | + void _parse(std::vector<std::string> &&args); |
| 10023 | + |
| 10024 | + /// Internal function to parse a stream |
| 10025 | + void _parse_stream(std::istream &input); |
| 10026 | + |
| 10027 | + /// Parse one config param, return false if not found in any subcommand, remove if it is |
| 10028 | + /// |
| 10029 | + /// If this has more than one dot.separated.name, go into the subcommand matching it |
| 10030 | + /// Returns true if it managed to find the option, if false you'll need to remove the arg manually. |
| 10031 | + void _parse_config(const std::vector<ConfigItem> &args); |
| 10032 | + |
| 10033 | + /// Fill in a single config option |
| 10034 | + bool _parse_single_config(const ConfigItem &item, std::size_t level = 0); |
| 10035 | + |
| 10036 | + /// @brief store the results for a flag like option |
| 10037 | + bool _add_flag_like_result(Option *op, const ConfigItem &item, const std::vector<std::string> &inputs); |
| 10038 | + |
| 10039 | + /// Parse "one" argument (some may eat more than one), delegate to parent if fails, add to missing if missing |
| 10040 | + /// from main return false if the parse has failed and needs to return to parent |
| 10041 | + bool _parse_single(std::vector<std::string> &args, bool &positional_only); |
| 10042 | + |
| 10043 | + /// Count the required remaining positional arguments |
| 10044 | + CLI11_NODISCARD std::size_t _count_remaining_positionals(bool required_only = false) const; |
| 10045 | + |
| 10046 | + /// Count the required remaining positional arguments |
| 10047 | + CLI11_NODISCARD bool _has_remaining_positionals() const; |
| 10048 | + |
| 10049 | + /// Parse a positional, go up the tree to check |
| 10050 | + /// @param haltOnSubcommand if set to true the operation will not process subcommands merely return false |
| 10051 | + /// Return true if the positional was used false otherwise |
| 10052 | + bool _parse_positional(std::vector<std::string> &args, bool haltOnSubcommand); |
| 10053 | + |
| 10054 | + /// Locate a subcommand by name with two conditions, should disabled subcommands be ignored, and should used |
| 10055 | + /// subcommands be ignored |
| 10056 | + CLI11_NODISCARD App * |
| 10057 | + _find_subcommand(const std::string &subc_name, bool ignore_disabled, bool ignore_used) const noexcept; |
| 10058 | + |
| 10059 | + /// Parse a subcommand, modify args and continue |
| 10060 | + /// |
| 10061 | + /// Unlike the others, this one will always allow fallthrough |
| 10062 | + /// return true if the subcommand was processed false otherwise |
| 10063 | + bool _parse_subcommand(std::vector<std::string> &args); |
| 10064 | + |
| 10065 | + /// Parse a short (false) or long (true) argument, must be at the top of the list |
| 10066 | + /// if local_processing_only is set to true then fallthrough is disabled will return false if not found |
| 10067 | + /// return true if the argument was processed or false if nothing was done |
| 10068 | + bool _parse_arg(std::vector<std::string> &args, detail::Classifier current_type, bool local_processing_only); |
| 10069 | + |
| 10070 | + /// Trigger the pre_parse callback if needed |
| 10071 | + void _trigger_pre_parse(std::size_t remaining_args); |
| 10072 | + |
| 10073 | + /// Get the appropriate parent to fallthrough to which is the first one that has a name or the main app |
| 10074 | + CLI11_NODISCARD App *_get_fallthrough_parent() noexcept; |
| 10075 | + |
| 10076 | + /// Get the appropriate parent to fallthrough to which is the first one that has a name or the main app |
| 10077 | + CLI11_NODISCARD const App *_get_fallthrough_parent() const noexcept; |
| 10078 | + |
| 10079 | + /// Helper function to run through all possible comparisons of subcommand names to check there is no overlap |
| 10080 | + CLI11_NODISCARD const std::string &_compare_subcommand_names(const App &subcom, const App &base) const; |
| 10081 | + |
| 10082 | + /// Helper function to place extra values in the most appropriate position |
| 10083 | + void _move_to_missing(detail::Classifier val_type, const std::string &val); |
| 10084 | + |
| 10085 | + public: |
| 10086 | + /// function that could be used by subclasses of App to shift options around into subcommands |
| 10087 | + void _move_option(Option *opt, App *app); |
| 10088 | +}; // namespace CLI |
| 10089 | + |
| 10090 | +/// Extension of App to better manage groups of options |
| 10091 | +class Option_group : public App { |
| 10092 | + public: |
| 10093 | + Option_group(std::string group_description, std::string group_name, App *parent) |
| 10094 | + : App(std::move(group_description), "", parent) { |
| 10095 | + group(group_name); |
| 10096 | + // option groups should have automatic fallthrough |
| 10097 | + if(group_name.empty() || group_name.front() == '+') { |
| 10098 | + // help will not be used by default in these contexts |
| 10099 | + set_help_flag(""); |
| 10100 | + set_help_all_flag(""); |
| 10101 | + } |
| 10102 | + } |
| 10103 | + using App::add_option; |
| 10104 | + /// Add an existing option to the Option_group |
| 10105 | + Option *add_option(Option *opt) { |
| 10106 | + if(get_parent() == nullptr) { |
| 10107 | + throw OptionNotFound("Unable to locate the specified option"); |
| 10108 | + } |
| 10109 | + get_parent()->_move_option(opt, this); |
| 10110 | + return opt; |
| 10111 | + } |
| 10112 | + /// Add an existing option to the Option_group |
| 10113 | + void add_options(Option *opt) { add_option(opt); } |
| 10114 | + /// Add a bunch of options to the group |
| 10115 | + template <typename... Args> void add_options(Option *opt, Args... args) { |
| 10116 | + add_option(opt); |
| 10117 | + add_options(args...); |
| 10118 | + } |
| 10119 | + using App::add_subcommand; |
| 10120 | + /// Add an existing subcommand to be a member of an option_group |
| 10121 | + App *add_subcommand(App *subcom) { |
| 10122 | + App_p subc = subcom->get_parent()->get_subcommand_ptr(subcom); |
| 10123 | + subc->get_parent()->remove_subcommand(subcom); |
| 10124 | + add_subcommand(std::move(subc)); |
| 10125 | + return subcom; |
| 10126 | + } |
| 10127 | +}; |
| 10128 | + |
| 10129 | +/// Helper function to enable one option group/subcommand when another is used |
| 10130 | +CLI11_INLINE void TriggerOn(App *trigger_app, App *app_to_enable); |
| 10131 | + |
| 10132 | +/// Helper function to enable one option group/subcommand when another is used |
| 10133 | +CLI11_INLINE void TriggerOn(App *trigger_app, std::vector<App *> apps_to_enable); |
| 10134 | + |
| 10135 | +/// Helper function to disable one option group/subcommand when another is used |
| 10136 | +CLI11_INLINE void TriggerOff(App *trigger_app, App *app_to_enable); |
| 10137 | + |
| 10138 | +/// Helper function to disable one option group/subcommand when another is used |
| 10139 | +CLI11_INLINE void TriggerOff(App *trigger_app, std::vector<App *> apps_to_enable); |
| 10140 | + |
| 10141 | +/// Helper function to mark an option as deprecated |
| 10142 | +CLI11_INLINE void deprecate_option(Option *opt, const std::string &replacement = ""); |
| 10143 | + |
| 10144 | +/// Helper function to mark an option as deprecated |
| 10145 | +inline void deprecate_option(App *app, const std::string &option_name, const std::string &replacement = "") { |
| 10146 | + auto *opt = app->get_option(option_name); |
| 10147 | + deprecate_option(opt, replacement); |
| 10148 | +} |
| 10149 | + |
| 10150 | +/// Helper function to mark an option as deprecated |
| 10151 | +inline void deprecate_option(App &app, const std::string &option_name, const std::string &replacement = "") { |
| 10152 | + auto *opt = app.get_option(option_name); |
| 10153 | + deprecate_option(opt, replacement); |
| 10154 | +} |
| 10155 | + |
| 10156 | +/// Helper function to mark an option as retired |
| 10157 | +CLI11_INLINE void retire_option(App *app, Option *opt); |
| 10158 | + |
| 10159 | +/// Helper function to mark an option as retired |
| 10160 | +CLI11_INLINE void retire_option(App &app, Option *opt); |
| 10161 | + |
| 10162 | +/// Helper function to mark an option as retired |
| 10163 | +CLI11_INLINE void retire_option(App *app, const std::string &option_name); |
| 10164 | + |
| 10165 | +/// Helper function to mark an option as retired |
| 10166 | +CLI11_INLINE void retire_option(App &app, const std::string &option_name); |
| 10167 | + |
| 10168 | +namespace detail { |
| 10169 | +/// This class is simply to allow tests access to App's protected functions |
| 10170 | +struct AppFriend { |
| 10171 | +#ifdef CLI11_CPP14 |
| 10172 | + |
| 10173 | + /// Wrap _parse_short, perfectly forward arguments and return |
| 10174 | + template <typename... Args> static decltype(auto) parse_arg(App *app, Args &&...args) { |
| 10175 | + return app->_parse_arg(std::forward<Args>(args)...); |
| 10176 | + } |
| 10177 | + |
| 10178 | + /// Wrap _parse_subcommand, perfectly forward arguments and return |
| 10179 | + template <typename... Args> static decltype(auto) parse_subcommand(App *app, Args &&...args) { |
| 10180 | + return app->_parse_subcommand(std::forward<Args>(args)...); |
| 10181 | + } |
| 10182 | +#else |
| 10183 | + /// Wrap _parse_short, perfectly forward arguments and return |
| 10184 | + template <typename... Args> |
| 10185 | + static auto parse_arg(App *app, Args &&...args) -> |
| 10186 | + typename std::result_of<decltype (&App::_parse_arg)(App, Args...)>::type { |
| 10187 | + return app->_parse_arg(std::forward<Args>(args)...); |
| 10188 | + } |
| 10189 | + |
| 10190 | + /// Wrap _parse_subcommand, perfectly forward arguments and return |
| 10191 | + template <typename... Args> |
| 10192 | + static auto parse_subcommand(App *app, Args &&...args) -> |
| 10193 | + typename std::result_of<decltype (&App::_parse_subcommand)(App, Args...)>::type { |
| 10194 | + return app->_parse_subcommand(std::forward<Args>(args)...); |
| 10195 | + } |
| 10196 | +#endif |
| 10197 | + /// Wrap the fallthrough parent function to make sure that is working correctly |
| 10198 | + static App *get_fallthrough_parent(App *app) { return app->_get_fallthrough_parent(); } |
| 10199 | + |
| 10200 | + /// Wrap the const fallthrough parent function to make sure that is working correctly |
| 10201 | + static const App *get_fallthrough_parent(const App *app) { return app->_get_fallthrough_parent(); } |
| 10202 | +}; |
| 10203 | +} // namespace detail |
| 10204 | + |
| 10205 | + |
| 10206 | + |
| 10207 | + |
| 10208 | +CLI11_INLINE App::App(std::string app_description, std::string app_name, App *parent) |
| 10209 | + : name_(std::move(app_name)), description_(std::move(app_description)), parent_(parent) { |
| 10210 | + // Inherit if not from a nullptr |
| 10211 | + if(parent_ != nullptr) { |
| 10212 | + if(parent_->help_ptr_ != nullptr) |
| 10213 | + set_help_flag(parent_->help_ptr_->get_name(false, true), parent_->help_ptr_->get_description()); |
| 10214 | + if(parent_->help_all_ptr_ != nullptr) |
| 10215 | + set_help_all_flag(parent_->help_all_ptr_->get_name(false, true), parent_->help_all_ptr_->get_description()); |
| 10216 | + |
| 10217 | + /// OptionDefaults |
| 10218 | + option_defaults_ = parent_->option_defaults_; |
| 10219 | + |
| 10220 | + // INHERITABLE |
| 10221 | + failure_message_ = parent_->failure_message_; |
| 10222 | + allow_extras_ = parent_->allow_extras_; |
| 10223 | + allow_config_extras_ = parent_->allow_config_extras_; |
| 10224 | + prefix_command_ = parent_->prefix_command_; |
| 10225 | + immediate_callback_ = parent_->immediate_callback_; |
| 10226 | + ignore_case_ = parent_->ignore_case_; |
| 10227 | + ignore_underscore_ = parent_->ignore_underscore_; |
| 10228 | + fallthrough_ = parent_->fallthrough_; |
| 10229 | + validate_positionals_ = parent_->validate_positionals_; |
| 10230 | + validate_optional_arguments_ = parent_->validate_optional_arguments_; |
| 10231 | + configurable_ = parent_->configurable_; |
| 10232 | + allow_windows_style_options_ = parent_->allow_windows_style_options_; |
| 10233 | + group_ = parent_->group_; |
| 10234 | + usage_ = parent_->usage_; |
| 10235 | + footer_ = parent_->footer_; |
| 10236 | + formatter_ = parent_->formatter_; |
| 10237 | + config_formatter_ = parent_->config_formatter_; |
| 10238 | + require_subcommand_max_ = parent_->require_subcommand_max_; |
| 10239 | + allow_prefix_matching_ = parent_->allow_prefix_matching_; |
| 10240 | + } |
| 10241 | +} |
| 10242 | + |
| 10243 | +CLI11_NODISCARD CLI11_INLINE char **App::ensure_utf8(char **argv) { |
| 10244 | +#ifdef _WIN32 |
| 10245 | + (void)argv; |
| 10246 | + |
| 10247 | + normalized_argv_ = detail::compute_win32_argv(); |
| 10248 | + |
| 10249 | + if(!normalized_argv_view_.empty()) { |
| 10250 | + normalized_argv_view_.clear(); |
| 10251 | + } |
| 10252 | + |
| 10253 | + normalized_argv_view_.reserve(normalized_argv_.size()); |
| 10254 | + for(auto &arg : normalized_argv_) { |
| 10255 | + // using const_cast is well-defined, string is known to not be const. |
| 10256 | + normalized_argv_view_.push_back(const_cast<char *>(arg.data())); |
| 10257 | + } |
| 10258 | + |
| 10259 | + return normalized_argv_view_.data(); |
| 10260 | +#else |
| 10261 | + return argv; |
| 10262 | +#endif |
| 10263 | +} |
| 10264 | + |
| 10265 | +CLI11_INLINE App *App::name(std::string app_name) { |
| 10266 | + |
| 10267 | + if(parent_ != nullptr) { |
| 10268 | + std::string oname = name_; |
| 10269 | + name_ = app_name; |
| 10270 | + const auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent()); |
| 10271 | + if(!res.empty()) { |
| 10272 | + name_ = oname; |
| 10273 | + throw(OptionAlreadyAdded(app_name + " conflicts with existing subcommand names")); |
| 10274 | + } |
| 10275 | + } else { |
| 10276 | + name_ = app_name; |
| 10277 | + } |
| 10278 | + has_automatic_name_ = false; |
| 10279 | + return this; |
| 10280 | +} |
| 10281 | + |
| 10282 | +CLI11_INLINE App *App::alias(std::string app_name) { |
| 10283 | + if(app_name.empty() || !detail::valid_alias_name_string(app_name)) { |
| 10284 | + throw IncorrectConstruction("Aliases may not be empty or contain newlines or null characters"); |
| 10285 | + } |
| 10286 | + if(parent_ != nullptr) { |
| 10287 | + aliases_.push_back(app_name); |
| 10288 | + const auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent()); |
| 10289 | + if(!res.empty()) { |
| 10290 | + aliases_.pop_back(); |
| 10291 | + throw(OptionAlreadyAdded("alias already matches an existing subcommand: " + app_name)); |
| 10292 | + } |
| 10293 | + } else { |
| 10294 | + aliases_.push_back(app_name); |
| 10295 | + } |
| 10296 | + |
| 10297 | + return this; |
| 10298 | +} |
| 10299 | + |
| 10300 | +CLI11_INLINE App *App::immediate_callback(bool immediate) { |
| 10301 | + immediate_callback_ = immediate; |
| 10302 | + if(immediate_callback_) { |
| 10303 | + if(final_callback_ && !(parse_complete_callback_)) { |
| 10304 | + std::swap(final_callback_, parse_complete_callback_); |
| 10305 | + } |
| 10306 | + } else if(!(final_callback_) && parse_complete_callback_) { |
| 10307 | + std::swap(final_callback_, parse_complete_callback_); |
| 10308 | + } |
| 10309 | + return this; |
| 10310 | +} |
| 10311 | + |
| 10312 | +CLI11_INLINE App *App::ignore_case(bool value) { |
| 10313 | + if(value && !ignore_case_) { |
| 10314 | + ignore_case_ = true; |
| 10315 | + auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this; |
| 10316 | + const auto &match = _compare_subcommand_names(*this, *p); |
| 10317 | + if(!match.empty()) { |
| 10318 | + ignore_case_ = false; // we are throwing so need to be exception invariant |
| 10319 | + throw OptionAlreadyAdded("ignore case would cause subcommand name conflicts: " + match); |
| 10320 | + } |
| 10321 | + } |
| 10322 | + ignore_case_ = value; |
| 10323 | + return this; |
| 10324 | +} |
| 10325 | + |
| 10326 | +CLI11_INLINE App *App::ignore_underscore(bool value) { |
| 10327 | + if(value && !ignore_underscore_) { |
| 10328 | + ignore_underscore_ = true; |
| 10329 | + auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this; |
| 10330 | + const auto &match = _compare_subcommand_names(*this, *p); |
| 10331 | + if(!match.empty()) { |
| 10332 | + ignore_underscore_ = false; |
| 10333 | + throw OptionAlreadyAdded("ignore underscore would cause subcommand name conflicts: " + match); |
| 10334 | + } |
| 10335 | + } |
| 10336 | + ignore_underscore_ = value; |
| 10337 | + return this; |
| 10338 | +} |
| 10339 | + |
| 10340 | +CLI11_INLINE Option *App::add_option(std::string option_name, |
| 10341 | + callback_t option_callback, |
| 10342 | + std::string option_description, |
| 10343 | + bool defaulted, |
| 10344 | + std::function<std::string()> func) { |
| 10345 | + Option myopt{option_name, option_description, option_callback, this, allow_non_standard_options_}; |
| 10346 | + |
| 10347 | + // do a quick search in current subcommand for options |
| 10348 | + auto res = |
| 10349 | + std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) { return *v == myopt; }); |
| 10350 | + if(res != options_.end()) { |
| 10351 | + const auto &matchname = (*res)->matching_name(myopt); |
| 10352 | + throw(OptionAlreadyAdded("added option matched existing option name: " + matchname)); |
| 10353 | + } |
| 10354 | + /** get a top level parent*/ |
| 10355 | + const App *top_level_parent = this; |
| 10356 | + while(top_level_parent->name_.empty() && top_level_parent->parent_ != nullptr) { |
| 10357 | + top_level_parent = top_level_parent->parent_; |
| 10358 | + } |
| 10359 | + |
| 10360 | + if(myopt.lnames_.empty() && myopt.snames_.empty()) { |
| 10361 | + // if the option is positional only there is additional potential for ambiguities in config files and needs |
| 10362 | + // to be checked |
| 10363 | + std::string test_name = "--" + myopt.get_single_name(); |
| 10364 | + if(test_name.size() == 3) { |
| 10365 | + test_name.erase(0, 1); |
| 10366 | + } |
| 10367 | + // if we are in option group |
| 10368 | + const auto *op = top_level_parent->get_option_no_throw(test_name); |
| 10369 | + if(op != nullptr && op->get_configurable()) { |
| 10370 | + throw(OptionAlreadyAdded("added option positional name matches existing option: " + test_name)); |
| 10371 | + } |
| 10372 | + // need to check if there is another positional with the same name that also doesn't have any long or |
| 10373 | + // short names |
| 10374 | + op = top_level_parent->get_option_no_throw(myopt.get_single_name()); |
| 10375 | + if(op != nullptr && op->lnames_.empty() && op->snames_.empty()) { |
| 10376 | + throw(OptionAlreadyAdded("unable to disambiguate with existing option: " + test_name)); |
| 10377 | + } |
| 10378 | + } else if(top_level_parent != this) { |
| 10379 | + for(auto &ln : myopt.lnames_) { |
| 10380 | + const auto *op = top_level_parent->get_option_no_throw(ln); |
| 10381 | + if(op != nullptr && op->get_configurable()) { |
| 10382 | + throw(OptionAlreadyAdded("added option matches existing positional option: " + ln)); |
| 10383 | + } |
| 10384 | + op = top_level_parent->get_option_no_throw("--" + ln); |
| 10385 | + if(op != nullptr && op->get_configurable()) { |
| 10386 | + throw(OptionAlreadyAdded("added option matches existing option: --" + ln)); |
| 10387 | + } |
| 10388 | + if(ln.size() == 1 || top_level_parent->get_allow_non_standard_option_names()) { |
| 10389 | + op = top_level_parent->get_option_no_throw("-" + ln); |
| 10390 | + if(op != nullptr && op->get_configurable()) { |
| 10391 | + throw(OptionAlreadyAdded("added option matches existing option: -" + ln)); |
| 10392 | + } |
| 10393 | + } |
| 10394 | + } |
| 10395 | + for(auto &sn : myopt.snames_) { |
| 10396 | + const auto *op = top_level_parent->get_option_no_throw(sn); |
| 10397 | + if(op != nullptr && op->get_configurable()) { |
| 10398 | + throw(OptionAlreadyAdded("added option matches existing positional option: " + sn)); |
| 10399 | + } |
| 10400 | + op = top_level_parent->get_option_no_throw("-" + sn); |
| 10401 | + if(op != nullptr && op->get_configurable()) { |
| 10402 | + throw(OptionAlreadyAdded("added option matches existing option: -" + sn)); |
| 10403 | + } |
| 10404 | + op = top_level_parent->get_option_no_throw("--" + sn); |
| 10405 | + if(op != nullptr && op->get_configurable()) { |
| 10406 | + throw(OptionAlreadyAdded("added option matches existing option: --" + sn)); |
| 10407 | + } |
| 10408 | + } |
| 10409 | + } |
| 10410 | + if(allow_non_standard_options_ && !myopt.snames_.empty()) { |
| 10411 | + |
| 10412 | + for(auto &sname : myopt.snames_) { |
| 10413 | + if(sname.length() > 1) { |
| 10414 | + std::string test_name; |
| 10415 | + test_name.push_back('-'); |
| 10416 | + test_name.push_back(sname.front()); |
| 10417 | + const auto *op = top_level_parent->get_option_no_throw(test_name); |
| 10418 | + if(op != nullptr) { |
| 10419 | + throw(OptionAlreadyAdded("added option interferes with existing short option: " + sname)); |
| 10420 | + } |
| 10421 | + } |
| 10422 | + } |
| 10423 | + for(auto &opt : top_level_parent->get_options()) { |
| 10424 | + for(const auto &osn : opt->snames_) { |
| 10425 | + if(osn.size() > 1) { |
| 10426 | + std::string test_name; |
| 10427 | + test_name.push_back(osn.front()); |
| 10428 | + if(myopt.check_sname(test_name)) { |
| 10429 | + throw(OptionAlreadyAdded("added option interferes with existing non standard option: " + osn)); |
| 10430 | + } |
| 10431 | + } |
| 10432 | + } |
| 10433 | + } |
| 10434 | + } |
| 10435 | + options_.emplace_back(); |
| 10436 | + Option_p &option = options_.back(); |
| 10437 | + option.reset(new Option(option_name, option_description, option_callback, this, allow_non_standard_options_)); |
| 10438 | + |
| 10439 | + // Set the default string capture function |
| 10440 | + option->default_function(func); |
| 10441 | + |
| 10442 | + // For compatibility with CLI11 1.7 and before, capture the default string here |
| 10443 | + if(defaulted) |
| 10444 | + option->capture_default_str(); |
| 10445 | + |
| 10446 | + // Transfer defaults to the new option |
| 10447 | + option_defaults_.copy_to(option.get()); |
| 10448 | + |
| 10449 | + // Don't bother to capture if we already did |
| 10450 | + if(!defaulted && option->get_always_capture_default()) |
| 10451 | + option->capture_default_str(); |
| 10452 | + |
| 10453 | + return option.get(); |
| 10454 | +} |
| 10455 | + |
| 10456 | +CLI11_INLINE Option *App::set_help_flag(std::string flag_name, const std::string &help_description) { |
| 10457 | + // take flag_description by const reference otherwise add_flag tries to assign to help_description |
| 10458 | + if(help_ptr_ != nullptr) { |
| 10459 | + remove_option(help_ptr_); |
| 10460 | + help_ptr_ = nullptr; |
| 10461 | + } |
| 10462 | + |
| 10463 | + // Empty name will simply remove the help flag |
| 10464 | + if(!flag_name.empty()) { |
| 10465 | + help_ptr_ = add_flag(flag_name, help_description); |
| 10466 | + help_ptr_->configurable(false)->callback_priority(CallbackPriority::First); |
| 10467 | + } |
| 10468 | + |
| 10469 | + return help_ptr_; |
| 10470 | +} |
| 10471 | + |
| 10472 | +CLI11_INLINE Option *App::set_help_all_flag(std::string help_name, const std::string &help_description) { |
| 10473 | + // take flag_description by const reference otherwise add_flag tries to assign to flag_description |
| 10474 | + if(help_all_ptr_ != nullptr) { |
| 10475 | + remove_option(help_all_ptr_); |
| 10476 | + help_all_ptr_ = nullptr; |
| 10477 | + } |
| 10478 | + |
| 10479 | + // Empty name will simply remove the help all flag |
| 10480 | + if(!help_name.empty()) { |
| 10481 | + help_all_ptr_ = add_flag(help_name, help_description); |
| 10482 | + help_all_ptr_->configurable(false)->callback_priority(CallbackPriority::First); |
| 10483 | + } |
| 10484 | + |
| 10485 | + return help_all_ptr_; |
| 10486 | +} |
| 10487 | + |
| 10488 | +CLI11_INLINE Option * |
| 10489 | +App::set_version_flag(std::string flag_name, const std::string &versionString, const std::string &version_help) { |
| 10490 | + // take flag_description by const reference otherwise add_flag tries to assign to version_description |
| 10491 | + if(version_ptr_ != nullptr) { |
| 10492 | + remove_option(version_ptr_); |
| 10493 | + version_ptr_ = nullptr; |
| 10494 | + } |
| 10495 | + |
| 10496 | + // Empty name will simply remove the version flag |
| 10497 | + if(!flag_name.empty()) { |
| 10498 | + version_ptr_ = add_flag_callback( |
| 10499 | + flag_name, [versionString]() { throw(CLI::CallForVersion(versionString, 0)); }, version_help); |
| 10500 | + version_ptr_->configurable(false)->callback_priority(CallbackPriority::First); |
| 10501 | + } |
| 10502 | + |
| 10503 | + return version_ptr_; |
| 10504 | +} |
| 10505 | + |
| 10506 | +CLI11_INLINE Option * |
| 10507 | +App::set_version_flag(std::string flag_name, std::function<std::string()> vfunc, const std::string &version_help) { |
| 10508 | + if(version_ptr_ != nullptr) { |
| 10509 | + remove_option(version_ptr_); |
| 10510 | + version_ptr_ = nullptr; |
| 10511 | + } |
| 10512 | + |
| 10513 | + // Empty name will simply remove the version flag |
| 10514 | + if(!flag_name.empty()) { |
| 10515 | + version_ptr_ = |
| 10516 | + add_flag_callback(flag_name, [vfunc]() { throw(CLI::CallForVersion(vfunc(), 0)); }, version_help); |
| 10517 | + version_ptr_->configurable(false)->callback_priority(CallbackPriority::First); |
| 10518 | + } |
| 10519 | + |
| 10520 | + return version_ptr_; |
| 10521 | +} |
| 10522 | + |
| 10523 | +CLI11_INLINE Option *App::_add_flag_internal(std::string flag_name, CLI::callback_t fun, std::string flag_description) { |
| 10524 | + Option *opt = nullptr; |
| 10525 | + if(detail::has_default_flag_values(flag_name)) { |
| 10526 | + // check for default values and if it has them |
| 10527 | + auto flag_defaults = detail::get_default_flag_values(flag_name); |
| 10528 | + detail::remove_default_flag_values(flag_name); |
| 10529 | + opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false); |
| 10530 | + for(const auto &fname : flag_defaults) |
| 10531 | + opt->fnames_.push_back(fname.first); |
| 10532 | + opt->default_flag_values_ = std::move(flag_defaults); |
| 10533 | + } else { |
| 10534 | + opt = add_option(std::move(flag_name), std::move(fun), std::move(flag_description), false); |
| 10535 | + } |
| 10536 | + // flags cannot have positional values |
| 10537 | + if(opt->get_positional()) { |
| 10538 | + auto pos_name = opt->get_name(true); |
| 10539 | + remove_option(opt); |
| 10540 | + throw IncorrectConstruction::PositionalFlag(pos_name); |
| 10541 | + } |
| 10542 | + opt->multi_option_policy(MultiOptionPolicy::TakeLast); |
| 10543 | + opt->expected(0); |
| 10544 | + opt->required(false); |
| 10545 | + return opt; |
| 10546 | +} |
| 10547 | + |
| 10548 | +CLI11_INLINE Option *App::add_flag_callback(std::string flag_name, |
| 10549 | + std::function<void(void)> function, ///< A function to call, void(void) |
| 10550 | + std::string flag_description) { |
| 10551 | + |
| 10552 | + CLI::callback_t fun = [function](const CLI::results_t &res) { |
| 10553 | + using CLI::detail::lexical_cast; |
| 10554 | + bool trigger{false}; |
| 10555 | + auto result = lexical_cast(res[0], trigger); |
| 10556 | + if(result && trigger) { |
| 10557 | + function(); |
| 10558 | + } |
| 10559 | + return result; |
| 10560 | + }; |
| 10561 | + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); |
| 10562 | +} |
| 10563 | + |
| 10564 | +CLI11_INLINE Option * |
| 10565 | +App::add_flag_function(std::string flag_name, |
| 10566 | + std::function<void(std::int64_t)> function, ///< A function to call, void(int) |
| 10567 | + std::string flag_description) { |
| 10568 | + |
| 10569 | + CLI::callback_t fun = [function](const CLI::results_t &res) { |
| 10570 | + using CLI::detail::lexical_cast; |
| 10571 | + std::int64_t flag_count{0}; |
| 10572 | + lexical_cast(res[0], flag_count); |
| 10573 | + function(flag_count); |
| 10574 | + return true; |
| 10575 | + }; |
| 10576 | + return _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)) |
| 10577 | + ->multi_option_policy(MultiOptionPolicy::Sum); |
| 10578 | +} |
| 10579 | + |
| 10580 | +CLI11_INLINE Option *App::set_config(std::string option_name, |
| 10581 | + std::string default_filename, |
| 10582 | + const std::string &help_message, |
| 10583 | + bool config_required) { |
| 10584 | + |
| 10585 | + // Remove existing config if present |
| 10586 | + if(config_ptr_ != nullptr) { |
| 10587 | + remove_option(config_ptr_); |
| 10588 | + config_ptr_ = nullptr; // need to remove the config_ptr completely |
| 10589 | + } |
| 10590 | + |
| 10591 | + // Only add config if option passed |
| 10592 | + if(!option_name.empty()) { |
| 10593 | + config_ptr_ = add_option(option_name, help_message); |
| 10594 | + if(config_required) { |
| 10595 | + config_ptr_->required(); |
| 10596 | + } |
| 10597 | + if(!default_filename.empty()) { |
| 10598 | + config_ptr_->default_str(std::move(default_filename)); |
| 10599 | + config_ptr_->force_callback_ = true; |
| 10600 | + } |
| 10601 | + config_ptr_->configurable(false); |
| 10602 | + // set the option to take the last value and reverse given by default |
| 10603 | + config_ptr_->multi_option_policy(MultiOptionPolicy::Reverse); |
| 10604 | + } |
| 10605 | + |
| 10606 | + return config_ptr_; |
| 10607 | +} |
| 10608 | + |
| 10609 | +CLI11_INLINE bool App::remove_option(Option *opt) { |
| 10610 | + // Make sure no links exist |
| 10611 | + for(Option_p &op : options_) { |
| 10612 | + op->remove_needs(opt); |
| 10613 | + op->remove_excludes(opt); |
| 10614 | + } |
| 10615 | + |
| 10616 | + if(help_ptr_ == opt) |
| 10617 | + help_ptr_ = nullptr; |
| 10618 | + if(help_all_ptr_ == opt) |
| 10619 | + help_all_ptr_ = nullptr; |
| 10620 | + if(config_ptr_ == opt) |
| 10621 | + config_ptr_ = nullptr; |
| 10622 | + |
| 10623 | + auto iterator = |
| 10624 | + std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; }); |
| 10625 | + if(iterator != std::end(options_)) { |
| 10626 | + options_.erase(iterator); |
| 10627 | + return true; |
| 10628 | + } |
| 10629 | + return false; |
| 10630 | +} |
| 10631 | + |
| 10632 | +CLI11_INLINE App *App::add_subcommand(std::string subcommand_name, std::string subcommand_description) { |
| 10633 | + if(!subcommand_name.empty() && !detail::valid_name_string(subcommand_name)) { |
| 10634 | + if(!detail::valid_first_char(subcommand_name[0])) { |
| 10635 | + throw IncorrectConstruction( |
| 10636 | + "Subcommand name starts with invalid character, '!' and '-' and control characters"); |
| 10637 | + } |
| 10638 | + for(auto c : subcommand_name) { |
| 10639 | + if(!detail::valid_later_char(c)) { |
| 10640 | + throw IncorrectConstruction(std::string("Subcommand name contains invalid character ('") + c + |
| 10641 | + "'), all characters are allowed except" |
| 10642 | + "'=',':','{','}', ' ', and control characters"); |
| 10643 | + } |
| 10644 | + } |
| 10645 | + } |
| 10646 | + CLI::App_p subcom = std::shared_ptr<App>(new App(std::move(subcommand_description), subcommand_name, this)); |
| 10647 | + return add_subcommand(std::move(subcom)); |
| 10648 | +} |
| 10649 | + |
| 10650 | +CLI11_INLINE App *App::add_subcommand(CLI::App_p subcom) { |
| 10651 | + if(!subcom) |
| 10652 | + throw IncorrectConstruction("passed App is not valid"); |
| 10653 | + auto *ckapp = (name_.empty() && parent_ != nullptr) ? _get_fallthrough_parent() : this; |
| 10654 | + const auto &mstrg = _compare_subcommand_names(*subcom, *ckapp); |
| 10655 | + if(!mstrg.empty()) { |
| 10656 | + throw(OptionAlreadyAdded("subcommand name or alias matches existing subcommand: " + mstrg)); |
| 10657 | + } |
| 10658 | + subcom->parent_ = this; |
| 10659 | + subcommands_.push_back(std::move(subcom)); |
| 10660 | + return subcommands_.back().get(); |
| 10661 | +} |
| 10662 | + |
| 10663 | +CLI11_INLINE bool App::remove_subcommand(App *subcom) { |
| 10664 | + // Make sure no links exist |
| 10665 | + for(App_p &sub : subcommands_) { |
| 10666 | + sub->remove_excludes(subcom); |
| 10667 | + sub->remove_needs(subcom); |
| 10668 | + } |
| 10669 | + |
| 10670 | + auto iterator = std::find_if( |
| 10671 | + std::begin(subcommands_), std::end(subcommands_), [subcom](const App_p &v) { return v.get() == subcom; }); |
| 10672 | + if(iterator != std::end(subcommands_)) { |
| 10673 | + subcommands_.erase(iterator); |
| 10674 | + return true; |
| 10675 | + } |
| 10676 | + return false; |
| 10677 | +} |
| 10678 | + |
| 10679 | +CLI11_INLINE App *App::get_subcommand(const App *subcom) const { |
| 10680 | + if(subcom == nullptr) |
| 10681 | + throw OptionNotFound("nullptr passed"); |
| 10682 | + for(const App_p &subcomptr : subcommands_) |
| 10683 | + if(subcomptr.get() == subcom) |
| 10684 | + return subcomptr.get(); |
| 10685 | + throw OptionNotFound(subcom->get_name()); |
| 10686 | +} |
| 10687 | + |
| 10688 | +CLI11_NODISCARD CLI11_INLINE App *App::get_subcommand(std::string subcom) const { |
| 10689 | + auto *subc = _find_subcommand(subcom, false, false); |
| 10690 | + if(subc == nullptr) |
| 10691 | + throw OptionNotFound(subcom); |
| 10692 | + return subc; |
| 10693 | +} |
| 10694 | + |
| 10695 | +CLI11_NODISCARD CLI11_INLINE App *App::get_subcommand_no_throw(std::string subcom) const noexcept { |
| 10696 | + return _find_subcommand(subcom, false, false); |
| 10697 | +} |
| 10698 | + |
| 10699 | +CLI11_NODISCARD CLI11_INLINE App *App::get_subcommand(int index) const { |
| 10700 | + if(index >= 0) { |
| 10701 | + auto uindex = static_cast<unsigned>(index); |
| 10702 | + if(uindex < subcommands_.size()) |
| 10703 | + return subcommands_[uindex].get(); |
| 10704 | + } |
| 10705 | + throw OptionNotFound(std::to_string(index)); |
| 10706 | +} |
| 10707 | + |
| 10708 | +CLI11_INLINE CLI::App_p App::get_subcommand_ptr(App *subcom) const { |
| 10709 | + if(subcom == nullptr) |
| 10710 | + throw OptionNotFound("nullptr passed"); |
| 10711 | + for(const App_p &subcomptr : subcommands_) |
| 10712 | + if(subcomptr.get() == subcom) |
| 10713 | + return subcomptr; |
| 10714 | + throw OptionNotFound(subcom->get_name()); |
| 10715 | +} |
| 10716 | + |
| 10717 | +CLI11_NODISCARD CLI11_INLINE CLI::App_p App::get_subcommand_ptr(std::string subcom) const { |
| 10718 | + for(const App_p &subcomptr : subcommands_) |
| 10719 | + if(subcomptr->check_name(subcom)) |
| 10720 | + return subcomptr; |
| 10721 | + throw OptionNotFound(subcom); |
| 10722 | +} |
| 10723 | + |
| 10724 | +CLI11_NODISCARD CLI11_INLINE CLI::App_p App::get_subcommand_ptr(int index) const { |
| 10725 | + if(index >= 0) { |
| 10726 | + auto uindex = static_cast<unsigned>(index); |
| 10727 | + if(uindex < subcommands_.size()) |
| 10728 | + return subcommands_[uindex]; |
| 10729 | + } |
| 10730 | + throw OptionNotFound(std::to_string(index)); |
| 10731 | +} |
| 10732 | + |
| 10733 | +CLI11_NODISCARD CLI11_INLINE CLI::App *App::get_option_group(std::string group_name) const { |
| 10734 | + for(const App_p &app : subcommands_) { |
| 10735 | + if(app->name_.empty() && app->group_ == group_name) { |
| 10736 | + return app.get(); |
| 10737 | + } |
| 10738 | + } |
| 10739 | + throw OptionNotFound(group_name); |
| 10740 | +} |
| 10741 | + |
| 10742 | +CLI11_NODISCARD CLI11_INLINE std::size_t App::count_all() const { |
| 10743 | + std::size_t cnt{0}; |
| 10744 | + for(const auto &opt : options_) { |
| 10745 | + cnt += opt->count(); |
| 10746 | + } |
| 10747 | + for(const auto &sub : subcommands_) { |
| 10748 | + cnt += sub->count_all(); |
| 10749 | + } |
| 10750 | + if(!get_name().empty()) { // for named subcommands add the number of times the subcommand was called |
| 10751 | + cnt += parsed_; |
| 10752 | + } |
| 10753 | + return cnt; |
| 10754 | +} |
| 10755 | + |
| 10756 | +CLI11_INLINE void App::clear() { |
| 10757 | + |
| 10758 | + parsed_ = 0; |
| 10759 | + pre_parse_called_ = false; |
| 10760 | + |
| 10761 | + missing_.clear(); |
| 10762 | + parsed_subcommands_.clear(); |
| 10763 | + parse_order_.clear(); |
| 10764 | + for(const Option_p &opt : options_) { |
| 10765 | + opt->clear(); |
| 10766 | + } |
| 10767 | + for(const App_p &subc : subcommands_) { |
| 10768 | + subc->clear(); |
| 10769 | + } |
| 10770 | +} |
| 10771 | + |
| 10772 | +CLI11_INLINE void App::parse(int argc, const char *const *argv) { parse_char_t(argc, argv); } |
| 10773 | +CLI11_INLINE void App::parse(int argc, const wchar_t *const *argv) { parse_char_t(argc, argv); } |
| 10774 | + |
| 10775 | +namespace detail { |
| 10776 | + |
| 10777 | +// Do nothing or perform narrowing |
| 10778 | +CLI11_INLINE const char *maybe_narrow(const char *str) { return str; } |
| 10779 | +CLI11_INLINE std::string maybe_narrow(const wchar_t *str) { return narrow(str); } |
| 10780 | + |
| 10781 | +} // namespace detail |
| 10782 | + |
| 10783 | +template <class CharT> CLI11_INLINE void App::parse_char_t(int argc, const CharT *const *argv) { |
| 10784 | + // If the name is not set, read from command line |
| 10785 | + if(name_.empty() || has_automatic_name_) { |
| 10786 | + has_automatic_name_ = true; |
| 10787 | + name_ = detail::maybe_narrow(argv[0]); |
| 10788 | + } |
| 10789 | + |
| 10790 | + std::vector<std::string> args; |
| 10791 | + args.reserve(static_cast<std::size_t>(argc) - 1U); |
| 10792 | + for(auto i = static_cast<std::size_t>(argc) - 1U; i > 0U; --i) |
| 10793 | + args.emplace_back(detail::maybe_narrow(argv[i])); |
| 10794 | + |
| 10795 | + parse(std::move(args)); |
| 10796 | +} |
| 10797 | + |
| 10798 | +CLI11_INLINE void App::parse(std::string commandline, bool program_name_included) { |
| 10799 | + |
| 10800 | + if(program_name_included) { |
| 10801 | + auto nstr = detail::split_program_name(commandline); |
| 10802 | + if((name_.empty()) || (has_automatic_name_)) { |
| 10803 | + has_automatic_name_ = true; |
| 10804 | + name_ = nstr.first; |
| 10805 | + } |
| 10806 | + commandline = std::move(nstr.second); |
| 10807 | + } else { |
| 10808 | + detail::trim(commandline); |
| 10809 | + } |
| 10810 | + // the next section of code is to deal with quoted arguments after an '=' or ':' for windows like operations |
| 10811 | + if(!commandline.empty()) { |
| 10812 | + commandline = detail::find_and_modify(commandline, "=", detail::escape_detect); |
| 10813 | + if(allow_windows_style_options_) |
| 10814 | + commandline = detail::find_and_modify(commandline, ":", detail::escape_detect); |
| 10815 | + } |
| 10816 | + |
| 10817 | + auto args = detail::split_up(std::move(commandline)); |
| 10818 | + // remove all empty strings |
| 10819 | + args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end()); |
| 10820 | + try { |
| 10821 | + detail::remove_quotes(args); |
| 10822 | + } catch(const std::invalid_argument &arg) { |
| 10823 | + throw CLI::ParseError(arg.what(), CLI::ExitCodes::InvalidError); |
| 10824 | + } |
| 10825 | + std::reverse(args.begin(), args.end()); |
| 10826 | + parse(std::move(args)); |
| 10827 | +} |
| 10828 | + |
| 10829 | +CLI11_INLINE void App::parse(std::wstring commandline, bool program_name_included) { |
| 10830 | + parse(narrow(commandline), program_name_included); |
| 10831 | +} |
| 10832 | + |
| 10833 | +CLI11_INLINE void App::parse(std::vector<std::string> &args) { |
| 10834 | + // Clear if parsed |
| 10835 | + if(parsed_ > 0) |
| 10836 | + clear(); |
| 10837 | + |
| 10838 | + // parsed_ is incremented in commands/subcommands, |
| 10839 | + // but placed here to make sure this is cleared when |
| 10840 | + // running parse after an error is thrown, even by _validate or _configure. |
| 10841 | + parsed_ = 1; |
| 10842 | + _validate(); |
| 10843 | + _configure(); |
| 10844 | + // set the parent as nullptr as this object should be the top now |
| 10845 | + parent_ = nullptr; |
| 10846 | + parsed_ = 0; |
| 10847 | + |
| 10848 | + _parse(args); |
| 10849 | + run_callback(); |
| 10850 | +} |
| 10851 | + |
| 10852 | +CLI11_INLINE void App::parse(std::vector<std::string> &&args) { |
| 10853 | + // Clear if parsed |
| 10854 | + if(parsed_ > 0) |
| 10855 | + clear(); |
| 10856 | + |
| 10857 | + // parsed_ is incremented in commands/subcommands, |
| 10858 | + // but placed here to make sure this is cleared when |
| 10859 | + // running parse after an error is thrown, even by _validate or _configure. |
| 10860 | + parsed_ = 1; |
| 10861 | + _validate(); |
| 10862 | + _configure(); |
| 10863 | + // set the parent as nullptr as this object should be the top now |
| 10864 | + parent_ = nullptr; |
| 10865 | + parsed_ = 0; |
| 10866 | + |
| 10867 | + _parse(std::move(args)); |
| 10868 | + run_callback(); |
| 10869 | +} |
| 10870 | + |
| 10871 | +CLI11_INLINE void App::parse_from_stream(std::istream &input) { |
| 10872 | + if(parsed_ == 0) { |
| 10873 | + _validate(); |
| 10874 | + _configure(); |
| 10875 | + // set the parent as nullptr as this object should be the top now |
| 10876 | + } |
| 10877 | + |
| 10878 | + _parse_stream(input); |
| 10879 | + run_callback(); |
| 10880 | +} |
| 10881 | + |
| 10882 | +CLI11_INLINE int App::exit(const Error &e, std::ostream &out, std::ostream &err) const { |
| 10883 | + |
| 10884 | + /// Avoid printing anything if this is a CLI::RuntimeError |
| 10885 | + if(e.get_name() == "RuntimeError") |
| 10886 | + return e.get_exit_code(); |
| 10887 | + |
| 10888 | + if(e.get_name() == "CallForHelp") { |
| 10889 | + out << help(); |
| 10890 | + return e.get_exit_code(); |
| 10891 | + } |
| 10892 | + |
| 10893 | + if(e.get_name() == "CallForAllHelp") { |
| 10894 | + out << help("", AppFormatMode::All); |
| 10895 | + return e.get_exit_code(); |
| 10896 | + } |
| 10897 | + |
| 10898 | + if(e.get_name() == "CallForVersion") { |
| 10899 | + out << e.what() << '\n'; |
| 10900 | + return e.get_exit_code(); |
| 10901 | + } |
| 10902 | + |
| 10903 | + if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) { |
| 10904 | + if(failure_message_) |
| 10905 | + err << failure_message_(this, e) << std::flush; |
| 10906 | + } |
| 10907 | + |
| 10908 | + return e.get_exit_code(); |
| 10909 | +} |
| 10910 | + |
| 10911 | +CLI11_INLINE std::vector<const App *> App::get_subcommands(const std::function<bool(const App *)> &filter) const { |
| 10912 | + std::vector<const App *> subcomms(subcommands_.size()); |
| 10913 | + std::transform( |
| 10914 | + std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) { return v.get(); }); |
| 10915 | + |
| 10916 | + if(filter) { |
| 10917 | + subcomms.erase(std::remove_if(std::begin(subcomms), |
| 10918 | + std::end(subcomms), |
| 10919 | + [&filter](const App *app) { return !filter(app); }), |
| 10920 | + std::end(subcomms)); |
| 10921 | + } |
| 10922 | + |
| 10923 | + return subcomms; |
| 10924 | +} |
| 10925 | + |
| 10926 | +CLI11_INLINE std::vector<App *> App::get_subcommands(const std::function<bool(App *)> &filter) { |
| 10927 | + std::vector<App *> subcomms(subcommands_.size()); |
| 10928 | + std::transform( |
| 10929 | + std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) { return v.get(); }); |
| 10930 | + |
| 10931 | + if(filter) { |
| 10932 | + subcomms.erase( |
| 10933 | + std::remove_if(std::begin(subcomms), std::end(subcomms), [&filter](App *app) { return !filter(app); }), |
| 10934 | + std::end(subcomms)); |
| 10935 | + } |
| 10936 | + |
| 10937 | + return subcomms; |
| 10938 | +} |
| 10939 | + |
| 10940 | +CLI11_INLINE bool App::remove_excludes(Option *opt) { |
| 10941 | + auto iterator = std::find(std::begin(exclude_options_), std::end(exclude_options_), opt); |
| 10942 | + if(iterator == std::end(exclude_options_)) { |
| 10943 | + return false; |
| 10944 | + } |
| 10945 | + exclude_options_.erase(iterator); |
| 10946 | + return true; |
| 10947 | +} |
| 10948 | + |
| 10949 | +CLI11_INLINE bool App::remove_excludes(App *app) { |
| 10950 | + auto iterator = std::find(std::begin(exclude_subcommands_), std::end(exclude_subcommands_), app); |
| 10951 | + if(iterator == std::end(exclude_subcommands_)) { |
| 10952 | + return false; |
| 10953 | + } |
| 10954 | + auto *other_app = *iterator; |
| 10955 | + exclude_subcommands_.erase(iterator); |
| 10956 | + other_app->remove_excludes(this); |
| 10957 | + return true; |
| 10958 | +} |
| 10959 | + |
| 10960 | +CLI11_INLINE bool App::remove_needs(Option *opt) { |
| 10961 | + auto iterator = std::find(std::begin(need_options_), std::end(need_options_), opt); |
| 10962 | + if(iterator == std::end(need_options_)) { |
| 10963 | + return false; |
| 10964 | + } |
| 10965 | + need_options_.erase(iterator); |
| 10966 | + return true; |
| 10967 | +} |
| 10968 | + |
| 10969 | +CLI11_INLINE bool App::remove_needs(App *app) { |
| 10970 | + auto iterator = std::find(std::begin(need_subcommands_), std::end(need_subcommands_), app); |
| 10971 | + if(iterator == std::end(need_subcommands_)) { |
| 10972 | + return false; |
| 10973 | + } |
| 10974 | + need_subcommands_.erase(iterator); |
| 10975 | + return true; |
| 10976 | +} |
| 10977 | + |
| 10978 | +CLI11_NODISCARD CLI11_INLINE std::string App::help(std::string prev, AppFormatMode mode) const { |
| 10979 | + if(prev.empty()) |
| 10980 | + prev = get_name(); |
| 10981 | + else |
| 10982 | + prev += " " + get_name(); |
| 10983 | + |
| 10984 | + // Delegate to subcommand if needed |
| 10985 | + auto selected_subcommands = get_subcommands(); |
| 10986 | + if(!selected_subcommands.empty()) { |
| 10987 | + return selected_subcommands.back()->help(prev, mode); |
| 10988 | + } |
| 10989 | + return formatter_->make_help(this, prev, mode); |
| 10990 | +} |
| 10991 | + |
| 10992 | +CLI11_NODISCARD CLI11_INLINE std::string App::version() const { |
| 10993 | + std::string val; |
| 10994 | + if(version_ptr_ != nullptr) { |
| 10995 | + // copy the results for reuse later |
| 10996 | + results_t rv = version_ptr_->results(); |
| 10997 | + version_ptr_->clear(); |
| 10998 | + version_ptr_->add_result("true"); |
| 10999 | + try { |
| 11000 | + version_ptr_->run_callback(); |
| 11001 | + } catch(const CLI::CallForVersion &cfv) { |
| 11002 | + val = cfv.what(); |
| 11003 | + } |
| 11004 | + version_ptr_->clear(); |
| 11005 | + version_ptr_->add_result(rv); |
| 11006 | + } |
| 11007 | + return val; |
| 11008 | +} |
| 11009 | + |
| 11010 | +CLI11_INLINE std::vector<const Option *> App::get_options(const std::function<bool(const Option *)> filter) const { |
| 11011 | + std::vector<const Option *> options(options_.size()); |
| 11012 | + std::transform( |
| 11013 | + std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) { return val.get(); }); |
| 11014 | + |
| 11015 | + if(filter) { |
| 11016 | + options.erase(std::remove_if(std::begin(options), |
| 11017 | + std::end(options), |
| 11018 | + [&filter](const Option *opt) { return !filter(opt); }), |
| 11019 | + std::end(options)); |
| 11020 | + } |
| 11021 | + for(const auto &subcp : subcommands_) { |
| 11022 | + // also check down into nameless subcommands |
| 11023 | + const App *subc = subcp.get(); |
| 11024 | + if(subc->get_name().empty() && !subc->get_group().empty() && subc->get_group().front() == '+') { |
| 11025 | + std::vector<const Option *> subcopts = subc->get_options(filter); |
| 11026 | + options.insert(options.end(), subcopts.begin(), subcopts.end()); |
| 11027 | + } |
| 11028 | + } |
| 11029 | + if(fallthrough_ && parent_ != nullptr) { |
| 11030 | + const auto *fallthrough_parent = _get_fallthrough_parent(); |
| 11031 | + std::vector<const Option *> subcopts = fallthrough_parent->get_options(filter); |
| 11032 | + for(const auto *opt : subcopts) { |
| 11033 | + if(std::find_if(options.begin(), options.end(), [opt](const Option *opt2) { |
| 11034 | + return opt->check_name(opt2->get_name()); |
| 11035 | + }) == options.end()) { |
| 11036 | + options.push_back(opt); |
| 11037 | + } |
| 11038 | + } |
| 11039 | + } |
| 11040 | + return options; |
| 11041 | +} |
| 11042 | + |
| 11043 | +CLI11_INLINE std::vector<Option *> App::get_options(const std::function<bool(Option *)> filter) { |
| 11044 | + std::vector<Option *> options(options_.size()); |
| 11045 | + std::transform( |
| 11046 | + std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) { return val.get(); }); |
| 11047 | + |
| 11048 | + if(filter) { |
| 11049 | + options.erase( |
| 11050 | + std::remove_if(std::begin(options), std::end(options), [&filter](Option *opt) { return !filter(opt); }), |
| 11051 | + std::end(options)); |
| 11052 | + } |
| 11053 | + for(auto &subc : subcommands_) { |
| 11054 | + // also check down into nameless subcommands and specific groups |
| 11055 | + if(subc->get_name().empty() || (!subc->get_group().empty() && subc->get_group().front() == '+')) { |
| 11056 | + auto subcopts = subc->get_options(filter); |
| 11057 | + options.insert(options.end(), subcopts.begin(), subcopts.end()); |
| 11058 | + } |
| 11059 | + } |
| 11060 | + if(fallthrough_ && parent_ != nullptr) { |
| 11061 | + auto *fallthrough_parent = _get_fallthrough_parent(); |
| 11062 | + std::vector<Option *> subcopts = fallthrough_parent->get_options(filter); |
| 11063 | + for(auto *opt : subcopts) { |
| 11064 | + if(std::find_if(options.begin(), options.end(), [opt](Option *opt2) { |
| 11065 | + return opt->check_name(opt2->get_name()); |
| 11066 | + }) == options.end()) { |
| 11067 | + options.push_back(opt); |
| 11068 | + } |
| 11069 | + } |
| 11070 | + } |
| 11071 | + return options; |
| 11072 | +} |
| 11073 | + |
| 11074 | +CLI11_NODISCARD CLI11_INLINE Option *App::get_option_no_throw(std::string option_name) noexcept { |
| 11075 | + for(Option_p &opt : options_) { |
| 11076 | + if(opt->check_name(option_name)) { |
| 11077 | + return opt.get(); |
| 11078 | + } |
| 11079 | + } |
| 11080 | + for(auto &subc : subcommands_) { |
| 11081 | + // also check down into nameless subcommands |
| 11082 | + if(subc->get_name().empty()) { |
| 11083 | + auto *opt = subc->get_option_no_throw(option_name); |
| 11084 | + if(opt != nullptr) { |
| 11085 | + return opt; |
| 11086 | + } |
| 11087 | + } |
| 11088 | + } |
| 11089 | + if(fallthrough_ && parent_ != nullptr) { |
| 11090 | + return _get_fallthrough_parent()->get_option_no_throw(option_name); |
| 11091 | + } |
| 11092 | + return nullptr; |
| 11093 | +} |
| 11094 | + |
| 11095 | +CLI11_NODISCARD CLI11_INLINE const Option *App::get_option_no_throw(std::string option_name) const noexcept { |
| 11096 | + for(const Option_p &opt : options_) { |
| 11097 | + if(opt->check_name(option_name)) { |
| 11098 | + return opt.get(); |
| 11099 | + } |
| 11100 | + } |
| 11101 | + for(const auto &subc : subcommands_) { |
| 11102 | + // also check down into nameless subcommands |
| 11103 | + if(subc->get_name().empty()) { |
| 11104 | + auto *opt = subc->get_option_no_throw(option_name); |
| 11105 | + if(opt != nullptr) { |
| 11106 | + return opt; |
| 11107 | + } |
| 11108 | + } |
| 11109 | + } |
| 11110 | + if(fallthrough_ && parent_ != nullptr) { |
| 11111 | + return _get_fallthrough_parent()->get_option_no_throw(option_name); |
| 11112 | + } |
| 11113 | + return nullptr; |
| 11114 | +} |
| 11115 | + |
| 11116 | +CLI11_NODISCARD CLI11_INLINE std::string App::get_display_name(bool with_aliases) const { |
| 11117 | + if(name_.empty()) { |
| 11118 | + return std::string("[Option Group: ") + get_group() + "]"; |
| 11119 | + } |
| 11120 | + if(aliases_.empty() || !with_aliases) { |
| 11121 | + return name_; |
| 11122 | + } |
| 11123 | + std::string dispname = name_; |
| 11124 | + for(const auto &lalias : aliases_) { |
| 11125 | + dispname.push_back(','); |
| 11126 | + dispname.push_back(' '); |
| 11127 | + dispname.append(lalias); |
| 11128 | + } |
| 11129 | + return dispname; |
| 11130 | +} |
| 11131 | + |
| 11132 | +CLI11_NODISCARD CLI11_INLINE bool App::check_name(std::string name_to_check) const { |
| 11133 | + auto result = check_name_detail(std::move(name_to_check)); |
| 11134 | + return (result != NameMatch::none); |
| 11135 | +} |
| 11136 | + |
| 11137 | +CLI11_NODISCARD CLI11_INLINE App::NameMatch App::check_name_detail(std::string name_to_check) const { |
| 11138 | + std::string local_name = name_; |
| 11139 | + if(ignore_underscore_) { |
| 11140 | + local_name = detail::remove_underscore(name_); |
| 11141 | + name_to_check = detail::remove_underscore(name_to_check); |
| 11142 | + } |
| 11143 | + if(ignore_case_) { |
| 11144 | + local_name = detail::to_lower(name_); |
| 11145 | + name_to_check = detail::to_lower(name_to_check); |
| 11146 | + } |
| 11147 | + |
| 11148 | + if(local_name == name_to_check) { |
| 11149 | + return App::NameMatch::exact; |
| 11150 | + } |
| 11151 | + if(allow_prefix_matching_ && name_to_check.size() < local_name.size()) { |
| 11152 | + if(local_name.compare(0, name_to_check.size(), name_to_check) == 0) { |
| 11153 | + return App::NameMatch::prefix; |
| 11154 | + } |
| 11155 | + } |
| 11156 | + for(std::string les : aliases_) { // NOLINT(performance-for-range-copy) |
| 11157 | + if(ignore_underscore_) { |
| 11158 | + les = detail::remove_underscore(les); |
| 11159 | + } |
| 11160 | + if(ignore_case_) { |
| 11161 | + les = detail::to_lower(les); |
| 11162 | + } |
| 11163 | + if(les == name_to_check) { |
| 11164 | + return App::NameMatch::exact; |
| 11165 | + } |
| 11166 | + if(allow_prefix_matching_ && name_to_check.size() < les.size()) { |
| 11167 | + if(les.compare(0, name_to_check.size(), name_to_check) == 0) { |
| 11168 | + return App::NameMatch::prefix; |
| 11169 | + } |
| 11170 | + } |
| 11171 | + } |
| 11172 | + return App::NameMatch::none; |
| 11173 | +} |
| 11174 | + |
| 11175 | +CLI11_NODISCARD CLI11_INLINE std::vector<std::string> App::get_groups() const { |
| 11176 | + std::vector<std::string> groups; |
| 11177 | + |
| 11178 | + for(const Option_p &opt : options_) { |
| 11179 | + // Add group if it is not already in there |
| 11180 | + if(std::find(groups.begin(), groups.end(), opt->get_group()) == groups.end()) { |
| 11181 | + groups.push_back(opt->get_group()); |
| 11182 | + } |
| 11183 | + } |
| 11184 | + |
| 11185 | + return groups; |
| 11186 | +} |
| 11187 | + |
| 11188 | +CLI11_NODISCARD CLI11_INLINE std::vector<std::string> App::remaining(bool recurse) const { |
| 11189 | + std::vector<std::string> miss_list; |
| 11190 | + for(const std::pair<detail::Classifier, std::string> &miss : missing_) { |
| 11191 | + miss_list.push_back(std::get<1>(miss)); |
| 11192 | + } |
| 11193 | + // Get from a subcommand that may allow extras |
| 11194 | + if(recurse) { |
| 11195 | + if(allow_extras_ == ExtrasMode::Error || allow_extras_ == ExtrasMode::Ignore) { |
| 11196 | + for(const auto &sub : subcommands_) { |
| 11197 | + if(sub->name_.empty() && !sub->missing_.empty()) { |
| 11198 | + for(const std::pair<detail::Classifier, std::string> &miss : sub->missing_) { |
| 11199 | + miss_list.push_back(std::get<1>(miss)); |
| 11200 | + } |
| 11201 | + } |
| 11202 | + } |
| 11203 | + } |
| 11204 | + // Recurse into subcommands |
| 11205 | + |
| 11206 | + for(const App *sub : parsed_subcommands_) { |
| 11207 | + std::vector<std::string> output = sub->remaining(recurse); |
| 11208 | + std::copy(std::begin(output), std::end(output), std::back_inserter(miss_list)); |
| 11209 | + } |
| 11210 | + } |
| 11211 | + return miss_list; |
| 11212 | +} |
| 11213 | + |
| 11214 | +CLI11_NODISCARD CLI11_INLINE std::vector<std::string> App::remaining_for_passthrough(bool recurse) const { |
| 11215 | + std::vector<std::string> miss_list = remaining(recurse); |
| 11216 | + std::reverse(std::begin(miss_list), std::end(miss_list)); |
| 11217 | + return miss_list; |
| 11218 | +} |
| 11219 | + |
| 11220 | +CLI11_NODISCARD CLI11_INLINE std::size_t App::remaining_size(bool recurse) const { |
| 11221 | + auto remaining_options = static_cast<std::size_t>(std::count_if( |
| 11222 | + std::begin(missing_), std::end(missing_), [](const std::pair<detail::Classifier, std::string> &val) { |
| 11223 | + return val.first != detail::Classifier::POSITIONAL_MARK; |
| 11224 | + })); |
| 11225 | + |
| 11226 | + if(recurse) { |
| 11227 | + for(const App_p &sub : subcommands_) { |
| 11228 | + remaining_options += sub->remaining_size(recurse); |
| 11229 | + } |
| 11230 | + } |
| 11231 | + return remaining_options; |
| 11232 | +} |
| 11233 | + |
| 11234 | +CLI11_INLINE void App::_validate() const { |
| 11235 | + // count the number of positional only args |
| 11236 | + auto pcount = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) { |
| 11237 | + return opt->get_items_expected_max() >= detail::expected_max_vector_size && !opt->nonpositional(); |
| 11238 | + }); |
| 11239 | + if(pcount > 1) { |
| 11240 | + auto pcount_req = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) { |
| 11241 | + return opt->get_items_expected_max() >= detail::expected_max_vector_size && !opt->nonpositional() && |
| 11242 | + opt->get_required(); |
| 11243 | + }); |
| 11244 | + if(pcount - pcount_req > 1) { |
| 11245 | + throw InvalidError(name_); |
| 11246 | + } |
| 11247 | + } |
| 11248 | + |
| 11249 | + std::size_t nameless_subs{0}; |
| 11250 | + for(const App_p &app : subcommands_) { |
| 11251 | + app->_validate(); |
| 11252 | + if(app->get_name().empty()) |
| 11253 | + ++nameless_subs; |
| 11254 | + } |
| 11255 | + |
| 11256 | + if(require_option_min_ > 0) { |
| 11257 | + if(require_option_max_ > 0) { |
| 11258 | + if(require_option_max_ < require_option_min_) { |
| 11259 | + throw(InvalidError("Required min options greater than required max options", ExitCodes::InvalidError)); |
| 11260 | + } |
| 11261 | + } |
| 11262 | + if(require_option_min_ > (options_.size() + nameless_subs)) { |
| 11263 | + throw( |
| 11264 | + InvalidError("Required min options greater than number of available options", ExitCodes::InvalidError)); |
| 11265 | + } |
| 11266 | + } |
| 11267 | +} |
| 11268 | + |
| 11269 | +CLI11_INLINE void App::_configure() { |
| 11270 | + if(default_startup == startup_mode::enabled) { |
| 11271 | + disabled_ = false; |
| 11272 | + } else if(default_startup == startup_mode::disabled) { |
| 11273 | + disabled_ = true; |
| 11274 | + } |
| 11275 | + for(const App_p &app : subcommands_) { |
| 11276 | + if(app->has_automatic_name_) { |
| 11277 | + app->name_.clear(); |
| 11278 | + } |
| 11279 | + if(app->name_.empty()) { |
| 11280 | + app->fallthrough_ = false; // make sure fallthrough_ is false to prevent infinite loop |
| 11281 | + app->prefix_command_ = PrefixCommandMode::Off; |
| 11282 | + } |
| 11283 | + // make sure the parent is set to be this object in preparation for parse |
| 11284 | + app->parent_ = this; |
| 11285 | + app->_configure(); |
| 11286 | + } |
| 11287 | +} |
| 11288 | + |
| 11289 | +CLI11_INLINE void App::run_callback(bool final_mode, bool suppress_final_callback) { |
| 11290 | + pre_callback(); |
| 11291 | + // in the main app if immediate_callback_ is set it runs the main callback before the used subcommands |
| 11292 | + if(!final_mode && parse_complete_callback_) { |
| 11293 | + parse_complete_callback_(); |
| 11294 | + } |
| 11295 | + // run the callbacks for the received subcommands |
| 11296 | + for(App *subc : get_subcommands()) { |
| 11297 | + if(subc->parent_ == this) { |
| 11298 | + subc->run_callback(true, suppress_final_callback); |
| 11299 | + } |
| 11300 | + } |
| 11301 | + // now run callbacks for option_groups |
| 11302 | + for(auto &subc : subcommands_) { |
| 11303 | + if(subc->name_.empty() && subc->count_all() > 0) { |
| 11304 | + subc->run_callback(true, suppress_final_callback); |
| 11305 | + } |
| 11306 | + } |
| 11307 | + |
| 11308 | + // finally run the main callback |
| 11309 | + if(final_callback_ && (parsed_ > 0) && (!suppress_final_callback)) { |
| 11310 | + if(!name_.empty() || count_all() > 0 || parent_ == nullptr) { |
| 11311 | + final_callback_(); |
| 11312 | + } |
| 11313 | + } |
| 11314 | +} |
| 11315 | + |
| 11316 | +CLI11_NODISCARD CLI11_INLINE bool App::_valid_subcommand(const std::string ¤t, bool ignore_used) const { |
| 11317 | + // Don't match if max has been reached - but still check parents |
| 11318 | + if(require_subcommand_max_ != 0 && parsed_subcommands_.size() >= require_subcommand_max_ && |
| 11319 | + subcommand_fallthrough_) { |
| 11320 | + return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used); |
| 11321 | + } |
| 11322 | + auto *com = _find_subcommand(current, true, ignore_used); |
| 11323 | + if(com != nullptr) { |
| 11324 | + return true; |
| 11325 | + } |
| 11326 | + // Check parent if exists, else return false |
| 11327 | + if(subcommand_fallthrough_) { |
| 11328 | + return parent_ != nullptr && parent_->_valid_subcommand(current, ignore_used); |
| 11329 | + } |
| 11330 | + return false; |
| 11331 | +} |
| 11332 | + |
| 11333 | +CLI11_NODISCARD CLI11_INLINE detail::Classifier App::_recognize(const std::string ¤t, |
| 11334 | + bool ignore_used_subcommands) const { |
| 11335 | + std::string dummy1, dummy2; |
| 11336 | + |
| 11337 | + if(current == "--") |
| 11338 | + return detail::Classifier::POSITIONAL_MARK; |
| 11339 | + if(_valid_subcommand(current, ignore_used_subcommands)) |
| 11340 | + return detail::Classifier::SUBCOMMAND; |
| 11341 | + if(detail::split_long(current, dummy1, dummy2)) |
| 11342 | + return detail::Classifier::LONG; |
| 11343 | + if(detail::split_short(current, dummy1, dummy2)) { |
| 11344 | + if((dummy1[0] >= '0' && dummy1[0] <= '9') || |
| 11345 | + (dummy1[0] == '.' && !dummy2.empty() && (dummy2[0] >= '0' && dummy2[0] <= '9'))) { |
| 11346 | + // it looks like a number but check if it could be an option |
| 11347 | + if(get_option_no_throw(std::string{'-', dummy1[0]}) == nullptr) { |
| 11348 | + return detail::Classifier::NONE; |
| 11349 | + } |
| 11350 | + } |
| 11351 | + return detail::Classifier::SHORT; |
| 11352 | + } |
| 11353 | + if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2))) |
| 11354 | + return detail::Classifier::WINDOWS_STYLE; |
| 11355 | + if((current == "++") && !name_.empty() && parent_ != nullptr) |
| 11356 | + return detail::Classifier::SUBCOMMAND_TERMINATOR; |
| 11357 | + auto dotloc = current.find_first_of('.'); |
| 11358 | + if(dotloc != std::string::npos) { |
| 11359 | + auto *cm = _find_subcommand(current.substr(0, dotloc), true, ignore_used_subcommands); |
| 11360 | + if(cm != nullptr) { |
| 11361 | + auto res = cm->_recognize(current.substr(dotloc + 1), ignore_used_subcommands); |
| 11362 | + if(res == detail::Classifier::SUBCOMMAND) { |
| 11363 | + return res; |
| 11364 | + } |
| 11365 | + } |
| 11366 | + } |
| 11367 | + return detail::Classifier::NONE; |
| 11368 | +} |
| 11369 | + |
| 11370 | +CLI11_INLINE bool App::_process_config_file(const std::string &config_file, bool throw_error) { |
| 11371 | + auto path_result = detail::check_path(config_file.c_str()); |
| 11372 | + if(path_result == detail::path_type::file) { |
| 11373 | + try { |
| 11374 | + std::vector<ConfigItem> values = config_formatter_->from_file(config_file); |
| 11375 | + _parse_config(values); |
| 11376 | + return true; |
| 11377 | + } catch(const FileError &) { |
| 11378 | + if(throw_error) { |
| 11379 | + throw; |
| 11380 | + } |
| 11381 | + return false; |
| 11382 | + } |
| 11383 | + } else if(throw_error) { |
| 11384 | + throw FileError::Missing(config_file); |
| 11385 | + } else { |
| 11386 | + return false; |
| 11387 | + } |
| 11388 | +} |
| 11389 | + |
| 11390 | +CLI11_INLINE void App::_process_config_file() { |
| 11391 | + if(config_ptr_ != nullptr) { |
| 11392 | + bool config_required = config_ptr_->get_required(); |
| 11393 | + auto file_given = config_ptr_->count() > 0; |
| 11394 | + if(!(file_given || config_ptr_->envname_.empty())) { |
| 11395 | + std::string ename_string = detail::get_environment_value(config_ptr_->envname_); |
| 11396 | + if(!ename_string.empty()) { |
| 11397 | + config_ptr_->add_result(ename_string); |
| 11398 | + } |
| 11399 | + } |
| 11400 | + config_ptr_->run_callback(); |
| 11401 | + |
| 11402 | + auto config_files = config_ptr_->as<std::vector<std::string>>(); |
| 11403 | + bool files_used{file_given}; |
| 11404 | + if(config_files.empty() || config_files.front().empty()) { |
| 11405 | + if(config_required) { |
| 11406 | + throw FileError("config file is required but none was given"); |
| 11407 | + } |
| 11408 | + return; |
| 11409 | + } |
| 11410 | + for(const auto &config_file : config_files) { |
| 11411 | + if(_process_config_file(config_file, config_required || file_given)) { |
| 11412 | + files_used = true; |
| 11413 | + } |
| 11414 | + } |
| 11415 | + if(!files_used) { |
| 11416 | + // this is done so the count shows as 0 if no callbacks were processed |
| 11417 | + config_ptr_->clear(); |
| 11418 | + bool force = config_ptr_->force_callback_; |
| 11419 | + config_ptr_->force_callback_ = false; |
| 11420 | + config_ptr_->run_callback(); |
| 11421 | + config_ptr_->force_callback_ = force; |
| 11422 | + } |
| 11423 | + } |
| 11424 | +} |
| 11425 | + |
| 11426 | +CLI11_INLINE void App::_process_env() { |
| 11427 | + for(const Option_p &opt : options_) { |
| 11428 | + if(opt->count() == 0 && !opt->envname_.empty()) { |
| 11429 | + std::string ename_string = detail::get_environment_value(opt->envname_); |
| 11430 | + if(!ename_string.empty()) { |
| 11431 | + std::string result = ename_string; |
| 11432 | + result = opt->_validate(result, 0); |
| 11433 | + if(result.empty()) { |
| 11434 | + opt->add_result(ename_string); |
| 11435 | + } |
| 11436 | + } |
| 11437 | + } |
| 11438 | + } |
| 11439 | + |
| 11440 | + for(App_p &sub : subcommands_) { |
| 11441 | + if(sub->get_name().empty() || (sub->count_all() > 0 && !sub->parse_complete_callback_)) { |
| 11442 | + // only process environment variables if the callback has actually been triggered already |
| 11443 | + sub->_process_env(); |
| 11444 | + } |
| 11445 | + } |
| 11446 | +} |
| 11447 | + |
| 11448 | +CLI11_INLINE void App::_process_callbacks(CallbackPriority priority) { |
| 11449 | + |
| 11450 | + for(App_p &sub : subcommands_) { |
| 11451 | + // process the priority option_groups first |
| 11452 | + if(sub->get_name().empty() && sub->parse_complete_callback_) { |
| 11453 | + if(sub->count_all() > 0) { |
| 11454 | + sub->_process_callbacks(priority); |
| 11455 | + if(priority == CallbackPriority::Normal) { |
| 11456 | + // only run the subcommand callback at normal priority |
| 11457 | + sub->run_callback(); |
| 11458 | + } |
| 11459 | + } |
| 11460 | + } |
| 11461 | + } |
| 11462 | + |
| 11463 | + for(const Option_p &opt : options_) { |
| 11464 | + if(opt->get_callback_priority() == priority) { |
| 11465 | + if((*opt) && !opt->get_callback_run()) { |
| 11466 | + opt->run_callback(); |
| 11467 | + } |
| 11468 | + } |
| 11469 | + } |
| 11470 | + for(App_p &sub : subcommands_) { |
| 11471 | + if(!sub->parse_complete_callback_) { |
| 11472 | + sub->_process_callbacks(priority); |
| 11473 | + } |
| 11474 | + } |
| 11475 | +} |
| 11476 | + |
| 11477 | +CLI11_INLINE void App::_process_help_flags(CallbackPriority priority, bool trigger_help, bool trigger_all_help) const { |
| 11478 | + const Option *help_ptr = get_help_ptr(); |
| 11479 | + const Option *help_all_ptr = get_help_all_ptr(); |
| 11480 | + |
| 11481 | + if(help_ptr != nullptr && help_ptr->count() > 0 && help_ptr->get_callback_priority() == priority) { |
| 11482 | + trigger_help = true; |
| 11483 | + } |
| 11484 | + if(help_all_ptr != nullptr && help_all_ptr->count() > 0 && help_all_ptr->get_callback_priority() == priority) { |
| 11485 | + trigger_all_help = true; |
| 11486 | + } |
| 11487 | + |
| 11488 | + // If there were parsed subcommands, call those. First subcommand wins if there are multiple ones. |
| 11489 | + if(!parsed_subcommands_.empty()) { |
| 11490 | + for(const App *sub : parsed_subcommands_) { |
| 11491 | + sub->_process_help_flags(priority, trigger_help, trigger_all_help); |
| 11492 | + } |
| 11493 | + |
| 11494 | + // Only the final subcommand should call for help. All help wins over help. |
| 11495 | + } else if(trigger_all_help) { |
| 11496 | + throw CallForAllHelp(); |
| 11497 | + } else if(trigger_help) { |
| 11498 | + throw CallForHelp(); |
| 11499 | + } |
| 11500 | +} |
| 11501 | + |
| 11502 | +CLI11_INLINE void App::_process_requirements() { |
| 11503 | + // check excludes |
| 11504 | + bool excluded{false}; |
| 11505 | + std::string excluder; |
| 11506 | + for(const auto &opt : exclude_options_) { |
| 11507 | + if(opt->count() > 0) { |
| 11508 | + excluded = true; |
| 11509 | + excluder = opt->get_name(); |
| 11510 | + } |
| 11511 | + } |
| 11512 | + for(const auto &subc : exclude_subcommands_) { |
| 11513 | + if(subc->count_all() > 0) { |
| 11514 | + excluded = true; |
| 11515 | + excluder = subc->get_display_name(); |
| 11516 | + } |
| 11517 | + } |
| 11518 | + if(excluded) { |
| 11519 | + if(count_all() > 0) { |
| 11520 | + throw ExcludesError(get_display_name(), excluder); |
| 11521 | + } |
| 11522 | + // if we are excluded but didn't receive anything, just return |
| 11523 | + return; |
| 11524 | + } |
| 11525 | + |
| 11526 | + // check excludes |
| 11527 | + bool missing_needed{false}; |
| 11528 | + std::string missing_need; |
| 11529 | + for(const auto &opt : need_options_) { |
| 11530 | + if(opt->count() == 0) { |
| 11531 | + missing_needed = true; |
| 11532 | + missing_need = opt->get_name(); |
| 11533 | + } |
| 11534 | + } |
| 11535 | + for(const auto &subc : need_subcommands_) { |
| 11536 | + if(subc->count_all() == 0) { |
| 11537 | + missing_needed = true; |
| 11538 | + missing_need = subc->get_display_name(); |
| 11539 | + } |
| 11540 | + } |
| 11541 | + if(missing_needed) { |
| 11542 | + if(count_all() > 0) { |
| 11543 | + throw RequiresError(get_display_name(), missing_need); |
| 11544 | + } |
| 11545 | + // if we missing something but didn't have any options, just return |
| 11546 | + return; |
| 11547 | + } |
| 11548 | + |
| 11549 | + std::size_t used_options = 0; |
| 11550 | + for(const Option_p &opt : options_) { |
| 11551 | + |
| 11552 | + if(opt->count() != 0) { |
| 11553 | + ++used_options; |
| 11554 | + } |
| 11555 | + // Required but empty |
| 11556 | + if(opt->get_required() && opt->count() == 0) { |
| 11557 | + throw RequiredError(opt->get_name()); |
| 11558 | + } |
| 11559 | + // Requires |
| 11560 | + for(const Option *opt_req : opt->needs_) |
| 11561 | + if(opt->count() > 0 && opt_req->count() == 0) |
| 11562 | + throw RequiresError(opt->get_name(), opt_req->get_name()); |
| 11563 | + // Excludes |
| 11564 | + for(const Option *opt_ex : opt->excludes_) |
| 11565 | + if(opt->count() > 0 && opt_ex->count() != 0) |
| 11566 | + throw ExcludesError(opt->get_name(), opt_ex->get_name()); |
| 11567 | + } |
| 11568 | + // check for the required number of subcommands |
| 11569 | + if(require_subcommand_min_ > 0) { |
| 11570 | + auto selected_subcommands = get_subcommands(); |
| 11571 | + if(require_subcommand_min_ > selected_subcommands.size()) |
| 11572 | + throw RequiredError::Subcommand(require_subcommand_min_); |
| 11573 | + } |
| 11574 | + |
| 11575 | + // Max error cannot occur, the extra subcommand will parse as an ExtrasError or a remaining item. |
| 11576 | + |
| 11577 | + // run this loop to check how many unnamed subcommands were actually used since they are considered options |
| 11578 | + // from the perspective of an App |
| 11579 | + for(App_p &sub : subcommands_) { |
| 11580 | + if(sub->disabled_) |
| 11581 | + continue; |
| 11582 | + if(sub->name_.empty() && sub->count_all() > 0) { |
| 11583 | + ++used_options; |
| 11584 | + } |
| 11585 | + } |
| 11586 | + |
| 11587 | + if(require_option_min_ > used_options || (require_option_max_ > 0 && require_option_max_ < used_options)) { |
| 11588 | + auto option_list = detail::join(options_, [this](const Option_p &ptr) { |
| 11589 | + if(ptr.get() == help_ptr_ || ptr.get() == help_all_ptr_) { |
| 11590 | + return std::string{}; |
| 11591 | + } |
| 11592 | + return ptr->get_name(false, true); |
| 11593 | + }); |
| 11594 | + |
| 11595 | + auto subc_list = get_subcommands([](App *app) { return ((app->get_name().empty()) && (!app->disabled_)); }); |
| 11596 | + if(!subc_list.empty()) { |
| 11597 | + option_list += "," + detail::join(subc_list, [](const App *app) { return app->get_display_name(); }); |
| 11598 | + } |
| 11599 | + throw RequiredError::Option(require_option_min_, require_option_max_, used_options, option_list); |
| 11600 | + } |
| 11601 | + |
| 11602 | + // now process the requirements for subcommands if needed |
| 11603 | + for(App_p &sub : subcommands_) { |
| 11604 | + if(sub->disabled_) |
| 11605 | + continue; |
| 11606 | + if(sub->name_.empty() && sub->required_ == false) { |
| 11607 | + if(sub->count_all() == 0) { |
| 11608 | + if(require_option_min_ > 0 && require_option_min_ <= used_options) { |
| 11609 | + continue; |
| 11610 | + // if we have met the requirement and there is nothing in this option group skip checking |
| 11611 | + // requirements |
| 11612 | + } |
| 11613 | + if(require_option_max_ > 0 && used_options >= require_option_min_) { |
| 11614 | + continue; |
| 11615 | + // if we have met the requirement and there is nothing in this option group skip checking |
| 11616 | + // requirements |
| 11617 | + } |
| 11618 | + } |
| 11619 | + } |
| 11620 | + if(sub->count() > 0 || sub->name_.empty()) { |
| 11621 | + sub->_process_requirements(); |
| 11622 | + } |
| 11623 | + |
| 11624 | + if(sub->required_ && sub->count_all() == 0) { |
| 11625 | + throw(CLI::RequiredError(sub->get_display_name())); |
| 11626 | + } |
| 11627 | + } |
| 11628 | +} |
| 11629 | + |
| 11630 | +CLI11_INLINE void App::_process() { |
| 11631 | + // help takes precedence over other potential errors and config and environment shouldn't be processed if help |
| 11632 | + // throws |
| 11633 | + _process_callbacks(CallbackPriority::FirstPreHelp); |
| 11634 | + _process_help_flags(CallbackPriority::First); |
| 11635 | + _process_callbacks(CallbackPriority::First); |
| 11636 | + |
| 11637 | + std::exception_ptr config_exception; |
| 11638 | + try { |
| 11639 | + // the config file might generate a FileError but that should not be processed until later in the process |
| 11640 | + // to allow for help, version and other errors to generate first. |
| 11641 | + _process_config_file(); |
| 11642 | + |
| 11643 | + // process env shouldn't throw but no reason to process it if config generated an error |
| 11644 | + _process_env(); |
| 11645 | + } catch(const CLI::FileError &) { |
| 11646 | + config_exception = std::current_exception(); |
| 11647 | + } |
| 11648 | + // callbacks and requirements processing can generate exceptions which should take priority |
| 11649 | + // over the config file error if one exists. |
| 11650 | + _process_callbacks(CallbackPriority::PreRequirementsCheckPreHelp); |
| 11651 | + _process_help_flags(CallbackPriority::PreRequirementsCheck); |
| 11652 | + _process_callbacks(CallbackPriority::PreRequirementsCheck); |
| 11653 | + |
| 11654 | + _process_requirements(); |
| 11655 | + |
| 11656 | + _process_callbacks(CallbackPriority::NormalPreHelp); |
| 11657 | + _process_help_flags(CallbackPriority::Normal); |
| 11658 | + _process_callbacks(CallbackPriority::Normal); |
| 11659 | + |
| 11660 | + if(config_exception) { |
| 11661 | + std::rethrow_exception(config_exception); |
| 11662 | + } |
| 11663 | + |
| 11664 | + _process_callbacks(CallbackPriority::LastPreHelp); |
| 11665 | + _process_help_flags(CallbackPriority::Last); |
| 11666 | + _process_callbacks(CallbackPriority::Last); |
| 11667 | +} |
| 11668 | + |
| 11669 | +CLI11_INLINE void App::_process_extras() { |
| 11670 | + if(allow_extras_ == ExtrasMode::Error && prefix_command_ == PrefixCommandMode::Off) { |
| 11671 | + std::size_t num_left_over = remaining_size(); |
| 11672 | + if(num_left_over > 0) { |
| 11673 | + throw ExtrasError(name_, remaining(false)); |
| 11674 | + } |
| 11675 | + } |
| 11676 | + if(allow_extras_ == ExtrasMode::Error && prefix_command_ == PrefixCommandMode::SeparatorOnly) { |
| 11677 | + std::size_t num_left_over = remaining_size(); |
| 11678 | + if(num_left_over > 0) { |
| 11679 | + if(remaining(false).front() != "--") { |
| 11680 | + throw ExtrasError(name_, remaining(false)); |
| 11681 | + } |
| 11682 | + } |
| 11683 | + } |
| 11684 | + for(App_p &sub : subcommands_) { |
| 11685 | + if(sub->count() > 0) |
| 11686 | + sub->_process_extras(); |
| 11687 | + } |
| 11688 | +} |
| 11689 | + |
| 11690 | +CLI11_INLINE void App::increment_parsed() { |
| 11691 | + ++parsed_; |
| 11692 | + for(App_p &sub : subcommands_) { |
| 11693 | + if(sub->get_name().empty()) |
| 11694 | + sub->increment_parsed(); |
| 11695 | + } |
| 11696 | +} |
| 11697 | + |
| 11698 | +CLI11_INLINE void App::_parse(std::vector<std::string> &args) { |
| 11699 | + increment_parsed(); |
| 11700 | + _trigger_pre_parse(args.size()); |
| 11701 | + bool positional_only = false; |
| 11702 | + |
| 11703 | + while(!args.empty()) { |
| 11704 | + if(!_parse_single(args, positional_only)) { |
| 11705 | + break; |
| 11706 | + } |
| 11707 | + } |
| 11708 | + |
| 11709 | + if(parent_ == nullptr) { |
| 11710 | + _process(); |
| 11711 | + |
| 11712 | + // Throw error if any items are left over (depending on settings) |
| 11713 | + _process_extras(); |
| 11714 | + // Convert missing (pairs) to extras (string only) ready for processing in another app |
| 11715 | + args = remaining_for_passthrough(false); |
| 11716 | + } else if(parse_complete_callback_) { |
| 11717 | + _process_callbacks(CallbackPriority::FirstPreHelp); |
| 11718 | + _process_help_flags(CallbackPriority::First); |
| 11719 | + _process_callbacks(CallbackPriority::First); |
| 11720 | + _process_env(); |
| 11721 | + _process_callbacks(CallbackPriority::PreRequirementsCheckPreHelp); |
| 11722 | + _process_help_flags(CallbackPriority::PreRequirementsCheck); |
| 11723 | + _process_callbacks(CallbackPriority::PreRequirementsCheck); |
| 11724 | + _process_requirements(); |
| 11725 | + _process_callbacks(CallbackPriority::NormalPreHelp); |
| 11726 | + _process_help_flags(CallbackPriority::Normal); |
| 11727 | + _process_callbacks(CallbackPriority::Normal); |
| 11728 | + _process_callbacks(CallbackPriority::LastPreHelp); |
| 11729 | + _process_help_flags(CallbackPriority::Last); |
| 11730 | + _process_callbacks(CallbackPriority::Last); |
| 11731 | + run_callback(false, true); |
| 11732 | + } |
| 11733 | +} |
| 11734 | + |
| 11735 | +CLI11_INLINE void App::_parse(std::vector<std::string> &&args) { |
| 11736 | + // this can only be called by the top level in which case parent == nullptr by definition |
| 11737 | + // operation is simplified |
| 11738 | + increment_parsed(); |
| 11739 | + _trigger_pre_parse(args.size()); |
| 11740 | + bool positional_only = false; |
| 11741 | + |
| 11742 | + while(!args.empty()) { |
| 11743 | + _parse_single(args, positional_only); |
| 11744 | + } |
| 11745 | + _process(); |
| 11746 | + |
| 11747 | + // Throw error if any items are left over (depending on settings) |
| 11748 | + _process_extras(); |
| 11749 | +} |
| 11750 | + |
| 11751 | +CLI11_INLINE void App::_parse_stream(std::istream &input) { |
| 11752 | + auto values = config_formatter_->from_config(input); |
| 11753 | + _parse_config(values); |
| 11754 | + increment_parsed(); |
| 11755 | + _trigger_pre_parse(values.size()); |
| 11756 | + _process(); |
| 11757 | + |
| 11758 | + // Throw error if any items are left over (depending on settings) |
| 11759 | + _process_extras(); |
| 11760 | +} |
| 11761 | + |
| 11762 | +CLI11_INLINE void App::_parse_config(const std::vector<ConfigItem> &args) { |
| 11763 | + for(const ConfigItem &item : args) { |
| 11764 | + if(!_parse_single_config(item) && allow_config_extras_ == ConfigExtrasMode::Error) |
| 11765 | + throw ConfigError::Extras(item.fullname()); |
| 11766 | + } |
| 11767 | +} |
| 11768 | + |
| 11769 | +CLI11_INLINE bool |
| 11770 | +App::_add_flag_like_result(Option *op, const ConfigItem &item, const std::vector<std::string> &inputs) { |
| 11771 | + if(item.inputs.size() <= 1) { |
| 11772 | + // Flag parsing |
| 11773 | + auto res = config_formatter_->to_flag(item); |
| 11774 | + bool converted{false}; |
| 11775 | + if(op->get_disable_flag_override()) { |
| 11776 | + auto val = detail::to_flag_value(res); |
| 11777 | + if(val == 1) { |
| 11778 | + res = op->get_flag_value(item.name, "{}"); |
| 11779 | + converted = true; |
| 11780 | + } |
| 11781 | + } |
| 11782 | + |
| 11783 | + if(!converted) { |
| 11784 | + errno = 0; |
| 11785 | + if(res != "{}" || op->get_expected_max() <= 1) { |
| 11786 | + res = op->get_flag_value(item.name, res); |
| 11787 | + } |
| 11788 | + } |
| 11789 | + |
| 11790 | + op->add_result(res); |
| 11791 | + return true; |
| 11792 | + } |
| 11793 | + if(static_cast<int>(inputs.size()) > op->get_items_expected_max() && |
| 11794 | + op->get_multi_option_policy() != MultiOptionPolicy::TakeAll && |
| 11795 | + op->get_multi_option_policy() != MultiOptionPolicy::Join) { |
| 11796 | + if(op->get_items_expected_max() > 1) { |
| 11797 | + throw ArgumentMismatch::AtMost(item.fullname(), op->get_items_expected_max(), inputs.size()); |
| 11798 | + } |
| 11799 | + |
| 11800 | + if(!op->get_disable_flag_override()) { |
| 11801 | + throw ConversionError::TooManyInputsFlag(item.fullname()); |
| 11802 | + } |
| 11803 | + // if the disable flag override is set then we must have the flag values match a known flag value |
| 11804 | + // this is true regardless of the output value, so an array input is possible and must be accounted for |
| 11805 | + for(const auto &res : inputs) { |
| 11806 | + bool valid_value{false}; |
| 11807 | + if(op->default_flag_values_.empty()) { |
| 11808 | + if(res == "true" || res == "false" || res == "1" || res == "0") { |
| 11809 | + valid_value = true; |
| 11810 | + } |
| 11811 | + } else { |
| 11812 | + for(const auto &valid_res : op->default_flag_values_) { |
| 11813 | + if(valid_res.second == res) { |
| 11814 | + valid_value = true; |
| 11815 | + break; |
| 11816 | + } |
| 11817 | + } |
| 11818 | + } |
| 11819 | + |
| 11820 | + if(valid_value) { |
| 11821 | + op->add_result(res); |
| 11822 | + } else { |
| 11823 | + throw InvalidError("invalid flag argument given"); |
| 11824 | + } |
| 11825 | + } |
| 11826 | + return true; |
| 11827 | + } |
| 11828 | + return false; |
| 11829 | +} |
| 11830 | + |
| 11831 | +CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t level) { |
| 11832 | + |
| 11833 | + if(level < item.parents.size()) { |
| 11834 | + auto *subcom = get_subcommand_no_throw(item.parents.at(level)); |
| 11835 | + return (subcom != nullptr) ? subcom->_parse_single_config(item, level + 1) : false; |
| 11836 | + } |
| 11837 | + // check for section open |
| 11838 | + if(item.name == "++") { |
| 11839 | + if(configurable_) { |
| 11840 | + increment_parsed(); |
| 11841 | + _trigger_pre_parse(2); |
| 11842 | + if(parent_ != nullptr) { |
| 11843 | + parent_->parsed_subcommands_.push_back(this); |
| 11844 | + } |
| 11845 | + } |
| 11846 | + return true; |
| 11847 | + } |
| 11848 | + // check for section close |
| 11849 | + if(item.name == "--") { |
| 11850 | + if(configurable_ && parse_complete_callback_) { |
| 11851 | + _process_callbacks(CallbackPriority::FirstPreHelp); |
| 11852 | + _process_callbacks(CallbackPriority::First); |
| 11853 | + _process_callbacks(CallbackPriority::PreRequirementsCheckPreHelp); |
| 11854 | + _process_callbacks(CallbackPriority::PreRequirementsCheck); |
| 11855 | + _process_requirements(); |
| 11856 | + _process_callbacks(CallbackPriority::NormalPreHelp); |
| 11857 | + _process_callbacks(CallbackPriority::Normal); |
| 11858 | + _process_callbacks(CallbackPriority::LastPreHelp); |
| 11859 | + _process_callbacks(CallbackPriority::Last); |
| 11860 | + run_callback(); |
| 11861 | + } |
| 11862 | + return true; |
| 11863 | + } |
| 11864 | + Option *op = get_option_no_throw("--" + item.name); |
| 11865 | + if(op == nullptr) { |
| 11866 | + if(item.name.size() == 1) { |
| 11867 | + op = get_option_no_throw("-" + item.name); |
| 11868 | + } |
| 11869 | + if(op == nullptr) { |
| 11870 | + op = get_option_no_throw(item.name); |
| 11871 | + } |
| 11872 | + } else if(!op->get_configurable()) { |
| 11873 | + if(item.name.size() == 1) { |
| 11874 | + auto *testop = get_option_no_throw("-" + item.name); |
| 11875 | + if(testop != nullptr && testop->get_configurable()) { |
| 11876 | + op = testop; |
| 11877 | + } |
| 11878 | + } |
| 11879 | + } |
| 11880 | + if(op == nullptr || !op->get_configurable()) { |
| 11881 | + std::string iname = item.name; |
| 11882 | + auto options = get_options([iname](const CLI::Option *opt) { |
| 11883 | + return (opt->get_configurable() && |
| 11884 | + (opt->check_name(iname) || opt->check_lname(iname) || opt->check_sname(iname))); |
| 11885 | + }); |
| 11886 | + if(!options.empty()) { |
| 11887 | + op = options[0]; |
| 11888 | + } |
| 11889 | + } |
| 11890 | + if(op == nullptr) { |
| 11891 | + // If the option was not present |
| 11892 | + if(get_allow_config_extras() == config_extras_mode::capture) { |
| 11893 | + // Should we worry about classifying the extras properly? |
| 11894 | + missing_.emplace_back(detail::Classifier::NONE, item.fullname()); |
| 11895 | + for(const auto &input : item.inputs) { |
| 11896 | + missing_.emplace_back(detail::Classifier::NONE, input); |
| 11897 | + } |
| 11898 | + } |
| 11899 | + return false; |
| 11900 | + } |
| 11901 | + |
| 11902 | + if(!op->get_configurable()) { |
| 11903 | + if(get_allow_config_extras() == config_extras_mode::ignore_all) { |
| 11904 | + return false; |
| 11905 | + } |
| 11906 | + throw ConfigError::NotConfigurable(item.fullname()); |
| 11907 | + } |
| 11908 | + if(op->empty()) { |
| 11909 | + std::vector<std::string> buffer; // a buffer to use for copying and modifying inputs in a few cases |
| 11910 | + bool useBuffer{false}; |
| 11911 | + if(item.multiline) { |
| 11912 | + if(!op->get_inject_separator()) { |
| 11913 | + buffer = item.inputs; |
| 11914 | + buffer.erase(std::remove(buffer.begin(), buffer.end(), "%%"), buffer.end()); |
| 11915 | + useBuffer = true; |
| 11916 | + } |
| 11917 | + } |
| 11918 | + const std::vector<std::string> &inputs = (useBuffer) ? buffer : item.inputs; |
| 11919 | + if(op->get_expected_min() == 0) { |
| 11920 | + if(_add_flag_like_result(op, item, inputs)) { |
| 11921 | + return true; |
| 11922 | + } |
| 11923 | + } |
| 11924 | + op->add_result(inputs); |
| 11925 | + op->run_callback(); |
| 11926 | + } |
| 11927 | + |
| 11928 | + return true; |
| 11929 | +} |
| 11930 | + |
| 11931 | +CLI11_INLINE bool App::_parse_single(std::vector<std::string> &args, bool &positional_only) { |
| 11932 | + bool retval = true; |
| 11933 | + detail::Classifier classifier = positional_only ? detail::Classifier::NONE : _recognize(args.back()); |
| 11934 | + switch(classifier) { |
| 11935 | + case detail::Classifier::POSITIONAL_MARK: |
| 11936 | + args.pop_back(); |
| 11937 | + positional_only = true; |
| 11938 | + if(get_prefix_command()) { |
| 11939 | + // don't care about extras mode here |
| 11940 | + missing_.emplace_back(classifier, "--"); |
| 11941 | + while(!args.empty()) { |
| 11942 | + missing_.emplace_back(detail::Classifier::NONE, args.back()); |
| 11943 | + args.pop_back(); |
| 11944 | + } |
| 11945 | + } else if((!_has_remaining_positionals()) && (parent_ != nullptr)) { |
| 11946 | + retval = false; |
| 11947 | + } else { |
| 11948 | + _move_to_missing(classifier, "--"); |
| 11949 | + } |
| 11950 | + break; |
| 11951 | + case detail::Classifier::SUBCOMMAND_TERMINATOR: |
| 11952 | + // treat this like a positional mark if in the parent app |
| 11953 | + args.pop_back(); |
| 11954 | + retval = false; |
| 11955 | + break; |
| 11956 | + case detail::Classifier::SUBCOMMAND: |
| 11957 | + retval = _parse_subcommand(args); |
| 11958 | + break; |
| 11959 | + case detail::Classifier::LONG: |
| 11960 | + case detail::Classifier::SHORT: |
| 11961 | + case detail::Classifier::WINDOWS_STYLE: |
| 11962 | + // If already parsed a subcommand, don't accept options_ |
| 11963 | + retval = _parse_arg(args, classifier, false); |
| 11964 | + break; |
| 11965 | + case detail::Classifier::NONE: |
| 11966 | + // Probably a positional or something for a parent (sub)command |
| 11967 | + retval = _parse_positional(args, false); |
| 11968 | + if(retval && positionals_at_end_) { |
| 11969 | + positional_only = true; |
| 11970 | + } |
| 11971 | + break; |
| 11972 | + // LCOV_EXCL_START |
| 11973 | + default: |
| 11974 | + throw HorribleError("unrecognized classifier (you should not see this!)"); |
| 11975 | + // LCOV_EXCL_STOP |
| 11976 | + } |
| 11977 | + return retval; |
| 11978 | +} |
| 11979 | + |
| 11980 | +CLI11_NODISCARD CLI11_INLINE std::size_t App::_count_remaining_positionals(bool required_only) const { |
| 11981 | + std::size_t retval = 0; |
| 11982 | + for(const Option_p &opt : options_) { |
| 11983 | + if(opt->get_positional() && (!required_only || opt->get_required())) { |
| 11984 | + if(opt->get_items_expected_min() > 0 && static_cast<int>(opt->count()) < opt->get_items_expected_min()) { |
| 11985 | + retval += static_cast<std::size_t>(opt->get_items_expected_min()) - opt->count(); |
| 11986 | + } |
| 11987 | + } |
| 11988 | + } |
| 11989 | + return retval; |
| 11990 | +} |
| 11991 | + |
| 11992 | +CLI11_NODISCARD CLI11_INLINE bool App::_has_remaining_positionals() const { |
| 11993 | + for(const Option_p &opt : options_) { |
| 11994 | + if(opt->get_positional() && ((static_cast<int>(opt->count()) < opt->get_items_expected_min()))) { |
| 11995 | + return true; |
| 11996 | + } |
| 11997 | + } |
| 11998 | + |
| 11999 | + return false; |
| 12000 | +} |
| 12001 | + |
| 12002 | +CLI11_INLINE bool App::_parse_positional(std::vector<std::string> &args, bool haltOnSubcommand) { |
| 12003 | + |
| 12004 | + const std::string &positional = args.back(); |
| 12005 | + Option *posOpt{nullptr}; |
| 12006 | + |
| 12007 | + if(positionals_at_end_) { |
| 12008 | + // deal with the case of required arguments at the end which should take precedence over other arguments |
| 12009 | + auto arg_rem = args.size(); |
| 12010 | + auto remreq = _count_remaining_positionals(true); |
| 12011 | + if(arg_rem <= remreq) { |
| 12012 | + for(const Option_p &opt : options_) { |
| 12013 | + if(opt->get_positional() && opt->required_) { |
| 12014 | + if(static_cast<int>(opt->count()) < opt->get_items_expected_min()) { |
| 12015 | + if(validate_positionals_) { |
| 12016 | + std::string pos = positional; |
| 12017 | + pos = opt->_validate(pos, 0); |
| 12018 | + if(!pos.empty()) { |
| 12019 | + continue; |
| 12020 | + } |
| 12021 | + } |
| 12022 | + posOpt = opt.get(); |
| 12023 | + break; |
| 12024 | + } |
| 12025 | + } |
| 12026 | + } |
| 12027 | + } |
| 12028 | + } |
| 12029 | + if(posOpt == nullptr) { |
| 12030 | + for(const Option_p &opt : options_) { |
| 12031 | + // Eat options, one by one, until done |
| 12032 | + if(opt->get_positional() && |
| 12033 | + (static_cast<int>(opt->count()) < opt->get_items_expected_max() || opt->get_allow_extra_args())) { |
| 12034 | + if(validate_positionals_) { |
| 12035 | + std::string pos = positional; |
| 12036 | + pos = opt->_validate(pos, 0); |
| 12037 | + if(!pos.empty()) { |
| 12038 | + continue; |
| 12039 | + } |
| 12040 | + } |
| 12041 | + posOpt = opt.get(); |
| 12042 | + break; |
| 12043 | + } |
| 12044 | + } |
| 12045 | + } |
| 12046 | + if(posOpt != nullptr) { |
| 12047 | + parse_order_.push_back(posOpt); |
| 12048 | + if(posOpt->get_inject_separator()) { |
| 12049 | + if(!posOpt->results().empty() && !posOpt->results().back().empty()) { |
| 12050 | + posOpt->add_result(std::string{}); |
| 12051 | + } |
| 12052 | + } |
| 12053 | + results_t prev; |
| 12054 | + if(posOpt->get_trigger_on_parse() && posOpt->current_option_state_ == Option::option_state::callback_run) { |
| 12055 | + prev = posOpt->results(); |
| 12056 | + posOpt->clear(); |
| 12057 | + } |
| 12058 | + if(posOpt->get_expected_min() == 0) { |
| 12059 | + ConfigItem item; |
| 12060 | + item.name = posOpt->pname_; |
| 12061 | + item.inputs.push_back(positional); |
| 12062 | + // input is singular guaranteed to return true in that case |
| 12063 | + _add_flag_like_result(posOpt, item, item.inputs); |
| 12064 | + } else { |
| 12065 | + posOpt->add_result(positional); |
| 12066 | + } |
| 12067 | + |
| 12068 | + if(posOpt->get_trigger_on_parse()) { |
| 12069 | + if(!posOpt->empty()) { |
| 12070 | + posOpt->run_callback(); |
| 12071 | + } else { |
| 12072 | + if(!prev.empty()) { |
| 12073 | + posOpt->add_result(prev); |
| 12074 | + } |
| 12075 | + } |
| 12076 | + } |
| 12077 | + |
| 12078 | + args.pop_back(); |
| 12079 | + return true; |
| 12080 | + } |
| 12081 | + |
| 12082 | + for(auto &subc : subcommands_) { |
| 12083 | + if((subc->name_.empty()) && (!subc->disabled_)) { |
| 12084 | + if(subc->_parse_positional(args, false)) { |
| 12085 | + if(!subc->pre_parse_called_) { |
| 12086 | + subc->_trigger_pre_parse(args.size()); |
| 12087 | + } |
| 12088 | + return true; |
| 12089 | + } |
| 12090 | + } |
| 12091 | + } |
| 12092 | + // let the parent deal with it if possible |
| 12093 | + if(parent_ != nullptr && fallthrough_) { |
| 12094 | + return _get_fallthrough_parent()->_parse_positional(args, static_cast<bool>(parse_complete_callback_)); |
| 12095 | + } |
| 12096 | + /// Try to find a local subcommand that is repeated |
| 12097 | + auto *com = _find_subcommand(args.back(), true, false); |
| 12098 | + if(com != nullptr && (require_subcommand_max_ == 0 || require_subcommand_max_ > parsed_subcommands_.size())) { |
| 12099 | + if(haltOnSubcommand) { |
| 12100 | + return false; |
| 12101 | + } |
| 12102 | + args.pop_back(); |
| 12103 | + com->_parse(args); |
| 12104 | + return true; |
| 12105 | + } |
| 12106 | + if(subcommand_fallthrough_) { |
| 12107 | + /// now try one last gasp at subcommands that have been executed before, go to root app and try to find a |
| 12108 | + /// subcommand in a broader way, if one exists let the parent deal with it |
| 12109 | + auto *parent_app = (parent_ != nullptr) ? _get_fallthrough_parent() : this; |
| 12110 | + com = parent_app->_find_subcommand(args.back(), true, false); |
| 12111 | + if(com != nullptr && (com->parent_->require_subcommand_max_ == 0 || |
| 12112 | + com->parent_->require_subcommand_max_ > com->parent_->parsed_subcommands_.size())) { |
| 12113 | + return false; |
| 12114 | + } |
| 12115 | + } |
| 12116 | + if(positionals_at_end_) { |
| 12117 | + std::vector<std::string> rargs; |
| 12118 | + rargs.resize(args.size()); |
| 12119 | + std::reverse_copy(args.begin(), args.end(), rargs.begin()); |
| 12120 | + throw CLI::ExtrasError(name_, rargs); |
| 12121 | + } |
| 12122 | + /// If this is an option group don't deal with it |
| 12123 | + if(parent_ != nullptr && name_.empty()) { |
| 12124 | + return false; |
| 12125 | + } |
| 12126 | + /// We are out of other options this goes to missing |
| 12127 | + _move_to_missing(detail::Classifier::NONE, positional); |
| 12128 | + args.pop_back(); |
| 12129 | + if(get_prefix_command()) { |
| 12130 | + while(!args.empty()) { |
| 12131 | + missing_.emplace_back(detail::Classifier::NONE, args.back()); |
| 12132 | + args.pop_back(); |
| 12133 | + } |
| 12134 | + } |
| 12135 | + |
| 12136 | + return true; |
| 12137 | +} |
| 12138 | + |
| 12139 | +CLI11_NODISCARD CLI11_INLINE App * |
| 12140 | +App::_find_subcommand(const std::string &subc_name, bool ignore_disabled, bool ignore_used) const noexcept { |
| 12141 | + App *bcom{nullptr}; |
| 12142 | + for(const App_p &com : subcommands_) { |
| 12143 | + if(com->disabled_ && ignore_disabled) |
| 12144 | + continue; |
| 12145 | + if(com->get_name().empty()) { |
| 12146 | + auto *subc = com->_find_subcommand(subc_name, ignore_disabled, ignore_used); |
| 12147 | + if(subc != nullptr) { |
| 12148 | + if(bcom != nullptr) { |
| 12149 | + return nullptr; |
| 12150 | + } |
| 12151 | + bcom = subc; |
| 12152 | + if(!allow_prefix_matching_) { |
| 12153 | + return bcom; |
| 12154 | + } |
| 12155 | + } |
| 12156 | + } |
| 12157 | + auto res = com->check_name_detail(subc_name); |
| 12158 | + if(res != NameMatch::none) { |
| 12159 | + if((!*com) || !ignore_used) { |
| 12160 | + if(res == NameMatch::exact) { |
| 12161 | + return com.get(); |
| 12162 | + } |
| 12163 | + if(bcom != nullptr) { |
| 12164 | + return nullptr; |
| 12165 | + } |
| 12166 | + bcom = com.get(); |
| 12167 | + if(!allow_prefix_matching_) { |
| 12168 | + return bcom; |
| 12169 | + } |
| 12170 | + } |
| 12171 | + } |
| 12172 | + } |
| 12173 | + return bcom; |
| 12174 | +} |
| 12175 | + |
| 12176 | +CLI11_INLINE bool App::_parse_subcommand(std::vector<std::string> &args) { |
| 12177 | + if(_count_remaining_positionals(/* required */ true) > 0) { |
| 12178 | + _parse_positional(args, false); |
| 12179 | + return true; |
| 12180 | + } |
| 12181 | + auto *com = _find_subcommand(args.back(), true, true); |
| 12182 | + if(com == nullptr) { |
| 12183 | + // the main way to get here is using .notation |
| 12184 | + auto dotloc = args.back().find_first_of('.'); |
| 12185 | + if(dotloc != std::string::npos) { |
| 12186 | + com = _find_subcommand(args.back().substr(0, dotloc), true, true); |
| 12187 | + if(com != nullptr) { |
| 12188 | + args.back() = args.back().substr(dotloc + 1); |
| 12189 | + args.push_back(com->get_display_name()); |
| 12190 | + } |
| 12191 | + } |
| 12192 | + } |
| 12193 | + if(com != nullptr) { |
| 12194 | + args.pop_back(); |
| 12195 | + if(!com->silent_) { |
| 12196 | + parsed_subcommands_.push_back(com); |
| 12197 | + } |
| 12198 | + com->_parse(args); |
| 12199 | + auto *parent_app = com->parent_; |
| 12200 | + while(parent_app != this) { |
| 12201 | + parent_app->_trigger_pre_parse(args.size()); |
| 12202 | + if(!com->silent_) { |
| 12203 | + parent_app->parsed_subcommands_.push_back(com); |
| 12204 | + } |
| 12205 | + parent_app = parent_app->parent_; |
| 12206 | + } |
| 12207 | + return true; |
| 12208 | + } |
| 12209 | + |
| 12210 | + if(parent_ == nullptr) |
| 12211 | + throw HorribleError("Subcommand " + args.back() + " missing"); |
| 12212 | + return false; |
| 12213 | +} |
| 12214 | + |
| 12215 | +CLI11_INLINE bool |
| 12216 | +App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type, bool local_processing_only) { |
| 12217 | + |
| 12218 | + std::string current = args.back(); |
| 12219 | + |
| 12220 | + std::string arg_name; |
| 12221 | + std::string value; |
| 12222 | + std::string rest; |
| 12223 | + |
| 12224 | + switch(current_type) { |
| 12225 | + case detail::Classifier::LONG: |
| 12226 | + if(!detail::split_long(current, arg_name, value)) |
| 12227 | + throw HorribleError("Long parsed but missing (you should not see this):" + args.back()); |
| 12228 | + break; |
| 12229 | + case detail::Classifier::SHORT: |
| 12230 | + if(!detail::split_short(current, arg_name, rest)) |
| 12231 | + throw HorribleError("Short parsed but missing! You should not see this"); |
| 12232 | + break; |
| 12233 | + case detail::Classifier::WINDOWS_STYLE: |
| 12234 | + if(!detail::split_windows_style(current, arg_name, value)) |
| 12235 | + throw HorribleError("windows option parsed but missing! You should not see this"); |
| 12236 | + break; |
| 12237 | + case detail::Classifier::SUBCOMMAND: |
| 12238 | + case detail::Classifier::SUBCOMMAND_TERMINATOR: |
| 12239 | + case detail::Classifier::POSITIONAL_MARK: |
| 12240 | + case detail::Classifier::NONE: |
| 12241 | + default: |
| 12242 | + throw HorribleError("parsing got called with invalid option! You should not see this"); |
| 12243 | + } |
| 12244 | + |
| 12245 | + auto op_ptr = std::find_if(std::begin(options_), std::end(options_), [arg_name, current_type](const Option_p &opt) { |
| 12246 | + if(current_type == detail::Classifier::LONG) |
| 12247 | + return opt->check_lname(arg_name); |
| 12248 | + if(current_type == detail::Classifier::SHORT) |
| 12249 | + return opt->check_sname(arg_name); |
| 12250 | + // this will only get called for detail::Classifier::WINDOWS_STYLE |
| 12251 | + return opt->check_lname(arg_name) || opt->check_sname(arg_name); |
| 12252 | + }); |
| 12253 | + |
| 12254 | + // Option not found |
| 12255 | + while(op_ptr == std::end(options_)) { |
| 12256 | + // using while so we can break |
| 12257 | + for(auto &subc : subcommands_) { |
| 12258 | + if(subc->name_.empty() && !subc->disabled_) { |
| 12259 | + if(subc->_parse_arg(args, current_type, local_processing_only)) { |
| 12260 | + if(!subc->pre_parse_called_) { |
| 12261 | + subc->_trigger_pre_parse(args.size()); |
| 12262 | + } |
| 12263 | + return true; |
| 12264 | + } |
| 12265 | + } |
| 12266 | + } |
| 12267 | + if(allow_non_standard_options_ && current_type == detail::Classifier::SHORT && current.size() > 2) { |
| 12268 | + std::string narg_name; |
| 12269 | + std::string nvalue; |
| 12270 | + detail::split_long(std::string{'-'} + current, narg_name, nvalue); |
| 12271 | + op_ptr = std::find_if(std::begin(options_), std::end(options_), [narg_name](const Option_p &opt) { |
| 12272 | + return opt->check_sname(narg_name); |
| 12273 | + }); |
| 12274 | + if(op_ptr != std::end(options_)) { |
| 12275 | + arg_name = narg_name; |
| 12276 | + value = nvalue; |
| 12277 | + rest.clear(); |
| 12278 | + break; |
| 12279 | + } |
| 12280 | + } |
| 12281 | + |
| 12282 | + // don't capture missing if this is a nameless subcommand and nameless subcommands can't fallthrough |
| 12283 | + if(parent_ != nullptr && name_.empty()) { |
| 12284 | + return false; |
| 12285 | + } |
| 12286 | + |
| 12287 | + // now check for '.' notation of subcommands |
| 12288 | + auto dotloc = arg_name.find_first_of('.', 1); |
| 12289 | + if(dotloc != std::string::npos && dotloc < arg_name.size() - 1) { |
| 12290 | + // using dot notation is equivalent to single argument subcommand |
| 12291 | + auto *sub = _find_subcommand(arg_name.substr(0, dotloc), true, false); |
| 12292 | + if(sub != nullptr) { |
| 12293 | + std::string v = args.back(); |
| 12294 | + args.pop_back(); |
| 12295 | + arg_name = arg_name.substr(dotloc + 1); |
| 12296 | + if(arg_name.size() > 1) { |
| 12297 | + args.push_back(std::string("--") + v.substr(dotloc + 3)); |
| 12298 | + current_type = detail::Classifier::LONG; |
| 12299 | + } else { |
| 12300 | + auto nval = v.substr(dotloc + 2); |
| 12301 | + nval.front() = '-'; |
| 12302 | + if(nval.size() > 2) { |
| 12303 | + // '=' not allowed in short form arguments |
| 12304 | + args.push_back(nval.substr(3)); |
| 12305 | + nval.resize(2); |
| 12306 | + } |
| 12307 | + args.push_back(nval); |
| 12308 | + current_type = detail::Classifier::SHORT; |
| 12309 | + } |
| 12310 | + std::string dummy1, dummy2; |
| 12311 | + bool val = false; |
| 12312 | + if((current_type == detail::Classifier::SHORT && detail::valid_first_char(args.back()[1])) || |
| 12313 | + detail::split_long(args.back(), dummy1, dummy2)) { |
| 12314 | + val = sub->_parse_arg(args, current_type, true); |
| 12315 | + } |
| 12316 | + |
| 12317 | + if(val) { |
| 12318 | + if(!sub->silent_) { |
| 12319 | + parsed_subcommands_.push_back(sub); |
| 12320 | + } |
| 12321 | + // deal with preparsing |
| 12322 | + increment_parsed(); |
| 12323 | + _trigger_pre_parse(args.size()); |
| 12324 | + // run the parse complete callback since the subcommand processing is now complete |
| 12325 | + if(sub->parse_complete_callback_) { |
| 12326 | + sub->_process_callbacks(CallbackPriority::FirstPreHelp); |
| 12327 | + sub->_process_help_flags(CallbackPriority::First); |
| 12328 | + sub->_process_callbacks(CallbackPriority::First); |
| 12329 | + sub->_process_env(); |
| 12330 | + sub->_process_callbacks(CallbackPriority::PreRequirementsCheckPreHelp); |
| 12331 | + sub->_process_help_flags(CallbackPriority::PreRequirementsCheck); |
| 12332 | + sub->_process_callbacks(CallbackPriority::PreRequirementsCheck); |
| 12333 | + sub->_process_requirements(); |
| 12334 | + sub->_process_callbacks(CallbackPriority::NormalPreHelp); |
| 12335 | + sub->_process_help_flags(CallbackPriority::Normal); |
| 12336 | + sub->_process_callbacks(CallbackPriority::Normal); |
| 12337 | + sub->_process_callbacks(CallbackPriority::LastPreHelp); |
| 12338 | + sub->_process_help_flags(CallbackPriority::Last); |
| 12339 | + sub->_process_callbacks(CallbackPriority::Last); |
| 12340 | + sub->run_callback(false, true); |
| 12341 | + } |
| 12342 | + return true; |
| 12343 | + } |
| 12344 | + args.pop_back(); |
| 12345 | + args.push_back(v); |
| 12346 | + } |
| 12347 | + } |
| 12348 | + if(local_processing_only) { |
| 12349 | + return false; |
| 12350 | + } |
| 12351 | + // If a subcommand, try the main command |
| 12352 | + if(parent_ != nullptr && fallthrough_) |
| 12353 | + return _get_fallthrough_parent()->_parse_arg(args, current_type, false); |
| 12354 | + |
| 12355 | + // Otherwise, add to missing |
| 12356 | + args.pop_back(); |
| 12357 | + _move_to_missing(current_type, current); |
| 12358 | + if(get_prefix_command_mode() == PrefixCommandMode::On) { |
| 12359 | + while(!args.empty()) { |
| 12360 | + missing_.emplace_back(detail::Classifier::NONE, args.back()); |
| 12361 | + args.pop_back(); |
| 12362 | + } |
| 12363 | + } else if(allow_extras_ == ExtrasMode::AssumeSingleArgument) { |
| 12364 | + if(!args.empty() && _recognize(args.back(), false) == detail::Classifier::NONE) { |
| 12365 | + _move_to_missing(detail::Classifier::NONE, args.back()); |
| 12366 | + args.pop_back(); |
| 12367 | + } |
| 12368 | + } else if(allow_extras_ == ExtrasMode::AssumeMultipleArguments) { |
| 12369 | + while(!args.empty() && _recognize(args.back(), false) == detail::Classifier::NONE) { |
| 12370 | + _move_to_missing(detail::Classifier::NONE, args.back()); |
| 12371 | + args.pop_back(); |
| 12372 | + } |
| 12373 | + } |
| 12374 | + return true; |
| 12375 | + } |
| 12376 | + |
| 12377 | + args.pop_back(); |
| 12378 | + |
| 12379 | + // Get a reference to the pointer to make syntax bearable |
| 12380 | + Option_p &op = *op_ptr; |
| 12381 | + /// if we require a separator add it here |
| 12382 | + if(op->get_inject_separator()) { |
| 12383 | + if(!op->results().empty() && !op->results().back().empty()) { |
| 12384 | + op->add_result(std::string{}); |
| 12385 | + } |
| 12386 | + } |
| 12387 | + if(op->get_trigger_on_parse() && op->current_option_state_ == Option::option_state::callback_run) { |
| 12388 | + op->clear(); |
| 12389 | + } |
| 12390 | + int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min()); |
| 12391 | + int max_num = op->get_items_expected_max(); |
| 12392 | + // check container like options to limit the argument size to a single type if the allow_extra_flags argument is |
| 12393 | + // set. 16 is somewhat arbitrary (needs to be at least 4) |
| 12394 | + if(max_num >= detail::expected_max_vector_size / 16 && !op->get_allow_extra_args()) { |
| 12395 | + auto tmax = op->get_type_size_max(); |
| 12396 | + max_num = detail::checked_multiply(tmax, op->get_expected_min()) ? tmax : detail::expected_max_vector_size; |
| 12397 | + } |
| 12398 | + // Make sure we always eat the minimum for unlimited vectors |
| 12399 | + int collected = 0; // total number of arguments collected |
| 12400 | + int result_count = 0; // local variable for number of results in a single arg string |
| 12401 | + // deal with purely flag like things |
| 12402 | + if(max_num == 0) { |
| 12403 | + auto res = op->get_flag_value(arg_name, value); |
| 12404 | + op->add_result(res); |
| 12405 | + parse_order_.push_back(op.get()); |
| 12406 | + } else if(!value.empty()) { // --this=value |
| 12407 | + op->add_result(value, result_count); |
| 12408 | + parse_order_.push_back(op.get()); |
| 12409 | + collected += result_count; |
| 12410 | + // -Trest |
| 12411 | + } else if(!rest.empty()) { |
| 12412 | + op->add_result(rest, result_count); |
| 12413 | + parse_order_.push_back(op.get()); |
| 12414 | + rest = ""; |
| 12415 | + collected += result_count; |
| 12416 | + } |
| 12417 | + |
| 12418 | + // gather the minimum number of arguments |
| 12419 | + while(min_num > collected && !args.empty()) { |
| 12420 | + std::string current_ = args.back(); |
| 12421 | + args.pop_back(); |
| 12422 | + op->add_result(current_, result_count); |
| 12423 | + parse_order_.push_back(op.get()); |
| 12424 | + collected += result_count; |
| 12425 | + } |
| 12426 | + |
| 12427 | + if(min_num > collected) { // if we have run out of arguments and the minimum was not met |
| 12428 | + throw ArgumentMismatch::TypedAtLeast(op->get_name(), min_num, op->get_type_name()); |
| 12429 | + } |
| 12430 | + |
| 12431 | + // now check for optional arguments |
| 12432 | + if(max_num > collected || op->get_allow_extra_args()) { // we allow optional arguments |
| 12433 | + auto remreqpos = _count_remaining_positionals(true); |
| 12434 | + // we have met the minimum now optionally check up to the maximum |
| 12435 | + while((collected < max_num || op->get_allow_extra_args()) && !args.empty() && |
| 12436 | + _recognize(args.back(), false) == detail::Classifier::NONE) { |
| 12437 | + // If any required positionals remain, don't keep eating |
| 12438 | + if(remreqpos >= args.size()) { |
| 12439 | + break; |
| 12440 | + } |
| 12441 | + if(validate_optional_arguments_) { |
| 12442 | + std::string arg = args.back(); |
| 12443 | + arg = op->_validate(arg, 0); |
| 12444 | + if(!arg.empty()) { |
| 12445 | + break; |
| 12446 | + } |
| 12447 | + } |
| 12448 | + op->add_result(args.back(), result_count); |
| 12449 | + parse_order_.push_back(op.get()); |
| 12450 | + args.pop_back(); |
| 12451 | + collected += result_count; |
| 12452 | + } |
| 12453 | + |
| 12454 | + // Allow -- to end an unlimited list and "eat" it |
| 12455 | + if(!args.empty() && _recognize(args.back()) == detail::Classifier::POSITIONAL_MARK) |
| 12456 | + args.pop_back(); |
| 12457 | + // optional flag that didn't receive anything now get the default value |
| 12458 | + if(min_num == 0 && max_num > 0 && collected == 0) { |
| 12459 | + auto res = op->get_flag_value(arg_name, std::string{}); |
| 12460 | + op->add_result(res); |
| 12461 | + parse_order_.push_back(op.get()); |
| 12462 | + } |
| 12463 | + } |
| 12464 | + // if we only partially completed a type then add an empty string if allowed for later processing |
| 12465 | + if(min_num > 0 && (collected % op->get_type_size_max()) != 0) { |
| 12466 | + if(op->get_type_size_max() != op->get_type_size_min()) { |
| 12467 | + op->add_result(std::string{}); |
| 12468 | + } else { |
| 12469 | + throw ArgumentMismatch::PartialType(op->get_name(), op->get_type_size_min(), op->get_type_name()); |
| 12470 | + } |
| 12471 | + } |
| 12472 | + if(op->get_trigger_on_parse()) { |
| 12473 | + op->run_callback(); |
| 12474 | + } |
| 12475 | + if(!rest.empty()) { |
| 12476 | + rest = "-" + rest; |
| 12477 | + args.push_back(rest); |
| 12478 | + } |
| 12479 | + return true; |
| 12480 | +} |
| 12481 | + |
| 12482 | +CLI11_INLINE void App::_trigger_pre_parse(std::size_t remaining_args) { |
| 12483 | + if(!pre_parse_called_) { |
| 12484 | + pre_parse_called_ = true; |
| 12485 | + if(pre_parse_callback_) { |
| 12486 | + pre_parse_callback_(remaining_args); |
| 12487 | + } |
| 12488 | + } else if(immediate_callback_) { |
| 12489 | + if(!name_.empty()) { |
| 12490 | + auto pcnt = parsed_; |
| 12491 | + missing_t extras = std::move(missing_); |
| 12492 | + clear(); |
| 12493 | + parsed_ = pcnt; |
| 12494 | + pre_parse_called_ = true; |
| 12495 | + missing_ = std::move(extras); |
| 12496 | + } |
| 12497 | + } |
| 12498 | +} |
| 12499 | + |
| 12500 | +CLI11_INLINE App *App::_get_fallthrough_parent() noexcept { |
| 12501 | + if(parent_ == nullptr) { |
| 12502 | + return nullptr; |
| 12503 | + } |
| 12504 | + auto *fallthrough_parent = parent_; |
| 12505 | + while((fallthrough_parent->parent_ != nullptr) && (fallthrough_parent->get_name().empty())) { |
| 12506 | + fallthrough_parent = fallthrough_parent->parent_; |
| 12507 | + } |
| 12508 | + return fallthrough_parent; |
| 12509 | +} |
| 12510 | + |
| 12511 | +CLI11_INLINE const App *App::_get_fallthrough_parent() const noexcept { |
| 12512 | + if(parent_ == nullptr) { |
| 12513 | + return nullptr; |
| 12514 | + } |
| 12515 | + const auto *fallthrough_parent = parent_; |
| 12516 | + while((fallthrough_parent->parent_ != nullptr) && (fallthrough_parent->get_name().empty())) { |
| 12517 | + fallthrough_parent = fallthrough_parent->parent_; |
| 12518 | + } |
| 12519 | + return fallthrough_parent; |
| 12520 | +} |
| 12521 | + |
| 12522 | +CLI11_NODISCARD CLI11_INLINE const std::string &App::_compare_subcommand_names(const App &subcom, |
| 12523 | + const App &base) const { |
| 12524 | + static const std::string estring; |
| 12525 | + if(subcom.disabled_) { |
| 12526 | + return estring; |
| 12527 | + } |
| 12528 | + for(const auto &subc : base.subcommands_) { |
| 12529 | + if(subc.get() != &subcom) { |
| 12530 | + if(subc->disabled_) { |
| 12531 | + continue; |
| 12532 | + } |
| 12533 | + if(!subcom.get_name().empty()) { |
| 12534 | + if(subc->check_name(subcom.get_name())) { |
| 12535 | + return subcom.get_name(); |
| 12536 | + } |
| 12537 | + } |
| 12538 | + if(!subc->get_name().empty()) { |
| 12539 | + if(subcom.check_name(subc->get_name())) { |
| 12540 | + return subc->get_name(); |
| 12541 | + } |
| 12542 | + } |
| 12543 | + for(const auto &les : subcom.aliases_) { |
| 12544 | + if(subc->check_name(les)) { |
| 12545 | + return les; |
| 12546 | + } |
| 12547 | + } |
| 12548 | + // this loop is needed in case of ignore_underscore or ignore_case on one but not the other |
| 12549 | + for(const auto &les : subc->aliases_) { |
| 12550 | + if(subcom.check_name(les)) { |
| 12551 | + return les; |
| 12552 | + } |
| 12553 | + } |
| 12554 | + // if the subcommand is an option group we need to check deeper |
| 12555 | + if(subc->get_name().empty()) { |
| 12556 | + const auto &cmpres = _compare_subcommand_names(subcom, *subc); |
| 12557 | + if(!cmpres.empty()) { |
| 12558 | + return cmpres; |
| 12559 | + } |
| 12560 | + } |
| 12561 | + // if the test subcommand is an option group we need to check deeper |
| 12562 | + if(subcom.get_name().empty()) { |
| 12563 | + const auto &cmpres = _compare_subcommand_names(*subc, subcom); |
| 12564 | + if(!cmpres.empty()) { |
| 12565 | + return cmpres; |
| 12566 | + } |
| 12567 | + } |
| 12568 | + } |
| 12569 | + } |
| 12570 | + return estring; |
| 12571 | +} |
| 12572 | + |
| 12573 | +inline bool capture_extras(ExtrasMode mode) { |
| 12574 | + return mode == ExtrasMode::Capture || mode == ExtrasMode::AssumeSingleArgument || |
| 12575 | + mode == ExtrasMode::AssumeMultipleArguments; |
| 12576 | +} |
| 12577 | +CLI11_INLINE void App::_move_to_missing(detail::Classifier val_type, const std::string &val) { |
| 12578 | + if(allow_extras_ == ExtrasMode::ErrorImmediately) { |
| 12579 | + throw ExtrasError(name_, std::vector<std::string>{val}); |
| 12580 | + } |
| 12581 | + if(capture_extras(allow_extras_) || subcommands_.empty() || get_prefix_command()) { |
| 12582 | + if(allow_extras_ != ExtrasMode::Ignore) { |
| 12583 | + missing_.emplace_back(val_type, val); |
| 12584 | + } |
| 12585 | + return; |
| 12586 | + } |
| 12587 | + // allow extra arguments to be placed in an option group if it is allowed there |
| 12588 | + for(auto &subc : subcommands_) { |
| 12589 | + if(subc->name_.empty() && capture_extras(subc->allow_extras_)) { |
| 12590 | + subc->missing_.emplace_back(val_type, val); |
| 12591 | + return; |
| 12592 | + } |
| 12593 | + } |
| 12594 | + if(allow_extras_ != ExtrasMode::Ignore) { |
| 12595 | + // if we haven't found any place to put them yet put them in missing |
| 12596 | + missing_.emplace_back(val_type, val); |
| 12597 | + } |
| 12598 | +} |
| 12599 | + |
| 12600 | +CLI11_INLINE void App::_move_option(Option *opt, App *app) { |
| 12601 | + if(opt == nullptr) { |
| 12602 | + throw OptionNotFound("the option is NULL"); |
| 12603 | + } |
| 12604 | + // verify that the give app is actually a subcommand |
| 12605 | + bool found = false; |
| 12606 | + for(auto &subc : subcommands_) { |
| 12607 | + if(app == subc.get()) { |
| 12608 | + found = true; |
| 12609 | + } |
| 12610 | + } |
| 12611 | + if(!found) { |
| 12612 | + throw OptionNotFound("The Given app is not a subcommand"); |
| 12613 | + } |
| 12614 | + |
| 12615 | + if((help_ptr_ == opt) || (help_all_ptr_ == opt)) |
| 12616 | + throw OptionAlreadyAdded("cannot move help options"); |
| 12617 | + |
| 12618 | + if(config_ptr_ == opt) |
| 12619 | + throw OptionAlreadyAdded("cannot move config file options"); |
| 12620 | + |
| 12621 | + auto iterator = |
| 12622 | + std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; }); |
| 12623 | + if(iterator != std::end(options_)) { |
| 12624 | + const auto &opt_p = *iterator; |
| 12625 | + if(std::find_if(std::begin(app->options_), std::end(app->options_), [&opt_p](const Option_p &v) { |
| 12626 | + return (*v == *opt_p); |
| 12627 | + }) == std::end(app->options_)) { |
| 12628 | + // only erase after the insertion was successful |
| 12629 | + app->options_.push_back(std::move(*iterator)); |
| 12630 | + options_.erase(iterator); |
| 12631 | + } else { |
| 12632 | + throw OptionAlreadyAdded("option was not located: " + opt->get_name()); |
| 12633 | + } |
| 12634 | + } else { |
| 12635 | + throw OptionNotFound("could not locate the given Option"); |
| 12636 | + } |
| 12637 | +} |
| 12638 | + |
| 12639 | +CLI11_INLINE void TriggerOn(App *trigger_app, App *app_to_enable) { |
| 12640 | + app_to_enable->enabled_by_default(false); |
| 12641 | + app_to_enable->disabled_by_default(); |
| 12642 | + trigger_app->preparse_callback([app_to_enable](std::size_t) { app_to_enable->disabled(false); }); |
| 12643 | +} |
| 12644 | + |
| 12645 | +CLI11_INLINE void TriggerOn(App *trigger_app, std::vector<App *> apps_to_enable) { |
| 12646 | + for(auto &app : apps_to_enable) { |
| 12647 | + app->enabled_by_default(false); |
| 12648 | + app->disabled_by_default(); |
| 12649 | + } |
| 12650 | + |
| 12651 | + trigger_app->preparse_callback([apps_to_enable](std::size_t) { |
| 12652 | + for(const auto &app : apps_to_enable) { |
| 12653 | + app->disabled(false); |
| 12654 | + } |
| 12655 | + }); |
| 12656 | +} |
| 12657 | + |
| 12658 | +CLI11_INLINE void TriggerOff(App *trigger_app, App *app_to_enable) { |
| 12659 | + app_to_enable->disabled_by_default(false); |
| 12660 | + app_to_enable->enabled_by_default(); |
| 12661 | + trigger_app->preparse_callback([app_to_enable](std::size_t) { app_to_enable->disabled(); }); |
| 12662 | +} |
| 12663 | + |
| 12664 | +CLI11_INLINE void TriggerOff(App *trigger_app, std::vector<App *> apps_to_enable) { |
| 12665 | + for(auto &app : apps_to_enable) { |
| 12666 | + app->disabled_by_default(false); |
| 12667 | + app->enabled_by_default(); |
| 12668 | + } |
| 12669 | + |
| 12670 | + trigger_app->preparse_callback([apps_to_enable](std::size_t) { |
| 12671 | + for(const auto &app : apps_to_enable) { |
| 12672 | + app->disabled(); |
| 12673 | + } |
| 12674 | + }); |
| 12675 | +} |
| 12676 | + |
| 12677 | +CLI11_INLINE void deprecate_option(Option *opt, const std::string &replacement) { |
| 12678 | + Validator deprecate_warning{[opt, replacement](std::string &) { |
| 12679 | + std::cout << opt->get_name() << " is deprecated please use '" << replacement |
| 12680 | + << "' instead\n"; |
| 12681 | + return std::string(); |
| 12682 | + }, |
| 12683 | + "DEPRECATED"}; |
| 12684 | + deprecate_warning.application_index(0); |
| 12685 | + opt->check(deprecate_warning); |
| 12686 | + if(!replacement.empty()) { |
| 12687 | + opt->description(opt->get_description() + " DEPRECATED: please use '" + replacement + "' instead"); |
| 12688 | + } |
| 12689 | +} |
| 12690 | + |
| 12691 | +CLI11_INLINE void retire_option(App *app, Option *opt) { |
| 12692 | + App temp; |
| 12693 | + auto *option_copy = temp.add_option(opt->get_name(false, true)) |
| 12694 | + ->type_size(opt->get_type_size_min(), opt->get_type_size_max()) |
| 12695 | + ->expected(opt->get_expected_min(), opt->get_expected_max()) |
| 12696 | + ->allow_extra_args(opt->get_allow_extra_args()); |
| 12697 | + |
| 12698 | + app->remove_option(opt); |
| 12699 | + auto *opt2 = app->add_option(option_copy->get_name(false, true), "option has been retired and has no effect"); |
| 12700 | + opt2->type_name("RETIRED") |
| 12701 | + ->default_str("RETIRED") |
| 12702 | + ->type_size(option_copy->get_type_size_min(), option_copy->get_type_size_max()) |
| 12703 | + ->expected(option_copy->get_expected_min(), option_copy->get_expected_max()) |
| 12704 | + ->allow_extra_args(option_copy->get_allow_extra_args()); |
| 12705 | + |
| 12706 | + // LCOV_EXCL_START |
| 12707 | + // something odd with coverage on new compilers |
| 12708 | + Validator retired_warning{[opt2](std::string &) { |
| 12709 | + std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n"; |
| 12710 | + return std::string(); |
| 12711 | + }, |
| 12712 | + ""}; |
| 12713 | + // LCOV_EXCL_STOP |
| 12714 | + retired_warning.application_index(0); |
| 12715 | + opt2->check(retired_warning); |
| 12716 | +} |
| 12717 | + |
| 12718 | +CLI11_INLINE void retire_option(App &app, Option *opt) { retire_option(&app, opt); } |
| 12719 | + |
| 12720 | +CLI11_INLINE void retire_option(App *app, const std::string &option_name) { |
| 12721 | + |
| 12722 | + auto *opt = app->get_option_no_throw(option_name); |
| 12723 | + if(opt != nullptr) { |
| 12724 | + retire_option(app, opt); |
| 12725 | + return; |
| 12726 | + } |
| 12727 | + auto *opt2 = app->add_option(option_name, "option has been retired and has no effect") |
| 12728 | + ->type_name("RETIRED") |
| 12729 | + ->expected(0, 1) |
| 12730 | + ->default_str("RETIRED"); |
| 12731 | + // LCOV_EXCL_START |
| 12732 | + // something odd with coverage on new compilers |
| 12733 | + Validator retired_warning{[opt2](std::string &) { |
| 12734 | + std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n"; |
| 12735 | + return std::string(); |
| 12736 | + }, |
| 12737 | + ""}; |
| 12738 | + // LCOV_EXCL_STOP |
| 12739 | + retired_warning.application_index(0); |
| 12740 | + opt2->check(retired_warning); |
| 12741 | +} |
| 12742 | + |
| 12743 | +CLI11_INLINE void retire_option(App &app, const std::string &option_name) { retire_option(&app, option_name); } |
| 12744 | + |
| 12745 | +namespace FailureMessage { |
| 12746 | + |
| 12747 | +CLI11_INLINE std::string simple(const App *app, const Error &e) { |
| 12748 | + std::string header = std::string(e.what()) + "\n"; |
| 12749 | + std::vector<std::string> names; |
| 12750 | + |
| 12751 | + // Collect names |
| 12752 | + if(app->get_help_ptr() != nullptr) |
| 12753 | + names.push_back(app->get_help_ptr()->get_name()); |
| 12754 | + |
| 12755 | + if(app->get_help_all_ptr() != nullptr) |
| 12756 | + names.push_back(app->get_help_all_ptr()->get_name()); |
| 12757 | + |
| 12758 | + // If any names found, suggest those |
| 12759 | + if(!names.empty()) |
| 12760 | + header += "Run with " + detail::join(names, " or ") + " for more information.\n"; |
| 12761 | + |
| 12762 | + return header; |
| 12763 | +} |
| 12764 | + |
| 12765 | +CLI11_INLINE std::string help(const App *app, const Error &e) { |
| 12766 | + std::string header = std::string("ERROR: ") + e.get_name() + ": " + e.what() + "\n"; |
| 12767 | + header += app->help(); |
| 12768 | + return header; |
| 12769 | +} |
| 12770 | + |
| 12771 | +} // namespace FailureMessage |
| 12772 | + |
| 12773 | + |
| 12774 | + |
| 12775 | + |
| 12776 | +namespace detail { |
| 12777 | + |
| 12778 | +std::string convert_arg_for_ini(const std::string &arg, |
| 12779 | + char stringQuote = '"', |
| 12780 | + char literalQuote = '\'', |
| 12781 | + bool disable_multi_line = false); |
| 12782 | + |
| 12783 | +/// Comma separated join, adds quotes if needed |
| 12784 | +std::string ini_join(const std::vector<std::string> &args, |
| 12785 | + char sepChar = ',', |
| 12786 | + char arrayStart = '[', |
| 12787 | + char arrayEnd = ']', |
| 12788 | + char stringQuote = '"', |
| 12789 | + char literalQuote = '\''); |
| 12790 | + |
| 12791 | +void clean_name_string(std::string &name, const std::string &keyChars); |
| 12792 | + |
| 12793 | +std::vector<std::string> generate_parents(const std::string §ion, std::string &name, char parentSeparator); |
| 12794 | + |
| 12795 | +/// assuming non default segments do a check on the close and open of the segments in a configItem structure |
| 12796 | +void checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tSection, char parentSeparator); |
| 12797 | +} // namespace detail |
| 12798 | + |
| 12799 | + |
| 12800 | + |
| 12801 | + |
| 12802 | +static constexpr auto multiline_literal_quote = R"(''')"; |
| 12803 | +static constexpr auto multiline_string_quote = R"(""")"; |
| 12804 | + |
| 12805 | +namespace detail { |
| 12806 | + |
| 12807 | +CLI11_INLINE bool is_printable(const std::string &test_string) { |
| 12808 | + return std::all_of(test_string.begin(), test_string.end(), [](char x) { |
| 12809 | + return (isprint(static_cast<unsigned char>(x)) != 0 || x == '\n' || x == '\t'); |
| 12810 | + }); |
| 12811 | +} |
| 12812 | + |
| 12813 | +CLI11_INLINE std::string |
| 12814 | +convert_arg_for_ini(const std::string &arg, char stringQuote, char literalQuote, bool disable_multi_line) { |
| 12815 | + if(arg.empty()) { |
| 12816 | + return std::string(2, stringQuote); |
| 12817 | + } |
| 12818 | + // some specifically supported strings |
| 12819 | + if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") { |
| 12820 | + return arg; |
| 12821 | + } |
| 12822 | + // floating point conversion can convert some hex codes, but don't try that here |
| 12823 | + if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) { |
| 12824 | + using CLI::detail::lexical_cast; |
| 12825 | + double val = 0.0; |
| 12826 | + if(lexical_cast(arg, val)) { |
| 12827 | + if(arg.find_first_not_of("0123456789.-+eE") == std::string::npos) { |
| 12828 | + return arg; |
| 12829 | + } |
| 12830 | + } |
| 12831 | + } |
| 12832 | + // just quote a single non numeric character |
| 12833 | + if(arg.size() == 1) { |
| 12834 | + if(isprint(static_cast<unsigned char>(arg.front())) == 0) { |
| 12835 | + return binary_escape_string(arg); |
| 12836 | + } |
| 12837 | + if(arg == "'") { |
| 12838 | + return std::string(1, stringQuote) + "'" + stringQuote; |
| 12839 | + } |
| 12840 | + return std::string(1, literalQuote) + arg + literalQuote; |
| 12841 | + } |
| 12842 | + // handle hex, binary or octal arguments |
| 12843 | + if(arg.front() == '0') { |
| 12844 | + if(arg[1] == 'x') { |
| 12845 | + if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { |
| 12846 | + return (x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f'); |
| 12847 | + })) { |
| 12848 | + return arg; |
| 12849 | + } |
| 12850 | + } else if(arg[1] == 'o') { |
| 12851 | + if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x >= '0' && x <= '7'); })) { |
| 12852 | + return arg; |
| 12853 | + } |
| 12854 | + } else if(arg[1] == 'b') { |
| 12855 | + if(std::all_of(arg.begin() + 2, arg.end(), [](char x) { return (x == '0' || x == '1'); })) { |
| 12856 | + return arg; |
| 12857 | + } |
| 12858 | + } |
| 12859 | + } |
| 12860 | + if(!is_printable(arg)) { |
| 12861 | + return binary_escape_string(arg); |
| 12862 | + } |
| 12863 | + if(detail::has_escapable_character(arg)) { |
| 12864 | + if(arg.size() > 100 && !disable_multi_line) { |
| 12865 | + if(arg.find(multiline_literal_quote) != std::string::npos) { |
| 12866 | + return binary_escape_string(arg, true); |
| 12867 | + } |
| 12868 | + std::string return_string{multiline_literal_quote}; |
| 12869 | + return_string.reserve(7 + arg.size()); |
| 12870 | + if(arg.front() == '\n' || arg.front() == '\r') { |
| 12871 | + return_string.push_back('\n'); |
| 12872 | + } |
| 12873 | + return_string.append(arg); |
| 12874 | + if(arg.back() == '\n' || arg.back() == '\r') { |
| 12875 | + return_string.push_back('\n'); |
| 12876 | + } |
| 12877 | + return_string.append(multiline_literal_quote, 3); |
| 12878 | + return return_string; |
| 12879 | + } |
| 12880 | + return std::string(1, stringQuote) + detail::add_escaped_characters(arg) + stringQuote; |
| 12881 | + } |
| 12882 | + return std::string(1, stringQuote) + arg + stringQuote; |
| 12883 | +} |
| 12884 | + |
| 12885 | +CLI11_INLINE std::string ini_join(const std::vector<std::string> &args, |
| 12886 | + char sepChar, |
| 12887 | + char arrayStart, |
| 12888 | + char arrayEnd, |
| 12889 | + char stringQuote, |
| 12890 | + char literalQuote) { |
| 12891 | + bool disable_multi_line{false}; |
| 12892 | + std::string joined; |
| 12893 | + if(args.size() > 1 && arrayStart != '\0') { |
| 12894 | + joined.push_back(arrayStart); |
| 12895 | + disable_multi_line = true; |
| 12896 | + } |
| 12897 | + std::size_t start = 0; |
| 12898 | + for(const auto &arg : args) { |
| 12899 | + if(start++ > 0) { |
| 12900 | + joined.push_back(sepChar); |
| 12901 | + if(!std::isspace<char>(sepChar, std::locale())) { |
| 12902 | + joined.push_back(' '); |
| 12903 | + } |
| 12904 | + } |
| 12905 | + joined.append(convert_arg_for_ini(arg, stringQuote, literalQuote, disable_multi_line)); |
| 12906 | + } |
| 12907 | + if(args.size() > 1 && arrayEnd != '\0') { |
| 12908 | + joined.push_back(arrayEnd); |
| 12909 | + } |
| 12910 | + return joined; |
| 12911 | +} |
| 12912 | + |
| 12913 | +CLI11_INLINE std::vector<std::string> |
| 12914 | +generate_parents(const std::string §ion, std::string &name, char parentSeparator) { |
| 12915 | + std::vector<std::string> parents; |
| 12916 | + if(detail::to_lower(section) != "default") { |
| 12917 | + if(section.find(parentSeparator) != std::string::npos) { |
| 12918 | + parents = detail::split_up(section, parentSeparator); |
| 12919 | + } else { |
| 12920 | + parents = {section}; |
| 12921 | + } |
| 12922 | + } |
| 12923 | + if(name.find(parentSeparator) != std::string::npos) { |
| 12924 | + std::vector<std::string> plist = detail::split_up(name, parentSeparator); |
| 12925 | + name = plist.back(); |
| 12926 | + plist.pop_back(); |
| 12927 | + parents.insert(parents.end(), plist.begin(), plist.end()); |
| 12928 | + } |
| 12929 | + // clean up quotes on the parents |
| 12930 | + try { |
| 12931 | + detail::remove_quotes(parents); |
| 12932 | + } catch(const std::invalid_argument &iarg) { |
| 12933 | + throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError); |
| 12934 | + } |
| 12935 | + return parents; |
| 12936 | +} |
| 12937 | + |
| 12938 | +CLI11_INLINE void |
| 12939 | +checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tSection, char parentSeparator) { |
| 12940 | + |
| 12941 | + std::string estring; |
| 12942 | + auto parents = detail::generate_parents(currentSection, estring, parentSeparator); |
| 12943 | + if(!output.empty() && output.back().name == "--") { |
| 12944 | + std::size_t msize = (parents.size() > 1U) ? parents.size() : 2; |
| 12945 | + while(output.back().parents.size() >= msize) { |
| 12946 | + output.push_back(output.back()); |
| 12947 | + output.back().parents.pop_back(); |
| 12948 | + } |
| 12949 | + |
| 12950 | + if(parents.size() > 1) { |
| 12951 | + std::size_t common = 0; |
| 12952 | + std::size_t mpair = (std::min)(output.back().parents.size(), parents.size() - 1); |
| 12953 | + for(std::size_t ii = 0; ii < mpair; ++ii) { |
| 12954 | + if(output.back().parents[ii] != parents[ii]) { |
| 12955 | + break; |
| 12956 | + } |
| 12957 | + ++common; |
| 12958 | + } |
| 12959 | + if(common == mpair) { |
| 12960 | + output.pop_back(); |
| 12961 | + } else { |
| 12962 | + while(output.back().parents.size() > common + 1) { |
| 12963 | + output.push_back(output.back()); |
| 12964 | + output.back().parents.pop_back(); |
| 12965 | + } |
| 12966 | + } |
| 12967 | + for(std::size_t ii = common; ii < parents.size() - 1; ++ii) { |
| 12968 | + output.emplace_back(); |
| 12969 | + output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1); |
| 12970 | + output.back().name = "++"; |
| 12971 | + } |
| 12972 | + } |
| 12973 | + } else if(parents.size() > 1) { |
| 12974 | + for(std::size_t ii = 0; ii < parents.size() - 1; ++ii) { |
| 12975 | + output.emplace_back(); |
| 12976 | + output.back().parents.assign(parents.begin(), parents.begin() + static_cast<std::ptrdiff_t>(ii) + 1); |
| 12977 | + output.back().name = "++"; |
| 12978 | + } |
| 12979 | + } |
| 12980 | + |
| 12981 | + // insert a section end which is just an empty items_buffer |
| 12982 | + output.emplace_back(); |
| 12983 | + output.back().parents = std::move(parents); |
| 12984 | + output.back().name = "++"; |
| 12985 | +} |
| 12986 | + |
| 12987 | +/// @brief checks if a string represents a multiline comment |
| 12988 | +CLI11_INLINE bool hasMLString(std::string const &fullString, char check) { |
| 12989 | + if(fullString.length() < 3) { |
| 12990 | + return false; |
| 12991 | + } |
| 12992 | + auto it = fullString.rbegin(); |
| 12993 | + return (*it == check) && (*(it + 1) == check) && (*(it + 2) == check); |
| 12994 | +} |
| 12995 | + |
| 12996 | +/// @brief find a matching configItem in a list |
| 12997 | +inline auto find_matching_config(std::vector<ConfigItem> &items, |
| 12998 | + const std::vector<std::string> &parents, |
| 12999 | + const std::string &name, |
| 13000 | + bool fullSearch) -> decltype(items.begin()) { |
| 13001 | + if(items.empty()) { |
| 13002 | + return items.end(); |
| 13003 | + } |
| 13004 | + auto search = items.end() - 1; |
| 13005 | + do { |
| 13006 | + if(search->parents == parents && search->name == name) { |
| 13007 | + return search; |
| 13008 | + } |
| 13009 | + if(search == items.begin()) { |
| 13010 | + break; |
| 13011 | + } |
| 13012 | + --search; |
| 13013 | + } while(fullSearch); |
| 13014 | + return items.end(); |
| 13015 | +} |
| 13016 | +} // namespace detail |
| 13017 | + |
| 13018 | +inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const { |
| 13019 | + std::string line; |
| 13020 | + std::string buffer; |
| 13021 | + std::string currentSection = "default"; |
| 13022 | + std::string previousSection = "default"; |
| 13023 | + std::vector<ConfigItem> output; |
| 13024 | + bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ','); |
| 13025 | + bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd; |
| 13026 | + bool inSection{false}; |
| 13027 | + bool inMLineComment{false}; |
| 13028 | + bool inMLineValue{false}; |
| 13029 | + |
| 13030 | + char aStart = (isINIArray) ? '[' : arrayStart; |
| 13031 | + char aEnd = (isINIArray) ? ']' : arrayEnd; |
| 13032 | + char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator; |
| 13033 | + int currentSectionIndex{0}; |
| 13034 | + |
| 13035 | + std::string line_sep_chars{parentSeparatorChar, commentChar, valueDelimiter}; |
| 13036 | + while(getline(input, buffer)) { |
| 13037 | + std::vector<std::string> items_buffer; |
| 13038 | + std::string name; |
| 13039 | + line = detail::trim_copy(buffer); |
| 13040 | + std::size_t len = line.length(); |
| 13041 | + // lines have to be at least 3 characters to have any meaning to CLI just skip the rest |
| 13042 | + if(len < 3) { |
| 13043 | + continue; |
| 13044 | + } |
| 13045 | + if(line.compare(0, 3, multiline_string_quote) == 0 || line.compare(0, 3, multiline_literal_quote) == 0) { |
| 13046 | + inMLineComment = true; |
| 13047 | + auto cchar = line.front(); |
| 13048 | + while(inMLineComment) { |
| 13049 | + if(getline(input, line)) { |
| 13050 | + detail::trim(line); |
| 13051 | + } else { |
| 13052 | + break; |
| 13053 | + } |
| 13054 | + if(detail::hasMLString(line, cchar)) { |
| 13055 | + inMLineComment = false; |
| 13056 | + } |
| 13057 | + } |
| 13058 | + continue; |
| 13059 | + } |
| 13060 | + if(line.front() == '[' && line.back() == ']') { |
| 13061 | + if(currentSection != "default") { |
| 13062 | + // insert a section end which is just an empty items_buffer |
| 13063 | + output.emplace_back(); |
| 13064 | + output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar); |
| 13065 | + output.back().name = "--"; |
| 13066 | + } |
| 13067 | + currentSection = line.substr(1, len - 2); |
| 13068 | + // deal with double brackets for TOML |
| 13069 | + if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') { |
| 13070 | + currentSection = currentSection.substr(1, currentSection.size() - 2); |
| 13071 | + } |
| 13072 | + if(detail::to_lower(currentSection) == "default") { |
| 13073 | + currentSection = "default"; |
| 13074 | + } else { |
| 13075 | + detail::checkParentSegments(output, currentSection, parentSeparatorChar); |
| 13076 | + } |
| 13077 | + inSection = false; |
| 13078 | + if(currentSection == previousSection) { |
| 13079 | + ++currentSectionIndex; |
| 13080 | + } else { |
| 13081 | + currentSectionIndex = 0; |
| 13082 | + previousSection = currentSection; |
| 13083 | + } |
| 13084 | + continue; |
| 13085 | + } |
| 13086 | + |
| 13087 | + // comment lines |
| 13088 | + if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) { |
| 13089 | + continue; |
| 13090 | + } |
| 13091 | + std::size_t search_start = 0; |
| 13092 | + if(line.find_first_of("\"'`") != std::string::npos) { |
| 13093 | + while(search_start < line.size()) { |
| 13094 | + auto test_char = line[search_start]; |
| 13095 | + if(test_char == '\"' || test_char == '\'' || test_char == '`') { |
| 13096 | + search_start = detail::close_sequence(line, search_start, line[search_start]); |
| 13097 | + ++search_start; |
| 13098 | + } else if(test_char == valueDelimiter || test_char == commentChar) { |
| 13099 | + --search_start; |
| 13100 | + break; |
| 13101 | + } else if(test_char == ' ' || test_char == '\t' || test_char == parentSeparatorChar) { |
| 13102 | + ++search_start; |
| 13103 | + } else { |
| 13104 | + search_start = line.find_first_of(line_sep_chars, search_start); |
| 13105 | + } |
| 13106 | + } |
| 13107 | + } |
| 13108 | + // Find = in string, split and recombine |
| 13109 | + auto delimiter_pos = line.find_first_of(valueDelimiter, search_start + 1); |
| 13110 | + auto comment_pos = line.find_first_of(commentChar, search_start); |
| 13111 | + if(comment_pos < delimiter_pos) { |
| 13112 | + delimiter_pos = std::string::npos; |
| 13113 | + } |
| 13114 | + if(delimiter_pos != std::string::npos) { |
| 13115 | + |
| 13116 | + name = detail::trim_copy(line.substr(0, delimiter_pos)); |
| 13117 | + std::string item = detail::trim_copy(line.substr(delimiter_pos + 1, std::string::npos)); |
| 13118 | + bool mlquote = |
| 13119 | + (item.compare(0, 3, multiline_literal_quote) == 0 || item.compare(0, 3, multiline_string_quote) == 0); |
| 13120 | + if(!mlquote && comment_pos != std::string::npos) { |
| 13121 | + auto citems = detail::split_up(item, commentChar); |
| 13122 | + item = detail::trim_copy(citems.front()); |
| 13123 | + } |
| 13124 | + if(mlquote) { |
| 13125 | + // multiline string |
| 13126 | + auto keyChar = item.front(); |
| 13127 | + item = buffer.substr(delimiter_pos + 1, std::string::npos); |
| 13128 | + detail::ltrim(item); |
| 13129 | + item.erase(0, 3); |
| 13130 | + inMLineValue = true; |
| 13131 | + bool lineExtension{false}; |
| 13132 | + bool firstLine = true; |
| 13133 | + if(!item.empty() && item.back() == '\\' && keyChar == '\"') { |
| 13134 | + item.pop_back(); |
| 13135 | + lineExtension = true; |
| 13136 | + } else if(detail::hasMLString(item, keyChar)) { |
| 13137 | + // deal with the first line closing the multiline literal |
| 13138 | + item.pop_back(); |
| 13139 | + item.pop_back(); |
| 13140 | + item.pop_back(); |
| 13141 | + if(keyChar == '\"') { |
| 13142 | + try { |
| 13143 | + item = detail::remove_escaped_characters(item); |
| 13144 | + } catch(const std::invalid_argument &iarg) { |
| 13145 | + throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError); |
| 13146 | + } |
| 13147 | + } |
| 13148 | + inMLineValue = false; |
| 13149 | + } |
| 13150 | + while(inMLineValue) { |
| 13151 | + std::string l2; |
| 13152 | + if(!std::getline(input, l2)) { |
| 13153 | + break; |
| 13154 | + } |
| 13155 | + line = l2; |
| 13156 | + detail::rtrim(line); |
| 13157 | + if(detail::hasMLString(line, keyChar)) { |
| 13158 | + line.pop_back(); |
| 13159 | + line.pop_back(); |
| 13160 | + line.pop_back(); |
| 13161 | + if(lineExtension) { |
| 13162 | + detail::ltrim(line); |
| 13163 | + } else if(!(firstLine && item.empty())) { |
| 13164 | + item.push_back('\n'); |
| 13165 | + } |
| 13166 | + firstLine = false; |
| 13167 | + item += line; |
| 13168 | + inMLineValue = false; |
| 13169 | + if(!item.empty() && item.back() == '\n') { |
| 13170 | + item.pop_back(); |
| 13171 | + } |
| 13172 | + if(keyChar == '\"') { |
| 13173 | + try { |
| 13174 | + item = detail::remove_escaped_characters(item); |
| 13175 | + } catch(const std::invalid_argument &iarg) { |
| 13176 | + throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError); |
| 13177 | + } |
| 13178 | + } |
| 13179 | + } else { |
| 13180 | + if(lineExtension) { |
| 13181 | + detail::trim(l2); |
| 13182 | + } else if(!(firstLine && item.empty())) { |
| 13183 | + item.push_back('\n'); |
| 13184 | + } |
| 13185 | + lineExtension = false; |
| 13186 | + firstLine = false; |
| 13187 | + if(!l2.empty() && l2.back() == '\\' && keyChar == '\"') { |
| 13188 | + lineExtension = true; |
| 13189 | + l2.pop_back(); |
| 13190 | + } |
| 13191 | + item += l2; |
| 13192 | + } |
| 13193 | + } |
| 13194 | + items_buffer = {item}; |
| 13195 | + } else if(!item.empty() && item.front() == aStart) { |
| 13196 | + for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) { |
| 13197 | + detail::trim(multiline); |
| 13198 | + item += multiline; |
| 13199 | + } |
| 13200 | + if(item.back() == aEnd) { |
| 13201 | + items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep); |
| 13202 | + } else { |
| 13203 | + items_buffer = detail::split_up(item.substr(1, std::string::npos), aSep); |
| 13204 | + } |
| 13205 | + } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) { |
| 13206 | + items_buffer = detail::split_up(item, aSep); |
| 13207 | + } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) { |
| 13208 | + items_buffer = detail::split_up(item, '\0'); |
| 13209 | + } else { |
| 13210 | + items_buffer = {item}; |
| 13211 | + } |
| 13212 | + } else { |
| 13213 | + name = detail::trim_copy(line.substr(0, comment_pos)); |
| 13214 | + items_buffer = {"true"}; |
| 13215 | + } |
| 13216 | + std::vector<std::string> parents; |
| 13217 | + try { |
| 13218 | + parents = detail::generate_parents(currentSection, name, parentSeparatorChar); |
| 13219 | + detail::process_quoted_string(name, '"', '\'', true); |
| 13220 | + // clean up quotes on the items and check for escaped strings |
| 13221 | + for(auto &it : items_buffer) { |
| 13222 | + detail::process_quoted_string(it, stringQuote, literalQuote); |
| 13223 | + } |
| 13224 | + } catch(const std::invalid_argument &ia) { |
| 13225 | + throw CLI::ParseError(ia.what(), CLI::ExitCodes::InvalidError); |
| 13226 | + } |
| 13227 | + |
| 13228 | + if(parents.size() > maximumLayers) { |
| 13229 | + continue; |
| 13230 | + } |
| 13231 | + if(!configSection.empty() && !inSection) { |
| 13232 | + if(parents.empty() || parents.front() != configSection) { |
| 13233 | + continue; |
| 13234 | + } |
| 13235 | + if(configIndex >= 0 && currentSectionIndex != configIndex) { |
| 13236 | + continue; |
| 13237 | + } |
| 13238 | + parents.erase(parents.begin()); |
| 13239 | + inSection = true; |
| 13240 | + } |
| 13241 | + auto match = detail::find_matching_config(output, parents, name, allowMultipleDuplicateFields); |
| 13242 | + if(match != output.end()) { |
| 13243 | + if((match->inputs.size() > 1 && items_buffer.size() > 1) || allowMultipleDuplicateFields) { |
| 13244 | + // insert a separator if one is not already present |
| 13245 | + if(!(match->inputs.back().empty() || items_buffer.front().empty() || match->inputs.back() == "%%" || |
| 13246 | + items_buffer.front() == "%%")) { |
| 13247 | + match->inputs.emplace_back("%%"); |
| 13248 | + match->multiline = true; |
| 13249 | + } |
| 13250 | + } |
| 13251 | + match->inputs.insert(match->inputs.end(), items_buffer.begin(), items_buffer.end()); |
| 13252 | + } else { |
| 13253 | + output.emplace_back(); |
| 13254 | + output.back().parents = std::move(parents); |
| 13255 | + output.back().name = std::move(name); |
| 13256 | + output.back().inputs = std::move(items_buffer); |
| 13257 | + } |
| 13258 | + } |
| 13259 | + if(currentSection != "default") { |
| 13260 | + // insert a section end which is just an empty items_buffer |
| 13261 | + std::string ename; |
| 13262 | + output.emplace_back(); |
| 13263 | + output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar); |
| 13264 | + output.back().name = "--"; |
| 13265 | + while(output.back().parents.size() > 1) { |
| 13266 | + output.push_back(output.back()); |
| 13267 | + output.back().parents.pop_back(); |
| 13268 | + } |
| 13269 | + } |
| 13270 | + return output; |
| 13271 | +} |
| 13272 | + |
| 13273 | +CLI11_INLINE std::string &clean_name_string(std::string &name, const std::string &keyChars) { |
| 13274 | + if(name.find_first_of(keyChars) != std::string::npos || (name.front() == '[' && name.back() == ']') || |
| 13275 | + (name.find_first_of("'`\"\\") != std::string::npos)) { |
| 13276 | + if(name.find_first_of('\'') == std::string::npos) { |
| 13277 | + name.insert(0, 1, '\''); |
| 13278 | + name.push_back('\''); |
| 13279 | + } else { |
| 13280 | + if(detail::has_escapable_character(name)) { |
| 13281 | + name = detail::add_escaped_characters(name); |
| 13282 | + } |
| 13283 | + name.insert(0, 1, '\"'); |
| 13284 | + name.push_back('\"'); |
| 13285 | + } |
| 13286 | + } |
| 13287 | + return name; |
| 13288 | +} |
| 13289 | + |
| 13290 | +CLI11_INLINE std::string |
| 13291 | +ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const { |
| 13292 | + std::stringstream out; |
| 13293 | + std::string commentLead; |
| 13294 | + commentLead.push_back(commentChar); |
| 13295 | + commentLead.push_back(' '); |
| 13296 | + |
| 13297 | + std::string commentTest = "#;"; |
| 13298 | + commentTest.push_back(commentChar); |
| 13299 | + commentTest.push_back(parentSeparatorChar); |
| 13300 | + |
| 13301 | + std::string keyChars = commentTest; |
| 13302 | + keyChars.push_back(literalQuote); |
| 13303 | + keyChars.push_back(stringQuote); |
| 13304 | + keyChars.push_back(arrayStart); |
| 13305 | + keyChars.push_back(arrayEnd); |
| 13306 | + keyChars.push_back(valueDelimiter); |
| 13307 | + keyChars.push_back(arraySeparator); |
| 13308 | + |
| 13309 | + std::vector<std::string> groups = app->get_groups(); |
| 13310 | + bool defaultUsed = false; |
| 13311 | + groups.insert(groups.begin(), std::string("OPTIONS")); |
| 13312 | + |
| 13313 | + for(auto &group : groups) { |
| 13314 | + if(group == "OPTIONS" || group.empty()) { |
| 13315 | + if(defaultUsed) { |
| 13316 | + continue; |
| 13317 | + } |
| 13318 | + defaultUsed = true; |
| 13319 | + } |
| 13320 | + if(write_description && group != "OPTIONS" && !group.empty()) { |
| 13321 | + out << '\n' << commentChar << commentLead << group << " Options\n"; |
| 13322 | + } |
| 13323 | + for(const Option *opt : app->get_options({})) { |
| 13324 | + // Only process options that are configurable |
| 13325 | + if(opt->get_configurable()) { |
| 13326 | + if(opt->get_group() != group) { |
| 13327 | + if(!(group == "OPTIONS" && opt->get_group().empty())) { |
| 13328 | + continue; |
| 13329 | + } |
| 13330 | + } |
| 13331 | + std::string single_name = opt->get_single_name(); |
| 13332 | + if(single_name.empty()) { |
| 13333 | + continue; |
| 13334 | + } |
| 13335 | + |
| 13336 | + auto results = opt->reduced_results(); |
| 13337 | + if(results.size() > 1 && opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Reverse) { |
| 13338 | + std::reverse(results.begin(), results.end()); |
| 13339 | + } |
| 13340 | + if(opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Sum && opt->count() >= 1 && |
| 13341 | + results.size() == 1) { |
| 13342 | + // if the multi option policy is sum then there is a possibility of incorrect fields being produced |
| 13343 | + // best to just use the original data for config files |
| 13344 | + auto pos = opt->_validate(results[0], 0); |
| 13345 | + if(!pos.empty()) { |
| 13346 | + results = opt->results(); |
| 13347 | + } |
| 13348 | + } |
| 13349 | + if(opt->get_multi_option_policy() == CLI::MultiOptionPolicy::Join && opt->count() > 1) { |
| 13350 | + char delim = opt->get_delimiter(); |
| 13351 | + if(delim == '\0') { |
| 13352 | + // this branch deals with a situation where the output would not be readable by a config file |
| 13353 | + results = opt->results(); |
| 13354 | + } else { |
| 13355 | + // this branch deals with the case of the strings containing the delimiter itself or empty |
| 13356 | + // strings which would be interpreted incorrectly |
| 13357 | + auto delim_count = std::count(results[0].begin(), results[0].end(), delim); |
| 13358 | + if(results[0].back() == delim || |
| 13359 | + static_cast<decltype(delim_count)>(opt->count()) < delim_count - 1 || |
| 13360 | + results[0].find(std::string(2, delim)) != std::string::npos) { |
| 13361 | + results = opt->results(); |
| 13362 | + } |
| 13363 | + } |
| 13364 | + } |
| 13365 | + std::string value; |
| 13366 | + |
| 13367 | + if(opt->count() == 1 && results.size() == 2 && results.front() == "{}" && results.back() == "%%") { |
| 13368 | + // there is a catch to allow for {} to used as as string in the output |
| 13369 | + // it will append a sequence terminator to the output so the lexical conversion handles it |
| 13370 | + // correctly but that is meant for config files so when outputting for a config file we need to |
| 13371 | + // makes sure to get the correct output |
| 13372 | + value = "\"{}\""; |
| 13373 | + } else { |
| 13374 | + value = detail::ini_join(results, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote); |
| 13375 | + } |
| 13376 | + |
| 13377 | + bool isDefault = false; |
| 13378 | + if(value.empty() && default_also) { |
| 13379 | + if(!opt->get_default_str().empty()) { |
| 13380 | + results_t res; |
| 13381 | + opt->results(res); |
| 13382 | + value = detail::ini_join(res, arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote); |
| 13383 | + } else if(opt->get_expected_min() == 0) { |
| 13384 | + value = "false"; |
| 13385 | + } else if(opt->get_run_callback_for_default() || !opt->get_required()) { |
| 13386 | + value = "\"\""; // empty string default value |
| 13387 | + } else { |
| 13388 | + value = "\"<REQUIRED>\""; |
| 13389 | + } |
| 13390 | + isDefault = true; |
| 13391 | + } |
| 13392 | + |
| 13393 | + if(!value.empty()) { |
| 13394 | + if(!opt->get_fnames().empty()) { |
| 13395 | + try { |
| 13396 | + value = opt->get_flag_value(single_name, value); |
| 13397 | + } catch(const CLI::ArgumentMismatch &) { |
| 13398 | + bool valid{false}; |
| 13399 | + for(const auto &test_name : opt->get_fnames()) { |
| 13400 | + try { |
| 13401 | + value = opt->get_flag_value(test_name, value); |
| 13402 | + single_name = test_name; |
| 13403 | + valid = true; |
| 13404 | + } catch(const CLI::ArgumentMismatch &) { |
| 13405 | + continue; |
| 13406 | + } |
| 13407 | + } |
| 13408 | + if(!valid) { |
| 13409 | + value = detail::ini_join( |
| 13410 | + opt->results(), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote); |
| 13411 | + } |
| 13412 | + } |
| 13413 | + } |
| 13414 | + if(write_description && opt->has_description()) { |
| 13415 | + if(out.tellp() != std::streampos(0)) { |
| 13416 | + out << '\n'; |
| 13417 | + } |
| 13418 | + out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n'; |
| 13419 | + } |
| 13420 | + clean_name_string(single_name, keyChars); |
| 13421 | + |
| 13422 | + std::string name = prefix + single_name; |
| 13423 | + if(commentDefaultsBool && isDefault) { |
| 13424 | + name = commentChar + name; |
| 13425 | + } |
| 13426 | + out << name << valueDelimiter << value << '\n'; |
| 13427 | + } |
| 13428 | + } |
| 13429 | + } |
| 13430 | + } |
| 13431 | + |
| 13432 | + auto subcommands = app->get_subcommands({}); |
| 13433 | + for(const App *subcom : subcommands) { |
| 13434 | + if(subcom->get_name().empty()) { |
| 13435 | + if(!default_also && (subcom->count_all() == 0)) { |
| 13436 | + continue; |
| 13437 | + } |
| 13438 | + if(write_description && !subcom->get_group().empty()) { |
| 13439 | + out << '\n' << commentLead << subcom->get_group() << " Options\n"; |
| 13440 | + } |
| 13441 | + /*if (!prefix.empty() || app->get_parent() == nullptr) { |
| 13442 | + out << '[' << prefix << "___"<< subcom->get_group() << "]\n"; |
| 13443 | + } else { |
| 13444 | + std::string subname = app->get_name() + parentSeparatorChar + "___"+subcom->get_group(); |
| 13445 | + const auto *p = app->get_parent(); |
| 13446 | + while(p->get_parent() != nullptr) { |
| 13447 | + subname = p->get_name() + parentSeparatorChar +subname; |
| 13448 | + p = p->get_parent(); |
| 13449 | + } |
| 13450 | + out << '[' << subname << "]\n"; |
| 13451 | + } |
| 13452 | + */ |
| 13453 | + out << to_config(subcom, default_also, write_description, prefix); |
| 13454 | + } |
| 13455 | + } |
| 13456 | + |
| 13457 | + for(const App *subcom : subcommands) { |
| 13458 | + if(!subcom->get_name().empty()) { |
| 13459 | + if(!default_also && (subcom->count_all() == 0)) { |
| 13460 | + continue; |
| 13461 | + } |
| 13462 | + std::string subname = subcom->get_name(); |
| 13463 | + clean_name_string(subname, keyChars); |
| 13464 | + |
| 13465 | + if(subcom->get_configurable() && (default_also || app->got_subcommand(subcom))) { |
| 13466 | + if(!prefix.empty() || app->get_parent() == nullptr) { |
| 13467 | + |
| 13468 | + out << '[' << prefix << subname << "]\n"; |
| 13469 | + } else { |
| 13470 | + std::string appname = app->get_name(); |
| 13471 | + clean_name_string(appname, keyChars); |
| 13472 | + subname = appname + parentSeparatorChar + subname; |
| 13473 | + const auto *p = app->get_parent(); |
| 13474 | + while(p->get_parent() != nullptr) { |
| 13475 | + std::string pname = p->get_name(); |
| 13476 | + clean_name_string(pname, keyChars); |
| 13477 | + subname = pname + parentSeparatorChar + subname; |
| 13478 | + p = p->get_parent(); |
| 13479 | + } |
| 13480 | + out << '[' << subname << "]\n"; |
| 13481 | + } |
| 13482 | + out << to_config(subcom, default_also, write_description, ""); |
| 13483 | + } else { |
| 13484 | + out << to_config(subcom, default_also, write_description, prefix + subname + parentSeparatorChar); |
| 13485 | + } |
| 13486 | + } |
| 13487 | + } |
| 13488 | + |
| 13489 | + if(write_description && !out.str().empty()) { |
| 13490 | + std::string outString = |
| 13491 | + commentChar + commentLead + detail::fix_newlines(commentChar + commentLead, app->get_description()) + '\n'; |
| 13492 | + return outString + out.str(); |
| 13493 | + } |
| 13494 | + return out.str(); |
| 13495 | +} |
| 13496 | + |
| 13497 | + |
| 13498 | + |
| 13499 | + |
| 13500 | + |
| 13501 | + |
| 13502 | +CLI11_INLINE std::string |
| 13503 | +Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const { |
| 13504 | + std::stringstream out; |
| 13505 | + |
| 13506 | + out << "\n" << group << ":\n"; |
| 13507 | + for(const Option *opt : opts) { |
| 13508 | + out << make_option(opt, is_positional); |
| 13509 | + } |
| 13510 | + |
| 13511 | + return out.str(); |
| 13512 | +} |
| 13513 | + |
| 13514 | +CLI11_INLINE std::string Formatter::make_positionals(const App *app) const { |
| 13515 | + std::vector<const Option *> opts = |
| 13516 | + app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); }); |
| 13517 | + |
| 13518 | + if(opts.empty()) |
| 13519 | + return {}; |
| 13520 | + |
| 13521 | + return make_group(get_label("POSITIONALS"), true, opts); |
| 13522 | +} |
| 13523 | + |
| 13524 | +CLI11_INLINE std::string Formatter::make_groups(const App *app, AppFormatMode mode) const { |
| 13525 | + std::stringstream out; |
| 13526 | + std::vector<std::string> groups = app->get_groups(); |
| 13527 | + |
| 13528 | + // Options |
| 13529 | + for(const std::string &group : groups) { |
| 13530 | + std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) { |
| 13531 | + return opt->get_group() == group // Must be in the right group |
| 13532 | + && opt->nonpositional() // Must not be a positional |
| 13533 | + && (mode != AppFormatMode::Sub // If mode is Sub, then |
| 13534 | + || (app->get_help_ptr() != opt // Ignore help pointer |
| 13535 | + && app->get_help_all_ptr() != opt)); // Ignore help all pointer |
| 13536 | + }); |
| 13537 | + if(!group.empty() && !opts.empty()) { |
| 13538 | + out << make_group(group, false, opts); |
| 13539 | + |
| 13540 | + // Removed double newline between groups for consistency of help text |
| 13541 | + // if(group != groups.back()) |
| 13542 | + // out << "\n"; |
| 13543 | + } |
| 13544 | + } |
| 13545 | + |
| 13546 | + return out.str(); |
| 13547 | +} |
| 13548 | + |
| 13549 | +CLI11_INLINE std::string Formatter::make_description(const App *app) const { |
| 13550 | + std::string desc = app->get_description(); |
| 13551 | + auto min_options = app->get_require_option_min(); |
| 13552 | + auto max_options = app->get_require_option_max(); |
| 13553 | + |
| 13554 | + if(app->get_required()) { |
| 13555 | + desc += " " + get_label("REQUIRED") + " "; |
| 13556 | + } |
| 13557 | + |
| 13558 | + if(min_options > 0) { |
| 13559 | + if(max_options == min_options) { |
| 13560 | + desc += " \n[Exactly " + std::to_string(min_options) + " of the following options are required]"; |
| 13561 | + } else if(max_options > 0) { |
| 13562 | + desc += " \n[Between " + std::to_string(min_options) + " and " + std::to_string(max_options) + |
| 13563 | + " of the following options are required]"; |
| 13564 | + } else { |
| 13565 | + desc += " \n[At least " + std::to_string(min_options) + " of the following options are required]"; |
| 13566 | + } |
| 13567 | + } else if(max_options > 0) { |
| 13568 | + desc += " \n[At most " + std::to_string(max_options) + " of the following options are allowed]"; |
| 13569 | + } |
| 13570 | + |
| 13571 | + return (!desc.empty()) ? desc + "\n\n" : std::string{}; |
| 13572 | +} |
| 13573 | + |
| 13574 | +CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name) const { |
| 13575 | + std::string usage = app->get_usage(); |
| 13576 | + if(!usage.empty()) { |
| 13577 | + return usage + "\n\n"; |
| 13578 | + } |
| 13579 | + |
| 13580 | + std::stringstream out; |
| 13581 | + out << '\n'; |
| 13582 | + |
| 13583 | + if(name.empty()) |
| 13584 | + out << get_label("Usage") << ':'; |
| 13585 | + else |
| 13586 | + out << name; |
| 13587 | + |
| 13588 | + std::vector<std::string> groups = app->get_groups(); |
| 13589 | + |
| 13590 | + // Print an Options badge if any options exist |
| 13591 | + std::vector<const Option *> non_pos_options = |
| 13592 | + app->get_options([](const Option *opt) { return opt->nonpositional(); }); |
| 13593 | + if(!non_pos_options.empty()) |
| 13594 | + out << " [" << get_label("OPTIONS") << "]"; |
| 13595 | + |
| 13596 | + // Positionals need to be listed here |
| 13597 | + std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); }); |
| 13598 | + |
| 13599 | + // Print out positionals if any are left |
| 13600 | + if(!positionals.empty()) { |
| 13601 | + // Convert to help names |
| 13602 | + std::vector<std::string> positional_names(positionals.size()); |
| 13603 | + std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) { |
| 13604 | + return make_option_usage(opt); |
| 13605 | + }); |
| 13606 | + |
| 13607 | + out << " " << detail::join(positional_names, " "); |
| 13608 | + } |
| 13609 | + |
| 13610 | + // Add a marker if subcommands are expected or optional |
| 13611 | + if(!app->get_subcommands( |
| 13612 | + [](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); }) |
| 13613 | + .empty()) { |
| 13614 | + out << ' ' << (app->get_require_subcommand_min() == 0 ? "[" : "") |
| 13615 | + << get_label(app->get_require_subcommand_max() == 1 ? "SUBCOMMAND" : "SUBCOMMANDS") |
| 13616 | + << (app->get_require_subcommand_min() == 0 ? "]" : ""); |
| 13617 | + } |
| 13618 | + |
| 13619 | + out << "\n\n"; |
| 13620 | + |
| 13621 | + return out.str(); |
| 13622 | +} |
| 13623 | + |
| 13624 | +CLI11_INLINE std::string Formatter::make_footer(const App *app) const { |
| 13625 | + std::string footer = app->get_footer(); |
| 13626 | + if(footer.empty()) { |
| 13627 | + return std::string{}; |
| 13628 | + } |
| 13629 | + return '\n' + footer + '\n'; |
| 13630 | +} |
| 13631 | + |
| 13632 | +CLI11_INLINE std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const { |
| 13633 | + // This immediately forwards to the make_expanded method. This is done this way so that subcommands can |
| 13634 | + // have overridden formatters |
| 13635 | + if(mode == AppFormatMode::Sub) |
| 13636 | + return make_expanded(app, mode); |
| 13637 | + |
| 13638 | + std::stringstream out; |
| 13639 | + if((app->get_name().empty()) && (app->get_parent() != nullptr)) { |
| 13640 | + if(app->get_group() != "SUBCOMMANDS") { |
| 13641 | + out << app->get_group() << ':'; |
| 13642 | + } |
| 13643 | + } |
| 13644 | + if(is_description_paragraph_formatting_enabled()) { |
| 13645 | + detail::streamOutAsParagraph( |
| 13646 | + out, make_description(app), description_paragraph_width_, ""); // Format description as paragraph |
| 13647 | + } else { |
| 13648 | + out << make_description(app) << '\n'; |
| 13649 | + } |
| 13650 | + out << make_usage(app, name); |
| 13651 | + out << make_positionals(app); |
| 13652 | + out << make_groups(app, mode); |
| 13653 | + out << make_subcommands(app, mode); |
| 13654 | + std::string footer_string = make_footer(app); |
| 13655 | + |
| 13656 | + if(is_footer_paragraph_formatting_enabled()) { |
| 13657 | + detail::streamOutAsParagraph(out, footer_string, footer_paragraph_width_); // Format footer as paragraph |
| 13658 | + } else { |
| 13659 | + out << footer_string; |
| 13660 | + } |
| 13661 | + |
| 13662 | + return out.str(); |
| 13663 | +} |
| 13664 | + |
| 13665 | +CLI11_INLINE std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const { |
| 13666 | + std::stringstream out; |
| 13667 | + |
| 13668 | + std::vector<const App *> subcommands = app->get_subcommands({}); |
| 13669 | + |
| 13670 | + // Make a list in definition order of the groups seen |
| 13671 | + std::vector<std::string> subcmd_groups_seen; |
| 13672 | + for(const App *com : subcommands) { |
| 13673 | + if(com->get_name().empty()) { |
| 13674 | + if(!com->get_group().empty() && com->get_group().front() != '+') { |
| 13675 | + out << make_expanded(com, mode); |
| 13676 | + } |
| 13677 | + continue; |
| 13678 | + } |
| 13679 | + std::string group_key = com->get_group(); |
| 13680 | + if(!group_key.empty() && |
| 13681 | + std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) { |
| 13682 | + return detail::to_lower(a) == detail::to_lower(group_key); |
| 13683 | + }) == subcmd_groups_seen.end()) |
| 13684 | + subcmd_groups_seen.push_back(group_key); |
| 13685 | + } |
| 13686 | + |
| 13687 | + // For each group, filter out and print subcommands |
| 13688 | + for(const std::string &group : subcmd_groups_seen) { |
| 13689 | + out << '\n' << group << ":\n"; |
| 13690 | + std::vector<const App *> subcommands_group = app->get_subcommands( |
| 13691 | + [&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); }); |
| 13692 | + for(const App *new_com : subcommands_group) { |
| 13693 | + if(new_com->get_name().empty()) |
| 13694 | + continue; |
| 13695 | + if(mode != AppFormatMode::All) { |
| 13696 | + out << make_subcommand(new_com); |
| 13697 | + } else { |
| 13698 | + out << new_com->help(new_com->get_name(), AppFormatMode::Sub); |
| 13699 | + out << '\n'; |
| 13700 | + } |
| 13701 | + } |
| 13702 | + } |
| 13703 | + |
| 13704 | + return out.str(); |
| 13705 | +} |
| 13706 | + |
| 13707 | +CLI11_INLINE std::string Formatter::make_subcommand(const App *sub) const { |
| 13708 | + std::stringstream out; |
| 13709 | + std::string name = " " + sub->get_display_name(true) + (sub->get_required() ? " " + get_label("REQUIRED") : ""); |
| 13710 | + |
| 13711 | + out << std::setw(static_cast<int>(column_width_)) << std::left << name; |
| 13712 | + detail::streamOutAsParagraph( |
| 13713 | + out, sub->get_description(), right_column_width_, std::string(column_width_, ' '), true); |
| 13714 | + out << '\n'; |
| 13715 | + return out.str(); |
| 13716 | +} |
| 13717 | + |
| 13718 | +CLI11_INLINE std::string Formatter::make_expanded(const App *sub, AppFormatMode mode) const { |
| 13719 | + std::stringstream out; |
| 13720 | + out << sub->get_display_name(true) << '\n'; |
| 13721 | + |
| 13722 | + if(is_description_paragraph_formatting_enabled()) { |
| 13723 | + detail::streamOutAsParagraph( |
| 13724 | + out, make_description(sub), description_paragraph_width_, " "); // Format description as paragraph |
| 13725 | + } else { |
| 13726 | + out << make_description(sub) << '\n'; |
| 13727 | + } |
| 13728 | + |
| 13729 | + if(sub->get_name().empty() && !sub->get_aliases().empty()) { |
| 13730 | + detail::format_aliases(out, sub->get_aliases(), column_width_ + 2); |
| 13731 | + } |
| 13732 | + |
| 13733 | + out << make_positionals(sub); |
| 13734 | + out << make_groups(sub, mode); |
| 13735 | + out << make_subcommands(sub, mode); |
| 13736 | + std::string footer_string = make_footer(sub); |
| 13737 | + |
| 13738 | + if(mode == AppFormatMode::Sub && !footer_string.empty()) { |
| 13739 | + const auto *parent = sub->get_parent(); |
| 13740 | + std::string parent_footer = (parent != nullptr) ? make_footer(sub->get_parent()) : std::string{}; |
| 13741 | + if(footer_string == parent_footer) { |
| 13742 | + footer_string = ""; |
| 13743 | + } |
| 13744 | + } |
| 13745 | + if(!footer_string.empty()) { |
| 13746 | + if(is_footer_paragraph_formatting_enabled()) { |
| 13747 | + detail::streamOutAsParagraph(out, footer_string, footer_paragraph_width_); // Format footer as paragraph |
| 13748 | + } else { |
| 13749 | + out << footer_string; |
| 13750 | + } |
| 13751 | + } |
| 13752 | + return out.str(); |
| 13753 | +} |
| 13754 | + |
| 13755 | +CLI11_INLINE std::string Formatter::make_option(const Option *opt, bool is_positional) const { |
| 13756 | + std::stringstream out; |
| 13757 | + if(is_positional) { |
| 13758 | + const std::string left = " " + make_option_name(opt, true) + make_option_opts(opt); |
| 13759 | + const std::string desc = make_option_desc(opt); |
| 13760 | + out << std::setw(static_cast<int>(column_width_)) << std::left << left; |
| 13761 | + |
| 13762 | + if(!desc.empty()) { |
| 13763 | + bool skipFirstLinePrefix = true; |
| 13764 | + if(left.length() >= column_width_) { |
| 13765 | + out << '\n'; |
| 13766 | + skipFirstLinePrefix = false; |
| 13767 | + } |
| 13768 | + detail::streamOutAsParagraph( |
| 13769 | + out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix); |
| 13770 | + } |
| 13771 | + } else { |
| 13772 | + const std::string namesCombined = make_option_name(opt, false); |
| 13773 | + const std::string opts = make_option_opts(opt); |
| 13774 | + const std::string desc = make_option_desc(opt); |
| 13775 | + |
| 13776 | + // Split all names at comma and sort them into short names and long names |
| 13777 | + const auto names = detail::split(namesCombined, ','); |
| 13778 | + std::vector<std::string> vshortNames; |
| 13779 | + std::vector<std::string> vlongNames; |
| 13780 | + std::for_each(names.begin(), names.end(), [&vshortNames, &vlongNames](const std::string &name) { |
| 13781 | + if(name.find("--", 0) != std::string::npos) |
| 13782 | + vlongNames.push_back(name); |
| 13783 | + else |
| 13784 | + vshortNames.push_back(name); |
| 13785 | + }); |
| 13786 | + |
| 13787 | + // Assemble short and long names |
| 13788 | + std::string shortNames = detail::join(vshortNames, ", "); |
| 13789 | + std::string longNames = detail::join(vlongNames, ", "); |
| 13790 | + |
| 13791 | + // Calculate setw sizes |
| 13792 | + // Short names take enough width to align long names at the desired ratio |
| 13793 | + const auto shortNamesColumnWidth = |
| 13794 | + static_cast<int>(static_cast<float>(column_width_) * long_option_alignment_ratio_); |
| 13795 | + const auto longNamesColumnWidth = static_cast<int>(column_width_) - shortNamesColumnWidth; |
| 13796 | + int shortNamesOverSize = 0; |
| 13797 | + |
| 13798 | + // Print short names |
| 13799 | + if(!shortNames.empty()) { |
| 13800 | + shortNames = " " + shortNames; // Indent |
| 13801 | + if(longNames.empty() && !opts.empty()) |
| 13802 | + shortNames += opts; // Add opts if only short names and no long names |
| 13803 | + if(!longNames.empty()) |
| 13804 | + shortNames += ","; |
| 13805 | + if(static_cast<int>(shortNames.length()) >= shortNamesColumnWidth) { |
| 13806 | + shortNames += " "; |
| 13807 | + shortNamesOverSize = static_cast<int>(shortNames.length()) - shortNamesColumnWidth; |
| 13808 | + } |
| 13809 | + out << std::setw(shortNamesColumnWidth) << std::left << shortNames; |
| 13810 | + } else { |
| 13811 | + out << std::setw(shortNamesColumnWidth) << std::left << ""; |
| 13812 | + } |
| 13813 | + |
| 13814 | + // Adjust long name column width in case of short names column reaching into long names column |
| 13815 | + shortNamesOverSize = |
| 13816 | + (std::min)(shortNamesOverSize, longNamesColumnWidth); // Prevent negative result with unsigned integers |
| 13817 | + const auto adjustedLongNamesColumnWidth = longNamesColumnWidth - shortNamesOverSize; |
| 13818 | + |
| 13819 | + // Print long names |
| 13820 | + if(!longNames.empty()) { |
| 13821 | + if(!opts.empty()) |
| 13822 | + longNames += opts; |
| 13823 | + if(static_cast<int>(longNames.length()) >= adjustedLongNamesColumnWidth) |
| 13824 | + longNames += " "; |
| 13825 | + |
| 13826 | + out << std::setw(adjustedLongNamesColumnWidth) << std::left << longNames; |
| 13827 | + } else { |
| 13828 | + out << std::setw(adjustedLongNamesColumnWidth) << std::left << ""; |
| 13829 | + } |
| 13830 | + |
| 13831 | + if(!desc.empty()) { |
| 13832 | + bool skipFirstLinePrefix = true; |
| 13833 | + if(out.str().length() > column_width_) { |
| 13834 | + out << '\n'; |
| 13835 | + skipFirstLinePrefix = false; |
| 13836 | + } |
| 13837 | + detail::streamOutAsParagraph( |
| 13838 | + out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix); |
| 13839 | + } |
| 13840 | + } |
| 13841 | + |
| 13842 | + out << '\n'; |
| 13843 | + return out.str(); |
| 13844 | +} |
| 13845 | + |
| 13846 | +CLI11_INLINE std::string Formatter::make_option_name(const Option *opt, bool is_positional) const { |
| 13847 | + if(is_positional) |
| 13848 | + return opt->get_name(true, false); |
| 13849 | + |
| 13850 | + return opt->get_name(false, true, !enable_default_flag_values_); |
| 13851 | +} |
| 13852 | + |
| 13853 | +CLI11_INLINE std::string Formatter::make_option_opts(const Option *opt) const { |
| 13854 | + std::stringstream out; |
| 13855 | + |
| 13856 | + if(!opt->get_option_text().empty()) { |
| 13857 | + out << " " << opt->get_option_text(); |
| 13858 | + } else { |
| 13859 | + if(opt->get_type_size() != 0) { |
| 13860 | + if(enable_option_type_names_) { |
| 13861 | + if(!opt->get_type_name().empty()) |
| 13862 | + out << " " << get_label(opt->get_type_name()); |
| 13863 | + } |
| 13864 | + if(enable_option_defaults_) { |
| 13865 | + if(!opt->get_default_str().empty()) |
| 13866 | + out << " [" << opt->get_default_str() << "] "; |
| 13867 | + } |
| 13868 | + if(opt->get_expected_max() == detail::expected_max_vector_size) |
| 13869 | + out << " ..."; |
| 13870 | + else if(opt->get_expected_min() > 1) |
| 13871 | + out << " x " << opt->get_expected(); |
| 13872 | + |
| 13873 | + if(opt->get_required()) |
| 13874 | + out << " " << get_label("REQUIRED"); |
| 13875 | + } |
| 13876 | + if(!opt->get_envname().empty()) |
| 13877 | + out << " (" << get_label("Env") << ":" << opt->get_envname() << ")"; |
| 13878 | + if(!opt->get_needs().empty()) { |
| 13879 | + out << " " << get_label("Needs") << ":"; |
| 13880 | + for(const Option *op : opt->get_needs()) |
| 13881 | + out << " " << op->get_name(); |
| 13882 | + } |
| 13883 | + if(!opt->get_excludes().empty()) { |
| 13884 | + out << " " << get_label("Excludes") << ":"; |
| 13885 | + for(const Option *op : opt->get_excludes()) |
| 13886 | + out << " " << op->get_name(); |
| 13887 | + } |
| 13888 | + } |
| 13889 | + return out.str(); |
| 13890 | +} |
| 13891 | + |
| 13892 | +CLI11_INLINE std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); } |
| 13893 | + |
| 13894 | +CLI11_INLINE std::string Formatter::make_option_usage(const Option *opt) const { |
| 13895 | + // Note that these are positionals usages |
| 13896 | + std::stringstream out; |
| 13897 | + out << make_option_name(opt, true); |
| 13898 | + if(opt->get_expected_max() >= detail::expected_max_vector_size) |
| 13899 | + out << "..."; |
| 13900 | + else if(opt->get_expected_max() > 1) |
| 13901 | + out << "(" << opt->get_expected() << "x)"; |
| 13902 | + |
| 13903 | + return opt->get_required() ? out.str() : "[" + out.str() + "]"; |
| 13904 | +} |
| 13905 | + |
| 13906 | + |
| 13907 | +} // namespace CLI |
| 13908 | diff --git a/src/realmachine/lib/cli11/LICENSE b/src/realmachine/lib/cli11/LICENSE |
| 13909 | new file mode 100644 |
| 13910 | index 0000000..dc6d29f |
| 13911 | --- /dev/null |
| 13912 | +++ b/src/realmachine/lib/cli11/LICENSE |
| 13913 | @@ -0,0 +1,25 @@ |
| 13914 | +CLI11 2.6.2 Copyright (c) 2017-2026 University of Cincinnati, developed by Henry |
| 13915 | +Schreiner under NSF AWARD 1414736. All rights reserved. |
| 13916 | + |
| 13917 | +Redistribution and use in source and binary forms of CLI11, with or without |
| 13918 | +modification, are permitted provided that the following conditions are met: |
| 13919 | + |
| 13920 | +1. Redistributions of source code must retain the above copyright notice, this |
| 13921 | + list of conditions and the following disclaimer. |
| 13922 | +2. Redistributions in binary form must reproduce the above copyright notice, |
| 13923 | + this list of conditions and the following disclaimer in the documentation |
| 13924 | + and/or other materials provided with the distribution. |
| 13925 | +3. Neither the name of the copyright holder nor the names of its contributors |
| 13926 | + may be used to endorse or promote products derived from this software without |
| 13927 | + specific prior written permission. |
| 13928 | + |
| 13929 | +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 13930 | +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 13931 | +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 13932 | +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR |
| 13933 | +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 13934 | +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 13935 | +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| 13936 | +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 13937 | +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 13938 | +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 13939 | diff --git a/src/realmachine/lib/cli11/cli11_wrapper.cpp b/src/realmachine/lib/cli11/cli11_wrapper.cpp |
| 13940 | new file mode 100644 |
| 13941 | index 0000000..1f64d6a |
| 13942 | --- /dev/null |
| 13943 | +++ b/src/realmachine/lib/cli11/cli11_wrapper.cpp |
| 13944 | @@ -0,0 +1 @@ |
| 13945 | +#include "CLI11.h" |
| 13946 | \ No newline at end of file |
| 13947 | diff --git a/src/realmachine/lib/cli11/cli11_wrapper.h b/src/realmachine/lib/cli11/cli11_wrapper.h |
| 13948 | new file mode 100644 |
| 13949 | index 0000000..49e6804 |
| 13950 | --- /dev/null |
| 13951 | +++ b/src/realmachine/lib/cli11/cli11_wrapper.h |
| 13952 | @@ -0,0 +1,2 @@ |
| 13953 | +#pragma once |
| 13954 | +#include "CLI11.h" |
| 13955 | \ No newline at end of file |
| 13956 | diff --git a/src/realmachine/lib/utils.cpp b/src/realmachine/lib/utils.cpp |
| 13957 | new file mode 100644 |
| 13958 | index 0000000..a1cd931 |
| 13959 | --- /dev/null |
| 13960 | +++ b/src/realmachine/lib/utils.cpp |
| 13961 | @@ -0,0 +1,318 @@ |
| 13962 | +#include <sys/utsname.h> |
| 13963 | +#include <cmath> |
| 13964 | +#include <cstdio> |
| 13965 | +#include <cctype> |
| 13966 | +#include <cstring> |
| 13967 | +#include <stdexcept> |
| 13968 | + |
| 13969 | +#include <flogger/flogger.h> |
| 13970 | +#include <shellescape/shellescape.h> |
| 13971 | + |
| 13972 | +#include "utils.h" |
| 13973 | + |
| 13974 | +auto log_realmachine_utils = flogger::bind_log("realmachine/utils"); |
| 13975 | + |
| 13976 | +namespace utils_realmachine { |
| 13977 | + |
| 13978 | + std::string human_size_str(const int64_t num) { |
| 13979 | + static const char *units[] = {"", "K", "M", "G", "T", "P"}; |
| 13980 | + auto n = static_cast<double>(num); |
| 13981 | + |
| 13982 | + for (const char *unit: units) { |
| 13983 | + if (std::abs(n) < 1024.0) { |
| 13984 | + char buf[32]; |
| 13985 | + std::snprintf(buf, sizeof(buf), "%.1f%s", n, unit); |
| 13986 | + return {buf}; |
| 13987 | + } |
| 13988 | + n /= 1024.0; |
| 13989 | + } |
| 13990 | + |
| 13991 | + char buf[32]; |
| 13992 | + std::snprintf(buf, sizeof(buf), "%.1fE", n); |
| 13993 | + return {buf}; |
| 13994 | + } |
| 13995 | + |
| 13996 | + std::string human_time_str(const std::chrono::microseconds &us) { |
| 13997 | + auto t = static_cast<double>(us.count()); |
| 13998 | + |
| 13999 | + struct Unit { |
| 14000 | + const char *name; |
| 14001 | + double scale; |
| 14002 | + }; |
| 14003 | + Unit units[] = { |
| 14004 | + {"µs", 1.0}, |
| 14005 | + {"ms", 1000.0}, |
| 14006 | + {"s", 1000.0}, |
| 14007 | + {"min", 60.0}, |
| 14008 | + {"h", 60.0} |
| 14009 | + }; |
| 14010 | + |
| 14011 | + for (auto &u: units) { |
| 14012 | + if (t < u.scale) { |
| 14013 | + char buf[32]; |
| 14014 | + std::snprintf(buf, sizeof(buf), "%.3g%s", t, u.name); |
| 14015 | + return {buf}; |
| 14016 | + } |
| 14017 | + t /= u.scale; |
| 14018 | + } |
| 14019 | + |
| 14020 | + char buf[32]; |
| 14021 | + std::snprintf(buf, sizeof(buf), "%.3gh", t); |
| 14022 | + return {buf}; |
| 14023 | + } |
| 14024 | + |
| 14025 | + int64_t parse_human_size(const std::string &s) { |
| 14026 | + double value = 0.0; |
| 14027 | + char unit = 0; |
| 14028 | + |
| 14029 | + if (std::sscanf(s.c_str(), "%lf%c", &value, &unit) < 1) |
| 14030 | + throw std::invalid_argument("invalid size"); |
| 14031 | + |
| 14032 | + int exp = 0; |
| 14033 | + switch (unit) { |
| 14034 | + case 'K': |
| 14035 | + exp = 1; |
| 14036 | + break; |
| 14037 | + case 'M': |
| 14038 | + exp = 2; |
| 14039 | + break; |
| 14040 | + case 'G': |
| 14041 | + exp = 3; |
| 14042 | + break; |
| 14043 | + case 'T': |
| 14044 | + exp = 4; |
| 14045 | + break; |
| 14046 | + case 'P': |
| 14047 | + exp = 5; |
| 14048 | + break; |
| 14049 | + case '\0': |
| 14050 | + exp = 0; |
| 14051 | + break; |
| 14052 | + default: |
| 14053 | + throw std::invalid_argument("unknown size unit"); |
| 14054 | + } |
| 14055 | + |
| 14056 | + double n = value; |
| 14057 | + while (exp-- > 0) |
| 14058 | + n *= 1024.0; |
| 14059 | + return static_cast<int64_t>(n); |
| 14060 | + } |
| 14061 | + |
| 14062 | + std::chrono::microseconds parse_human_time(const std::string &s) { |
| 14063 | + double value = 0.0; |
| 14064 | + char unit[8] = {0}; |
| 14065 | + |
| 14066 | + if (std::sscanf(s.c_str(), "%lf%7s", &value, unit) < 1) |
| 14067 | + throw std::invalid_argument("invalid time"); |
| 14068 | + |
| 14069 | + double scale = 1.0; |
| 14070 | + |
| 14071 | + if (std::strcmp(unit, "µs") == 0 || std::strcmp(unit, "us") == 0) |
| 14072 | + scale = 1.0; |
| 14073 | + else if (std::strcmp(unit, "ms") == 0) |
| 14074 | + scale = 1000.0; |
| 14075 | + else if (std::strcmp(unit, "s") == 0) |
| 14076 | + scale = 1000.0 * 1000.0; |
| 14077 | + else if (std::strcmp(unit, "min") == 0) |
| 14078 | + scale = 60.0 * 1000.0 * 1000.0; |
| 14079 | + else if (std::strcmp(unit, "h") == 0) |
| 14080 | + scale = 60.0 * 60.0 * 1000.0 * 1000.0; |
| 14081 | + else if (unit[0] == '\0') |
| 14082 | + scale = 1.0; |
| 14083 | + else |
| 14084 | + throw std::invalid_argument("unknown time unit"); |
| 14085 | + |
| 14086 | + return std::chrono::microseconds(static_cast<int64_t>(value * scale)); |
| 14087 | + } |
| 14088 | + |
| 14089 | + void create_standard_dirs() { |
| 14090 | + if (geteuid() != 0) { |
| 14091 | + throw std::runtime_error("Root privileges are required to create system directories"); |
| 14092 | + } |
| 14093 | + |
| 14094 | + constexpr std::array<const char *, 3> dirs{ |
| 14095 | + "/etc/app", |
| 14096 | + "/var/cache/app", |
| 14097 | + "/var/lib/app" |
| 14098 | + }; |
| 14099 | + |
| 14100 | + constexpr std::array<const char *, 4> files{ |
| 14101 | + "/etc/app/world", |
| 14102 | + "/etc/app/repositories", |
| 14103 | + "/etc/app/keys", |
| 14104 | + "/etc/app/arch" |
| 14105 | + }; |
| 14106 | + |
| 14107 | + for (auto &&d: dirs) { |
| 14108 | + std::filesystem::create_directories(d); |
| 14109 | + } |
| 14110 | + |
| 14111 | + for (auto &&f: files) { |
| 14112 | + if (!std::filesystem::exists(f)) { |
| 14113 | + std::ofstream ofs(f); // create empty file |
| 14114 | + } |
| 14115 | + } |
| 14116 | + } |
| 14117 | + |
| 14118 | + void parse_package_version(const std::string& v, uint32_t& major, uint32_t& minor, uint32_t& patch, uint32_t& release) { |
| 14119 | + major = minor = patch = release = 0; |
| 14120 | + |
| 14121 | + if (v.empty()) |
| 14122 | + throw std::invalid_argument("empty version string"); |
| 14123 | + |
| 14124 | + std::string ver = v; |
| 14125 | + size_t dash = ver.find('-'); |
| 14126 | + |
| 14127 | + if (dash != std::string::npos) { |
| 14128 | + std::string r = ver.substr(dash + 1); |
| 14129 | + ver = ver.substr(0, dash); |
| 14130 | + |
| 14131 | + if (!r.empty()) { |
| 14132 | + if (r[0] == 'r' || r[0] == 'R') |
| 14133 | + release = std::stoul(r.substr(1)); |
| 14134 | + else |
| 14135 | + release = std::stoul(r); |
| 14136 | + } |
| 14137 | + } |
| 14138 | + |
| 14139 | + const size_t p1 = ver.find('.'); |
| 14140 | + if (p1 == std::string::npos) { |
| 14141 | + major = std::stoul(ver); |
| 14142 | + return; |
| 14143 | + } |
| 14144 | + |
| 14145 | + const size_t p2 = ver.find('.', p1 + 1); |
| 14146 | + |
| 14147 | + major = std::stoul(ver.substr(0, p1)); |
| 14148 | + |
| 14149 | + if (p2 == std::string::npos) { |
| 14150 | + minor = std::stoul(ver.substr(p1 + 1)); |
| 14151 | + patch = 0; |
| 14152 | + return; |
| 14153 | + } |
| 14154 | + |
| 14155 | + minor = std::stoul(ver.substr(p1 + 1, p2 - p1 - 1)); |
| 14156 | + patch = std::stoul(ver.substr(p2 + 1)); |
| 14157 | + } |
| 14158 | + |
| 14159 | + void move_recursive(const std::filesystem::path &src, const std::filesystem::path &dst) { |
| 14160 | + printf_debug(log_realmachine_utils, "src: %s, dst: %s", src.string().c_str(), dst.string().c_str()); |
| 14161 | + |
| 14162 | + std::error_code ec; |
| 14163 | + std::filesystem::create_directories(dst, ec); |
| 14164 | + if (ec) throw std::runtime_error("Failed to create directory: " + dst.string()); |
| 14165 | + |
| 14166 | + for (auto &entry : std::filesystem::directory_iterator(src, std::filesystem::directory_options::skip_permission_denied, ec)) { |
| 14167 | + if (ec) |
| 14168 | + throw std::runtime_error("Failed to iterate directory: " + src.string()); |
| 14169 | + |
| 14170 | + auto target_path = dst / entry.path().filename(); |
| 14171 | + const auto st = std::filesystem::symlink_status(entry.path(), ec); |
| 14172 | + if (ec) |
| 14173 | + throw std::runtime_error("Failed to get status for: " + entry.path().string()); |
| 14174 | + |
| 14175 | + if (std::filesystem::is_directory(st)) { |
| 14176 | + move_recursive(entry.path(), target_path); |
| 14177 | + std::filesystem::remove(entry.path(), ec); |
| 14178 | + if (ec) |
| 14179 | + throw std::runtime_error("Failed to remove directory: " + entry.path().string()); |
| 14180 | + } else { // regular file or symlink |
| 14181 | + if (std::filesystem::exists(target_path, ec)) { |
| 14182 | + std::filesystem::remove_all(target_path, ec); |
| 14183 | + if (ec) |
| 14184 | + throw std::runtime_error("Failed to remove existing target: " + target_path.string()); |
| 14185 | + } |
| 14186 | + std::filesystem::rename(entry.path(), target_path, ec); |
| 14187 | + if (ec) |
| 14188 | + throw std::runtime_error("Failed to move " + entry.path().string() + " -> " + target_path.string()); |
| 14189 | + } |
| 14190 | + } |
| 14191 | + } |
| 14192 | + |
| 14193 | + bool has_program(const std::string& name) { |
| 14194 | + const char* path = std::getenv("PATH"); |
| 14195 | + if (!path) |
| 14196 | + return false; |
| 14197 | + |
| 14198 | + std::string p(path); |
| 14199 | + size_t start = 0; |
| 14200 | + |
| 14201 | + while (true) { |
| 14202 | + const size_t end = p.find(':', start); |
| 14203 | + std::string dir = p.substr(start, end == std::string::npos ? std::string::npos : end - start); |
| 14204 | + |
| 14205 | + if (!dir.empty()) { |
| 14206 | + std::string full = dir + "/" + name; |
| 14207 | + struct stat st{}; |
| 14208 | + if (stat(full.c_str(), &st) == 0 && access(full.c_str(), X_OK) == 0) |
| 14209 | + return true; |
| 14210 | + } |
| 14211 | + |
| 14212 | + if (end == std::string::npos) |
| 14213 | + break; |
| 14214 | + |
| 14215 | + start = end + 1; |
| 14216 | + } |
| 14217 | + |
| 14218 | + return false; |
| 14219 | + } |
| 14220 | + |
| 14221 | + std::filesystem::path find_program(const std::string& name) { |
| 14222 | + if (name.find('/') != std::string::npos) { |
| 14223 | + std::filesystem::path p = name; |
| 14224 | + if (std::filesystem::exists(p) && std::filesystem::is_regular_file(p)) { |
| 14225 | + return std::filesystem::absolute(p); |
| 14226 | + } |
| 14227 | + return {}; |
| 14228 | + } |
| 14229 | + |
| 14230 | + const char* path_env = std::getenv("PATH"); |
| 14231 | + if (!path_env) { |
| 14232 | + return {}; |
| 14233 | + } |
| 14234 | + |
| 14235 | + std::string_view path_sv(path_env); |
| 14236 | + size_t start = 0; |
| 14237 | + |
| 14238 | + while (start <= path_sv.size()) { |
| 14239 | + size_t end = path_sv.find(':', start); |
| 14240 | + if (end == std::string_view::npos) { |
| 14241 | + end = path_sv.size(); |
| 14242 | + } |
| 14243 | + |
| 14244 | + std::filesystem::path dir = std::string(path_sv.substr(start, end - start)); |
| 14245 | + std::filesystem::path candidate = dir / name; |
| 14246 | + |
| 14247 | + if (std::filesystem::exists(candidate) && |
| 14248 | + std::filesystem::is_regular_file(candidate)) { |
| 14249 | + return std::filesystem::absolute(candidate); |
| 14250 | + } |
| 14251 | + |
| 14252 | + start = end + 1; |
| 14253 | + } |
| 14254 | + |
| 14255 | + return {}; |
| 14256 | + } |
| 14257 | + |
| 14258 | + std::string runtime_arch() { |
| 14259 | + utsname u {}; |
| 14260 | + if (uname(&u) != 0) { |
| 14261 | + return "unknown"; |
| 14262 | + } |
| 14263 | + |
| 14264 | + const std::string machine = u.machine; |
| 14265 | + std::string rtn; |
| 14266 | + |
| 14267 | + if (machine == "x86_64") rtn = "amd64"; |
| 14268 | + if (machine == "aarch64") rtn = "arm64"; |
| 14269 | + if (machine == "armv7l" || machine == "armv6l") rtn = "arm"; |
| 14270 | + if (machine == "i386" || machine == "i686") rtn = "386"; |
| 14271 | + |
| 14272 | + if (!rtn.empty()) { |
| 14273 | + printf_debug(log_realmachine_utils, "arch=%s", rtn.c_str()); |
| 14274 | + return rtn; |
| 14275 | + } |
| 14276 | + |
| 14277 | + throw std::runtime_error("unsupported architecture"); |
| 14278 | + } |
| 14279 | +} |
| 14280 | diff --git a/src/realmachine/lib/utils.h b/src/realmachine/lib/utils.h |
| 14281 | new file mode 100644 |
| 14282 | index 0000000..f7c7a63 |
| 14283 | --- /dev/null |
| 14284 | +++ b/src/realmachine/lib/utils.h |
| 14285 | @@ -0,0 +1,60 @@ |
| 14286 | +#pragma once |
| 14287 | + |
| 14288 | +#include <vector> |
| 14289 | +#include <string> |
| 14290 | +#include <array> |
| 14291 | +#include <fstream> |
| 14292 | +#include <unistd.h> |
| 14293 | +#include <stdexcept> |
| 14294 | +#include <cstdlib> |
| 14295 | +#include <sys/stat.h> |
| 14296 | +#include <filesystem> |
| 14297 | +#include <sys/types.h> |
| 14298 | +#include <sys/wait.h> |
| 14299 | +#include <sys/ptrace.h> |
| 14300 | +#include <cerrno> |
| 14301 | + |
| 14302 | +#include <flogger/flogger.h> |
| 14303 | + |
| 14304 | +namespace utils_realmachine { |
| 14305 | + void create_standard_dirs(); |
| 14306 | + |
| 14307 | + inline bool endswith(const std::string &str, const std::string &suffix) { |
| 14308 | + return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; |
| 14309 | + } |
| 14310 | + |
| 14311 | + inline bool ensure_directory(const std::string &path) { |
| 14312 | + struct stat st{}; |
| 14313 | + if (stat(path.c_str(), &st) != 0) |
| 14314 | + return false; |
| 14315 | + return S_ISDIR(st.st_mode); |
| 14316 | + } |
| 14317 | + |
| 14318 | + std::string human_size_str(int64_t num); |
| 14319 | + std::string human_time_str(const std::chrono::microseconds& us); |
| 14320 | + int64_t parse_human_size(const std::string& s); |
| 14321 | + std::chrono::microseconds parse_human_time(const std::string& s); |
| 14322 | + |
| 14323 | + inline std::vector<std::string> split_bytes(const std::vector<uint8_t>& bytes, const uint8_t delimiter = ',') { |
| 14324 | + std::vector<std::string> result; |
| 14325 | + size_t start = 0; |
| 14326 | + for (size_t i = 0; i < bytes.size(); ++i) { |
| 14327 | + if (bytes[i] == delimiter) { |
| 14328 | + result.emplace_back(bytes.begin() + start, bytes.begin() + i); |
| 14329 | + start = i + 1; |
| 14330 | + } |
| 14331 | + } |
| 14332 | + if (start < bytes.size()) |
| 14333 | + result.emplace_back(bytes.begin() + start, bytes.end()); |
| 14334 | + return result; |
| 14335 | + } |
| 14336 | + |
| 14337 | + void parse_package_version(const std::string& v, uint32_t& major, uint32_t& minor, uint32_t& patch, uint32_t& release); |
| 14338 | + |
| 14339 | + void move_recursive(const std::filesystem::path &src, const std::filesystem::path &dst); |
| 14340 | + |
| 14341 | + bool has_program(const std::string& name); |
| 14342 | + std::filesystem::path find_program(const std::string& name); |
| 14343 | + |
| 14344 | + std::string runtime_arch(); |
| 14345 | +} |
| 14346 | diff --git a/src/realmachine/machine.cpp b/src/realmachine/machine.cpp |
| 14347 | new file mode 100644 |
| 14348 | index 0000000..f9b5c32 |
| 14349 | --- /dev/null |
| 14350 | +++ b/src/realmachine/machine.cpp |
| 14351 | @@ -0,0 +1,1182 @@ |
| 14352 | +#include <sys/stat.h> |
| 14353 | +#include <unistd.h> |
| 14354 | +#include <cstdlib> |
| 14355 | +#include <cstdio> |
| 14356 | +#include <sstream> |
| 14357 | +#include <fstream> |
| 14358 | +#include <array> |
| 14359 | +#include <chrono> |
| 14360 | +#include <regex> |
| 14361 | +#include <thread> |
| 14362 | + |
| 14363 | +#include <shellescape/shellescape.h> |
| 14364 | + |
| 14365 | +#include "template.h" |
| 14366 | +#include "machine.h" |
| 14367 | +#include "backend.h" |
| 14368 | +#include "globals.h" |
| 14369 | +#include "lib/utils.h" |
| 14370 | + |
| 14371 | +auto log_realmachine_machine = flogger::bind_log("realmachine/machine"); |
| 14372 | + |
| 14373 | +using namespace std; |
| 14374 | + |
| 14375 | +namespace realmachine { |
| 14376 | + |
| 14377 | + bool merged_usr_system() { |
| 14378 | + struct stat sb{}; |
| 14379 | + if (lstat("/bin", &sb) != 0) |
| 14380 | + return false; |
| 14381 | + return S_ISLNK(sb.st_mode); |
| 14382 | + } |
| 14383 | + |
| 14384 | + static const string& modinfo_output(const string &modname, const string &kernel_release) { |
| 14385 | + static unordered_map<string, string> cache; |
| 14386 | + |
| 14387 | + const string cache_key = kernel_release + "\n" + modname; |
| 14388 | + if (const auto it = cache.find(cache_key); it != cache.end()) |
| 14389 | + return it->second; |
| 14390 | + |
| 14391 | + array<char, 4096> buffer{}; |
| 14392 | + string out; |
| 14393 | + |
| 14394 | + string cmd = "modinfo -k " + kernel_release + " " + modname; |
| 14395 | + FILE *pipe = popen(cmd.c_str(), "r"); |
| 14396 | + if (!pipe) |
| 14397 | + throw runtime_error("modinfo failed"); |
| 14398 | + |
| 14399 | + while (fgets(buffer.data(), buffer.size(), pipe)) { |
| 14400 | + out += buffer.data(); |
| 14401 | + } |
| 14402 | + pclose(pipe); |
| 14403 | + |
| 14404 | + return cache.emplace(cache_key, std::move(out)).first->second; |
| 14405 | + } |
| 14406 | + |
| 14407 | + vector<string> get_mod_data(const string &modname, const string &fieldname, const string &kernel_release) { |
| 14408 | + const string& out = modinfo_output(modname, kernel_release); |
| 14409 | + |
| 14410 | + vector<string> field_value; |
| 14411 | + istringstream ss(out); |
| 14412 | + string line; |
| 14413 | + |
| 14414 | + while (getline(ss, line)) { |
| 14415 | + line = regex_replace(line, regex("^\\s+|\\s+$"), ""); |
| 14416 | + |
| 14417 | + vector<string> parts; |
| 14418 | + string tmp; |
| 14419 | + stringstream ls(line); |
| 14420 | + |
| 14421 | + while (getline(ls, tmp, ':')) { |
| 14422 | + tmp = regex_replace(tmp, regex("^\\s+|\\s+$"), ""); |
| 14423 | + parts.emplace_back(tmp); |
| 14424 | + } |
| 14425 | + |
| 14426 | + if (!parts.empty() && parts[0] == fieldname) { |
| 14427 | + if (parts.size() > 1) { |
| 14428 | + field_value.emplace_back(parts[1]); |
| 14429 | + } else { |
| 14430 | + field_value.emplace_back(""); |
| 14431 | + } |
| 14432 | + } |
| 14433 | + } |
| 14434 | + |
| 14435 | + return field_value; |
| 14436 | + } |
| 14437 | + |
| 14438 | + string get_mod_path(const string &modname, const string &kernel_release) { |
| 14439 | + auto paths = get_mod_data(modname, "filename", kernel_release); |
| 14440 | + if (!paths.empty()) |
| 14441 | + return paths[0]; |
| 14442 | + return ""; |
| 14443 | + } |
| 14444 | + |
| 14445 | + vector<string> get_mod_depends(const string &modname, const string &kernel_release) { |
| 14446 | + printf_debug(log_realmachine_machine, "enter modname=%s kernel_release=%s", modname.c_str(), kernel_release.c_str()); |
| 14447 | + |
| 14448 | + static unordered_map<string, vector<string>> resolved; |
| 14449 | + const string cache_key = kernel_release + "\n" + modname; |
| 14450 | + if (const auto it = resolved.find(cache_key); it != resolved.end()) |
| 14451 | + return it->second; |
| 14452 | + |
| 14453 | + auto deps = get_mod_data(modname, "depends", kernel_release); |
| 14454 | + printf_debug(log_realmachine_machine, "deps_count=%zu modname=%s", deps.size(), modname.c_str()); |
| 14455 | + |
| 14456 | + vector<string> modlist; |
| 14457 | + |
| 14458 | + for (auto &v: deps) { |
| 14459 | + if (!v.empty()) { |
| 14460 | + size_t start = 0; |
| 14461 | + while (true) { |
| 14462 | + size_t pos = v.find(',', start); |
| 14463 | + if (pos == string::npos) { |
| 14464 | + auto token = v.substr(start); |
| 14465 | + if (!token.empty()) modlist.emplace_back(token); |
| 14466 | + printf_trace(log_realmachine_machine, "dep_token mod=%s token=%s", modname.c_str(), token.c_str()); |
| 14467 | + break; |
| 14468 | + } |
| 14469 | + auto token = v.substr(start, pos - start); |
| 14470 | + if (!token.empty()) modlist.emplace_back(token); |
| 14471 | + printf_trace(log_realmachine_machine, "dep_token mod=%s token=%s", modname.c_str(), token.c_str()); |
| 14472 | + start = pos + 1; |
| 14473 | + } |
| 14474 | + } |
| 14475 | + } |
| 14476 | + |
| 14477 | + printf_debug(log_realmachine_machine, "direct_deps_count=%zu modname=%s", modlist.size(), modname.c_str()); |
| 14478 | + |
| 14479 | + // Busybox expects a full dependency list for each module rather than just |
| 14480 | + // direct dependencies, so recurse the module dependency tree: |
| 14481 | + // https://github.com/mirror/busybox/blob/1dd2685dcc735496d7adde87ac60b9434ed4a04c/modutils/modprobe.c#L46-L49 |
| 14482 | + |
| 14483 | + vector<string> sublist; |
| 14484 | + for (auto &mod: modlist) { |
| 14485 | + printf_trace(log_realmachine_machine, "recurse mod=%s parent=%s", mod.c_str(), modname.c_str()); |
| 14486 | + auto sub = get_mod_depends(mod, kernel_release); |
| 14487 | + printf_trace(log_realmachine_machine, "recurse_done mod=%s sub_count=%zu", mod.c_str(), sub.size()); |
| 14488 | + sublist.insert(sublist.end(), sub.begin(), sub.end()); |
| 14489 | + } |
| 14490 | + |
| 14491 | + modlist.insert(modlist.end(), sublist.begin(), sublist.end()); |
| 14492 | + |
| 14493 | + printf_debug(log_realmachine_machine, "exit modname=%s total=%zu", modname.c_str(), modlist.size()); |
| 14494 | + return resolved.emplace(cache_key, std::move(modlist)).first->second; |
| 14495 | + } |
| 14496 | + |
| 14497 | + void Machine::resolve_modules( |
| 14498 | + std::vector<ResolvedModule>& resolved, |
| 14499 | + const std::string& modname, |
| 14500 | + std::unordered_map<std::string, bool>& copied_modules |
| 14501 | + ) { |
| 14502 | + printf_trace(log_realmachine_machine, "enter resolve_modules modname=%s", modname.c_str()); |
| 14503 | + |
| 14504 | + const auto release = backend->kernel_release(); |
| 14505 | + printf_debug(log_realmachine_machine, "kernel release=%s", release.c_str()); |
| 14506 | + |
| 14507 | + std::string modpath = get_mod_path(modname, release); |
| 14508 | + printf_debug(log_realmachine_machine, "modpath=%s", modpath.c_str()); |
| 14509 | + |
| 14510 | + if (modpath.empty()) { |
| 14511 | + printf_error(log_realmachine_machine, "modpath empty -> invalid module path"); |
| 14512 | + throw std::invalid_argument("invalid module path"); |
| 14513 | + } |
| 14514 | + |
| 14515 | + if (modpath == "(builtin)" || copied_modules[modname]) { |
| 14516 | + printf_trace(log_realmachine_machine, "skip module modname=%s builtin=%d already_copied=%d", |
| 14517 | + modname.c_str(), |
| 14518 | + modpath == "(builtin)", |
| 14519 | + copied_modules[modname]); |
| 14520 | + return; |
| 14521 | + } |
| 14522 | + |
| 14523 | + bool found = false; |
| 14524 | + |
| 14525 | + for (auto it = KO_SUFFIXES.begin(); it != KO_SUFFIXES.end(); ++it) { |
| 14526 | + const auto& suffix = it->first; |
| 14527 | + const auto& fn = it->second; |
| 14528 | + |
| 14529 | + printf_trace(log_realmachine_machine, "checking suffix=%s", suffix.c_str()); |
| 14530 | + |
| 14531 | + if (modpath.ends_with(suffix)) { |
| 14532 | + printf_trace(log_realmachine_machine, "suffix match modpath=%s suffix=%s", modpath.c_str(), suffix.c_str()); |
| 14533 | + |
| 14534 | + if (!std::filesystem::exists(modpath)) { |
| 14535 | + printf_trace(log_realmachine_machine, "module not found at path=%s", modpath.c_str()); |
| 14536 | + throw std::filesystem::filesystem_error( |
| 14537 | + "module not found", |
| 14538 | + modpath, |
| 14539 | + std::make_error_code(std::errc::no_such_file_or_directory) |
| 14540 | + ); |
| 14541 | + } |
| 14542 | + |
| 14543 | + std::string dest = |
| 14544 | + modpath.substr(0, modpath.size() - suffix.size()) + ".ko"; |
| 14545 | + |
| 14546 | + printf_debug(log_realmachine_machine, "raw dest=%s", dest.c_str()); |
| 14547 | + |
| 14548 | + if (merged_usr_system() && !dest.starts_with("/usr")) { |
| 14549 | + printf_debug(log_realmachine_machine, "prefixing /usr to dest"); |
| 14550 | + dest = "/usr" + dest; |
| 14551 | + } |
| 14552 | + |
| 14553 | + printf_debug(log_realmachine_machine, "final dest=%s", dest.c_str()); |
| 14554 | + |
| 14555 | + if (fn) { |
| 14556 | + printf_trace(log_realmachine_machine, "resolved modpath=%s dest=%s", modpath.c_str(), dest.c_str()); |
| 14557 | + resolved.emplace_back(ResolvedModule{modpath, dest, fn}); |
| 14558 | + } else { |
| 14559 | + printf_error(log_realmachine_machine, "no transform function for suffix=%s", suffix.c_str()); |
| 14560 | + } |
| 14561 | + |
| 14562 | + found = true; |
| 14563 | + break; |
| 14564 | + } |
| 14565 | + } |
| 14566 | + |
| 14567 | + if (!found) { |
| 14568 | + printf_error(log_realmachine_machine, "no matching suffix for modpath=%s", modpath.c_str()); |
| 14569 | + throw std::invalid_argument("invalid module suffix"); |
| 14570 | + } |
| 14571 | + |
| 14572 | + copied_modules[modname] = true; |
| 14573 | + printf_trace(log_realmachine_machine, "marked copied modname=%s", modname.c_str()); |
| 14574 | + |
| 14575 | + auto deps = get_mod_depends(modname, release); |
| 14576 | + printf_trace(log_realmachine_machine, "deps count=%zu for modname=%s", deps.size(), modname.c_str()); |
| 14577 | + |
| 14578 | + for (auto& mod : deps) { |
| 14579 | + printf_trace(log_realmachine_machine, "recursing dependency=%s", mod.c_str()); |
| 14580 | + resolve_modules(resolved, mod, copied_modules); |
| 14581 | + } |
| 14582 | + |
| 14583 | + printf_debug(log_realmachine_machine, "exit resolve_modules modname=%s", modname.c_str()); |
| 14584 | + } |
| 14585 | + |
| 14586 | + // Evaluate any symbolic link, then return the path's directory. Returns an |
| 14587 | + // absolute path. Think of it as realpath(1) + dirname(1) in bash. |
| 14588 | + filesystem::path real_dir(const filesystem::path &path) { |
| 14589 | + const auto abs = filesystem::absolute(path); |
| 14590 | + |
| 14591 | + std::error_code ec; |
| 14592 | + const auto resolved = filesystem::weakly_canonical(abs, ec); |
| 14593 | + if (ec) { |
| 14594 | + return abs.parent_path(); |
| 14595 | + } |
| 14596 | + |
| 14597 | + return resolved.parent_path(); |
| 14598 | + } |
| 14599 | + |
| 14600 | + Machine::Machine() = default; |
| 14601 | + |
| 14602 | + shared_ptr<Machine> Machine::new_machine() { |
| 14603 | + if (!realmachine_g::APP_INIT) populate_ko_suffix_map(); |
| 14604 | + return new_machine_with_backend("auto"); |
| 14605 | + } |
| 14606 | + |
| 14607 | + shared_ptr<Machine> Machine::new_machine_with_backend(const string &backend_name) { |
| 14608 | + if (!realmachine_g::APP_INIT) populate_ko_suffix_map(); |
| 14609 | + auto m = make_shared<Machine>(); |
| 14610 | + |
| 14611 | + m->memory = 2048; |
| 14612 | + m->num_cpus = thread::hardware_concurrency(); |
| 14613 | + m->sector_size = 512; |
| 14614 | + |
| 14615 | + string goarch = utils_realmachine::runtime_arch(); |
| 14616 | + |
| 14617 | + if (!arch_map.contains(goarch)) { |
| 14618 | + throw runtime_error("unsupported arch " + goarch); |
| 14619 | + } |
| 14620 | + |
| 14621 | + m->arch = arch_map[goarch]; |
| 14622 | + |
| 14623 | + m->backend = new_backend(backend_name, m); |
| 14624 | + |
| 14625 | + // usr is mounted by specific label via /init |
| 14626 | + m->add_static_volume("/usr", "usr"); |
| 14627 | + |
| 14628 | + if (!merged_usr_system()) { |
| 14629 | + m->add_static_volume("/sbin", "sbin"); |
| 14630 | + m->add_static_volume("/bin", "bin"); |
| 14631 | + m->add_static_volume("/lib", "lib"); |
| 14632 | + } |
| 14633 | + |
| 14634 | + // Mounts for ssl certificates |
| 14635 | + if (filesystem::exists("/etc/ca-certificates")) { |
| 14636 | + m->add_volume("/etc/ca-certificates"); |
| 14637 | + } |
| 14638 | + if (filesystem::exists("/etc/ssl")) { |
| 14639 | + m->add_volume("/etc/ssl"); |
| 14640 | + } |
| 14641 | + |
| 14642 | + // Mounts for java VM configuration, especially security policies |
| 14643 | + for (auto &p : filesystem::directory_iterator("/etc")) { |
| 14644 | + if (p.is_directory() && |
| 14645 | + p.path().filename().string().rfind("java", 0) == 0) { |
| 14646 | + m->add_volume(p.path()); |
| 14647 | + } |
| 14648 | + } |
| 14649 | + |
| 14650 | + // Dbus configuration |
| 14651 | + if (filesystem::exists("/etc/dbus-1")) { |
| 14652 | + m->add_volume("/etc/dbus-1"); |
| 14653 | + } |
| 14654 | + |
| 14655 | + // Debian alternative symlinks |
| 14656 | + if (filesystem::exists("/etc/alternatives")) { |
| 14657 | + m->add_volume("/etc/alternatives"); |
| 14658 | + } |
| 14659 | + |
| 14660 | + // Debians binfmt registry |
| 14661 | + if (filesystem::exists("/var/lib/binfmts")) { |
| 14662 | + m->add_volume("/var/lib/binfmts"); |
| 14663 | + } |
| 14664 | + |
| 14665 | + return m; |
| 14666 | + } |
| 14667 | + |
| 14668 | + bool Machine::in_machine() { |
| 14669 | + const bool v = std::getenv("IN_FAKE_MACHINE") != nullptr; |
| 14670 | + // for(char **env = ::environ; env && *env; ++env) { |
| 14671 | + // printf_debug(log_realmachine_machine, "%s", *env); |
| 14672 | + // } |
| 14673 | + printf_debug(log_realmachine_machine, "IN_FAKE_MACHINE=%d", v); |
| 14674 | + return v; |
| 14675 | + } |
| 14676 | + |
| 14677 | + bool supported() { |
| 14678 | + return new_backend("auto", nullptr) != nullptr; |
| 14679 | + } |
| 14680 | + |
| 14681 | + std::string join_strings(const std::vector<std::string>& v, const std::string& sep) { |
| 14682 | + std::string out; |
| 14683 | + for (size_t i = 0; i < v.size(); i++) { |
| 14684 | + if (i) out += sep; |
| 14685 | + out += v[i]; |
| 14686 | + } |
| 14687 | + return out; |
| 14688 | + } |
| 14689 | + |
| 14690 | + std::string tmpl_mount_volume(const std::unique_ptr<Backend>& b, const std::shared_ptr<MountPoint>& m) { |
| 14691 | + auto [fs_type, options] = b->mount_parameters(m); |
| 14692 | + |
| 14693 | + std::vector<std::string> cmd; |
| 14694 | + cmd.emplace_back("busybox"); |
| 14695 | + cmd.emplace_back("mount"); |
| 14696 | + cmd.emplace_back("-v"); |
| 14697 | + |
| 14698 | + cmd.emplace_back("-t"); |
| 14699 | + cmd.emplace_back(fs_type); |
| 14700 | + |
| 14701 | + if (!options.empty()) { |
| 14702 | + cmd.emplace_back("-o"); |
| 14703 | + cmd.emplace_back(join_strings(options, ",")); |
| 14704 | + } |
| 14705 | + |
| 14706 | + cmd.emplace_back(m->label); |
| 14707 | + cmd.emplace_back(m->machine_directory.string()); |
| 14708 | + |
| 14709 | + return join_strings(cmd, " "); |
| 14710 | + } |
| 14711 | + |
| 14712 | + std::vector<std::shared_ptr<MountPoint>> tmpl_static_volumes( |
| 14713 | + const Machine& m, |
| 14714 | + const std::unique_ptr<Backend>& b |
| 14715 | + ) { |
| 14716 | + std::vector<std::shared_ptr<MountPoint>> mounts; |
| 14717 | + |
| 14718 | + auto combined = m.mounts; |
| 14719 | + auto backend_vols = b->init_static_volumes(); |
| 14720 | + |
| 14721 | + combined.insert(combined.end(), backend_vols.begin(), backend_vols.end()); |
| 14722 | + |
| 14723 | + for (const auto& mount : combined) { |
| 14724 | + if (mount->is_static) { |
| 14725 | + mounts.emplace_back(mount); |
| 14726 | + } |
| 14727 | + } |
| 14728 | + |
| 14729 | + return mounts; |
| 14730 | + } |
| 14731 | + |
| 14732 | + std::string build_service_env(const Machine& m) { |
| 14733 | + return join_strings(m.environ, " "); |
| 14734 | + } |
| 14735 | + |
| 14736 | + std::string build_init_script(const Machine* m, const std::unique_ptr<Backend>& b) { |
| 14737 | + std::vector<std::string> modprobe_lines; |
| 14738 | + |
| 14739 | + for (const auto& mod : b->init_modules()) { |
| 14740 | + modprobe_lines.emplace_back("busybox modprobe " + mod); |
| 14741 | + } |
| 14742 | + |
| 14743 | + std::vector<std::string> mount_lines; |
| 14744 | + |
| 14745 | + auto vols = tmpl_static_volumes(*m, b); |
| 14746 | + for (const auto& v : vols) { |
| 14747 | + mount_lines.emplace_back(tmpl_mount_volume(b, v)); |
| 14748 | + } |
| 14749 | + |
| 14750 | + return templating::get_init_script( |
| 14751 | + join_strings(modprobe_lines, "\n"), |
| 14752 | + join_strings(mount_lines, "\n") |
| 14753 | + ); |
| 14754 | + } |
| 14755 | + |
| 14756 | + std::string build_command_wrapper(const std::string& command) { |
| 14757 | + return templating::get_command_wrapper(command); |
| 14758 | + } |
| 14759 | + |
| 14760 | + std::string render_service(const Machine& m, const std::string& tty) { |
| 14761 | + return templating::get_service( |
| 14762 | + build_service_env(m), |
| 14763 | + tty |
| 14764 | + ); |
| 14765 | + } |
| 14766 | + |
| 14767 | + void Machine::add_static_volume(const std::filesystem::path& directory, const std::string& label) { |
| 14768 | + printf_debug(log_realmachine_machine, "host=%s machine=%s label=%s static=%d", |
| 14769 | + directory.string().c_str(), |
| 14770 | + directory.c_str(), |
| 14771 | + label.c_str(), |
| 14772 | + true |
| 14773 | + ); |
| 14774 | + |
| 14775 | + // usr is mounted by specific label via /init |
| 14776 | + auto m = std::make_shared<MountPoint>(); |
| 14777 | + m->host_directory = directory; |
| 14778 | + m->machine_directory = directory; |
| 14779 | + m->label = label; |
| 14780 | + m->is_static = true; |
| 14781 | + |
| 14782 | + mounts.emplace_back(m); |
| 14783 | + } |
| 14784 | + |
| 14785 | + void Machine::add_volume_at(const std::string& host_directory, const std::string& machine_directory) { |
| 14786 | + std::string label = "virtfs-" + std::to_string(count_); |
| 14787 | + |
| 14788 | + for (const auto& mount : mounts) { |
| 14789 | + if (mount->host_directory == host_directory && |
| 14790 | + mount->machine_directory == machine_directory) { |
| 14791 | + // Do not need to add already existing mount |
| 14792 | + return; |
| 14793 | + } |
| 14794 | + } |
| 14795 | + |
| 14796 | + printf_trace(log_realmachine_machine, "mount_add host=%s machine=%s label=%s readonly=%d", |
| 14797 | + host_directory.c_str(), |
| 14798 | + machine_directory.c_str(), |
| 14799 | + label.c_str(), |
| 14800 | + false |
| 14801 | + ); |
| 14802 | + |
| 14803 | + mounts.emplace_back(std::make_shared<MountPoint>( |
| 14804 | + host_directory, |
| 14805 | + machine_directory, |
| 14806 | + label, |
| 14807 | + false |
| 14808 | + )); |
| 14809 | + |
| 14810 | + count_ += 1; |
| 14811 | + } |
| 14812 | + |
| 14813 | + void Machine::add_volume(const std::filesystem::path& directory) { |
| 14814 | + if (directory.empty()) |
| 14815 | + throw std::invalid_argument("add_volume: directory is empty"); |
| 14816 | + add_volume_at(directory, directory); |
| 14817 | + } |
| 14818 | + |
| 14819 | + // CreateImageWithLabel creates an image file at path a given size and exposes |
| 14820 | + // it in the fake machine using the given label as the serial id. If size is -1 |
| 14821 | + // then the image should already exist and the size isn't modified. |
| 14822 | + // |
| 14823 | + // label needs to be less than 20 characters due to limitations from qemu |
| 14824 | + // |
| 14825 | + // The returned string is the device path of the new image as seen inside |
| 14826 | + // realmachine. |
| 14827 | + std::string Machine::create_image_with_label(const std::string& path, int64_t size, const std::string& label) { |
| 14828 | + printf_debug(log_realmachine_machine, "enter path=%s size=%ld label=%s", path.c_str(), size, label.c_str()); |
| 14829 | + |
| 14830 | + if (size < 0) { |
| 14831 | + std::filesystem::file_status st = std::filesystem::status(path); |
| 14832 | + } |
| 14833 | + |
| 14834 | + if (label.size() >= 20) { |
| 14835 | + printf_trace(log_realmachine_machine, "label_too_long len=%zu label=%s", label.size(), label.c_str()); |
| 14836 | + throw std::invalid_argument("label too long"); |
| 14837 | + } |
| 14838 | + |
| 14839 | + for (const auto& image : images) { |
| 14840 | + if (image->label == label) { |
| 14841 | + printf_trace(log_realmachine_machine, "duplicate_label label=%s", label.c_str()); |
| 14842 | + throw std::invalid_argument("duplicate label"); |
| 14843 | + } |
| 14844 | + } |
| 14845 | + |
| 14846 | + std::filesystem::path p(path); |
| 14847 | + |
| 14848 | + std::ofstream i(p, std::ios::binary | std::ios::out | std::ios::trunc); |
| 14849 | + if (!i.is_open()) { |
| 14850 | + printf_trace(log_realmachine_machine, "file_open_failed path=%s", path.c_str()); |
| 14851 | + throw std::runtime_error("file open failed"); |
| 14852 | + } |
| 14853 | + |
| 14854 | + if (size >= 0) { |
| 14855 | + std::filesystem::resize_file(p, static_cast<std::uintmax_t>(size)); |
| 14856 | + } |
| 14857 | + |
| 14858 | + i.close(); |
| 14859 | + |
| 14860 | + images.emplace_back(make_unique<Image>(path, label)); |
| 14861 | + printf_debug(log_realmachine_machine, "image_created path=%s label=%s rtn=%s", path.c_str(), label.c_str(), std::string("/dev/disk/by-realmachine-label/" + label).c_str()); |
| 14862 | + |
| 14863 | + return "/dev/disk/by-realmachine-label/" + label; |
| 14864 | + } |
| 14865 | + |
| 14866 | + // CreateImage does the same as CreateImageWithLabel but lets the library pick the label. |
| 14867 | + std::string Machine::create_image(const std::string& imagepath,const int64_t size) { |
| 14868 | + const std::string label = "fakedisk-" + std::to_string(images.size()); |
| 14869 | + printf_debug(log_realmachine_machine, "%s", label.c_str()); |
| 14870 | + return create_image_with_label(imagepath, size, label); |
| 14871 | + } |
| 14872 | + |
| 14873 | + // SetMemory sets the realmachines amount of memory (in megabytes). Defaults to 2048 MB |
| 14874 | + void Machine::set_memory(int new_memory) { |
| 14875 | + printf_debug(log_realmachine_machine, "set_memory %d", new_memory); |
| 14876 | + memory = new_memory; |
| 14877 | + } |
| 14878 | + |
| 14879 | + // SetNumCPUs sets the number of CPUs exposed to the realmachine. Defaults to |
| 14880 | + // the number of available cores in the system. |
| 14881 | + void Machine::set_num_cpus(int new_num_cpus) { |
| 14882 | + printf_debug(log_realmachine_machine, "set_num_cpus %d", new_num_cpus); |
| 14883 | + num_cpus = new_num_cpus; |
| 14884 | + } |
| 14885 | + |
| 14886 | + // SetSectorSize overrides the default sector size(512 bytes) for the image exposed to the realmachine |
| 14887 | + void Machine::set_sector_size(int new_sector_size) { |
| 14888 | + printf_debug(log_realmachine_machine, "set_sector_size %d", new_sector_size); |
| 14889 | + sector_size = new_sector_size; |
| 14890 | + } |
| 14891 | + |
| 14892 | + // SetShowBoot sets whether to show boot/console messages from the realmachine. |
| 14893 | + void Machine::set_show_boot(bool show_boot_status) { |
| 14894 | + printf_debug(log_realmachine_machine, "set_show_boot %d", show_boot_status); |
| 14895 | + show_boot = show_boot_status; |
| 14896 | + } |
| 14897 | + |
| 14898 | + // SetQuiet sets whether realmachine should print additional information (e.g. the command |
| 14899 | + // to be ran) or just print the stdout/stderr of the command to be ran. |
| 14900 | + void Machine::set_quiet(bool quiet_status) { |
| 14901 | + printf_debug(log_realmachine_machine, "set_quiet %d", quiet_status); |
| 14902 | + quiet = quiet_status; |
| 14903 | + } |
| 14904 | + |
| 14905 | + void Machine::set_module_cache(const bool enabled, const std::string& path) { |
| 14906 | + module_cache = enabled; |
| 14907 | + module_cache_path = path; |
| 14908 | + } |
| 14909 | + |
| 14910 | + // SetScratch sets the size and location of on-disk scratch space to allocate |
| 14911 | + // (sparsely) for /scratch. If not set /scratch will be backed by memory. If |
| 14912 | + // Path is "" then the working directory is used as a default storage location |
| 14913 | + void Machine::set_scratch(int64_t new_scratch_size, const std::string& path) { |
| 14914 | + scratch_size = new_scratch_size; |
| 14915 | + |
| 14916 | + if (path.empty()) { |
| 14917 | + scratch_path = std::filesystem::current_path().string(); |
| 14918 | + } else { |
| 14919 | + scratch_path = path; |
| 14920 | + } |
| 14921 | + } |
| 14922 | + |
| 14923 | + void Machine::generate_fstab(cpio::WriterHelper* w, const std::unique_ptr<Backend>& backend) const { |
| 14924 | + std::vector<std::string> fstab; |
| 14925 | + fstab.emplace_back("# Generated fstab file by realmachine"); |
| 14926 | + |
| 14927 | + if (scratch_file.empty()) { |
| 14928 | + fstab.emplace_back("none /scratch tmpfs size=95% 0 0"); |
| 14929 | + } else { |
| 14930 | + fstab.emplace_back( |
| 14931 | + scratch_dev + std::string(" /scratch ext4 defaults,relatime 0 0") |
| 14932 | + ); |
| 14933 | + } |
| 14934 | + |
| 14935 | + for (const auto& point : mounts) { |
| 14936 | + auto [fstype, options] = backend->mount_parameters(point); |
| 14937 | + |
| 14938 | + std::string opt_joined; |
| 14939 | + for (size_t i = 0; i < options.size(); ++i) { |
| 14940 | + if (i) opt_joined += ","; |
| 14941 | + opt_joined += options[i]; |
| 14942 | + } |
| 14943 | + |
| 14944 | + fstab.emplace_back( |
| 14945 | + point->label + " " + |
| 14946 | + point->machine_directory.string() + " " + |
| 14947 | + fstype + " " + |
| 14948 | + opt_joined + |
| 14949 | + " 0 0" |
| 14950 | + ); |
| 14951 | + } |
| 14952 | + |
| 14953 | + fstab.emplace_back(""); |
| 14954 | + |
| 14955 | + w->write_file( |
| 14956 | + "/etc/fstab", |
| 14957 | + [&]() { |
| 14958 | + std::string out; |
| 14959 | + for (size_t i = 0; i < fstab.size(); ++i) { |
| 14960 | + if (i) out += "\n"; |
| 14961 | + out += fstab[i]; |
| 14962 | + } |
| 14963 | + return out; |
| 14964 | + }(), |
| 14965 | + 0755 |
| 14966 | + ); |
| 14967 | + } |
| 14968 | + |
| 14969 | + std::string strip_compression_suffix(const std::string& module) { |
| 14970 | + printf_debug(log_realmachine_machine, "enter strip_compression_suffix module=%s", module.c_str()); |
| 14971 | + |
| 14972 | + for (const auto& [suffix, _] : KO_SUFFIXES) { |
| 14973 | + if (module.size() >= suffix.size() && |
| 14974 | + module.compare(module.size() - suffix.size(), |
| 14975 | + suffix.size(), |
| 14976 | + suffix) == 0) { |
| 14977 | + |
| 14978 | + return module.substr(0, module.size() - suffix.size()) + ".ko"; |
| 14979 | + } |
| 14980 | + } |
| 14981 | + |
| 14982 | + printf_debug(log_realmachine_machine, "no suffix match module=%s", module.c_str()); |
| 14983 | + return ""; |
| 14984 | + } |
| 14985 | + |
| 14986 | + std::string Machine::generate_modules_dep( |
| 14987 | + const std::unordered_map<std::string, bool>& modules |
| 14988 | + ) const { |
| 14989 | + printf_debug(log_realmachine_machine, "generate_modules_dep: start modules=%zu", modules.size()); |
| 14990 | + |
| 14991 | + std::vector<std::string> output; |
| 14992 | + output.resize(modules.size()); |
| 14993 | + |
| 14994 | + const std::string release = backend->kernel_release(); |
| 14995 | + printf_trace(log_realmachine_machine, "generate_modules_dep: kernel_release=%s", release.c_str()); |
| 14996 | + |
| 14997 | + size_t i = 0; |
| 14998 | + for (const auto& mod : modules) { |
| 14999 | + printf_trace(log_realmachine_machine, "generate_modules_dep: mod=%s", mod.first.c_str()); |
| 15000 | + |
| 15001 | + std::string modpath = |
| 15002 | + strip_compression_suffix(get_mod_path(mod.first, release)); |
| 15003 | + |
| 15004 | + auto deplist = get_mod_depends(mod.first, release); |
| 15005 | + |
| 15006 | + std::vector<std::string> deps; |
| 15007 | + deps.resize(deplist.size()); |
| 15008 | + |
| 15009 | + for (size_t j = 0; j < deplist.size(); ++j) { |
| 15010 | + const std::string dep_path = |
| 15011 | + strip_compression_suffix(get_mod_path(deplist[j], release)); |
| 15012 | + deps[j] = dep_path; |
| 15013 | + } |
| 15014 | + |
| 15015 | + std::string joined; |
| 15016 | + for (size_t k = 0; k < deps.size(); ++k) { |
| 15017 | + if (k) joined += " "; |
| 15018 | + joined += deps[k]; |
| 15019 | + } |
| 15020 | + |
| 15021 | + if (modpath == "(builtin)") { |
| 15022 | + modpath.clear(); |
| 15023 | + } |
| 15024 | + |
| 15025 | + output[i] = modpath + ": " + joined; |
| 15026 | + i++; |
| 15027 | + } |
| 15028 | + |
| 15029 | + std::string out; |
| 15030 | + for (size_t k = 0; k < output.size(); ++k) { |
| 15031 | + if (k) out += "\n"; |
| 15032 | + out += output[k]; |
| 15033 | + } |
| 15034 | + |
| 15035 | + printf_debug(log_realmachine_machine, "generate_modules_dep: done"); |
| 15036 | + return out; |
| 15037 | + } |
| 15038 | + |
| 15039 | + void Machine::set_environ(const std::vector<std::string>& new_environ) { |
| 15040 | + environ = new_environ; |
| 15041 | + } |
| 15042 | + |
| 15043 | + void Machine::writer_kernel_modules( |
| 15044 | + cpio::WriterHelper* w, |
| 15045 | + const std::string& moddir, |
| 15046 | + const std::vector<std::string>& modules |
| 15047 | + ) { |
| 15048 | + printf_debug(log_realmachine_machine, "start moddir=%s modules=%zu", |
| 15049 | + moddir.c_str(), modules.size()); |
| 15050 | + |
| 15051 | + if (modules.empty()) { |
| 15052 | + printf_trace(log_realmachine_machine, "skipped (no modules)"); |
| 15053 | + return; |
| 15054 | + } |
| 15055 | + |
| 15056 | + std::vector<std::string> modfiles = { |
| 15057 | + "modules.builtin", |
| 15058 | + "modules.alias", |
| 15059 | + "modules.symbols" |
| 15060 | + }; |
| 15061 | + |
| 15062 | + for (const auto& v : modfiles) { |
| 15063 | + std::string path = moddir + "/" + v; |
| 15064 | + printf_trace(log_realmachine_machine, "copy_file=%s", path.c_str()); |
| 15065 | + w->copy_file(path); |
| 15066 | + } |
| 15067 | + |
| 15068 | + const auto cache_base = module_cache_path.empty() |
| 15069 | + ? default_module_cache_directory() |
| 15070 | + : module_cache_path; |
| 15071 | + |
| 15072 | + const auto cache_dir = cache_base / |
| 15073 | + module_cache_key(backend->kernel_release(), moddir, modules, merged_usr_system()); |
| 15074 | + |
| 15075 | + ModuleSet set; |
| 15076 | + std::filesystem::path staging; |
| 15077 | + |
| 15078 | + if (!module_cache || !module_cache_load(cache_dir, set)) { |
| 15079 | + printf_debug(log_realmachine_machine, "writer_kernel_modules: decompressing modules (cache=%d)", module_cache); |
| 15080 | + |
| 15081 | + staging = module_cache |
| 15082 | + ? cache_base / (cache_dir.filename().string() + ".tmp." + std::to_string(getpid())) |
| 15083 | + : std::filesystem::temp_directory_path() / ("realmachine-modules." + std::to_string(getpid())); |
| 15084 | + |
| 15085 | + std::error_code ec; |
| 15086 | + std::filesystem::remove_all(staging, ec); |
| 15087 | + std::filesystem::create_directories(staging, ec); |
| 15088 | + if (ec) { |
| 15089 | + throw std::runtime_error("cannot create module staging directory " + staging.string() + ": " + ec.message()); |
| 15090 | + } |
| 15091 | + |
| 15092 | + set = build_module_set(moddir, modules, staging); |
| 15093 | + |
| 15094 | + if (module_cache) { |
| 15095 | + module_cache_store(staging, cache_dir, set); |
| 15096 | + } |
| 15097 | + } |
| 15098 | + |
| 15099 | + for (const auto& file : set.files) { |
| 15100 | + printf_trace(log_realmachine_machine, "writer_kernel_modules: copy %s -> %s", file[0].c_str(), file[1].c_str()); |
| 15101 | + w->copy_file_to(file[0], file[1]); |
| 15102 | + } |
| 15103 | + |
| 15104 | + w->write_file( |
| 15105 | + (std::filesystem::path(moddir) / "modules.dep").string(), |
| 15106 | + set.modules_dep, |
| 15107 | + 0644 |
| 15108 | + ); |
| 15109 | + |
| 15110 | + if (!staging.empty()) { |
| 15111 | + std::error_code ec; |
| 15112 | + std::filesystem::remove_all(staging, ec); |
| 15113 | + } |
| 15114 | + |
| 15115 | + printf_trace(log_realmachine_machine, "writer_kernel_modules: done"); |
| 15116 | + } |
| 15117 | + |
| 15118 | + ModuleSet Machine::build_module_set( |
| 15119 | + const std::string& moddir, |
| 15120 | + const std::vector<std::string>& modules, |
| 15121 | + const std::filesystem::path& staging |
| 15122 | + ) { |
| 15123 | + std::vector<ResolvedModule> resolved; |
| 15124 | + std::unordered_map<std::string, bool> copied_modules; |
| 15125 | + |
| 15126 | + for (const auto& modname : modules) { |
| 15127 | + printf_trace(log_realmachine_machine, "build_module_set: resolve=%s", modname.c_str()); |
| 15128 | + resolve_modules(resolved, modname, copied_modules); |
| 15129 | + } |
| 15130 | + |
| 15131 | + ModuleSet set; |
| 15132 | + set.modules_dep = generate_modules_dep(copied_modules); |
| 15133 | + |
| 15134 | + for (size_t i = 0; i < resolved.size(); ++i) { |
| 15135 | + const auto blob = staging / (std::to_string(i) + ".ko"); |
| 15136 | + |
| 15137 | + std::ifstream src(resolved[i].source, std::ios::binary); |
| 15138 | + if (!src.is_open()) { |
| 15139 | + throw std::runtime_error("cannot read kernel module " + resolved[i].source); |
| 15140 | + } |
| 15141 | + |
| 15142 | + std::ofstream dst(blob, std::ios::binary | std::ios::trunc); |
| 15143 | + if (!dst.is_open()) { |
| 15144 | + throw std::runtime_error("cannot write module cache blob " + blob.string()); |
| 15145 | + } |
| 15146 | + |
| 15147 | + resolved[i].transformer(dst, src); |
| 15148 | + dst.close(); |
| 15149 | + |
| 15150 | + set.files.push_back({blob.string(), resolved[i].destination}); |
| 15151 | + } |
| 15152 | + |
| 15153 | + return set; |
| 15154 | + } |
| 15155 | + |
| 15156 | + void Machine::setup_scratch() { |
| 15157 | + printf_debug(log_realmachine_machine, "setup_scratch: start"); |
| 15158 | + |
| 15159 | + if (scratch_size == 0) { |
| 15160 | + printf_debug(log_realmachine_machine, "setup_scratch: skipped (scratch_size=0)"); |
| 15161 | + return; |
| 15162 | + } |
| 15163 | + |
| 15164 | + std::filesystem::path tmpfile = |
| 15165 | + std::filesystem::temp_directory_path(); |
| 15166 | + |
| 15167 | + tmpfile /= "fake-scratch.img."; |
| 15168 | + printf_trace(log_realmachine_machine, "setup_scratch: tmpfile=%s", tmpfile.string().c_str()); |
| 15169 | + |
| 15170 | + std::ofstream f(tmpfile, std::ios::binary); |
| 15171 | + if (!f.is_open()) { |
| 15172 | + printf_trace(log_realmachine_machine, "setup_scratch: failed to open file"); |
| 15173 | + throw std::system_error(std::make_error_code(std::errc::io_error)); |
| 15174 | + } |
| 15175 | + |
| 15176 | + scratch_file = tmpfile.string(); |
| 15177 | + printf_debug(log_realmachine_machine, "setup_scratch: scratch_file=%s", scratch_file.c_str()); |
| 15178 | + |
| 15179 | + auto res = create_image_with_label( |
| 15180 | + tmpfile.string(), |
| 15181 | + scratch_size, |
| 15182 | + "fake-scratch" |
| 15183 | + ); |
| 15184 | + |
| 15185 | + if (res.empty()) { |
| 15186 | + printf_error(log_realmachine_machine, "create_image_with_label failed"); |
| 15187 | + throw std::runtime_error("create_image_with_label failed"); |
| 15188 | + } |
| 15189 | + |
| 15190 | + scratch_dev = res; |
| 15191 | + printf_trace(log_realmachine_machine, "setup_scratch: scratch_dev=%s", scratch_dev.c_str()); |
| 15192 | + |
| 15193 | + std::string cmd = "mkfs.ext4 -q " + tmpfile.string(); |
| 15194 | + printf_trace(log_realmachine_machine, "setup_scratch: cmd=%s", cmd.c_str()); |
| 15195 | + |
| 15196 | + int rc = std::system(cmd.c_str()); |
| 15197 | + if (rc != 0) { |
| 15198 | + printf_error(log_realmachine_machine, "setup_scratch: mkfs failed rc=%d", rc); |
| 15199 | + throw std::system_error(std::make_error_code(std::errc::io_error)); |
| 15200 | + } |
| 15201 | + |
| 15202 | + printf_trace(log_realmachine_machine, "setup_scratch: done"); |
| 15203 | + } |
| 15204 | + |
| 15205 | + void Machine::cleanup() { |
| 15206 | + if (!scratch_file.empty()) { |
| 15207 | + std::filesystem::remove(scratch_file); |
| 15208 | + } |
| 15209 | + |
| 15210 | + scratch_file.clear(); |
| 15211 | + } |
| 15212 | + |
| 15213 | + // Start the machine running the given command and adding the extra content to |
| 15214 | + // the cpio. Extracontent is a list of {source, dest} tuples |
| 15215 | + int Machine::startup(const std::string& command, const std::vector<std::array<std::string, 2>>& extracontent) { |
| 15216 | + flogger::load_env(); |
| 15217 | +#ifdef DEBUG |
| 15218 | + flogger::enable_time_measurement(true); |
| 15219 | + flogger::set_log_level(flogger::log_level::trace); |
| 15220 | +#endif |
| 15221 | + |
| 15222 | + cleanup(); |
| 15223 | + std::filesystem::path tmpdir_path; |
| 15224 | + std::string content; |
| 15225 | + |
| 15226 | + setenv("PATH", (std::string(getenv("PATH")) + ":/sbin:/usr/sbin").c_str(), 1); |
| 15227 | + |
| 15228 | + /* Sanity check mountpoints */ |
| 15229 | + for(const auto& v : mounts) { |
| 15230 | + const auto stat = std::filesystem::status(v->host_directory); |
| 15231 | + if(!std::filesystem::is_directory(stat)) { |
| 15232 | + if (realmachine_g::APP_THROW_ON_BAD_MOUNTS) { |
| 15233 | + printf_error(log_realmachine_machine, "invalid mountpoint host_directory=%s label=%s reason=not_a_directory", v->host_directory.c_str(), v->label.c_str()); |
| 15234 | + throw std::invalid_argument("invalid mountpoint"); |
| 15235 | + } else { |
| 15236 | + return -1; |
| 15237 | + } |
| 15238 | + } |
| 15239 | + |
| 15240 | + if(std::regex_search(v->machine_directory.string(), std::regex("\\s"))) { |
| 15241 | + if (realmachine_g::APP_THROW_ON_BAD_MOUNTS) { |
| 15242 | + printf_error(log_realmachine_machine, "invalid mountpoint machine_directory=%s label=%s reason=whitespace", v->machine_directory.string().c_str(), v->label.c_str()); |
| 15243 | + throw std::invalid_argument("invalid mountpoint"); |
| 15244 | + } else { |
| 15245 | + return -1; |
| 15246 | + } |
| 15247 | + } |
| 15248 | + |
| 15249 | + if(std::regex_search(v->label, std::regex("\\s"))) { |
| 15250 | + if (realmachine_g::APP_THROW_ON_BAD_MOUNTS) { |
| 15251 | + printf_error(log_realmachine_machine, "invalid mountpoint label=%s reason=whitespace", v->label.c_str()); |
| 15252 | + throw std::invalid_argument("invalid mountpoint"); |
| 15253 | + } else { |
| 15254 | + return -1; |
| 15255 | + } |
| 15256 | + } |
| 15257 | + } |
| 15258 | + |
| 15259 | + tmpdir_path = std::filesystem::temp_directory_path(); |
| 15260 | + auto epoch = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count(); |
| 15261 | + tmpdir_path /= "realmachine-" + std::to_string(epoch); |
| 15262 | + printf_debug(log_realmachine_machine, "tmpdir_path=%s", tmpdir_path.string().c_str()); |
| 15263 | + |
| 15264 | + std::string tmpdir = tmpdir_path.string(); |
| 15265 | + std::filesystem::create_directories(tmpdir_path); |
| 15266 | + |
| 15267 | + add_volume_at(tmpdir, "/run/realmachine"); |
| 15268 | + std::filesystem::remove_all(tmpdir_path); |
| 15269 | + |
| 15270 | + setup_scratch(); |
| 15271 | + |
| 15272 | + std::filesystem::create_directory(tmpdir_path); |
| 15273 | + initrd_path = std::filesystem::path(tmpdir) / "initramfs.cpio"; |
| 15274 | + |
| 15275 | + const auto cpio_start = std::chrono::steady_clock::now(); |
| 15276 | + |
| 15277 | + std::ofstream f(initrd_path, std::ios::binary | std::ios::out | std::ios::trunc); |
| 15278 | + if (!f.is_open()) { |
| 15279 | + throw std::runtime_error("io error"); |
| 15280 | + } |
| 15281 | + |
| 15282 | + std::string kernel_module_dir = backend->module_path(); |
| 15283 | + if (kernel_module_dir.empty()) { |
| 15284 | + throw std::runtime_error("not supported"); |
| 15285 | + } |
| 15286 | + |
| 15287 | + auto *w = new cpio::WriterHelper(f); |
| 15288 | + |
| 15289 | + w->write_directories({ |
| 15290 | + {"/scratch", 01777}, |
| 15291 | + {"/var/tmp", 01777}, |
| 15292 | + {"/var/lib/dbus", 0755}, |
| 15293 | + {"/tmp", 01777}, |
| 15294 | + {"/sys", 0755}, |
| 15295 | + {"/proc", 0755}, |
| 15296 | + {"/run", 0755}, |
| 15297 | + {"/usr", 0755}, |
| 15298 | + {"/usr/bin", 0755}, |
| 15299 | + {"/lib64", 0755} |
| 15300 | + }); |
| 15301 | + |
| 15302 | + w->write_symlink("/run", "/var/run", 0755); |
| 15303 | + |
| 15304 | + if (merged_usr_system()) { |
| 15305 | + w->write_symlinks({ |
| 15306 | + {"/usr/sbin", "/sbin", 0755}, |
| 15307 | + {"/usr/bin", "/bin", 0755}, |
| 15308 | + {"/usr/lib", "/lib", 0755}, |
| 15309 | + {"/usr/lib64", "/lib64", 0755} |
| 15310 | + }); |
| 15311 | + } else { |
| 15312 | + w->write_directories({ |
| 15313 | + {"/sbin", 0744}, |
| 15314 | + {"/bin", 0755}, |
| 15315 | + {"/lib", 0755} |
| 15316 | + }); |
| 15317 | + } |
| 15318 | + |
| 15319 | + std::string prefix; |
| 15320 | + if (merged_usr_system()) { |
| 15321 | + prefix = "/usr"; |
| 15322 | + } |
| 15323 | + |
| 15324 | + // search for busybox. note: in some distros it's located under /sbin |
| 15325 | + auto busybox = std::filesystem::path(utils_realmachine::find_program("busybox")); |
| 15326 | + if (busybox.empty()) { |
| 15327 | + throw std::runtime_error("busybox not found"); |
| 15328 | + } |
| 15329 | + |
| 15330 | + w->copy_file_to(busybox.string(), prefix + "/bin/busybox"); |
| 15331 | + |
| 15332 | + /* Ensure systemd-resolved is available */ |
| 15333 | + if (!std::filesystem::exists("/lib/systemd/systemd-resolved")) { |
| 15334 | + throw std::runtime_error("systemd-resolved missing"); |
| 15335 | + } |
| 15336 | + |
| 15337 | + std::string dynamic_linker = arch_dynamic_linker[arch]; |
| 15338 | + w->copy_file(prefix + dynamic_linker); |
| 15339 | + |
| 15340 | + /* C libraries */ |
| 15341 | + std::string library_dir = real_dir(dynamic_linker); |
| 15342 | + if (library_dir.empty()) { |
| 15343 | + throw std::runtime_error("missing library dir"); |
| 15344 | + } |
| 15345 | + |
| 15346 | + w->copy_file(library_dir + "/libc.so.6"); |
| 15347 | + w->copy_file(library_dir + "/libresolv.so.2"); |
| 15348 | + |
| 15349 | + w->write_char_device("/dev/console", 5, 1, 0700); |
| 15350 | + |
| 15351 | + // Linker configuration |
| 15352 | + w->copy_file("/etc/ld.so.conf"); |
| 15353 | + w->copy_tree("/etc/ld.so.conf.d"); |
| 15354 | + |
| 15355 | + // Core system configuration |
| 15356 | + w->write_file("/etc/machine-id", "", 0444); |
| 15357 | + w->write_file("/etc/hostname", "realmachine", 0444); |
| 15358 | + |
| 15359 | + w->copy_file("/etc/passwd"); |
| 15360 | + w->copy_file("/etc/group"); |
| 15361 | + w->copy_file("/etc/nsswitch.conf"); |
| 15362 | + |
| 15363 | + // udev rules |
| 15364 | + std::string udev_joined; |
| 15365 | + for (const auto& r : backend->udev_rules()) { |
| 15366 | + udev_joined += r; |
| 15367 | + udev_joined += "\n"; |
| 15368 | + } |
| 15369 | + |
| 15370 | + w->write_file( |
| 15371 | + "/etc/udev/rules.d/61-realmachine.rules", |
| 15372 | + udev_joined, |
| 15373 | + 0444 |
| 15374 | + ); |
| 15375 | + |
| 15376 | + w->write_file( |
| 15377 | + "/etc/systemd/network/ethernet.network", |
| 15378 | + templating::networkdTemplate, |
| 15379 | + 0444 |
| 15380 | + ); |
| 15381 | + |
| 15382 | + w->write_file( |
| 15383 | + "/etc/systemd/network/10-ethernet.link", |
| 15384 | + templating::networkdLinkTemplate, |
| 15385 | + 0444 |
| 15386 | + ); |
| 15387 | + |
| 15388 | + w->write_symlink( |
| 15389 | + "/lib/systemd/resolv.conf", |
| 15390 | + "/etc/resolv.conf", |
| 15391 | + 0755 |
| 15392 | + ); |
| 15393 | + |
| 15394 | + const auto modules_start = std::chrono::steady_clock::now(); |
| 15395 | + writer_kernel_modules(w, kernel_module_dir, backend->init_modules()); |
| 15396 | + printf_trace(log_realmachine_machine, "TIMING pre_modules=%ldms kernel_modules=%ldms", |
| 15397 | + std::chrono::duration_cast<std::chrono::milliseconds>(modules_start - cpio_start).count(), |
| 15398 | + std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - modules_start).count()); |
| 15399 | + |
| 15400 | + // realmachine.service |
| 15401 | + std::string env_joined; |
| 15402 | + for (size_t i = 0; i < environ.size(); ++i) { |
| 15403 | + if (i) env_joined += " "; |
| 15404 | + env_joined += environ[i]; |
| 15405 | + } |
| 15406 | + |
| 15407 | + content = templating::get_service(env_joined, backend->job_output_tty()); |
| 15408 | + |
| 15409 | + printf_info(log_realmachine_machine, "etc/systemd/system/realmachine.service: %s", content.c_str()); |
| 15410 | + |
| 15411 | + w->write_file( |
| 15412 | + "etc/systemd/system/realmachine.service", |
| 15413 | + content, |
| 15414 | + 0644 |
| 15415 | + ); |
| 15416 | + |
| 15417 | + w->write_symlink( |
| 15418 | + "/lib/systemd/system/serial-getty@ttyS0.service", |
| 15419 | + "/dev/null", |
| 15420 | + 0755 |
| 15421 | + ); |
| 15422 | + |
| 15423 | + // /wrapper |
| 15424 | + std::string wrapper_content = templating::get_command_wrapper(command); |
| 15425 | + printf_info(log_realmachine_machine, "/wrapper: %s", wrapper_content.c_str()); |
| 15426 | + |
| 15427 | + w->write_file( |
| 15428 | + "/wrapper", |
| 15429 | + wrapper_content, |
| 15430 | + 0755 |
| 15431 | + ); |
| 15432 | + |
| 15433 | + auto init_script = build_init_script(this, backend); |
| 15434 | + |
| 15435 | + if (init_script.empty()) { |
| 15436 | + throw std::invalid_argument("invalid init script"); |
| 15437 | + } |
| 15438 | + |
| 15439 | + w->write_file_raw( |
| 15440 | + "/init", |
| 15441 | + std::vector<char>(init_script.begin(), init_script.end()), |
| 15442 | + 0755 |
| 15443 | + ); |
| 15444 | + |
| 15445 | + generate_fstab(w, backend); |
| 15446 | + |
| 15447 | + for (const auto& v : extracontent) { |
| 15448 | + w->copy_file_to(v[0], v[1]); |
| 15449 | + } |
| 15450 | + |
| 15451 | + w->writer->close(); |
| 15452 | + f.close(); |
| 15453 | + |
| 15454 | + printf_trace(log_realmachine_machine, "TIMING total_cpio=%ldms", |
| 15455 | + std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - cpio_start).count()); |
| 15456 | + |
| 15457 | + if (!quiet) { |
| 15458 | + printf_info(log_realmachine_machine, "Running %s using %s backend", command.c_str(), backend->name().c_str()); |
| 15459 | + } |
| 15460 | + |
| 15461 | + const bool success = backend->start(); |
| 15462 | + if (!success) { |
| 15463 | + throw std::runtime_error("backend start failed"); |
| 15464 | + } |
| 15465 | + |
| 15466 | + std::filesystem::path result_path = std::filesystem::path(tmpdir) / "result"; |
| 15467 | + std::ifstream result(result_path); |
| 15468 | + printf_debug(log_realmachine_machine, "checking result file: %s", result_path.string().c_str()); |
| 15469 | + if (!result.is_open()) { |
| 15470 | + throw std::runtime_error("missing result file"); |
| 15471 | + } |
| 15472 | + |
| 15473 | + std::string exit_str; |
| 15474 | + std::getline(result, exit_str); |
| 15475 | + |
| 15476 | + if (tmpdir_path.filename().string().rfind("realmachine", 0) == 0) { |
| 15477 | + printf_debug(log_realmachine_machine, "removing %s", tmpdir_path.filename().string().c_str()); |
| 15478 | + std::filesystem::remove_all(tmpdir_path); |
| 15479 | + } |
| 15480 | + |
| 15481 | + return std::stoi(exit_str); |
| 15482 | + } |
| 15483 | + |
| 15484 | + int Machine::run_in_machine_with_args(const std::vector<std::string>& args) { |
| 15485 | + const std::string host_exe = executable_name; |
| 15486 | + printf_debug(log_realmachine_machine, "host_exe=%s", host_exe.c_str()); |
| 15487 | + if (host_exe.empty()) { |
| 15488 | + throw std::runtime_error("executable_name not set"); |
| 15489 | + } |
| 15490 | + |
| 15491 | + const std::filesystem::path exe_path(host_exe); |
| 15492 | + const std::filesystem::path name = |
| 15493 | + std::filesystem::path("/") / exe_path.filename(); |
| 15494 | + |
| 15495 | + printf_trace(log_realmachine_machine, "resolved target name: %s", name.string().c_str()); |
| 15496 | + |
| 15497 | + const std::string quoted_args = shellescape::quote_command(args); |
| 15498 | + const std::string command = name.string() + " " + quoted_args; |
| 15499 | + |
| 15500 | + printf_debug(log_realmachine_machine, "final command: %s", command.c_str()); |
| 15501 | + |
| 15502 | + const std::filesystem::path executable = utils_realmachine::find_program(host_exe); |
| 15503 | + |
| 15504 | + printf_trace(log_realmachine_machine, "resolved executable: %s", executable.string().c_str()); |
| 15505 | + |
| 15506 | + if (executable.empty()) { |
| 15507 | + throw std::runtime_error("failed to find executable"); |
| 15508 | + } |
| 15509 | + |
| 15510 | + std::vector<std::array<std::string, 2>> extracontent = { |
| 15511 | + {executable.string(), name.string()} |
| 15512 | + }; |
| 15513 | + |
| 15514 | + printf_trace(log_realmachine_machine, "prepared extracontent mapping: host=%s guest=%s", |
| 15515 | + executable.string().c_str(), |
| 15516 | + name.string().c_str()); |
| 15517 | + |
| 15518 | + return startup(command, extracontent); |
| 15519 | + } |
| 15520 | + |
| 15521 | + // Run creates the machine running the given command |
| 15522 | + int Machine::run(const std::string& command) { |
| 15523 | + return startup(command, {}); |
| 15524 | + } |
| 15525 | + |
| 15526 | + // RunInMachine runs the caller binary inside the realmachine with the same |
| 15527 | + // commandline arguments as the parent |
| 15528 | + int Machine::run_in_machine() { |
| 15529 | + return run_in_machine_with_args( |
| 15530 | + std::vector(argv.begin() + 1, argv.end()) |
| 15531 | + ); |
| 15532 | + } |
| 15533 | +} |
| 15534 | diff --git a/src/realmachine/machine.h b/src/realmachine/machine.h |
| 15535 | new file mode 100644 |
| 15536 | index 0000000..3c6a8ec |
| 15537 | --- /dev/null |
| 15538 | +++ b/src/realmachine/machine.h |
| 15539 | @@ -0,0 +1,94 @@ |
| 15540 | +#pragma once |
| 15541 | + |
| 15542 | +#include <string> |
| 15543 | +#include <vector> |
| 15544 | +#include <map> |
| 15545 | +#include <filesystem> |
| 15546 | +#include <functional> |
| 15547 | +#include <memory> |
| 15548 | + |
| 15549 | +#include <cpiocpplame/writerhelper.h> |
| 15550 | + |
| 15551 | +#include "models.h" |
| 15552 | +#include "module_cache.h" |
| 15553 | + |
| 15554 | +namespace realmachine { |
| 15555 | + class Backend; |
| 15556 | + |
| 15557 | + struct ResolvedModule { |
| 15558 | + std::string source; |
| 15559 | + std::string destination; |
| 15560 | + cpio::Transformer transformer; |
| 15561 | + }; |
| 15562 | + |
| 15563 | + class Machine { |
| 15564 | + public: |
| 15565 | + Machine(); |
| 15566 | + Arch arch = Arch::amd64; |
| 15567 | + std::unique_ptr<Backend> backend; |
| 15568 | + std::vector<std::shared_ptr<MountPoint>> mounts; |
| 15569 | + std::vector<std::unique_ptr<Image>> images; |
| 15570 | + std::string executable_name; |
| 15571 | + uint32_t memory = 2048; |
| 15572 | + uint8_t num_cpus = 0; |
| 15573 | + uint32_t sector_size = 512; |
| 15574 | + bool show_boot = false; |
| 15575 | + bool quiet = false; |
| 15576 | + |
| 15577 | + bool module_cache = true; |
| 15578 | + std::filesystem::path module_cache_path; |
| 15579 | + |
| 15580 | + int64_t scratch_size = 0; |
| 15581 | + std::filesystem::path scratch_path; |
| 15582 | + std::filesystem::path scratch_file; |
| 15583 | + std::string scratch_dev; |
| 15584 | + |
| 15585 | + std::filesystem::path initrd_path; |
| 15586 | + std::vector<std::string> argv; |
| 15587 | + std::vector<std::string> environ; |
| 15588 | + |
| 15589 | + static std::shared_ptr<Machine> new_machine(); |
| 15590 | + static std::shared_ptr<Machine> new_machine_with_backend(const std::string &backend_name); |
| 15591 | + int startup(const std::string& command, const std::vector<std::array<std::string, 2>>& extracontent); |
| 15592 | + |
| 15593 | + int run(const std::string& command); |
| 15594 | + int run_in_machine_with_args(const std::vector<std::string>& args); |
| 15595 | + int run_in_machine(); |
| 15596 | + |
| 15597 | + void add_volume(const std::filesystem::path &directory); |
| 15598 | + std::string create_image_with_label(const std::string &path, int64_t size, const std::string &label); |
| 15599 | + std::string create_image(const std::string &imagepath, int64_t size); |
| 15600 | + void set_memory(int new_memory); |
| 15601 | + void set_num_cpus(int new_num_cpus); |
| 15602 | + void set_sector_size(int new_sector_size); |
| 15603 | + void set_show_boot(bool show_boot_status); |
| 15604 | + void set_quiet(bool quiet_status); |
| 15605 | + void set_scratch(int64_t new_scratch_size, const std::string &path); |
| 15606 | + void set_module_cache(bool enabled, const std::string &path = ""); |
| 15607 | + std::string generate_modules_dep(const std::unordered_map<std::string, bool> &modules) const; |
| 15608 | + void set_environ(const std::vector<std::string> &new_environ); |
| 15609 | + void writer_kernel_modules(cpio::WriterHelper *w, const std::string &moddir, const std::vector<std::string> &modules); |
| 15610 | + void setup_scratch(); |
| 15611 | + void cleanup(); |
| 15612 | + void add_static_volume(const std::filesystem::path &host, const std::string &label); |
| 15613 | + void add_volume_at(const std::string &host_directory, const std::string &machine_directory); |
| 15614 | + |
| 15615 | + static bool in_machine(); |
| 15616 | + |
| 15617 | + private: |
| 15618 | + void resolve_modules(std::vector<ResolvedModule> &resolved, const std::string &modname, std::unordered_map<std::string, bool> &copied_modules); |
| 15619 | + ModuleSet build_module_set(const std::string &moddir, const std::vector<std::string> &modules, const std::filesystem::path &staging); |
| 15620 | + void generate_fstab(cpio::WriterHelper* w, const std::unique_ptr<Backend>& backend) const; |
| 15621 | + |
| 15622 | + int count_ = 0; |
| 15623 | + }; |
| 15624 | + |
| 15625 | + bool merged_usr_system(); |
| 15626 | + std::vector<std::string> get_mod_data(const std::string &modname, const std::string &fieldname, const std::string &kernel_release); |
| 15627 | + std::string get_mod_path(const std::string &modname, const std::string &kernel_release); |
| 15628 | + std::vector<std::string> get_mod_depends(const std::string &modname, const std::string &kernel_release); |
| 15629 | + std::filesystem::path real_dir(const std::filesystem::path &path); |
| 15630 | + std::string tmpl_mount_volume(std::unique_ptr<Backend>& b, const std::shared_ptr<MountPoint> &m); |
| 15631 | + std::vector<std::shared_ptr<MountPoint>> tmpl_static_volumes(const Machine& m, const std::unique_ptr<Backend>& b); |
| 15632 | + std::vector<uint8_t> execute_init_script_template(const Machine* m, const std::unique_ptr<Backend>& b); |
| 15633 | +} |
| 15634 | diff --git a/src/realmachine/models.cpp b/src/realmachine/models.cpp |
| 15635 | new file mode 100644 |
| 15636 | index 0000000..37562bb |
| 15637 | --- /dev/null |
| 15638 | +++ b/src/realmachine/models.cpp |
| 15639 | @@ -0,0 +1,22 @@ |
| 15640 | +#include "models.h" |
| 15641 | +#include "decompressors.h" |
| 15642 | + |
| 15643 | +namespace realmachine { |
| 15644 | + |
| 15645 | + std::map<std::string, cpio::Transformer> KO_SUFFIXES = { |
| 15646 | + {".ko", nullptr}, |
| 15647 | + {".ko.gz", nullptr}, |
| 15648 | + {".ko.xz", nullptr}, |
| 15649 | + {".ko.zst", nullptr} |
| 15650 | + }; |
| 15651 | + |
| 15652 | + void populate_ko_suffix_map() { |
| 15653 | + KO_SUFFIXES = { |
| 15654 | + {".ko", decompressors::null_decompressor}, |
| 15655 | + {".ko.gz", decompressors::gzip_decompressor}, |
| 15656 | + {".ko.xz", decompressors::xz_decompressor}, |
| 15657 | + {".ko.zst", decompressors::zstd_decompressor} |
| 15658 | + }; |
| 15659 | + } |
| 15660 | + |
| 15661 | +} |
| 15662 | \ No newline at end of file |
| 15663 | diff --git a/src/realmachine/models.h b/src/realmachine/models.h |
| 15664 | new file mode 100644 |
| 15665 | index 0000000..e522dca |
| 15666 | --- /dev/null |
| 15667 | +++ b/src/realmachine/models.h |
| 15668 | @@ -0,0 +1,44 @@ |
| 15669 | +#pragma once |
| 15670 | +#include <map> |
| 15671 | +#include <string> |
| 15672 | +#include <filesystem> |
| 15673 | + |
| 15674 | +#include <cpiocpplame/writerhelper.h> |
| 15675 | + |
| 15676 | +namespace realmachine { |
| 15677 | + extern std::map<std::string, cpio::Transformer> KO_SUFFIXES; |
| 15678 | + void populate_ko_suffix_map(); |
| 15679 | + |
| 15680 | + struct QemuMachine { |
| 15681 | + std::string binary; |
| 15682 | + std::string console; |
| 15683 | + std::string machine; |
| 15684 | + std::string qemu_cpu; |
| 15685 | + }; |
| 15686 | + |
| 15687 | + enum class Arch { |
| 15688 | + amd64, |
| 15689 | + arm64 |
| 15690 | + }; |
| 15691 | + |
| 15692 | + static QemuMachine qemu_machine_amd64 = {"qemu-system-x86_64", "ttyS0", "pc", ""}; |
| 15693 | + static QemuMachine qemu_machine_arm64 = {"qemu-system-aarch64", "ttyAMA0", "virt", "cortex-a53"}; |
| 15694 | + static std::map<std::string, Arch> arch_map = {{"amd64", Arch::amd64}, {"arm64", Arch::arm64}, {"aarch64", Arch::arm64}}; |
| 15695 | + |
| 15696 | + static std::map<Arch, std::string> arch_dynamic_linker = { |
| 15697 | + {Arch::amd64, "/lib64/ld-linux-x86-64.so.2"}, |
| 15698 | + {Arch::arm64, "/lib/ld-linux-aarch64.so.1"} |
| 15699 | + }; |
| 15700 | + |
| 15701 | + struct MountPoint { |
| 15702 | + std::filesystem::path host_directory; |
| 15703 | + std::filesystem::path machine_directory; |
| 15704 | + std::string label; |
| 15705 | + bool is_static; |
| 15706 | + }; |
| 15707 | + |
| 15708 | + struct Image { |
| 15709 | + std::filesystem::path path; |
| 15710 | + std::string label; |
| 15711 | + }; |
| 15712 | +} |
| 15713 | \ No newline at end of file |
| 15714 | diff --git a/src/realmachine/module_cache.cpp b/src/realmachine/module_cache.cpp |
| 15715 | new file mode 100644 |
| 15716 | index 0000000..481f504 |
| 15717 | --- /dev/null |
| 15718 | +++ b/src/realmachine/module_cache.cpp |
| 15719 | @@ -0,0 +1,163 @@ |
| 15720 | +#include <cstdlib> |
| 15721 | +#include <fstream> |
| 15722 | +#include <sstream> |
| 15723 | + |
| 15724 | +#include <flogger/flogger.h> |
| 15725 | + |
| 15726 | +#include "module_cache.h" |
| 15727 | + |
| 15728 | +auto log_realmachine_module_cache = flogger::bind_log("realmachine/module_cache"); |
| 15729 | + |
| 15730 | +namespace realmachine { |
| 15731 | + |
| 15732 | + namespace { |
| 15733 | + constexpr const char* CACHE_FORMAT_VERSION = "1"; |
| 15734 | + |
| 15735 | + std::string fnv1a(const std::string& s) { |
| 15736 | + uint64_t h = 1469598103934665603ULL; |
| 15737 | + for (unsigned char c : s) { |
| 15738 | + h ^= c; |
| 15739 | + h *= 1099511628211ULL; |
| 15740 | + } |
| 15741 | + |
| 15742 | + std::ostringstream o; |
| 15743 | + o << std::hex << h; |
| 15744 | + return o.str(); |
| 15745 | + } |
| 15746 | + |
| 15747 | + std::string stat_signature(const std::filesystem::path& p) { |
| 15748 | + std::error_code ec; |
| 15749 | + const auto size = std::filesystem::file_size(p, ec); |
| 15750 | + if (ec) return "-"; |
| 15751 | + |
| 15752 | + const auto mtime = std::filesystem::last_write_time(p, ec); |
| 15753 | + if (ec) return "-"; |
| 15754 | + |
| 15755 | + return std::to_string(static_cast<unsigned long long>(size)) + ":" + |
| 15756 | + std::to_string(static_cast<long long>(mtime.time_since_epoch().count())); |
| 15757 | + } |
| 15758 | + } |
| 15759 | + |
| 15760 | + std::filesystem::path default_module_cache_directory() { |
| 15761 | + if (const char* xdg = std::getenv("XDG_CACHE_HOME"); xdg && *xdg) |
| 15762 | + return std::filesystem::path(xdg) / "realmachine" / "kernel-modules"; |
| 15763 | + |
| 15764 | + if (const char* home = std::getenv("HOME"); home && *home) |
| 15765 | + return std::filesystem::path(home) / ".cache" / "realmachine" / "kernel-modules"; |
| 15766 | + |
| 15767 | + return std::filesystem::temp_directory_path() / "realmachine-kernel-modules"; |
| 15768 | + } |
| 15769 | + |
| 15770 | + std::string module_cache_key( |
| 15771 | + const std::string& kernel_release, |
| 15772 | + const std::string& moddir, |
| 15773 | + const std::vector<std::string>& modules, |
| 15774 | + const bool merged_usr |
| 15775 | + ) { |
| 15776 | + std::string key = CACHE_FORMAT_VERSION; |
| 15777 | + key += "|" + kernel_release; |
| 15778 | + key += "|" + moddir; |
| 15779 | + key += "|" + std::to_string(merged_usr); |
| 15780 | + |
| 15781 | + for (const auto& m : modules) { |
| 15782 | + key += "|" + m; |
| 15783 | + } |
| 15784 | + |
| 15785 | + for (const auto* f : {"modules.dep", "modules.alias", "modules.symbols", "modules.builtin", "modules.order"}) { |
| 15786 | + key += "|" + std::string(f) + "=" + stat_signature(std::filesystem::path(moddir) / f); |
| 15787 | + } |
| 15788 | + |
| 15789 | + const std::string hashed = kernel_release + "-" + fnv1a(key); |
| 15790 | + printf_debug(log_realmachine_module_cache, "key=%s hashed=%s", key.c_str(), hashed.c_str()); |
| 15791 | + return hashed; |
| 15792 | + } |
| 15793 | + |
| 15794 | + bool module_cache_load(const std::filesystem::path& dir, ModuleSet& out) { |
| 15795 | + std::error_code ec; |
| 15796 | + if (!std::filesystem::is_directory(dir, ec) || ec) { |
| 15797 | + return false; |
| 15798 | + } |
| 15799 | + |
| 15800 | + std::ifstream manifest(dir / "manifest"); |
| 15801 | + if (!manifest.is_open()) { |
| 15802 | + return false; |
| 15803 | + } |
| 15804 | + |
| 15805 | + ModuleSet set; |
| 15806 | + std::string line; |
| 15807 | + |
| 15808 | + while (std::getline(manifest, line)) { |
| 15809 | + if (line.empty()) continue; |
| 15810 | + |
| 15811 | + const auto tab = line.find('\t'); |
| 15812 | + if (tab == std::string::npos) { |
| 15813 | + printf_debug(log_realmachine_module_cache, "malformed manifest line=%s", line.c_str()); |
| 15814 | + return false; |
| 15815 | + } |
| 15816 | + |
| 15817 | + const auto blob = dir / line.substr(0, tab); |
| 15818 | + if (!std::filesystem::exists(blob, ec) || ec) { |
| 15819 | + printf_debug(log_realmachine_module_cache, "missing blob=%s", blob.string().c_str()); |
| 15820 | + return false; |
| 15821 | + } |
| 15822 | + |
| 15823 | + set.files.push_back({blob.string(), line.substr(tab + 1)}); |
| 15824 | + } |
| 15825 | + |
| 15826 | + std::ifstream dep(dir / "modules.dep", std::ios::binary); |
| 15827 | + if (!dep.is_open()) { |
| 15828 | + return false; |
| 15829 | + } |
| 15830 | + |
| 15831 | + std::ostringstream buf; |
| 15832 | + buf << dep.rdbuf(); |
| 15833 | + set.modules_dep = buf.str(); |
| 15834 | + |
| 15835 | + out = std::move(set); |
| 15836 | + printf_debug(log_realmachine_module_cache, "cache hit dir=%s modules=%zu", dir.string().c_str(), out.files.size()); |
| 15837 | + return true; |
| 15838 | + } |
| 15839 | + |
| 15840 | + void module_cache_store( |
| 15841 | + const std::filesystem::path& tmpdir, |
| 15842 | + const std::filesystem::path& dir, |
| 15843 | + ModuleSet& set |
| 15844 | + ) { |
| 15845 | + { |
| 15846 | + std::ofstream manifest(tmpdir / "manifest", std::ios::binary | std::ios::trunc); |
| 15847 | + if (!manifest.is_open()) { |
| 15848 | + printf_error(log_realmachine_module_cache, "cannot write manifest in %s", tmpdir.string().c_str()); |
| 15849 | + return; |
| 15850 | + } |
| 15851 | + |
| 15852 | + for (const auto& f : set.files) { |
| 15853 | + manifest << std::filesystem::path(f[0]).filename().string() << "\t" << f[1] << "\n"; |
| 15854 | + } |
| 15855 | + } |
| 15856 | + |
| 15857 | + { |
| 15858 | + std::ofstream dep(tmpdir / "modules.dep", std::ios::binary | std::ios::trunc); |
| 15859 | + if (!dep.is_open()) { |
| 15860 | + printf_error(log_realmachine_module_cache, "cannot write modules.dep in %s", tmpdir.string().c_str()); |
| 15861 | + return; |
| 15862 | + } |
| 15863 | + dep << set.modules_dep; |
| 15864 | + } |
| 15865 | + |
| 15866 | + std::error_code ec; |
| 15867 | + std::filesystem::create_directories(dir.parent_path(), ec); |
| 15868 | + std::filesystem::rename(tmpdir, dir, ec); |
| 15869 | + |
| 15870 | + if (ec) { |
| 15871 | + printf_debug(log_realmachine_module_cache, "rename failed (%s), keeping temporary blobs", ec.message().c_str()); |
| 15872 | + return; |
| 15873 | + } |
| 15874 | + |
| 15875 | + for (auto& f : set.files) { |
| 15876 | + f[0] = (dir / std::filesystem::path(f[0]).filename()).string(); |
| 15877 | + } |
| 15878 | + |
| 15879 | + printf_debug(log_realmachine_module_cache, "cache stored dir=%s modules=%zu", dir.string().c_str(), set.files.size()); |
| 15880 | + } |
| 15881 | + |
| 15882 | +} |
| 15883 | diff --git a/src/realmachine/module_cache.h b/src/realmachine/module_cache.h |
| 15884 | new file mode 100644 |
| 15885 | index 0000000..2d0f867 |
| 15886 | --- /dev/null |
| 15887 | +++ b/src/realmachine/module_cache.h |
| 15888 | @@ -0,0 +1,32 @@ |
| 15889 | +#pragma once |
| 15890 | + |
| 15891 | +#include <array> |
| 15892 | +#include <filesystem> |
| 15893 | +#include <string> |
| 15894 | +#include <vector> |
| 15895 | + |
| 15896 | +namespace realmachine { |
| 15897 | + |
| 15898 | + struct ModuleSet { |
| 15899 | + std::vector<std::array<std::string, 2>> files; |
| 15900 | + std::string modules_dep; |
| 15901 | + }; |
| 15902 | + |
| 15903 | + std::filesystem::path default_module_cache_directory(); |
| 15904 | + |
| 15905 | + std::string module_cache_key( |
| 15906 | + const std::string& kernel_release, |
| 15907 | + const std::string& moddir, |
| 15908 | + const std::vector<std::string>& modules, |
| 15909 | + bool merged_usr |
| 15910 | + ); |
| 15911 | + |
| 15912 | + bool module_cache_load(const std::filesystem::path& dir, ModuleSet& out); |
| 15913 | + |
| 15914 | + void module_cache_store( |
| 15915 | + const std::filesystem::path& tmpdir, |
| 15916 | + const std::filesystem::path& dir, |
| 15917 | + ModuleSet& set |
| 15918 | + ); |
| 15919 | + |
| 15920 | +} |
| 15921 | diff --git a/src/realmachine/template.cpp b/src/realmachine/template.cpp |
| 15922 | new file mode 100644 |
| 15923 | index 0000000..b0a4bfa |
| 15924 | --- /dev/null |
| 15925 | +++ b/src/realmachine/template.cpp |
| 15926 | @@ -0,0 +1,132 @@ |
| 15927 | +#include <string> |
| 15928 | + |
| 15929 | +#include <flogger/flogger.h> |
| 15930 | +#include <shellescape/shellescape.h> |
| 15931 | + |
| 15932 | +#include "template.h" |
| 15933 | + |
| 15934 | +auto log_realmachine_templating = flogger::bind_log("realmachine/templating"); |
| 15935 | + |
| 15936 | +namespace templating { |
| 15937 | + |
| 15938 | + const std::string initScript = |
| 15939 | + R"(#!/bin/busybox sh |
| 15940 | +busybox mount -t proc proc /proc |
| 15941 | +busybox mount -t sysfs none /sys |
| 15942 | + |
| 15943 | +# probe additional modules |
| 15944 | +{modules} |
| 15945 | + |
| 15946 | +# mount static volumes |
| 15947 | +{mounts} |
| 15948 | + |
| 15949 | +exec /lib/systemd/systemd |
| 15950 | +)"; |
| 15951 | + |
| 15952 | + const std::string networkdTemplate = |
| 15953 | + R"([Match] |
| 15954 | +Type=ether |
| 15955 | + |
| 15956 | +[Network] |
| 15957 | +DHCP=ipv4 |
| 15958 | +# Disable link-local address to speedup boot |
| 15959 | +LinkLocalAddressing=no |
| 15960 | +IPv6AcceptRA=no |
| 15961 | +)"; |
| 15962 | + |
| 15963 | + const std::string networkdLinkTemplate = |
| 15964 | + R"([Match] |
| 15965 | +Type=ether |
| 15966 | + |
| 15967 | +[Link] |
| 15968 | +# Give the interface a static name |
| 15969 | +Name=ethernet0 |
| 15970 | +)"; |
| 15971 | + |
| 15972 | + const std::string commandWrapper = |
| 15973 | + R"(#!/bin/sh |
| 15974 | +/lib/systemd/systemd-networkd-wait-online -q --interface=ethernet0 |
| 15975 | +if [ $? != 0 ]; then |
| 15976 | + echo "WARNING: Network setup failed" |
| 15977 | + echo "== Journal ==" |
| 15978 | + journalctl -a --no-pager |
| 15979 | + echo "== networkd ==" |
| 15980 | + networkctl status |
| 15981 | + networkctl list |
| 15982 | + echo 1 > /run/realmachine/result |
| 15983 | + exit |
| 15984 | +fi |
| 15985 | + |
| 15986 | +{command} |
| 15987 | + |
| 15988 | +echo $? > /run/realmachine/result |
| 15989 | +)"; |
| 15990 | + |
| 15991 | + const std::string serviceTemplate = |
| 15992 | + R"([Unit] |
| 15993 | +Description=realmachine runner |
| 15994 | +Conflicts=shutdown.target |
| 15995 | +Before=shutdown.target |
| 15996 | +Requires=basic.target |
| 15997 | +Wants=systemd-resolved.service binfmt-support.service systemd-networkd.service |
| 15998 | +After=basic.target systemd-resolved.service binfmt-support.service systemd-networkd.service |
| 15999 | +OnFailure=poweroff.target |
| 16000 | + |
| 16001 | +[Service] |
| 16002 | +Environment=HOME=/root IN_FAKE_MACHINE=yes {env} |
| 16003 | +WorkingDirectory=-/scratch |
| 16004 | +ExecStart=/wrapper |
| 16005 | +ExecStopPost=/bin/sync |
| 16006 | +ExecStopPost=/bin/systemctl poweroff -q -ff |
| 16007 | +Type=idle |
| 16008 | +TTYPath={tty} |
| 16009 | +StandardInput=tty-force |
| 16010 | +StandardOutput=inherit |
| 16011 | +StandardError=inherit |
| 16012 | +KillMode=process |
| 16013 | +IgnoreSIGPIPE=no |
| 16014 | +SendSIGHUP=yes |
| 16015 | +LimitNOFILE=4096 |
| 16016 | +)"; |
| 16017 | + |
| 16018 | + static std::string replace_all(std::string s, const std::string& key, const std::string& val) { |
| 16019 | + size_t pos = 0; |
| 16020 | + while ((pos = s.find(key, pos)) != std::string::npos) { |
| 16021 | + s.replace(pos, key.size(), val); |
| 16022 | + pos += val.size(); |
| 16023 | + } |
| 16024 | + return s; |
| 16025 | + } |
| 16026 | + |
| 16027 | + std::string get_init_script(const std::string& modules, const std::string& mounts) { |
| 16028 | + std::string out = initScript; |
| 16029 | + out = replace_all(out, "{modules}", modules); |
| 16030 | + out = replace_all(out, "{mounts}", mounts); |
| 16031 | + printf_debug(log_realmachine_templating, "init_script: %s", out.c_str()); |
| 16032 | + return out; |
| 16033 | + } |
| 16034 | + |
| 16035 | + std::string get_networkd_config() { |
| 16036 | + return networkdTemplate; |
| 16037 | + } |
| 16038 | + |
| 16039 | + std::string get_networkd_link_config() { |
| 16040 | + return networkdLinkTemplate; |
| 16041 | + } |
| 16042 | + |
| 16043 | + std::string get_command_wrapper(const std::string& command) { |
| 16044 | + std::string out = commandWrapper; |
| 16045 | + out = replace_all(out, "{command}", command); |
| 16046 | + printf_debug(log_realmachine_templating, "command_wrapper: %s", command.c_str()); |
| 16047 | + return out; |
| 16048 | + } |
| 16049 | + |
| 16050 | + std::string get_service(const std::string& env, const std::string& tty) { |
| 16051 | + std::string out = serviceTemplate; |
| 16052 | + out = replace_all(out, "{env}", env); |
| 16053 | + out = replace_all(out, "{tty}", tty); |
| 16054 | + printf_debug(log_realmachine_templating, "command_wrapper: %s", out.c_str()); |
| 16055 | + return out; |
| 16056 | + } |
| 16057 | + |
| 16058 | +} |
| 16059 | \ No newline at end of file |
| 16060 | diff --git a/src/realmachine/template.h b/src/realmachine/template.h |
| 16061 | new file mode 100644 |
| 16062 | index 0000000..99da227 |
| 16063 | --- /dev/null |
| 16064 | +++ b/src/realmachine/template.h |
| 16065 | @@ -0,0 +1,16 @@ |
| 16066 | +#pragma once |
| 16067 | +#include <string> |
| 16068 | + |
| 16069 | +namespace templating { |
| 16070 | + extern const std::string initScript; |
| 16071 | + extern const std::string networkdTemplate; |
| 16072 | + extern const std::string networkdLinkTemplate; |
| 16073 | + extern const std::string commandWrapper; |
| 16074 | + extern const std::string serviceTemplate; |
| 16075 | + |
| 16076 | + std::string get_init_script(const std::string& modules, const std::string& mounts); |
| 16077 | + std::string get_networkd_config(); |
| 16078 | + std::string get_networkd_link_config(); |
| 16079 | + std::string get_command_wrapper(const std::string& command); |
| 16080 | + std::string get_service(const std::string& env, const std::string& tty); |
| 16081 | +} |
| 16082 | \ No newline at end of file |
| 16083 | diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt |
| 16084 | new file mode 100644 |
| 16085 | index 0000000..fb6ae77 |
| 16086 | --- /dev/null |
| 16087 | +++ b/tests/CMakeLists.txt |
| 16088 | @@ -0,0 +1,11 @@ |
| 16089 | +add_executable(realmachine_tests |
| 16090 | + test.cpp |
| 16091 | +) |
| 16092 | + |
| 16093 | +target_include_directories(realmachine_tests PRIVATE |
| 16094 | + ${CMAKE_SOURCE_DIR}/src |
| 16095 | +) |
| 16096 | + |
| 16097 | +target_link_libraries(realmachine_tests PRIVATE realmachine_lib) |
| 16098 | + |
| 16099 | +add_test(NAME realmachine_tests COMMAND realmachine_tests) |
| 16100 | \ No newline at end of file |
| 16101 | diff --git a/tests/test b/tests/test |
| 16102 | new file mode 100644 |
| 16103 | index 0000000..dd33a6b |
| 16104 | Binary files /dev/null and b/tests/test differ |
| 16105 | diff --git a/tests/test.cpp b/tests/test.cpp |
| 16106 | new file mode 100644 |
| 16107 | index 0000000..2a088d7 |
| 16108 | --- /dev/null |
| 16109 | +++ b/tests/test.cpp |
| 16110 | @@ -0,0 +1,392 @@ |
| 16111 | +// should print 'okidoki' at the end of the tests |
| 16112 | +// included: |
| 16113 | +// - command execution success/failure |
| 16114 | +// - filesystem mounts (tmpfs, ext4 scratch) |
| 16115 | +// - disk image creation |
| 16116 | +// - memory sizing |
| 16117 | +// - sector size correctness |
| 16118 | +// - volume mapping behavior |
| 16119 | +// - environment variable propagation |
| 16120 | +// - command escaping correctness |
| 16121 | + |
| 16122 | +#include <iostream> |
| 16123 | +#include <string> |
| 16124 | +#include <vector> |
| 16125 | +#include <array> |
| 16126 | +#include <fstream> |
| 16127 | +#include <sstream> |
| 16128 | + |
| 16129 | +#include <flogger/flogger.h> |
| 16130 | + |
| 16131 | +#include "realmachine/globals.h" |
| 16132 | +#include "realmachine/machine.h" |
| 16133 | +#include "realmachine/decompressors.h" |
| 16134 | + |
| 16135 | +auto log_test = flogger::bind_log("realmachine/test"); |
| 16136 | + |
| 16137 | +using namespace realmachine; |
| 16138 | + |
| 16139 | +static std::string backendName = "kvm"; |
| 16140 | +static std::string testArg; |
| 16141 | + |
| 16142 | +static std::shared_ptr<Machine> create_machine() { |
| 16143 | + auto m = Machine::new_machine_with_backend(backendName); |
| 16144 | + m->set_show_boot(true); |
| 16145 | + m->set_num_cpus(2); |
| 16146 | + |
| 16147 | + m->executable_name = |
| 16148 | + std::filesystem::canonical("/proc/self/exe").string(); |
| 16149 | + |
| 16150 | + return m; |
| 16151 | +} |
| 16152 | + |
| 16153 | +static void assert_dev_sector_size(const std::string& device, int sectorsize) { |
| 16154 | + const std::vector<std::string> types = {"physical", "logical"}; |
| 16155 | + |
| 16156 | + for (auto& t : types) { |
| 16157 | + std::ifstream f("/sys/block/" + device + "/queue/" + t + "_block_size"); |
| 16158 | + std::string line; |
| 16159 | + std::getline(f, line); |
| 16160 | + int v = std::stoi(line); |
| 16161 | + if (v != sectorsize) { |
| 16162 | + printf_error(log_test, "assert_dev_sector_size(): sector size mismatch"); |
| 16163 | + std::exit(1); |
| 16164 | + } |
| 16165 | + } |
| 16166 | +} |
| 16167 | + |
| 16168 | +static void assert_sector_size(int sectorsize) { |
| 16169 | + if (!Machine::in_machine()) { |
| 16170 | + const auto m = create_machine(); |
| 16171 | + m->set_sector_size(sectorsize); |
| 16172 | + m->create_image("test.img", 1024 * 1024); |
| 16173 | + |
| 16174 | + std::string test = |
| 16175 | + (sectorsize == 512) |
| 16176 | + ? "TestImage512SectorSize" |
| 16177 | + : "TestImage4kSectorSize"; |
| 16178 | + |
| 16179 | + if (m->run_in_machine_with_args({ |
| 16180 | + "-test.run", |
| 16181 | + test, |
| 16182 | + backendName |
| 16183 | + }) != 0) { |
| 16184 | + printf_error(log_test, "assert_sector_size(): run_in_machine_with_args() failed"); |
| 16185 | + std::exit(1); |
| 16186 | + } |
| 16187 | + |
| 16188 | + } else { |
| 16189 | + assert_dev_sector_size("vda", sectorsize); |
| 16190 | + } |
| 16191 | +} |
| 16192 | + |
| 16193 | +static void assert_mount(const std::string& mp, const std::string& fs) { |
| 16194 | + std::ifstream f("/proc/self/mounts"); |
| 16195 | + std::string line; |
| 16196 | + |
| 16197 | + while (std::getline(f, line)) { |
| 16198 | + std::istringstream iss(line); |
| 16199 | + std::string d, m, t; |
| 16200 | + iss >> d >> m >> t; |
| 16201 | + if (m == mp) { |
| 16202 | + if (t != fs) std::exit(1); |
| 16203 | + return; |
| 16204 | + } |
| 16205 | + } |
| 16206 | + |
| 16207 | + printf_error(log_test, "assert_mount(): mount() failed"); |
| 16208 | + std::exit(1); |
| 16209 | +} |
| 16210 | + |
| 16211 | +static void test_success() { |
| 16212 | + const auto m = create_machine(); |
| 16213 | + if (m->run("ls /") != 0) { |
| 16214 | + printf_error(log_test, "test_success() failed"); |
| 16215 | + std::exit(1); |
| 16216 | + } |
| 16217 | +} |
| 16218 | + |
| 16219 | +static void test_notfound() { |
| 16220 | + const auto m = create_machine(); |
| 16221 | + if (m->run("/a/b/c /") != 127) { |
| 16222 | + printf_error(log_test, "test_notfound() failed"); |
| 16223 | + std::exit(1); |
| 16224 | + } |
| 16225 | +} |
| 16226 | + |
| 16227 | +static void test_image() { |
| 16228 | + const auto m = create_machine(); |
| 16229 | + m->create_image("test.img", 1024 * 1024); |
| 16230 | + if (m->run("test -b /dev/disk/by-realmachine-label/fakedisk-0") != 0) { |
| 16231 | + printf_error(log_test, "test_image() failed"); |
| 16232 | + std::exit(1); |
| 16233 | + } |
| 16234 | +} |
| 16235 | + |
| 16236 | +static void test_512() { assert_sector_size(512); } |
| 16237 | + |
| 16238 | +static void test_4k() { assert_sector_size(4096); } |
| 16239 | + |
| 16240 | +static void test_tmpfs() { |
| 16241 | + if (Machine::in_machine()) { |
| 16242 | + assert_mount("/scratch", "tmpfs"); |
| 16243 | + return; |
| 16244 | + } |
| 16245 | + |
| 16246 | + auto m = create_machine(); |
| 16247 | + if (m->run_in_machine_with_args({"-test.run", "TestScratchTmp"}) != 0) { |
| 16248 | + printf_error(log_test, "test_tmpfs() failed"); |
| 16249 | + std::exit(1); |
| 16250 | + } |
| 16251 | +} |
| 16252 | + |
| 16253 | +static void test_disk() { |
| 16254 | + if (Machine::in_machine()) { |
| 16255 | + assert_mount("/scratch", "ext4"); |
| 16256 | + return; |
| 16257 | + } |
| 16258 | + |
| 16259 | + auto m = create_machine(); |
| 16260 | + m->set_scratch(1024 * 1024 * 1024, ""); |
| 16261 | + if (m->run_in_machine_with_args({"-test.run", "TestScratchDisk"}) != 0) { |
| 16262 | + printf_error(log_test, "test_disk() failed"); |
| 16263 | + std::exit(1); |
| 16264 | + } |
| 16265 | +} |
| 16266 | + |
| 16267 | +static void test_memory() { |
| 16268 | + auto m = create_machine(); |
| 16269 | + m->set_show_boot(true); |
| 16270 | + m->set_memory(1024); |
| 16271 | + |
| 16272 | + const std::string cmd = R"(MEM=$(grep MemTotal /proc/meminfo | awk '{print $2}') |
| 16273 | +if [ "$MEM" -lt 900000 ] || [ "$MEM" -gt 1024000 ]; then |
| 16274 | + echo "ERROR: unexpected MemTotal=$MEM" |
| 16275 | + echo 1 > /run/realmachine/result |
| 16276 | + exit 1 |
| 16277 | +else |
| 16278 | + echo "MemTotal OK=$MEM" |
| 16279 | + echo 0 > /run/realmachine/result |
| 16280 | + exit 0 |
| 16281 | +fi |
| 16282 | +)"; |
| 16283 | + |
| 16284 | + if (m->run(cmd) != 0) { |
| 16285 | + printf_error(log_test, "memory size test failed"); |
| 16286 | + std::exit(1); |
| 16287 | + } |
| 16288 | +} |
| 16289 | + |
| 16290 | +static void test_spawn() { |
| 16291 | + if (Machine::in_machine()) return; |
| 16292 | + |
| 16293 | + auto m = create_machine(); |
| 16294 | + if (m->run_in_machine_with_args({"-test.run", "TestSpawnMachine"}) != 0) |
| 16295 | + std::exit(1); |
| 16296 | +} |
| 16297 | + |
| 16298 | +static void test_label() { |
| 16299 | + if (Machine::in_machine()) return; |
| 16300 | + |
| 16301 | + const auto m = create_machine(); |
| 16302 | + const auto a = m->create_image("a.img", 1024 * 1024); |
| 16303 | + const auto b = m->create_image_with_label("b.img", 1024 * 1024, "label"); |
| 16304 | + |
| 16305 | + if (m->run_in_machine_with_args({"-test.run", "TestImageLabel", a, b}) != 0) { |
| 16306 | + printf_error(log_test, "test_label() failed"); |
| 16307 | + std::exit(1); |
| 16308 | + } |
| 16309 | +} |
| 16310 | + |
| 16311 | +static void test_volumes() { |
| 16312 | + realmachine_g::APP_THROW_ON_BAD_MOUNTS = false; |
| 16313 | + if (Machine::in_machine()) |
| 16314 | + return; |
| 16315 | + |
| 16316 | + { |
| 16317 | + auto m = create_machine(); |
| 16318 | + m->add_volume("missing_dir"); |
| 16319 | + if (m->run_in_machine_with_args({"-test.run", "TestVolumes"}) != -1) { |
| 16320 | + printf_error(log_test, "test_volumes() failed #1"); |
| 16321 | + std::exit(1); |
| 16322 | + } |
| 16323 | + } |
| 16324 | + |
| 16325 | + { |
| 16326 | + auto m = create_machine(); |
| 16327 | + m->add_volume("/dev/zero"); |
| 16328 | + if (m->run_in_machine_with_args({"-test.run", "TestVolumes"}) != -1) { |
| 16329 | + printf_error(log_test, "test_volumes() failed #2"); |
| 16330 | + std::exit(1); |
| 16331 | + } |
| 16332 | + } |
| 16333 | + |
| 16334 | + { |
| 16335 | + auto m = create_machine(); |
| 16336 | + m->add_volume_at("/dev", "/dev ices"); |
| 16337 | + if (m->run_in_machine_with_args({"-test.run", "TestVolumes"}) != -1) { |
| 16338 | + printf_error(log_test, "test_volumes() failed #3"); |
| 16339 | + std::exit(1); |
| 16340 | + } |
| 16341 | + } |
| 16342 | + realmachine_g::APP_THROW_ON_BAD_MOUNTS = true; |
| 16343 | +} |
| 16344 | + |
| 16345 | +static void test_escape() { |
| 16346 | + if (Machine::in_machine()) { |
| 16347 | + if (testArg != "$s'n\\akes") { |
| 16348 | + printf_error(log_test, "test_escape() failed #1"); |
| 16349 | + std::exit(1); |
| 16350 | + } |
| 16351 | + return; |
| 16352 | + } |
| 16353 | + |
| 16354 | + const auto m = create_machine(); |
| 16355 | + if (m->run_in_machine_with_args({ |
| 16356 | + "-test.run", "TestCommandEscaping", |
| 16357 | + "-testarg", "$s'n\\akes" |
| 16358 | + }) != 0) { |
| 16359 | + printf_error(log_test, "test_escape() failed #2"); |
| 16360 | + std::exit(1); |
| 16361 | + } |
| 16362 | +} |
| 16363 | + |
| 16364 | +static void test_decompressors() { |
| 16365 | + printf_info(log_test, "testing decompression"); |
| 16366 | + |
| 16367 | + auto compare_streams = [](std::istream& a, std::istream& b) { |
| 16368 | + int i = 0; |
| 16369 | + while (true) { |
| 16370 | + char ca, cb; |
| 16371 | + bool ae = !a.get(ca); |
| 16372 | + bool be = !b.get(cb); |
| 16373 | + |
| 16374 | + if (ae || be) { |
| 16375 | + if (ae && be) return; |
| 16376 | + printf_error(log_test, "decompressor mismatch EOF at byte %d", i); |
| 16377 | + std::exit(1); |
| 16378 | + } |
| 16379 | + |
| 16380 | + if (ca != cb) { |
| 16381 | + printf_error(log_test, "decompressor mismatch at byte %d (%d vs %d)", i, (unsigned char)ca, (unsigned char)cb); |
| 16382 | + std::exit(1); |
| 16383 | + } |
| 16384 | + |
| 16385 | + i++; |
| 16386 | + } |
| 16387 | + }; |
| 16388 | + |
| 16389 | + struct Case { |
| 16390 | + const char* suffix; |
| 16391 | + void (*fn)(std::ostream&, std::istream&); |
| 16392 | + }; |
| 16393 | + |
| 16394 | + Case cases[] = { |
| 16395 | + {"", decompressors::null_decompressor}, |
| 16396 | + {".gz", decompressors::gzip_decompressor}, |
| 16397 | + {".xz", decompressors::xz_decompressor}, |
| 16398 | + {".zst", decompressors::zstd_decompressor} |
| 16399 | + }; |
| 16400 | + |
| 16401 | + int ok = 0; |
| 16402 | + int total = sizeof(cases) / sizeof(cases[0]); |
| 16403 | + |
| 16404 | + for (auto& c : cases) { |
| 16405 | + std::string in_path = std::string("test") + c.suffix; |
| 16406 | + std::ifstream src(in_path, std::ios::binary); |
| 16407 | + |
| 16408 | + if (!src) { |
| 16409 | + printf_error(log_test, "failed to open input file: '%s' (expected working dir: tests/)", in_path.c_str()); |
| 16410 | + std::exit(1); |
| 16411 | + } |
| 16412 | + |
| 16413 | + std::ostringstream out(std::ios::binary); |
| 16414 | + c.fn(out, src); |
| 16415 | + |
| 16416 | + std::string ref_path = "test"; |
| 16417 | + std::ifstream ref(ref_path, std::ios::binary); |
| 16418 | + |
| 16419 | + if (!ref) { |
| 16420 | + printf_error(log_test, "failed to open reference file: '%s'", ref_path.c_str()); |
| 16421 | + std::exit(1); |
| 16422 | + } |
| 16423 | + |
| 16424 | + std::istringstream result(out.str(), std::ios::binary); |
| 16425 | + |
| 16426 | + compare_streams(result, ref); |
| 16427 | + |
| 16428 | + ok++; |
| 16429 | + printf_info(log_test, "decompressor OK: %s", in_path.c_str()); |
| 16430 | + } |
| 16431 | + |
| 16432 | + printf_info(log_test, "decompression tests finished: %d/%d passed", ok, total); |
| 16433 | +} |
| 16434 | + |
| 16435 | +int main(const int argc, char** argv) { |
| 16436 | + if (!Machine::in_machine()) { |
| 16437 | + test_decompressors(); |
| 16438 | + } |
| 16439 | + |
| 16440 | + std::string runFilter; |
| 16441 | + printf_info(log_test, "booting test runner"); |
| 16442 | + realmachine_g::current_log_level = realmachine_g::log_level::trace; |
| 16443 | + |
| 16444 | + for (int i = 1; i < argc; i++) { |
| 16445 | + std::string a = argv[i]; |
| 16446 | + printf_debug(log_test, "arg: %s", a.c_str()); |
| 16447 | + |
| 16448 | + if (a == "-backend" && i + 1 < argc) { |
| 16449 | + backendName = argv[++i]; |
| 16450 | + printf_info(log_test, "backend=%s", backendName.c_str()); |
| 16451 | + } else if (a == "-testarg" && i + 1 < argc) { |
| 16452 | + testArg = argv[++i]; |
| 16453 | + printf_info(log_test, "testarg set"); |
| 16454 | + } else if (a == "-test.run" && i + 1 < argc) { |
| 16455 | + runFilter = argv[++i]; |
| 16456 | + printf_info(log_test, "run filter=%s", runFilter.c_str()); |
| 16457 | + } |
| 16458 | + } |
| 16459 | + |
| 16460 | + struct T { const char* n; void (*f)(); }; |
| 16461 | + T tests[] = { |
| 16462 | + {"TestSuccessfulCommand", test_success}, |
| 16463 | + {"TestCommandNotFound", test_notfound}, |
| 16464 | + {"TestImage", test_image}, |
| 16465 | + {"TestImage512SectorSize", test_512}, |
| 16466 | + {"TestImage4kSectorSize", test_4k}, |
| 16467 | + {"TestScratchTmp", test_tmpfs}, |
| 16468 | + {"TestScratchDisk", test_disk}, |
| 16469 | + {"TestMemory", test_memory}, |
| 16470 | + {"TestSpawnMachine", test_spawn}, |
| 16471 | + {"TestImageLabel", test_label}, |
| 16472 | + {"TestVolumes", test_volumes}, |
| 16473 | + {"TestCommandEscaping", test_escape} |
| 16474 | + }; |
| 16475 | + |
| 16476 | + auto match = [&](const char* n) { |
| 16477 | + if (runFilter.empty()) return true; |
| 16478 | + return std::string(n) == runFilter; |
| 16479 | + }; |
| 16480 | + |
| 16481 | + int ran = 0; |
| 16482 | + printf_info(log_test, "starting tests"); |
| 16483 | + |
| 16484 | + for (auto&[n, f] : tests) { |
| 16485 | + if (match(n)) { |
| 16486 | + printf_debug(log_test, "running %s", n); |
| 16487 | + f(); |
| 16488 | + ran++; |
| 16489 | + printf_debug(log_test, "finished %s", n); |
| 16490 | + } |
| 16491 | + } |
| 16492 | + |
| 16493 | + if (!runFilter.empty() && ran == 0) { |
| 16494 | + printf_error(log_test, "no tests matched filter: %s", runFilter.c_str()); |
| 16495 | + std::exit(1); |
| 16496 | + } |
| 16497 | + |
| 16498 | + printf_info(log_test, "done, ran=%d", ran); |
| 16499 | + if (!Machine::in_machine()) printf_info(log_test, "okidoki"); |
| 16500 | + |
| 16501 | + return 0; |
| 16502 | +} |
| 16503 | diff --git a/tests/test.gz b/tests/test.gz |
| 16504 | new file mode 100644 |
| 16505 | index 0000000..91d59b5 |
| 16506 | Binary files /dev/null and b/tests/test.gz differ |
| 16507 | diff --git a/tests/test.xz b/tests/test.xz |
| 16508 | new file mode 100644 |
| 16509 | index 0000000..6a17b3a |
| 16510 | Binary files /dev/null and b/tests/test.xz differ |
| 16511 | diff --git a/tests/test.zst b/tests/test.zst |
| 16512 | new file mode 100644 |
| 16513 | index 0000000..5641b91 |
| 16514 | Binary files /dev/null and b/tests/test.zst differ |