src / ferdios / librealmachine

Open in Gitea

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
1commit d8ecbb5048dbe290d7a5c4c9b6d07a70a8f92a04
2Author: Sander <sander@sanderf.nl>
3Date: Fri Sep 4 17:28:29 2026 +0300
4
5 Initial commit
6
7diff --git a/.clang-format b/.clang-format
8new file mode 100644
9index 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+...
68diff --git a/.gitignore b/.gitignore
69new file mode 100644
70index 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
82diff --git a/CMakeLists.txt b/CMakeLists.txt
83new file mode 100644
84index 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()
249diff --git a/LICENSE b/LICENSE
250new file mode 100644
251index 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.
456diff --git a/README.md b/README.md
457new file mode 100644
458index 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
579diff --git a/cmake/config-realmachine.h.cmake b/cmake/config-realmachine.h.cmake
580new file mode 100644
581index 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
599diff --git a/cmake/realmachineConfig.cmake.in b/cmake/realmachineConfig.cmake.in
600new file mode 100644
601index 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
619diff --git a/main.cpp b/main.cpp
620new file mode 100644
621index 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
875diff --git a/src/realmachine/backend.cpp b/src/realmachine/backend.cpp
876new file mode 100644
877index 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
1378diff --git a/src/realmachine/backend.h b/src/realmachine/backend.h
1379new file mode 100644
1380index 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
1496diff --git a/src/realmachine/decompressors.cpp b/src/realmachine/decompressors.cpp
1497new file mode 100644
1498index 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
1638diff --git a/src/realmachine/decompressors.h b/src/realmachine/decompressors.h
1639new file mode 100644
1640index 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
1656diff --git a/src/realmachine/globals.h b/src/realmachine/globals.h
1657new file mode 100644
1658index 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
1686diff --git a/src/realmachine/lib/cli11/CLI11.h b/src/realmachine/lib/cli11/CLI11.h
1687new file mode 100644
1688index 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 &ltrim(std::string &str);
2266+
2267+/// Trim anything from left of string
2268+CLI11_INLINE std::string &ltrim(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 &ltrim(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 &ltrim(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 &current, 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 &current, 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 &current, 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 &current, 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 &current, 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 &current, 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 &sectionRef() { return configSection; }
5535+ /// get the section
5536+ CLI11_NODISCARD const std::string &section() const { return configSection; }
5537+ /// specify a particular section of the configuration file to use
5538+ ConfigBase *section(const std::string &sectionName) {
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 &current, 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 &current,
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 &current, 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 &current,
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 &section, 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 &currentSection, 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 &section, 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 &currentSection, 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
13908diff --git a/src/realmachine/lib/cli11/LICENSE b/src/realmachine/lib/cli11/LICENSE
13909new file mode 100644
13910index 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.
13939diff --git a/src/realmachine/lib/cli11/cli11_wrapper.cpp b/src/realmachine/lib/cli11/cli11_wrapper.cpp
13940new file mode 100644
13941index 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
13947diff --git a/src/realmachine/lib/cli11/cli11_wrapper.h b/src/realmachine/lib/cli11/cli11_wrapper.h
13948new file mode 100644
13949index 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
13956diff --git a/src/realmachine/lib/utils.cpp b/src/realmachine/lib/utils.cpp
13957new file mode 100644
13958index 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+}
14280diff --git a/src/realmachine/lib/utils.h b/src/realmachine/lib/utils.h
14281new file mode 100644
14282index 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+}
14346diff --git a/src/realmachine/machine.cpp b/src/realmachine/machine.cpp
14347new file mode 100644
14348index 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+}
15534diff --git a/src/realmachine/machine.h b/src/realmachine/machine.h
15535new file mode 100644
15536index 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+}
15634diff --git a/src/realmachine/models.cpp b/src/realmachine/models.cpp
15635new file mode 100644
15636index 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
15663diff --git a/src/realmachine/models.h b/src/realmachine/models.h
15664new file mode 100644
15665index 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
15714diff --git a/src/realmachine/module_cache.cpp b/src/realmachine/module_cache.cpp
15715new file mode 100644
15716index 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+}
15883diff --git a/src/realmachine/module_cache.h b/src/realmachine/module_cache.h
15884new file mode 100644
15885index 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+}
15921diff --git a/src/realmachine/template.cpp b/src/realmachine/template.cpp
15922new file mode 100644
15923index 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
16060diff --git a/src/realmachine/template.h b/src/realmachine/template.h
16061new file mode 100644
16062index 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
16083diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
16084new file mode 100644
16085index 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
16101diff --git a/tests/test b/tests/test
16102new file mode 100644
16103index 0000000..dd33a6b
16104Binary files /dev/null and b/tests/test differ
16105diff --git a/tests/test.cpp b/tests/test.cpp
16106new file mode 100644
16107index 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+}
16503diff --git a/tests/test.gz b/tests/test.gz
16504new file mode 100644
16505index 0000000..91d59b5
16506Binary files /dev/null and b/tests/test.gz differ
16507diff --git a/tests/test.xz b/tests/test.xz
16508new file mode 100644
16509index 0000000..6a17b3a
16510Binary files /dev/null and b/tests/test.xz differ
16511diff --git a/tests/test.zst b/tests/test.zst
16512new file mode 100644
16513index 0000000..5641b91
16514Binary files /dev/null and b/tests/test.zst differ