SNode.C
Loading...
Searching...
No Matches
SubCommand.cpp
Go to the documentation of this file.
1/*
2 * SNode.C - A Slim Toolkit for Network Communication
3 * Copyright (C) Volker Christian <me@vchrist.at>
4 * 2020, 2021, 2022, 2023, 2024, 2025, 2026
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published
8 * by the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/*
21 * MIT License
22 *
23 * Permission is hereby granted, free of charge, to any person obtaining a copy
24 * of this software and associated documentation files (the "Software"), to deal
25 * in the Software without restriction, including without limitation the rights
26 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27 * copies of the Software, and to permit persons to whom the Software is
28 * furnished to do so, subject to the following conditions:
29 *
30 * The above copyright notice and this permission notice shall be included in
31 * all copies or substantial portions of the Software.
32 *
33 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39 * THE SOFTWARE.
40 */
41
42#include "SubCommand.h"
43
44#include "utils/Formatter.h"
45
46#ifndef DOXYGEN_SHOULD_SKIP_THIS
47
48#include <cstddef>
49#include <cstdint>
50#include <memory>
51#include <vector>
52
53#endif // DOXYGEN_SHOULD_SKIP_THIS
54
55namespace utils {
56
57 SubCommand::SubCommand(SubCommand* parent, std::shared_ptr<utils::AppWithPtr> appWithPtr, const std::string& group, bool final)
58 : subCommandApp(appWithPtr.get())
59 , name(subCommandApp != nullptr ? subCommandApp->get_name() : "<NOAPP>")
60 , parent(parent)
61 , final(final) {
62 if (appWithPtr != nullptr) {
63 if (parent != nullptr) {
64 parent->addSubCommandApp(appWithPtr);
65 } else {
66 selfAnchoredSubCommandApp = std::move(appWithPtr);
67 }
68
69 subCommandApp->group(group);
70 subCommandApp->configurable(false);
71 subCommandApp->fallthrough();
72
73 static const std::shared_ptr<CLI::HelpFormatter> helpFormatter = std::make_shared<CLI::HelpFormatter>();
74 subCommandApp->formatter(helpFormatter);
75
76 static const std::shared_ptr<CLI::Config> configFormatter = std::make_shared<CLI::ConfigFormatter>();
77 subCommandApp->config_formatter(configFormatter);
78
79 subCommandApp->option_defaults()->take_last()->group(subCommandApp->get_formatter()->get_label("Nonpersistent Options"));
80
82 ->set_help_flag(
83 "--help{exact},-h{exact}",
84 [subCommandApp = this->subCommandApp](std::size_t) {
85 helpTriggerApp = subCommandApp;
86 },
87 "Print help message and exit\n"
88 "* standard: display help for the last command processed\n"
89 "* exact: display help for the command directly preceding --help")
90 ->type_name("MODE")
91 ->take_first()
92 ->check(CLI::IsMember({"standard", "exact"}))
93 ->trigger_on_parse(),
94 false);
95
97 ->add_flag_function(
98 "-s,--show-config",
99 [subCommandApp = this->subCommandApp](std::size_t) {
100 showConfigTriggerApp = subCommandApp;
101 },
102 "Show current configuration and exit")
103 ->take_first()
104 ->disable_flag_override()
105 ->configurable(false)
106 ->trigger_on_parse(),
107 false);
108
110 ->add_flag_function(
111 "--command-line{standard}",
112 [subCommandApp = this->subCommandApp]([[maybe_unused]] std::int64_t count) {
113 commandlineTriggerApp = subCommandApp;
114 },
115 "Print command-line\n"
116 "* standard (default): Show all non-default and required options\n"
117 "* active: Show all active options\n"
118 "* complete: Show the complete option set with default values\n"
119 "* required: Show only required options")
120 ->type_name("MODE")
121 ->take_first()
122 ->check(CLI::IsMember({"standard", "active", "complete", "required"}))
123 ->trigger_on_parse(),
124 false);
125 }
126 }
127
131
132 std::string SubCommand::getName() const {
133 return name;
134 }
135
136 std::string SubCommand::version() const {
137 return subCommandApp->version();
138 }
139
140 void SubCommand::parse(int argc, char* argv[]) const {
141 subCommandApp->parse(argc, argv);
142 }
143
144 SubCommand* SubCommand::description(const std::string& description) {
145 subCommandApp->description(description);
146
147 return this;
148 }
149
150 SubCommand* SubCommand::footer(const std::string& footer) {
151 subCommandApp->footer(footer);
152
153 return this;
154 }
155
157 while (!childSubCommands.empty()) {
158 SubCommand* child = *childSubCommands.begin();
159 childSubCommands.erase(child);
160
161 delete child;
162 }
163
164 if (parent != nullptr) {
165 parent->childSubCommands.erase(this);
166
167 if (parent->subCommandApp != nullptr && subCommandApp != nullptr) {
168 parent->subCommandApp->remove_subcommand(subCommandApp);
169 }
170
171 parent = nullptr;
172 }
173 }
174
175 std::string SubCommand::configToStr() const {
176 return subCommandApp->config_to_str(true, true);
177 }
178
179 std::string SubCommand::help(const CLI::App* helpApp, const CLI::AppFormatMode& mode) const {
180 return subCommandApp->help(helpApp, "", mode);
181 }
182
183 bool SubCommand::hasParent() const {
184 return subCommandApp->get_parent() != nullptr;
185 }
186
188 return parent;
189 }
190
192 subCommandApp->allow_extras(allow);
193
194 return this;
195 }
196
198 const bool previouslyRequired = subCommandApp->get_required();
199 const bool countedRequired = subCommandApp->get_ignore_case();
200
201 requiredForced = unrequired;
202
203 const bool effectiveRequired = requiredForced ? false : countedRequired;
204 if (previouslyRequired != effectiveRequired) {
205 subCommandApp->required(effectiveRequired);
206
207 if (hasParent()) {
208 SubCommand* parent = getParent();
209 parent->needs(this, effectiveRequired);
210 parent->required(effectiveRequired);
211 }
212 }
213
214 return this;
215 }
216
217 SubCommand* SubCommand::required(bool required) {
218 const bool previousEffectiveRequired = subCommandApp->get_required();
219
220 requiredCount += required ? 1 : -1;
221 const bool countedRequired = requiredCount > 0;
222 const bool effectiveRequired = requiredForced ? false : countedRequired;
223
224 subCommandApp->ignore_case(countedRequired);
225 if (subCommandApp->get_required() != effectiveRequired) {
226 subCommandApp->required(effectiveRequired);
227 }
228
229 if (hasParent() && previousEffectiveRequired != effectiveRequired) {
230 SubCommand* parent = getParent();
231
232 parent->needs(this, effectiveRequired);
233 parent->required(effectiveRequired);
234 }
235
236 return this;
237 }
238
239 SubCommand* SubCommand::required(CLI::Option* option, bool required) {
240 if (option->get_required() != required) {
241 option->required(required);
242
243 if (required) {
244 subCommandApp->needs(option);
245 option->default_str("");
246 } else {
247 subCommandApp->remove_needs(option);
248 }
249
250 this->required(required);
251 }
252
253 return this;
254 }
255
256 bool SubCommand::getRequired() const {
257 return subCommandApp->get_required();
258 }
259
260 SubCommand* SubCommand::needs(SubCommand* subCommand, bool needs) {
261 if (needs) {
262 subCommandApp->needs(subCommand->subCommandApp);
263 } else {
264 subCommandApp->remove_needs(subCommand->subCommandApp);
265 }
266
267 return this;
268 }
269
270 SubCommand* SubCommand::setRequireCallback(const std::function<void()>& callback) {
271 subCommandApp->require_callback(callback);
272
273 return this;
274 }
275
276 SubCommand* SubCommand::finalCallback(const std::function<void()>& finalCallback) {
277 subCommandApp->final_callback(finalCallback);
278
279 return this;
280 }
281
282 void SubCommand::addSubCommandApp(std::shared_ptr<AppWithPtr> subCommand) {
283 if (subCommandApp->get_subcommands({}).empty()) {
284 helpOpt->description("Print help message and exit\n"
285 "* standard: display help for the last command processed\n"
286 "* exact: display help for the command directly preceding --help\n"
287 "* expanded: display help including all descendant sections");
288 if (auto* existing = helpOpt->get_validator(); existing != nullptr) {
289 *existing = std::move(CLI::IsMember({"standard", "exact", "expanded"})); // replace, do not append
290 }
291 }
292
293 subCommandApp->add_subcommand(subCommand);
294 }
295
296 CLI::Option* SubCommand::addConfigFlag(const std::string& defaultConfigFile) const {
297 return subCommandApp
298 ->set_config( //
299 "-c,--config-file",
300 defaultConfigFile,
301 "Read a config file",
302 false) //
303 ->take_all()
304 ->type_name("configfile")
305 ->check(!CLI::ExistingDirectory);
306 }
307
308 CLI::Option* utils::SubCommand::addLogFileFlag(const std::string& defaultLogFile) const {
309 return addOption("-l,--log-file", "Log file", "logFile", defaultLogFile, !CLI::ExistingDirectory);
310 }
311
312 CLI::Option* SubCommand::addVersionFlag(const std::string& version) const {
313 return subCommandApp->set_version_flag("-v,--version", version, "Framework version");
314 }
315
316 CLI::Option* SubCommand::addOption(const std::string& name,
317 const std::string& description,
318 const std::string& typeName,
319 const CLI::Validator& validator) const {
321 ->add_option(name, description),
322 typeName,
323 validator,
324 !subCommandApp->get_disabled());
325 }
326
327 CLI::Option* SubCommand::addOptionFunction(const std::string& name,
328 const std::function<void(const std::string&)>& callback,
329 const std::string& description,
330 const std::string& typeName,
331 const CLI::Validator& validator) const {
333 ->add_option_function(name, callback, description),
334 typeName,
335 validator,
336 !subCommandApp->get_disabled());
337 }
338
339 CLI::Option* SubCommand::addFlag(const std::string& name,
340 const std::string& description,
341 const std::string& typeName,
342 const CLI::Validator& validator) const {
344 ->add_flag(name, description),
345 typeName,
346 validator,
347 !subCommandApp->get_disabled());
348 }
349
350 CLI::Option* SubCommand::addFlagFunction(const std::string& name,
351 const std::function<void()>& callback,
352 const std::string& description,
353 const std::string& typeName,
354 const CLI::Validator& validator) const {
355 CLI::Option* opt = subCommandApp //
356 ->add_flag_function(
357 name,
358 [callback](std::int64_t) {
359 callback();
360 },
361 description)
362 ->type_name(typeName)
363 ->check(validator)
364 ->take_last();
365 if (opt->get_configurable()) {
366 opt->group(subCommandApp->get_formatter()->get_label("Persistent Options"));
367 }
368
369 return opt;
370 }
371
372 CLI::Option* SubCommand::addFlagFunction(const std::string& name,
373 const std::function<void()>& callback,
374 const std::string& description,
375 const std::string& typeName,
376 const std::string& defaultValue,
377 const CLI::Validator& validator) const {
378 return setDefaultValue(addFlagFunction(name, callback, description, typeName, validator), defaultValue);
379 }
380
381 CLI::Option* SubCommand::getOption(const std::string& name) const {
382 return subCommandApp->get_option_no_throw(name);
383 }
384
385 CLI::Option* SubCommand::setConfigurable(CLI::Option* option, bool configurable) const {
386 return option //
387 ->configurable(configurable)
388 ->group(subCommandApp->get_formatter()->get_label(configurable ? "Persistent Options" : "Nonpersistent Options"));
389 }
390
392 return helpTriggerApp;
393 }
394
398
402
403 CLI::Option*
404 SubCommand::initialize(CLI::Option* option, const std::string& typeName, const CLI::Validator& validator, bool configurable) const {
405 return setConfigurable(option, configurable) //
406 ->type_name(typeName)
407 ->check(validator);
408 }
409
410 AppWithPtr::AppWithPtr(const std::string& description, const std::string& name, SubCommand* t)
411 : CLI::App(description, name)
412 , ptr(t) {
413 }
414
417
418 const SubCommand* AppWithPtr::getPtr() const {
419 return ptr;
420 }
421
423 return ptr;
424 }
425
426 std::map<std::string, std::string> SubCommand::aliases;
427
428 CLI::App* SubCommand::helpTriggerApp = nullptr;
429 CLI::App* SubCommand::showConfigTriggerApp = nullptr;
430 CLI::App* SubCommand::commandlineTriggerApp = nullptr;
431
432} // namespace utils
const SubCommand * getPtr() const
SubCommand * getPtr()
~AppWithPtr() override
AppWithPtr(const std::string &description, const std::string &name, SubCommand *t)
SubCommand * ptr
Definition SubCommand.h:94
CLI::Option * addFlagFunction(const std::string &name, const std::function< void()> &callback, const std::string &description, const std::string &typeName, const CLI::Validator &validator) const
std::string version() const
SubCommand * required(bool required=true)
SubCommand * description(const std::string &description)
std::shared_ptr< AppWithPtr > selfAnchoredSubCommandApp
Definition SubCommand.h:258
SubCommand * forceUnrequired(bool unrequired=true)
SubCommand * allowExtras(bool allow=true)
CLI::Option * addFlagFunction(const std::string &name, const std::function< void()> &callback, const std::string &description, const std::string &typeName, const std::string &defaultValue, const CLI::Validator &validator) const
bool getRequired() const
SubCommand * footer(const std::string &footer)
void parse(int argc, char *argv[]) const
static CLI::App * showConfigTriggerApp
Definition SubCommand.h:242
CLI::Option * addOption(const std::string &name, const std::string &description, const std::string &typeName, ValueTypeT defaultValue, const CLI::Validator &validator) const
Definition SubCommand.h:312
SubCommand * required(CLI::Option *option, bool required=true)
bool hasParent() const
SubCommand(SubCommand *parent, std::shared_ptr< utils::AppWithPtr > appWithPtr, const std::string &group, bool final=false)
CLI::Option * addLogFileFlag(const std::string &defaultLogFile) const
CLI::Option * setDefaultValue(CLI::Option *option, const ValueTypeT &value, bool clear=true) const
Definition SubCommand.h:360
std::set< SubCommand * > childSubCommands
Definition SubCommand.h:262
CLI::Option * addVersionFlag(const std::string &version) const
CLI::Option * commandlineOpt
Definition SubCommand.h:255
CLI::Option * helpOpt
Definition SubCommand.h:253
SubCommand * finalCallback(const std::function< void()> &finalCallback)
AppWithPtr * subCommandApp
Definition SubCommand.h:248
virtual ~SubCommand()
SubCommand * parent
Definition SubCommand.h:250
static CLI::App * helpTriggerApp
Definition SubCommand.h:241
std::string name
Definition SubCommand.h:249
CLI::Option * addOption(const std::string &name, const std::string &description, const std::string &typeName, const CLI::Validator &validator) const
CLI::Option * getOption(const std::string &name) const
static CLI::App * getHelpTriggerApp()
static CLI::App * getCommandlineTriggerApp()
CLI::Option * showConfigOpt
Definition SubCommand.h:254
static std::map< std::string, std::string > aliases
Definition SubCommand.h:239
std::string help(const CLI::App *helpApp, const CLI::AppFormatMode &mode) const
SubCommand * needs(SubCommand *subCommand, bool needs=true)
std::string configToStr() const
static CLI::App * commandlineTriggerApp
Definition SubCommand.h:243
void addSubCommandApp(std::shared_ptr< utils::AppWithPtr > subCommand)
CLI::Option * addOptionFunction(const std::string &name, const std::function< void(const std::string &)> &callback, const std::string &description, const std::string &typeName, const CLI::Validator &validator) const
SubCommand * getParent() const
static CLI::App * getShowConfigTriggerApp()
CLI::Option * setConfigurable(CLI::Option *option, bool configurable) const
std::string getName() const
CLI::Option * addFlag(const std::string &name, const std::string &description, const std::string &typeName, const CLI::Validator &validator) const
CLI::Option * initialize(CLI::Option *option, const std::string &typeName, const CLI::Validator &validator, bool configurable) const
CLI::Option * addConfigFlag(const std::string &defaultConfigFile) const
SubCommand * setRequireCallback(const std::function< void(void)> &callback)