goptions
Loading...
Searching...
No Matches
goptions.cc
Go to the documentation of this file.
1
13
14// goptions
15#include "goptions.h"
16#include "goptionsConventions.h"
17#include "gversion.h"
18
19// gemc
20#include "gutilities.h"
21#include <gemc/glogging/gmessageCatalog.h>
22
23// c++
24#include <algorithm>
25#include <iostream>
26#include <cstring>
27#include <cctype>
28#include <cstdlib>
29
30using namespace std;
31
32namespace {
33// Parse a human-friendly boolean token used for switches (-switch=value and YAML switch values).
34// Accepts, case-insensitively: true/1/yes/y/on and false/0/no/n/off. Returns false if @p raw is
35// not a recognized token, leaving @p out untouched.
36bool parse_bool_token(const std::string& raw, bool& out) {
37 std::string v = raw;
38 std::transform(v.begin(), v.end(), v.begin(),
39 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
40 if (v == "true" || v == "1" || v == "yes" || v == "y" || v == "on") { out = true; return true; }
41 if (v == "false" || v == "0" || v == "no" || v == "n" || v == "off") { out = false; return true; }
42 return false;
43}
44
45}
46
47// See goptions.h for full constructor API documentation.
48/*
49 * Parsing precedence implemented here:
50 * 1. YAML file(s), applied in argv order
51 * 2. Command-line tokens override YAML values
52 *
53 * Notes:
54 * - "help <option>" is treated as an immediate action and exits after printing.
55 * - Dot-notation routes structured updates to the owning option via GOption::set_sub_option_value().
56 */
57GOptions::GOptions(int argc, char* argv[], const GOptions& user_defined_options) {
58 executableName = gutilities::getFileFromPath(argv[0]);
59 executableCallingDir = gutilities::getDirFromPath(argv[0]);
60 installDir = gutilities::executable_path().parent_path().string();
61 cout << endl;
62
63 // Add user-defined options.
64 addGOptions(user_defined_options);
65
66 // switches for all everyone
67 defineSwitch("gui", "run with the graphical user interface (Qt window)");
68 defineSwitch("i", "drop into the interactive Geant4 terminal session (non-GUI mode)");
69 defineOption(GVariable("language", "auto",
70 "language for GEMC logging messages, startup banner, and GUI text"),
71 "Supported values:\n"
72 " auto: detect your language automatically (default).\n"
73 " en: English (original English messages).\n"
74 " it: italiano (Italian).\n"
75 " ja: 日本語 (Japanese).\n"
76 " fr: français (French).\n"
77 " zh: 中文 (Chinese).\n\n"
78 "Automatic selection checks GEMC_LANGUAGE, LC_ALL, LC_MESSAGES, LANG, then system settings.\n"
79 "Use -language=<code> or YAML language: <code> to override automatic selection.\n"
80 "Regional tags such as it_IT.UTF-8 are accepted; missing regional entries use the base language.\n"
81 "Missing languages or messages fall back to English. C and POSIX locales select English.\n"
82 "Numeric formatting is unchanged.\n");
84 GVariable("conf_yaml", "saved_configuration", "infix for the YAML file that records the resolved options"),
85 "On exit the resolved configuration is written to <executable>.<conf_yaml>.yaml,\n"
86 "so the default value produces, for example, gemc.saved_configuration.yaml.\n \n"
87 "Example: -conf_yaml=run12 -> saves to gemc.run12.yaml\n \n");
88
89 // add test timeout for the tests
90 defineOption(GVariable("tt", 500, "GUI test timeout (ms)"),
91 "Milliseconds a GUI-based test waits before it auto-closes, so the module\n"
92 "example/test programs can run unattended in CI.\n \n"
93 "Example: -tt=1000\n \n");
94
95 // version is a special option, not settable by the user
96 // it is set by the gversion.h file
97 // we add it here so it can be saved to the yaml file
98 vector<GVariable> version = {
99 {"release", gversion, "release version number"},
100 {"release_date", grelease_date, "release date"},
101 {"Reference", greference, "article reference"},
102 {"Homepage", gweb, "homepage"},
103 {"Author", gauthor, "author"}
104 };
105 defineOption(goptions::GVERSION_STRING, "version information", version,
106 "Version information. Not settable by user.");
107
108 // verbosity option: convention used across modules consuming verbosity levels
109 string help = "Levels: \n \n";
110 help += " - 0: (default) = shush\n";
111 help += " - 1: log detailed information\n";
112 help += " - 2: log extra detailed information\n \n";
113 help += "Each key names a class or module; run 'help verbosity' to list the available keys.\n \n";
114 help += "Example (one key): -verbosity.gemc=1\n";
115 help += "Example (several keys): -verbosity=\"[{gemc: 1}, {<another_key>: 2}]\"\n \n";
116 help += "Equivalent YAML:\n";
117 help += " verbosity:\n";
118 help += " - gemc: 1\n";
119 help += " - <another_key>: 2\n \n";
120 defineOption("verbosity", "Sets the log verbosity for various classes", option_verbosity_names, help);
121
122 // debug option: boolean or integer, depending on consumer expectations
123 help = "Debug information Types: \n \n";
124 help += " - false: (default): do not print debug information\n";
125 help += " - true: print debug information\n\n";
126 help += "Each key names a class or module; run 'help debug' to list the available keys.\n \n";
127 help += "Example (on/off): -debug.gemc=true\n";
128 help += "Example (several keys): -debug=\"[{gemc: true}, {<another_key>: 1}]\"\n \n";
129 help += "Equivalent YAML:\n";
130 help += " debug:\n";
131 help += " - gemc: true\n \n";
132 defineOption("debug", "Sets the debug level for various classes", option_verbosity_names, help);
133
134 // Locate YAML input files up front so they can be excluded from command-line validation.
135 yaml_files = findYamls(argc, argv);
136
137 // Validate every command-line token before acting on any early-exit flag (-v, help, ...).
138 // This ensures a typo such as --language=fr is reported instead of being silently ignored
139 // when it accompanies a flag that would otherwise print and exit immediately.
140 validateCommandLineArguments(argc, argv, yaml_files);
141
142 // Process help/version command-line arguments.
143 // These are handled early and exit immediately (they do not proceed to parse YAML/options).
144 for (int i = 1; i < argc; i++) {
145 if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--h") == 0 ||
146 strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0) {
147 printHelp();
148 }
149 else if (strcmp(argv[i], "-hweb") == 0) {
150 printWebHelp();
151 }
152 else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--v") == 0 ||
153 strcmp(argv[i], "-version") == 0 || strcmp(argv[i], "--version") == 0) {
154 // Version remains an early action, but its language override may appear anywhere in argv.
155 for (int argument = 1; argument < argc; ++argument) {
156 string candidate = argv[argument];
157 if (candidate.starts_with("--language=")) candidate.erase(0, 1); // --language= -> -language=
158 if (candidate.starts_with("-language=")) {
159 string value = candidate.substr(string("-language=").size());
160 if (value.size() >= 2 && value.front() == '"' && value.back() == '"') {
161 value = value.substr(1, value.size() - 2);
162 }
163 setOptionValuesFromCommandLineArgument("language", value);
164 }
165 }
166 print_version();
167 exit(EXIT_SUCCESS);
168 }
169 else if (strcmp(argv[i], "help") == 0) {
170 // "gemc help <topic>" shows topic help; bare "gemc help" shows the general help index.
171 if (i + 1 < argc) { printOptionOrSwitchHelp(argv[i + 1]); }
172 else { printHelp(); }
173 exit(EXIT_SUCCESS);
174 }
175 else if (strcmp(argv[i], "search") == 0) {
176 // "gemc search <value>" lists options/switches whose name or description contains <value>.
177 if (i + 1 < argc) { printSearch(argv[i + 1]); }
178 else { printHelp(); }
179 exit(EXIT_SUCCESS);
180 }
181 }
182
183 // Parse the yaml files located above.
184 // YAML file tokens are treated as inputs, not as "invalid command-line arguments".
185 for (auto& yaml_file : yaml_files) {
186 cout << " Parsing " << yaml_file << endl;
187 setOptionsValuesFromYamlFile(yaml_file);
188 }
189
190 // Parse command-line arguments (supports both standard YAML–style and dot–notation).
191 for (int i = 1; i < argc; i++) {
192 string candidate = argv[i];
193 if (candidate.empty()) continue;
194
195 // Skip YAML file tokens: they were already handled above.
196 if (find(yaml_files.begin(), yaml_files.end(), candidate) != yaml_files.end()) continue;
197
198 if (candidate[0] == '-') {
199 // Accept a long form (--name) by normalizing it to the canonical single-dash form,
200 // so both -language=fr and --language=fr behave identically and error messages do
201 // not carry a spurious leading dash.
202 string argStr = candidate.substr(1);
203 if (!argStr.empty() && argStr[0] == '-') argStr = argStr.substr(1);
204 size_t eqPos = argStr.find('=');
205
206 if (eqPos != string::npos) {
207 string keyPart = argStr.substr(0, eqPos);
208 string valuePart = argStr.substr(eqPos + 1);
209
210 // Switch with an explicit boolean value: -gui=false / -gui=true. This gives the
211 // CLI a way to turn a switch off, honoring the documented CLI-over-YAML precedence.
212 if (switches.contains(keyPart)) {
213 bool on = false;
214 if (parse_bool_token(valuePart, on)) { on ? switches[keyPart].turnOn() : switches[keyPart].turnOff(); }
215 else {
216 cerr << "The switch " << keyPart << " accepts only true/false/yes/no/on/off/1/0." << endl;
218 }
219 continue;
220 }
221
222 // Strip outer quotes if present (e.g., -gstreamer="[...]")
223 if (!valuePart.empty() && valuePart.front() == '"' && valuePart.back() == '"') {
224 valuePart = valuePart.substr(1, valuePart.length() - 2);
225 }
226
227 // Dot-notation targets a subkey in a structured option (e.g., verbosity.gemc).
228 size_t dotPos = keyPart.find('.');
229 if (dotPos != string::npos) {
230 string mainOption = keyPart.substr(0, dotPos);
231 string subOption = keyPart.substr(dotPos + 1);
232
233 if (doesOptionExist(mainOption)) {
234 auto it = getOptionIterator(mainOption);
235 it->set_sub_option_value(subOption, valuePart);
236 }
237 else { reportUnknownNameAndExit("option", mainOption); }
238 }
239 else {
240 // Standard option syntax: -name=value
241 if (doesOptionExist(keyPart)) {
242 setOptionValuesFromCommandLineArgument(keyPart, valuePart);
243 }
244 else { reportUnknownNameAndExit("option", keyPart); }
245 }
246 }
247 else {
248 // Treat as a switch: -gui, -i, etc.
249 const string& possibleSwitch = argStr;
250 if (switches.contains(possibleSwitch)) {
251 switches[possibleSwitch].turnOn();
252 }
253 else { reportUnknownNameAndExit("switch", possibleSwitch); }
254 }
255 }
256 else {
257 cerr << "The command-line argument \"" << candidate << "\" is not valid." << endl;
259 }
260 }
261
262 // Always print version information.
263 print_version();
264
265 // Save the final configuration to a YAML file.
266 string yamlConf_filename = executableName + "." + getRequiredScalarString("conf_yaml") + ".yaml";
267 cout << " Saving options to " << yamlConf_filename << endl << endl;
268 yamlConf = new std::ofstream(yamlConf_filename);
269 saveOptions();
270}
271
272// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
273void GOptions::defineSwitch(const std::string& name, const std::string& description, bool default_status) {
274 if (!switches.contains(name)) {
275 switches[name] = GSwitch(description, default_status);
276 }
277 else {
278 std::cerr << guts::FATALERRORL << "The " << guts::YELLOWHHL << name << guts::RSTHHR
279 << " switch is already present." << std::endl;
281 }
282}
283
284// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
285void GOptions::defineOption(const GVariable& gvar, const std::string& help) {
286 if (doesOptionExist(gvar.name)) {
287 std::cerr << guts::FATALERRORL << "The " << guts::YELLOWHHL << gvar.name << guts::RSTHHR
288 << " option is already present." << std::endl;
290 }
291 else {
292 goptions.emplace_back(gvar, help);
293 }
294}
295
296// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
297void GOptions::defineOption(const std::string& name, const std::string& description,
298 const std::vector<GVariable>& gvars,
299 const std::string& help) {
300 if (doesOptionExist(name)) {
301 std::cerr << guts::FATALERRORL << "The " << guts::YELLOWHHL << name << guts::RSTHHR
302 << " option is already present." << std::endl;
304 }
305 else {
306 goptions.emplace_back(name, description, gvars, help);
307 }
308}
309
310// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
311int GOptions::getRequiredScalarInt(const std::string& tag) const {
312 auto it = getOptionIterator(tag);
313 if (it == goptions.end()) {
314 cerr << guts::FATALERRORL << "The option " << guts::YELLOWHHL << tag << guts::RSTHHR
315 << " was not found." << endl;
317 }
318 const YAML::Node node = it->value.begin()->second;
319 if (!node.IsDefined() || node.IsNull()) {
320 cerr << guts::FATALERRORL << "The required option " << guts::YELLOWHHL << tag << guts::RSTHHR
321 << " was not provided." << endl;
323 }
324 return node.as<int>();
325}
326
327std::optional<int> GOptions::getOptionalScalarInt(const std::string& tag) const {
328 auto it = getOptionIterator(tag);
329 if (it == goptions.end()) {
330 cerr << guts::FATALERRORL << "The option " << guts::YELLOWHHL << tag << guts::RSTHHR
331 << " was not found." << endl;
333 }
334 const YAML::Node node = it->value.begin()->second;
335 if (!node.IsDefined() || node.IsNull()) return std::nullopt;
336 return node.as<int>();
337}
338
339// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
340double GOptions::getRequiredScalarDouble(const std::string& tag) const {
341 auto it = getOptionIterator(tag);
342 if (it == goptions.end()) {
343 cerr << guts::FATALERRORL << "The option " << guts::YELLOWHHL << tag << guts::RSTHHR
344 << " was not found." << endl;
346 }
347 const YAML::Node node = it->value.begin()->second;
348 if (!node.IsDefined() || node.IsNull()) {
349 cerr << guts::FATALERRORL << "The required option " << guts::YELLOWHHL << tag << guts::RSTHHR
350 << " was not provided." << endl;
352 }
353 return node.as<double>();
354}
355
356std::optional<double> GOptions::getOptionalScalarDouble(const std::string& tag) const {
357 auto it = getOptionIterator(tag);
358 if (it == goptions.end()) {
359 cerr << guts::FATALERRORL << "The option " << guts::YELLOWHHL << tag << guts::RSTHHR
360 << " was not found." << endl;
362 }
363 const YAML::Node node = it->value.begin()->second;
364 if (!node.IsDefined() || node.IsNull()) return std::nullopt;
365 return node.as<double>();
366}
367
368std::string GOptions::getRequiredScalarString(const std::string& tag) const {
369 auto it = getOptionIterator(tag);
370 if (it == goptions.end()) {
371 cerr << guts::FATALERRORL << "The option " << guts::YELLOWHHL << tag << guts::RSTHHR
372 << " was not found." << endl;
374 }
375 const YAML::Node node = it->value.begin()->second;
376 if (!node.IsDefined() || node.IsNull()) {
377 cerr << guts::FATALERRORL << "The required option " << guts::YELLOWHHL << tag << guts::RSTHHR
378 << " was not provided." << endl;
380 }
381 return node.as<std::string>();
382}
383
384// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
385std::optional<std::string> GOptions::getOptionalScalarString(const std::string& tag) const {
386 auto it = getOptionIterator(tag);
387 if (it == goptions.end()) {
388 std::cerr << guts::FATALERRORL << "The option " << guts::YELLOWHHL << tag << guts::RSTHHR
389 << " was not found." << std::endl;
391 }
392 const YAML::Node node = it->value.begin()->second;
393 if (!node.IsDefined() || node.IsNull()) return std::nullopt;
394 return node.as<std::string>();
395}
396
397
398// Private method: see header. Kept undocumented here to avoid duplicate param docs.
399void GOptions::reportUnknownNameAndExit(const std::string& kind, const std::string& name) const {
400 // Build the pool of known names (options + switches) to search for a close match.
401 vector<string> known;
402 known.reserve(goptions.size() + switches.size());
403 for (const auto& option : goptions) {
404 if (option.name != goptions::GVERSION_STRING) known.push_back(option.name);
405 }
406 for (const auto& s : switches) known.push_back(s.first);
407
408 cerr << guts::FATALERRORL << "The " << kind << " " << guts::YELLOWHHL << name << guts::RSTHHR
409 << " is not known to this system." << endl;
410
411 if (auto suggestion = gutilities::closestMatch(name, known)) {
412 cerr << guts::TPOINTITEM << "Did you mean " << guts::KGRN << "-" << *suggestion << guts::RST << "?" << endl;
413 }
414 cerr << guts::TPOINTITEM << "Run " << guts::KGRN << executableName << " -h" << guts::RST
415 << " for the list of options and switches." << endl;
417}
418
419// Private method: see header. Kept undocumented here to avoid duplicate param docs.
420void GOptions::validateCommandLineArguments(int argc, char* argv[], const vector<string>& yamls) const {
421 // Tokens handled by the early help/version loop; they are legal on their own.
422 static const vector<string> meta = {
423 "-h", "--h", "-help", "--help", "-hweb", "-v", "--v", "-version", "--version"
424 };
425
426 for (int i = 1; i < argc; i++) {
427 string candidate = argv[i];
428 if (candidate.empty()) continue;
429
430 // "help"/"search" consume the following token as their argument (topic / search text).
431 if (candidate == "help" || candidate == "search") {
432 if (i + 1 < argc) ++i;
433 continue;
434 }
435 if (find(meta.begin(), meta.end(), candidate) != meta.end()) continue;
436 if (find(yamls.begin(), yamls.end(), candidate) != yamls.end()) continue;
437
438 if (candidate[0] == '-') {
439 // Normalize a leading long form (--name -> -name) before extracting the key.
440 string argStr = candidate.substr(1);
441 if (!argStr.empty() && argStr[0] == '-') argStr = argStr.substr(1);
442
443 const size_t eqPos = argStr.find('=');
444 const bool hasValue = eqPos != string::npos;
445 string keyPart = hasValue ? argStr.substr(0, eqPos) : argStr;
446
447 // Dot-notation (verbosity.gemc) is validated against the owning option.
448 const size_t dotPos = keyPart.find('.');
449 if (dotPos != string::npos) keyPart = keyPart.substr(0, dotPos);
450
451 if (switches.contains(keyPart) || doesOptionExist(keyPart)) continue;
452 reportUnknownNameAndExit(hasValue ? "option" : "switch", keyPart);
453 }
454 else {
455 cerr << guts::FATALERRORL << "The command-line argument " << guts::YELLOWHHL << candidate
456 << guts::RSTHHR << " is not valid." << endl;
458 }
459 }
460}
461
462// Private method: see header. Kept undocumented here to avoid duplicate param docs.
463void GOptions::printOptionOrSwitchHelp(const std::string& tag) const {
464 auto switchIt = switches.find(tag);
465 if (switchIt != switches.end()) {
466 cout << guts::KGRN << "-" << tag << guts::RST << ": " << switchIt->second.getDescription() << endl << endl;
467 cout << guts::TPOINTITEM << "Default value is " << (switchIt->second.getStatus() ? "on" : "off") << endl
468 << endl;
469 exit(EXIT_SUCCESS);
470 }
471 for (const auto& goption : goptions) {
472 if (goption.name == tag) {
473 goption.printHelp(true);
474 exit(EXIT_SUCCESS);
475 }
476 }
477 reportUnknownNameAndExit("option", tag);
478}
479
480// Private method: see header. Kept undocumented here to avoid duplicate param docs.
481void GOptions::printSearch(const std::string& tag) const {
482 // Case-insensitive substring match against switch/option names and descriptions.
483 auto to_lower = [](string s) {
484 transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
485 return s;
486 };
487 const string needle = to_lower(tag);
488 auto matches = [&](const string& a, const string& b) {
489 return to_lower(a).find(needle) != string::npos || to_lower(b).find(needle) != string::npos;
490 };
491
492 long int fill_width = string(goptions::HELPFILLSPACE).size() + 1;
493 cout.fill('.');
494 cout << guts::KGRN << guts::KBOLD << " Options and switches matching \"" << tag << "\":" << guts::RST
495 << endl
496 << endl;
497
498 bool found = false;
499 for (auto& s : switches) {
500 if (matches(s.first, s.second.getDescription())) {
501 found = true;
502 cout << guts::KGRN << " " << left;
503 cout.width(fill_width);
504 cout << "-" + s.first + guts::RST + " " << ": " << s.second.getDescription() << endl;
505 }
506 }
507 for (auto& option : goptions) {
508 if (option.name != goptions::GVERSION_STRING && matches(option.name, option.description)) {
509 found = true;
510 option.printHelp(false);
511 }
512 }
513 if (!found) { cout << guts::TPOINTITEM << "no match found." << endl; }
514 cout << endl
515 << " Use " << guts::KGRN << "help <value>" << guts::RST << " for the detailed help of a single option."
516 << endl
517 << endl;
518}
519
520// Private method: see header. Kept undocumented here to avoid duplicate param docs.
521vector<string> GOptions::findYamls(int argc, char* argv[]) {
522 vector<string> yaml_files;
523 for (int i = 1; i < argc; i++) {
524 string arg = argv[i];
525 // Match a trailing .yaml/.yml extension, not any substring, so option values such as
526 // -prefix=run.yaml.bak are not mistaken for input files (and silently dropped).
527 if (arg.ends_with(".yaml") || arg.ends_with(".yml")) yaml_files.push_back(arg);
528 }
529 return yaml_files;
530}
531
532// checks if the option exists
533// Public API docs are in goptions.h; doxygen param docs are kept only there.
534bool GOptions::doesOptionExist(const std::string& tag) const {
535 // [&tag] ensures we're referencing the original tag passed to the function
536 return std::any_of(goptions.begin(), goptions.end(),
537 [&tag](const auto& option) {
538 return option.name == tag;
539 });
540}
541
542// Private method: behavior described in header and top-of-file overview.
543void GOptions::setOptionsValuesFromYamlFile(const std::string& yaml) {
544 YAML::Node config;
545 try {
546 config = YAML::LoadFile(yaml);
547 }
548 catch (YAML::BadFile& e) {
549 cerr << guts::FATALERRORL << "Cannot open yaml file " << guts::YELLOWHHL << yaml << guts::RSTHHR
550 << ". Check the path and spelling." << endl;
552 }
553 catch (YAML::ParserException& e) {
554 cerr << guts::FATALERRORL << "Error parsing " << guts::YELLOWHHL << yaml << guts::RSTHHR
555 << " yaml file." << endl;
556 cerr << e.what() << endl;
557 cerr << "Try validating the yaml file with an online yaml validator, e.g., https://www.yamllint.com" << endl;
559 }
560
561 for (auto it = config.begin(); it != config.end(); ++it) {
562 auto option_name = it->first.as<std::string>();
563 auto option_it = getOptionIterator(option_name);
564
565 // If it is not an option, it may still be a switch.
566 if (option_it == goptions.end()) {
567 if (!switches.contains(option_name)) { reportUnknownNameAndExit("option or switch", option_name); }
568 else {
569 // A bare key (null value) means "present" -> on; an explicit boolean value is honored,
570 // so a default-on switch can be turned off from YAML (e.g. print_summary: false).
571 bool on = true;
572 if (it->second.IsScalar() && !parse_bool_token(it->second.as<std::string>(), on)) {
573 cerr << guts::FATALERRORL << "The switch " << guts::YELLOWHHL << option_name << guts::RSTHHR
574 << " accepts only true/false/yes/no/on/off/1/0." << endl;
576 }
577 on ? switches[option_name].turnOn() : switches[option_name].turnOff();
578 }
579 }
580 else {
581 YAML::NodeType::value type = it->second.Type();
582 switch (type) {
583 case YAML::NodeType::Scalar:
584 option_it->set_scalar_value(it->second.as<std::string>());
585 break;
586 case YAML::NodeType::Sequence:
587 option_it->set_value(it->second);
588 break;
589 case YAML::NodeType::Map:
590 option_it->set_value(it->second);
591 break;
592 default:
593 break;
594 }
595 }
596 }
597}
598
599// Private method: behavior described in header and top-of-file overview.
600void GOptions::setOptionValuesFromCommandLineArgument(const std::string& optionName,
601 const std::string& possibleYamlNode) {
602 auto option_it = getOptionIterator(optionName);
603 if (possibleYamlNode.empty()) {
604 option_it->set_scalar_value("");
605 return;
606 }
607
608 YAML::Node node = YAML::Load(possibleYamlNode);
609
610 if (node.Type() == YAML::NodeType::Scalar) {
611 option_it->set_scalar_value(possibleYamlNode);
612 }
613 else {
614 option_it->set_value(node);
615 }
616}
617
618void GOptions::setOptionValueFromString(const std::string& optionName,
619 const std::string& possibleYamlNode) {
620 setOptionValuesFromCommandLineArgument(optionName, possibleYamlNode);
621}
622
623// Private method (private API): do not \ref; use \c getOptionIterator() in docs if needed.
624std::vector<GOption>::iterator GOptions::getOptionIterator(const std::string& name) {
625 return std::find_if(goptions.begin(), goptions.end(),
626 [&name](GOption& option) { return option.name == name; });
627}
628
629// Private method (private API): do not \ref; use \c getOptionIterator() in docs if needed.
630std::vector<GOption>::const_iterator GOptions::getOptionIterator(const std::string& name) const {
631 return std::find_if(goptions.begin(), goptions.end(),
632 [&name](const GOption& option) { return option.name == name; });
633}
634
635// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
636bool GOptions::getSwitch(const std::string& tag) const {
637 auto it = switches.find(tag);
638 if (it != switches.end()) {
639 return it->second.getStatus();
640 }
641 else {
642 std::cerr << guts::FATALERRORL << "The switch " << guts::YELLOWHHL << tag << guts::RSTHHR
643 << " was not found." << std::endl;
645 }
646}
647
648// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
649YAML::Node GOptions::getOptionMapInNode(const string& option_name, const string& map_key) const {
650 auto sequence_node = getOptionNode(option_name);
651
652 for (auto seq_item : sequence_node) {
653 for (auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
654 if (map_item->first.as<string>() == map_key) {
655 return map_item->second;
656 }
657 }
658 }
659
660 cerr << guts::FATALERRORL << "The key " << guts::YELLOWHHL << map_key << guts::RSTHHR
661 << " was not found in " << guts::YELLOWHHL << option_name << guts::RSTHHR << endl;
663}
664
665// Template documentation lives in the header to avoid duplicate \param blocks.
666template <typename T>
667T GOptions::get_variable_in_option(const YAML::Node& node, const std::string& variable_name, const T& default_value) {
668 if (node[variable_name]) {
669 return node[variable_name].as<T>();
670 }
671 return default_value;
672}
673
674template <typename T>
675T GOptions::get_required_variable_in_option(const YAML::Node& node, const std::string& variable_name) {
676 if (!node[variable_name] || node[variable_name].IsNull()) {
677 std::cerr << guts::FATALERRORL << "The mandatory key " << guts::YELLOWHHL << variable_name
678 << guts::RSTHHR << " was not provided." << std::endl;
680 }
681 return node[variable_name].as<T>();
682}
683
684template <typename T>
686 const YAML::Node& node, const std::string& variable_name) {
687 if (!node[variable_name] || node[variable_name].IsNull()) return std::nullopt;
688 return node[variable_name].as<T>();
689}
690
691// Explicit template instantiations.
692template int GOptions::get_variable_in_option<int>(const YAML::Node& node, const std::string& variable_name,
693 const int& default_value);
694template double GOptions::get_variable_in_option<double>(const YAML::Node& node, const std::string& variable_name,
695 const double& default_value);
696template string GOptions::get_variable_in_option<string>(const YAML::Node& node, const std::string& variable_name,
697 const string& default_value);
698template bool GOptions::get_variable_in_option<bool>(const YAML::Node& node, const std::string& variable_name,
699 const bool& default_value);
700template int GOptions::get_required_variable_in_option<int>(const YAML::Node&, const std::string&);
701template double GOptions::get_required_variable_in_option<double>(const YAML::Node&, const std::string&);
702template string GOptions::get_required_variable_in_option<string>(const YAML::Node&, const std::string&);
703template bool GOptions::get_required_variable_in_option<bool>(const YAML::Node&, const std::string&);
704template std::optional<int> GOptions::get_optional_variable_in_option<int>(
705 const YAML::Node&, const std::string&);
706template std::optional<double> GOptions::get_optional_variable_in_option<double>(const YAML::Node&,
707 const std::string&);
708template std::optional<string> GOptions::get_optional_variable_in_option<string>(const YAML::Node&,
709 const std::string&);
710
711// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
712int GOptions::getVerbosityFor(const std::string& tag) const {
713 YAML::Node verbosity_node = getOptionNode("verbosity");
714 for (auto v : verbosity_node) {
715 if (v.begin()->first.as<string>() == tag) {
716 return v.begin()->second.as<int>();
717 }
718 }
719
720 // not found. error
721 std::cerr << guts::KRED << " Invalid verbosity or debug requested: " << tag << guts::RST << std::endl;
723}
724
725// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
726int GOptions::getDebugFor(const std::string& tag) const {
727 YAML::Node debug_node = getOptionNode("debug");
728 for (auto d : debug_node) {
729 if (d.begin()->first.as<string>() == tag) {
730 YAML::Node valNode = d.begin()->second;
731 if (valNode.IsScalar()) {
732 auto s = valNode.as<string>();
733 if (s == "true") return 1;
734 if (s == "false") return 0;
735 }
736 try {
737 return valNode.as<int>();
738 }
739 catch (const YAML::BadConversion&) {
740 std::cerr << "Invalid debug value for " << tag << std::endl;
742 }
743 }
744 }
745 // not found. error
746 std::cerr << guts::KRED << " Invalid verbosity or debug requested: " << tag << guts::RST << std::endl;
748}
749
750// Private method: no Doxygen block here to avoid duplicate \param docs.
751void GOptions::printHelp() const {
752 long int fill_width = string(goptions::HELPFILLSPACE).size() + 1;
753 cout.fill('.');
754 cout << guts::KGRN << guts::KBOLD << " " << executableName << guts::RST << " [options] [yaml files]" << endl
755 << endl;
756 cout << " Switches: " << endl << endl;
757 for (auto& s : switches) {
758 string help = "-" + s.first + guts::RST + " ";
759 cout << guts::KGRN << " " << left;
760 cout.width(fill_width);
761 cout << help;
762 cout << ": " << s.second.getDescription() << endl;
763 }
764 cout << endl;
765 cout << " Options: " << endl << endl;
766 for (auto& option : goptions) {
767 option.printHelp(false);
768 }
769 cout << endl;
770 cout << endl << " Help / Search / Introspection: " << endl << endl;
771 vector<string> helps = {
772 string("-h, --h, -help, --help") + guts::RST,
773 string("print this help and exit"),
774 string("-hweb") + guts::RST,
775 string("print this help in web format and exit"),
776 string("-v, --v, -version, --version") + guts::RST,
777 string("print the version and exit\n"),
778 string("help <value>") + guts::RST,
779 string("print detailed help for option <value> and exit"),
780 string("search <value>") + guts::RST,
781 string("list all options/switches whose name or description contains <value> and exit\n")
782 };
783 unsigned half_help = helps.size() / 2;
784 for (unsigned i = 0; i < half_help; i++) {
785 cout << guts::KGRN << " " << left;
786 cout.width(fill_width);
787 cout << helps[i * 2] << ": " << helps[i * 2 + 1] << endl;
788 }
789 cout << endl;
790 cout << " Note: command line options overwrite YAML file(s)." << endl << endl;
791 exit(EXIT_SUCCESS);
792}
793
794// Private method: no Doxygen block here to avoid duplicate \param docs.
795void GOptions::printWebHelp() const {
796 exit(EXIT_SUCCESS);
797}
798
799// Private method: no Doxygen block here to avoid duplicate \param docs.
800void GOptions::saveOptions() const {
801 for (auto& s : switches) {
802 string status = s.second.getStatus() ? "true" : "false";
803 *yamlConf << s.first + ": " + status << "," << endl;
804 }
805 for (const auto& option : goptions) {
806 option.saveOption(yamlConf);
807 }
808 yamlConf->close();
809}
810
811// Private method: no Doxygen block here to avoid duplicate \param docs.
812void GOptions::print_version() {
813 string asterisks = "*******************************************************************";
814 cout << endl << asterisks << endl;
815 const auto catalog = GMessageCatalog::load(this);
816 const auto colored = [](const string& value) { return string(guts::KGRN) + value + guts::RST; };
817 const string executable = string(guts::KGRN) + guts::KBOLD + executableName + guts::RST;
818 cout << catalog->format(" %1 version: %2", executable, colored(gversion)) << endl;
819 cout << catalog->format(" Called from: %1", colored(executableCallingDir)) << endl;
820 cout << catalog->format(" Install: %1", colored(installDir)) << endl;
821 cout << catalog->format(" Released on: %1", colored(grelease_date)) << endl;
822
823 // Report the plugin search path when one was provided, either through the
824 // -plugin_path option (or a plugin_path: YAML key) or the GEMC_PLUGIN_PATH
825 // environment variable.
826 string plugin_path = doesOptionExist("plugin_path")
827 ? getOptionalScalarString("plugin_path").value_or("")
828 : "";
829 const char* plugin_env = std::getenv("GEMC_PLUGIN_PATH");
830 if (!plugin_path.empty() || (plugin_env != nullptr && plugin_env[0] != '\0')) {
831 string combined = plugin_path;
832 if (plugin_env != nullptr && plugin_env[0] != '\0') {
833 if (!combined.empty()) combined += ':';
834 combined += plugin_env;
835 }
836 cout << catalog->format(" Plugin path: %1", colored(combined)) << endl;
837 }
838
839 cout << catalog->format(" GEMC Reference: %1", colored(greference)) << endl;
840 cout << catalog->format(" GEMC Homepage: %1", colored(gweb)) << endl;
841 cout << catalog->format(" Author: %1", colored(gauthor)) << endl << endl;
842 cout << asterisks << endl << endl;
843}
844
845// Operator documentation is provided in goptions.h; do not duplicate it here.
846GOptions& operator+=(GOptions& gopts, const GOptions& goptions_to_add) {
847 gopts.addGOptions(goptions_to_add);
848 return gopts;
849}
static std::shared_ptr< const GMessageCatalog > load(const GOptions *options=nullptr)
Stores one configuration option (scalar or structured), including schema defaults and current value.
Definition goption.h:120
Parses, stores, and exposes command-line options and YAML configuration values.
Definition goptions.h:47
bool getSwitch(const std::string &tag) const
Retrieves the status of a switch.
Definition goptions.cc:636
double getRequiredScalarDouble(const std::string &tag) const
Retrieves the required value of a scalar double option.
Definition goptions.cc:340
YAML::Node getOptionNode(const std::string &tag) const
Retrieves the YAML node for the specified option.
Definition goptions.h:221
void setOptionValueFromString(const std::string &optionName, const std::string &possibleYamlNode)
Updates an option value from a YAML-formatted string.
Definition goptions.cc:618
GOptions()
Default constructor.
Definition goptions.h:57
void defineOption(const GVariable &gvar, const std::string &help)
Defines and adds a scalar option.
Definition goptions.cc:285
YAML::Node getOptionMapInNode(const std::string &option_name, const std::string &map_key) const
Retrieves a map entry value from a structured option stored as a sequence of maps.
Definition goptions.cc:649
T get_variable_in_option(const YAML::Node &node, const std::string &variable_name, const T &default_value)
Retrieves a typed variable from a YAML node within an option.
Definition goptions.cc:667
bool doesOptionExist(const std::string &tag) const
Checks if an option exists.
Definition goptions.cc:534
void addGOptions(const GOptions &src)
Merges options and switches from another GOptions : into this one.
Definition goptions.h:315
std::optional< std::string > getOptionalScalarString(const std::string &tag) const
Retrieves the optional value of a scalar string option.
Definition goptions.cc:385
std::optional< double > getOptionalScalarDouble(const std::string &tag) const
Definition goptions.cc:356
std::optional< T > get_optional_variable_in_option(const YAML::Node &node, const std::string &variable_name)
Definition goptions.cc:685
std::string getRequiredScalarString(const std::string &tag) const
Retrieves the required value of a scalar string option.
Definition goptions.cc:368
int getDebugFor(const std::string &tag) const
Retrieves the debug level for the specified tag.
Definition goptions.cc:726
std::optional< int > getOptionalScalarInt(const std::string &tag) const
Definition goptions.cc:327
int getVerbosityFor(const std::string &tag) const
Retrieves the verbosity level for the specified tag.
Definition goptions.cc:712
T get_required_variable_in_option(const YAML::Node &node, const std::string &variable_name)
Definition goptions.cc:675
void defineSwitch(const std::string &name, const std::string &description, bool default_status=false)
Defines and adds a command-line switch.
Definition goptions.cc:273
std::vector< GVariable > option_verbosity_names
Schema entries used to define the verbosity and debug structured options.
Definition goptions.h:349
int getRequiredScalarInt(const std::string &tag) const
Retrieves the required value of a scalar integer option.
Definition goptions.cc:311
Represents a boolean command-line switch with a description and a status.
Definition gswitch.h:28
Conventions, constants, and error codes for the GOptions : / GOption : subsystem.
GOptions & operator+=(GOptions &gopts, const GOptions &goptions_to_add)
Overloaded operator to add options and switches from one GOptions : to another.
Definition goptions.cc:846
Public interface for GOptions : the YAML + command-line configuration manager.
constexpr int EC__NOOPTIONFOUND
Option/switch/key not found, or invalid command-line token.
constexpr int EC__DEFINED_SWITCHALREADYPRESENT
Attempted to define a switch name more than once.
constexpr int EC__BAD_CONVERSION
YAML value could not be converted to requested type.
constexpr int EC__YAML_PARSING_ERROR
YAML file failed to parse (syntax error or parser failure).
constexpr int EC__MANDATORY_NOT_FILLED
Mandatory structured option key missing.
constexpr char GVERSION_STRING[]
Reserved option tag used to store version information.
constexpr int EC__DEFINED_OPTION_ALREADY_PRESENT
Attempted to define an option name more than once.
constexpr char HELPFILLSPACE[]
Padding used when printing option/switch help.
std::filesystem::path executable_path()
std::optional< std::string > closestMatch(const std::string &query, const std::vector< std::string > &candidates, std::size_t max_distance=3)
string getDirFromPath(const std::string &path)
string getFileFromPath(const std::string &path)
constexpr char YELLOWHHL[]
constexpr char RST[]
constexpr char FATALERRORL[]
constexpr char RSTHHR[]
constexpr char KGRN[]
constexpr char KRED[]
constexpr char TPOINTITEM[]
constexpr char KBOLD[]
Describes a schema entry: key name, default value, and user-facing description.
Definition goption.h:35
std::string name
Variable name (option name for scalar options, schema key name for structured options).
Definition goption.h:36