SNode.C
Loading...
Searching...
No Matches
Config.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
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#include "utils/Config.h"
21
22#include "utils/Daemon.h"
23#include "utils/Exceptions.h"
24#include "utils/Formatter.h"
25
26#ifndef DOXYGEN_SHOULD_SKIP_THIS
27
28#include "log/Logger.h"
29
30#include <cerrno>
31#include <cstdint>
32#include <cstdlib>
33#include <cstring>
34#include <fcntl.h>
35#include <filesystem>
36#include <fstream>
37#include <functional>
38#include <grp.h>
39#include <iostream>
40#include <memory>
41#include <pwd.h>
42#include <sstream>
43#include <sys/types.h>
44#include <unistd.h>
45#include <vector>
46
47#ifdef __GNUC__
48#pragma GCC diagnostic push
49#pragma GCC diagnostic ignored "-Wfloat-equal"
50#ifdef __has_warning
51#if __has_warning("-Wweak-vtables")
52#pragma GCC diagnostic ignored "-Wweak-vtables"
53#endif
54#if __has_warning("-Wcovered-switch-default")
55#pragma GCC diagnostic ignored "-Wcovered-switch-default"
56#endif
57#endif
58#endif
59#include "utils/CLI11.hpp"
60#ifdef __GNUC__
61#pragma GCC diagnostic pop
62#endif
63
64#endif /* DOXYGEN_SHOULD_SKIP_THIS */
65
66#define XSTR(s) STR(s)
67#define STR(s) #s
68
69namespace utils {
70
71 static const std::shared_ptr<CLI::App> makeApp() { // NO_LINT
72 const std::shared_ptr<CLI::App> app = std::make_shared<CLI::App>();
73
74 app->configurable(false);
75 app->allow_extras();
76 app->allow_config_extras();
77
78 const std::shared_ptr<CLI::HelpFormatter> helpFormatter = std::make_shared<CLI::HelpFormatter>();
79
80 helpFormatter->label("SUBCOMMAND", "INSTANCE");
81 helpFormatter->label("SUBCOMMANDS", "INSTANCES");
82 helpFormatter->label("PERSISTENT", "");
83 helpFormatter->label("Persistent Options", "Options (persistent)");
84 helpFormatter->label("Nonpersistent Options", "Options (nonpersistent)");
85 helpFormatter->label("Usage", "\nUsage");
86 helpFormatter->label("bool:{true,false}", "{true,false}");
87 helpFormatter->label(":{standard,required,full,default}", "{standard,required,full,default}");
88 helpFormatter->label(":{standard,expanded}", "{standard,expanded}");
89 helpFormatter->column_width(7);
90
91 app->formatter(helpFormatter);
92
93 app->config_formatter(std::make_shared<CLI::ConfigFormatter>());
94 app->get_config_formatter_base()->arrayDelimiter(' ');
95
96 app->option_defaults()->take_last();
97 app->option_defaults()->group(app->get_formatter()->get_label("Nonpersistent Options"));
98
100
101 return app;
102 }
103
104 std::shared_ptr<CLI::App> Config::app = makeApp();
105
106 bool Config::init(int argc, char* argv[]) {
107 bool proceed = true;
108
109 Config::argc = argc;
110 Config::argv = argv;
111
112 applicationName = std::filesystem::path(argv[0]).filename();
113
114 uid_t euid = 0;
115 struct passwd* pw = nullptr;
116 struct group* gr = nullptr;
117
118 if ((pw = getpwuid(getuid())) == nullptr) {
119 proceed = false;
120 } else if ((gr = getgrgid(pw->pw_gid)) == nullptr) {
121 proceed = false;
122 } else if ((euid = geteuid()) == 0) {
123 configDirectory = "/etc/snode.c";
124 logDirectory = "/var/log/snode.c";
125 pidDirectory = "/var/run/snode.c";
126 } else {
127 const char* homedir = nullptr;
128 if ((homedir = std::getenv("XDG_CONFIG_HOME")) == nullptr) {
129 if ((homedir = std::getenv("HOME")) == nullptr) {
130 homedir = pw->pw_dir;
131 }
132 }
133
134 if (homedir != nullptr) {
135 configDirectory = std::string(homedir) + "/.config/snode.c";
136 logDirectory = std::string(homedir) + "/.local/log/snode.c";
137 pidDirectory = std::string(homedir) + "/.local/run/snode.c";
138 } else {
139 proceed = false;
140 }
141 }
142
143 if (proceed && !std::filesystem::exists(configDirectory)) {
144 if (std::filesystem::create_directories(configDirectory)) {
145 std::filesystem::permissions(
146 configDirectory,
147 (std::filesystem::perms::owner_all | std::filesystem::perms::group_read | std::filesystem::perms::group_exec) &
148 ~std::filesystem::perms::others_all);
149 if (geteuid() == 0) {
150 struct group* gr = nullptr;
151 if ((gr = getgrnam(XSTR(GROUP_NAME))) != nullptr) {
152 if (chown(configDirectory.c_str(), euid, gr->gr_gid) < 0) {
153 std::cout << "Warning: Can not set group ownership of '" << configDirectory
154 << "' to 'snodec':" << strerror(errno) << std::endl;
155 }
156 } else {
157 std::cout << "Error: Can not find group 'snodec'. Add it using groupadd or addgroup" << std::endl;
158 std::cout << " and add the current user to this group." << std::endl;
159 std::filesystem::remove(configDirectory);
160 proceed = false;
161 }
162 }
163 } else {
164 std::cout << "Error: Can not create directory '" << configDirectory << "'" << std::endl;
165 proceed = false;
166 }
167 }
168
169 if (proceed && !std::filesystem::exists(logDirectory)) {
170 if (std::filesystem::create_directories(logDirectory)) {
171 std::filesystem::permissions(logDirectory,
172 (std::filesystem::perms::owner_all | std::filesystem::perms::group_all) &
173 ~std::filesystem::perms::others_all);
174 if (geteuid() == 0) {
175 struct group* gr = nullptr;
176 if ((gr = getgrnam(XSTR(GROUP_NAME))) != nullptr) {
177 if (chown(logDirectory.c_str(), euid, gr->gr_gid) < 0) {
178 std::cout << "Warning: Can not set group ownership of '" << logDirectory << "' to 'snodec':" << strerror(errno)
179 << std::endl;
180 }
181 } else {
182 std::cout << "Error: Can not find group 'snodec'. Add it using groupadd or addgroup" << std::endl;
183 std::cout << " and add the current user to this group." << std::endl;
184 std::filesystem::remove(configDirectory);
185 proceed = false;
186 }
187 }
188 } else {
189 std::cout << "Error: Can not create directory '" << logDirectory << "'" << std::endl;
190 proceed = false;
191 }
192 }
193
194 if (proceed && !std::filesystem::exists(pidDirectory)) {
195 if (std::filesystem::create_directories(pidDirectory)) {
196 std::filesystem::permissions(pidDirectory,
197 (std::filesystem::perms::owner_all | std::filesystem::perms::group_all) &
198 ~std::filesystem::perms::others_all);
199 if (geteuid() == 0) {
200 struct group* gr = nullptr;
201 if ((gr = getgrnam(XSTR(GROUP_NAME))) != nullptr) {
202 if (chown(pidDirectory.c_str(), euid, gr->gr_gid) < 0) {
203 std::cout << "Warning: Can not set group ownership of '" << pidDirectory << "' to 'snodec':" << strerror(errno)
204 << std::endl;
205 }
206 } else {
207 std::cout << "Error: Can not find group 'snodec'. Add it using groupadd or addgroup." << std::endl;
208 std::cout << " and add the current user to this group." << std::endl;
209 std::filesystem::remove(configDirectory);
210 proceed = false;
211 }
212 }
213 } else {
214 std::cout << "Error: Can not create directory '" << pidDirectory << "'" << std::endl;
215 proceed = false;
216 }
217 }
218
219 if (proceed) {
220 app->description("Configuration for Application '" + applicationName + "'");
221
222 app->footer("Application '" + applicationName +
223 "' powered by SNode.C\n"
224 "(C) 2020-2025 Volker Christian <me@vchrist.at>\n"
225 "https://github.com/SNodeC/snode.c");
226
227 app->set_config( //
228 "-c,--config-file",
229 configDirectory + "/" + applicationName + ".conf",
230 "Read a config file",
231 false) //
232 ->take_all()
233 ->type_name("configfile")
234 ->check(!CLI::ExistingDirectory);
235
236 app->add_option_function<std::string>(
237 "-w,--write-config",
238 []([[maybe_unused]] const std::string& configFile) {
239 throw CLI::CallForWriteConfig(configFile);
240 },
241 "Write config file and exit")
242 ->configurable(false)
243 ->default_val(configDirectory + "/" + applicationName + ".conf")
244 ->type_name("[configfile]")
245 ->check(!CLI::ExistingDirectory)
246 ->expected(0, 1);
247
248 app->add_flag( //
249 "-k,--kill",
250 "Kill running daemon") //
251 ->configurable(false)
252 ->disable_flag_override();
253
254 app->add_option( //
255 "-i,--instance-alias",
256 "Make an instance also known as an alias in configuration files")
257 ->configurable(false)
258 ->type_name("instance=instance_alias [instance=instance_alias [...]]")
259 ->each([](const std::string& item) {
260 const auto it = item.find('=');
261 if (it != std::string::npos) {
262 aliases[item.substr(0, it)] = item.substr(it + 1);
263 } else {
264 throw CLI::ConversionError("Can not convert '" + item + "' to a 'instance=instance_alias' pair");
265 }
266 });
267
268 addStandardFlags(app.get());
269
270 logLevelOpt = app->add_option( //
271 "-l,--log-level",
272 "Log level") //
273 ->default_val(4)
274 ->type_name("level")
275 ->check(CLI::Range(0, 6))
276 ->group(app->get_formatter()->get_label("Persistent Options"));
277
278 verboseLevelOpt = app->add_option( //
279 "-v,--verbose-level",
280 "Verbose level") //
281 ->default_val(2)
282 ->type_name("level")
283 ->check(CLI::Range(0, 10))
284 ->group(app->get_formatter()->get_label("Persistent Options"));
285
286 quietOpt = app->add_flag( //
287 "-q{true},!-u,--quiet{true}",
288 "Quiet mode") //
289 ->default_val("false")
290 ->type_name("bool")
291 ->check(CLI::IsMember({"true", "false"}))
292 ->group(app->get_formatter()->get_label("Persistent Options"));
293
294 logFileOpt = app->add_option( //
295 "--log-file",
296 "Log file path") //
297 ->default_val(logDirectory + "/" + applicationName + ".log")
298 ->type_name("logfile")
299 ->check(!CLI::ExistingDirectory)
300 ->group(app->get_formatter()->get_label("Persistent Options"));
301
302 enforceLogFileOpt = app->add_flag( //
303 "-e{true},!-n,--enforce-log-file{true}",
304 "Enforce writing of logs to file for foreground applications") //
305 ->default_val("false")
306 ->type_name("bool")
307 ->check(CLI::IsMember({"true", "false"}))
308 ->group(app->get_formatter()->get_label("Persistent Options"));
309
310 daemonizeOpt = app->add_flag( //
311 "-d{true},!-f,--daemonize{true}",
312 "Start application as daemon") //
313 ->default_val("false")
314 ->type_name("bool")
315 ->check(CLI::IsMember({"true", "false"}))
316 ->group(app->get_formatter()->get_label("Persistent Options"));
317
318 userNameOpt = app->add_option( //
319 "--user-name",
320 "Run daemon under specific user permissions") //
321 ->default_val(pw->pw_name)
322 ->type_name("username")
323 ->needs(daemonizeOpt)
324 ->group(app->get_formatter()->get_label("Persistent Options"));
325
326 groupNameOpt = app->add_option( //
327 "--group-name",
328 "Run daemon under specific group permissions")
329 ->default_val(gr->gr_name)
330 ->type_name("groupname")
331 ->needs(daemonizeOpt)
332 ->group(app->get_formatter()->get_label("Persistent Options"));
333
334 proceed = parse1(); // for stopDaemon and pre init application options
335
336 app->set_version_flag("--version", "1.0-rc1", "Framework version");
337 addHelp(app.get());
338 }
339
340 return proceed;
341 }
342
344 aliases.clear();
345
346 app->final_callback([]() {
347 if (daemonizeOpt->as<bool>() && (*app)["--show-config"]->count() == 0 && (*app)["--write-config"]->count() == 0 &&
348 (*app)["--command-line"]->count() == 0) {
349 std::cout << "Running as daemon (double fork)" << std::endl;
350
351 utils::Daemon::startDaemon(
352 pidDirectory + "/" + applicationName + ".pid", userNameOpt->as<std::string>(), groupNameOpt->as<std::string>());
353
354 logger::Logger::setQuiet();
355
356 const std::string logFile = logFileOpt->as<std::string>();
357 if (!logFile.empty()) {
358 logger::Logger::logToFile(logFile);
359 }
360 } else if ((*app)["--enforce-log-file"]->as<bool>()) {
361 const std::string logFile = logFileOpt->as<std::string>();
362 if (!logFile.empty()) {
363 std::cout << "Writing logs to file " << logFile << std::endl;
364
365 logger::Logger::logToFile(logFile);
366 }
367 }
368 });
369
370 return parse2();
371 }
372
373 bool Config::parse1() {
374 bool proceed = true;
375
376 try {
377 app->parse(argc, argv);
378 } catch (const CLI::ParseError&) {
379 // Do not process ParseError here but on second parse pass
380 }
381
382 if ((*app)["--kill"]->count() > 0) {
383 try {
384 const pid_t daemonPid = utils::Daemon::stopDaemon(pidDirectory + "/" + applicationName + ".pid");
385 std::cout << "Daemon terminated: Pid = " << daemonPid << std::endl;
386 } catch (const DaemonError& e) {
387 std::cout << "DaemonError: " << e.what() << std::endl;
388 } catch (const DaemonFailure& e) {
389 std::cout << "DaemonFailure: " << e.what() << std::endl;
390 }
391
392 proceed = false;
393 } else {
394 if ((*app)["--show-config"]->count() == 0 && (*app)["--write-config"]->count() == 0 && (*app)["--command-line"]->count() == 0) {
395 app->allow_extras(false);
396 }
397
398 if (!quietOpt->as<bool>()) {
399 logger::Logger::setLogLevel(logLevelOpt->as<int>());
400 logger::Logger::setVerboseLevel(verboseLevelOpt->as<int>());
401 }
402
403 logger::Logger::setQuiet(quietOpt->as<bool>());
404 }
405
406 return proceed;
407 }
408
409 static void createCommandLineOptions(std::stringstream& out, CLI::App* app, CLI::CallForCommandline::Mode mode) {
410 CLI::Option* disabledOpt = app->get_option_no_throw("--disabled");
411 const bool disabled = disabledOpt != nullptr ? disabledOpt->as<bool>() : false;
412 if (!disabled || mode == CLI::CallForCommandline::Mode::DEFAULT) {
413 for (const CLI::Option* option : app->get_options()) {
414 if (option->get_configurable()) {
415 std::string value;
416
417 switch (mode) {
418 case CLI::CallForCommandline::Mode::STANDARD:
419 if (option->count() > 0) {
420 value = option->as<std::string>();
421 } else if (option->get_required()) {
422 value = "<REQUIRED>";
423 }
424 break;
425 case CLI::CallForCommandline::Mode::REQUIRED:
426 if (option->get_required()) {
427 if (option->count() > 0) {
428 value = option->as<std::string>();
429 } else {
430 value = "<REQUIRED>";
431 }
432 }
433 break;
434 case CLI::CallForCommandline::Mode::FULL:
435 if (option->count() > 0) {
436 value = option->as<std::string>();
437 } else if (!option->get_default_str().empty()) {
438 value = option->get_default_str();
439 } else if (!option->get_required()) {
440 value = "\"\"";
441 } else {
442 value = "<REQUIRED>";
443 }
444 break;
445 case CLI::CallForCommandline::Mode::DEFAULT: {
446 if (!option->get_default_str().empty()) {
447 value = option->get_default_str();
448 } else if (!option->get_required()) {
449 value = "\"\"";
450 } else {
451 value = "<REQUIRED>";
452 }
453 break;
454 }
455 }
456
457 if (!value.empty()) {
458 out << "--" << option->get_single_name() << ((option->get_items_expected_max() == 0) ? "=" : " ") << value << " ";
459 }
460 }
461 }
462 } else if (disabledOpt->get_default_str() == "false") {
463 out << "--disabled=true ";
464 }
465 }
466
468 std::stringstream out;
469
470 createCommandLineOptions(out, app, mode);
471
472 std::string optionString = out.str();
473 if (optionString.back() == ' ') {
474 optionString.pop_back();
475 }
476
477 return optionString;
478 }
479
480 static void createCommandLineTemplate(std::stringstream& out, CLI::App* app, CLI::CallForCommandline::Mode mode);
481
483 std::stringstream out;
484
485 CLI::Option* disabledOpt = app->get_option_no_throw("--disabled");
486 if (disabledOpt == nullptr || !disabledOpt->as<bool>() || mode == CLI::CallForCommandline::Mode::DEFAULT) {
487 for (CLI::App* subcommand : app->get_subcommands({})) {
488 if (!subcommand->get_name().empty()) {
489 createCommandLineTemplate(out, subcommand, mode);
490 }
491 }
492 }
493
494 return out.str();
495 }
496
497 static void createCommandLineTemplate(std::stringstream& out, CLI::App* app, CLI::CallForCommandline::Mode mode) {
498 std::string outString;
499
500 outString = createCommandLineOptions(app, mode);
501 if (!outString.empty()) {
502 outString += " ";
503 }
504
505 outString += createCommandLineSubcommands(app, mode);
506
507 if (!outString.empty()) {
508 out << app->get_name() << " " << outString;
509 }
510 }
511
513 std::stringstream out;
514
515 createCommandLineTemplate(out, app, mode);
516
517 std::string outString = out.str();
518 while (app->get_parent() != nullptr) {
519 app = app->get_parent();
520 std::string parentOptions = createCommandLineOptions(app, mode);
521 outString =
522 std::string(app->get_name()).append(" ").append(!parentOptions.empty() ? parentOptions.append(" ") : "").append(outString);
523 }
524
525 if (outString.empty()) {
526 outString = Config::getApplicationName();
527 }
528
529 return outString;
530 }
531
532 bool Config::parse2() {
533 bool success = false;
534
535 try {
536 try {
537 app->parse(argc, argv);
538 success = true;
539 } catch (const DaemonError& e) {
540 std::cout << "Daemon error: " << e.what() << " ... exiting" << std::endl;
541 } catch (const DaemonFailure& e) {
542 std::cout << "Daemon failure: " << e.what() << " ... exiting" << std::endl;
543 } catch (const DaemonSignaled& e) {
544 std::cout << "Pid: " << getpid() << ", child pid: " << e.getPid() << ": " << e.what() << std::endl;
545 } catch (const CLI::CallForHelp&) {
546 std::cout << app->help() << std::endl;
547 } catch (const CLI::CallForAllHelp&) {
548 std::cout << app->help("", CLI::AppFormatMode::All) << std::endl;
549 } catch (const CLI::CallForVersion&) {
550 std::cout << app->version() << std::endl << std::endl;
551 } catch (const CLI::CallForCommandline& e) {
552 std::cout << e.what() << std::endl;
553 std::cout << std::endl
554 << Color::Code::FG_GREEN << "command@line" << Color::Code::FG_DEFAULT << ":" << Color::Code::FG_BLUE << "~/> "
555 << Color::Code::FG_DEFAULT << createCommandLineTemplate(e.getApp(), e.getMode()) << std::endl
556 << std::endl;
557 } catch (const CLI::CallForShowConfig& e) {
558 try {
559 std::cout << e.getApp()->config_to_str(true, true);
560 } catch (const CLI::ParseError& e1) {
561 std::cout << "Error showing config file: " << e.getApp() << " " << e1.get_name() << " " << e1.what() << std::endl;
562 throw;
563 }
564 } catch (const CLI::CallForWriteConfig& e) {
565 std::cout << e.what() << std::endl;
566 std::ofstream confFile(e.getConfigFile());
567 if (confFile.is_open()) {
568 try {
569 confFile << app->config_to_str(true, true);
570 confFile.close();
571 } catch (const CLI::ParseError& e1) {
572 confFile.close();
573 std::cout << "Error writing config file: " << e1.get_name() << " " << e1.what() << std::endl;
574 throw;
575 }
576 } else {
577 std::cout << "Error writing config file: " << std::strerror(errno) << std::endl;
578 }
579 } catch (const CLI::ConversionError& e) {
580 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what() << std::endl;
581 throw;
582 } catch (const CLI::ArgumentMismatch& e) {
583 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what() << std::endl;
584 throw;
585 } catch (const CLI::ConfigError& e) {
586 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << e.what() << std::endl;
587 std::cout << " Adding '-w' on the command line may solve this problem" << std::endl;
588 throw;
589 } catch (const CLI::ParseError& e) {
590 const std::string what = e.what();
591 if (what.find("[Option Group: ") != std::string::npos) { // If CLI11 throws that error it means for us there are
592 // unconfigured anonymous instances
593 std::cout << Color::Code::FG_RED << "[BootstrapError]" << Color::Code::FG_DEFAULT
594 << " Anonymous instance(s) not configured in source code " << std::endl;
595 } else {
596 std::cout << "[" << Color::Code::FG_RED << e.get_name() << Color::Code::FG_DEFAULT << "] " << what << std::endl;
597 }
598 throw;
599 }
600 } catch (const CLI::ParseError&) {
601 std::cout << std::endl << "Append -h or --help to your command line for more information." << std::endl;
602 } catch (const CLI::Error& e) {
603 std::cout << "Error: " << e.get_name() << " " << e.what() << std::endl;
604 std::cout << "Append -h or --help to your command line for more information." << std::endl;
605 }
606
607 if (!success) {
609 }
610
611 return success;
612 }
613
615 if ((*app)["--daemonize"]->as<bool>()) {
616 std::ifstream pidFile(pidDirectory + "/" + applicationName + ".pid", std::ifstream::in);
617
618 if (pidFile.good()) {
619 pid_t pid = 0;
620 pidFile >> pid;
621
622 if (getpid() == pid) {
623 Daemon::erasePidFile(pidDirectory + "/" + applicationName + ".pid");
624 }
625 }
626 } else if (fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) >= 0) {
627 char buf[1024];
628 while (read(STDIN_FILENO, buf, 1024) > 0) {
629 }
630 }
631 }
632
633 static const std::shared_ptr<CLI::HelpFormatter> makeSectionFormatter() {
634 const std::shared_ptr<CLI::HelpFormatter> sectionFormatter = std::make_shared<CLI::HelpFormatter>();
635
636 sectionFormatter->label("SUBCOMMAND", "SECTION");
637 sectionFormatter->label("SUBCOMMANDS", "SECTIONS");
638 sectionFormatter->label("PERSISTENT", "");
639 sectionFormatter->label("Persistent Options", "Options (persistent)");
640 sectionFormatter->label("Nonpersistent Options", "Options (nonpersistent)");
641 sectionFormatter->label("Usage", "\nUsage");
642 sectionFormatter->label("bool:{true,false}", "{true,false}");
643 sectionFormatter->label(":{standard,required,full,default}", "{standard,required,full,default}");
644 sectionFormatter->label(":{standard,expanded}", "{standard,expanded}");
645 sectionFormatter->column_width(7);
646
647 return sectionFormatter;
648 }
649
650 std::shared_ptr<CLI::Formatter> Config::sectionFormatter = makeSectionFormatter();
651
652 CLI::App* Config::addInstance(const std::string& name, const std::string& description, const std::string& group) {
653 CLI::App* instance = app->add_subcommand(name, description) //
654 ->group(group)
655 ->fallthrough()
656 ->formatter(sectionFormatter)
657 ->configurable(false)
658 ->allow_extras(false)
659 ->disabled(name.empty());
660
661 instance //
662 ->option_defaults()
663 ->configurable(!instance->get_disabled());
664
665 if (!instance->get_disabled()) {
666 if (aliases.contains(name)) {
667 instance //
668 ->alias(aliases[name]);
669 }
670 }
671
672 return instance;
673 }
674
675 CLI::App* Config::addStandardFlags(CLI::App* app) {
676 app //
677 ->add_flag_callback(
678 "-s,--show-config",
679 [app]() {
680 throw CLI::CallForShowConfig(app);
681 },
682 "Show current configuration and exit") //
683 ->configurable(false)
684 ->disable_flag_override();
685
686 app //
687 ->add_flag(
688 "--command-line{standard}",
689 [app]([[maybe_unused]] std::int64_t count) {
690 const std::string& result = app->get_option("--command-line")->as<std::string>();
691 if (result == "standard") {
692 throw CLI::CallForCommandline( //
693 app,
694 "Below is a command line viewing all non-default and required options:\n"
695 "* Options show their configured value\n"
696 "* Required but not yet configured options show <REQUIRED> as value\n"
697 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap",
699 }
700 if (result == "required") {
701 throw CLI::CallForCommandline( //
702 app,
703 "Below is a command line viewing required options only:\n"
704 "* Options show either their configured or default value\n"
705 "* Required but not yet configured options show <REQUIRED> as value\n"
706 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap",
708 }
709 if (result == "full") {
710 throw CLI::CallForCommandline( //
711 app,
712 "Below is a command line viewing the full set of options with their default or configured values:\n"
713 "* Options show either their configured or default value\n"
714 "* Required but not yet configured options show <REQUIRED> as value\n"
715 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap",
717 }
718 if (result == "default") {
719 throw CLI::CallForCommandline( //
720 app,
721 "Below is a command line viewing the full set of options with their default values\n"
722 "* Options show their default value\n"
723 "* Required but not yet configured options show <REQUIRED> as value\n"
724 "* Options marked as <REQUIRED> need to be configured for a successful bootstrap",
726 }
727 },
728 "Print a command line\n"
729 " standard (default): View all non-default and required options\n"
730 " required: View required options only\n"
731 " full: View the full set of options with their default or configured values\n"
732 " default: View the full set of options with their default values")
733 ->configurable(false)
734 ->check(CLI::IsMember({"standard", "required", "full", "default"}));
735
736 return app;
737 }
738
739 CLI::App* Config::addHelp(CLI::App* app) {
740 app //
741 ->set_help_flag();
742
743 app //
744 ->add_flag(
745 "-h{standard},--help{standard}",
746 [app]([[maybe_unused]] std::int64_t count) {
747 const std::size_t disabledCount =
748 app->get_subcommands([](CLI::App* app) -> bool {
749 return app->get_group() == "Instance" && app->get_option("--disabled")->as<bool>();
750 })
751 .size();
752 const std::size_t enabledCount =
753 app->get_subcommands([](CLI::App* app) -> bool {
754 return app->get_group() == "Instance" && !app->get_option("--disabled")->as<bool>();
755 })
756 .size();
757
758 for (auto* instance : app->get_subcommands({})) {
759 const std::string& group = instance->get_group();
760 if (group == "Instance") {
761 if (instance->get_option("--disabled")->as<bool>()) {
762 instance->group(std::string("Instance").append((disabledCount > 1) ? "s" : "").append(" (disabled)"));
763 } else {
764 instance->group(std::string("Instance").append((enabledCount > 1) ? "s" : ""));
765 }
766 }
767 }
768
769 const std::string& result = app->get_option("--help")->as<std::string>();
770
771 if (result == "standard") {
772 throw CLI::CallForHelp();
773 }
774 if (result == "expanded") {
775 throw CLI::CallForAllHelp();
776 }
777 },
778 "Print help message")
779 ->configurable(false)
780 ->check(CLI::IsMember({"standard", "expanded"}))
781 ->trigger_on_parse();
782
783 return app;
784 }
785
786 CLI::App* Config::addSimpleHelp(CLI::App* app) {
787 app //
788 ->set_help_flag();
789
790 app //
791 ->add_flag(
792 "-h,--help",
793 []([[maybe_unused]] std::int64_t count) {
794 throw CLI::CallForHelp();
795 },
796 "Print help message")
797 ->configurable(false)
798 ->disable_flag_override()
799 ->trigger_on_parse();
800
801 return app;
802 }
803
804 void Config::required(CLI::App* instance, bool reqired) {
805 if (reqired) {
806 app->needs(instance);
807
808 for (const auto& sub : instance->get_subcommands([](const CLI::App* sc) -> bool {
809 return sc->get_required();
810 })) {
811 instance->needs(sub);
812 }
813 } else {
814 app->remove_needs(instance);
815
816 for (const auto& sub : instance->get_subcommands([](const CLI::App* sc) -> bool {
817 return sc->get_required();
818 })) {
819 instance->remove_needs(sub);
820 }
821 }
822
823 instance->required(reqired);
824 }
825
826 void Config::disabled(CLI::App* instance, bool disabled) {
827 if (disabled) {
828 app->remove_needs(instance);
829
830 for (const auto& sub : instance->get_subcommands([](const CLI::App* sc) -> bool {
831 return !sc->get_disabled();
832 })) {
833 sub->disabled();
834
835 if (sub->get_required()) {
836 instance->remove_needs(sub);
837 }
838 }
839 } else {
840 app->needs(instance);
841
842 for (const auto& sub : instance->get_subcommands([](const CLI::App* sc) -> bool {
843 return sc->get_disabled();
844 })) {
845 sub->disabled(false);
846
847 if (sub->get_required()) {
848 instance->needs(sub);
849 }
850 }
851 }
852
853 instance->required(!disabled);
854 }
855
856 bool Config::removeInstance(CLI::App* instance) {
857 Config::required(instance, false);
858
859 return app->remove_subcommand(instance);
860 }
861
862 CLI::Option* Config::addStringOption(const std::string& name, const std::string& description, const std::string& typeName) {
863 applicationOptions[name] = app //
864 ->add_option(name, description)
865 ->type_name(typeName)
866 ->configurable()
867 ->required()
868 ->group("Application Options");
869
870 app->needs(applicationOptions[name]);
871
872 return applicationOptions[name];
873 }
874
875 CLI::Option*
876 Config::addStringOption(const std::string& name, const std::string& description, const std::string& typeName, bool configurable) {
877 addStringOption(name, description, typeName);
878 return applicationOptions[name] //
879 ->configurable(configurable);
880 }
881
882 CLI::Option* Config::addStringOption(const std::string& name,
883 const std::string& description,
884 const std::string& typeName,
885 const std::string& defaultValue) {
886 addStringOption(name, description, typeName);
887
888 applicationOptions[name] //
889 ->required(false)
890 ->default_val(defaultValue);
891
892 app->remove_needs(applicationOptions[name]);
893
894 return applicationOptions[name];
895 }
896
897 CLI::Option* Config::addStringOption(const std::string& name,
898 const std::string& description,
899 const std::string& typeName,
900 const std::string& defaultValue,
901 bool configurable) {
902 addStringOption(name, description, typeName, defaultValue);
903 return applicationOptions[name] //
904 ->configurable(configurable);
905 }
906
907 CLI::Option* Config::addStringOption(const std::string& name,
908 const std::string& description,
909 const std::string& typeName,
910 const char* defaultValue) {
911 return addStringOption(name, description, typeName, std::string(defaultValue));
912 }
913
914 CLI::Option* Config::addStringOption(
915 const std::string& name, const std::string& description, const std::string& typeName, const char* defaultValue, bool configurable) {
916 return addStringOption(name, description, typeName, std::string(defaultValue), configurable);
917 }
918
919 std::string Config::getStringOptionValue(const std::string& name) {
920 if (app->get_option(name) == nullptr) {
921 throw CLI::OptionNotFound(name);
922 }
923
924 return (*app)[name]->as<std::string>();
925 }
926
927 void Config::addFlag(const std::string& name,
928 bool& variable,
929 const std::string& description,
930 bool required,
931 bool configurable,
932 const std::string& groupName) {
933 app->add_flag(name, variable, description) //
934 ->required(required) //
935 ->configurable(configurable) //
936 ->group(groupName);
937 }
938
939 std::string Config::getApplicationName() {
940 return applicationName;
941 }
942
944 return logLevelOpt->as<int>();
945 }
946
948 return verboseLevelOpt->as<int>();
949 }
950
951 int Config::argc = 0;
952 char** Config::argv = nullptr;
953
955
959
960 CLI::Option* Config::daemonizeOpt = nullptr;
961 CLI::Option* Config::logFileOpt = nullptr;
962 CLI::Option* Config::userNameOpt = nullptr;
963 CLI::Option* Config::groupNameOpt = nullptr;
964 CLI::Option* Config::enforceLogFileOpt = nullptr;
965 CLI::Option* Config::logLevelOpt = nullptr;
966 CLI::Option* Config::verboseLevelOpt = nullptr;
967 CLI::Option* Config::quietOpt = nullptr;
968
971
972} // namespace utils
#define XSTR(s)
CLI::App * getApp() const
CallForShowConfig(CLI::App *app)
static void init()
Definition Logger.cpp:32
static void setQuiet(bool quiet=true)
Definition Logger.cpp:114
static bool parse1()
Definition Config.cpp:373
static char ** argv
Definition Config.h:107
static CLI::Option * logLevelOpt
Definition Config.h:120
static std::map< std::string, CLI::Option * > applicationOptions
Definition Config.h:125
static int getVerboseLevel()
Definition Config.cpp:947
static bool parse2()
Definition Config.cpp:532
static std::string configDirectory
Definition Config.h:111
static std::shared_ptr< CLI::App > app
Definition Config.h:103
static CLI::Option * groupNameOpt
Definition Config.h:118
static CLI::Option * verboseLevelOpt
Definition Config.h:121
static CLI::Option * quietOpt
Definition Config.h:122
static bool init(int argc, char *argv[])
Definition Config.cpp:106
static int argc
Definition Config.h:106
static std::string pidDirectory
Definition Config.h:113
static std::shared_ptr< CLI::Formatter > sectionFormatter
Definition Config.h:104
static std::map< std::string, std::string > aliases
Definition Config.h:124
static void required(CLI::App *instance, bool reqired=true)
Definition Config.cpp:804
static CLI::Option * logFileOpt
Definition Config.h:116
static CLI::Option * enforceLogFileOpt
Definition Config.h:119
static bool bootstrap()
Definition Config.cpp:343
static int getLogLevel()
Definition Config.cpp:943
static std::string applicationName
Definition Config.h:109
static std::string logDirectory
Definition Config.h:112
static void terminate()
Definition Config.cpp:614
static CLI::Option * userNameOpt
Definition Config.h:117
static CLI::Option * daemonizeOpt
Definition Config.h:115
#define STR(a)
Definition clients.h:26
Definition Config.h:37
static const std::shared_ptr< CLI::App > makeApp()
Definition Config.cpp:71
static std::string createCommandLineTemplate(CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:512
static const std::shared_ptr< CLI::HelpFormatter > makeSectionFormatter()
Definition Config.cpp:633
static void createCommandLineTemplate(std::stringstream &out, CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:497
static void createCommandLineOptions(std::stringstream &out, CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:409
static std::string createCommandLineOptions(CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:467
static std::string createCommandLineSubcommands(CLI::App *app, CLI::CallForCommandline::Mode mode)
Definition Config.cpp:482