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
22// c++
23#include <algorithm>
24#include <iostream>
25#include <cstring>
26#include <cctype>
27#include <cstdlib>
28
29using namespace std;
30
31namespace {
32// Parse a human-friendly boolean token used for switches (-switch=value and YAML switch values).
33// Accepts, case-insensitively: true/1/yes/y/on and false/0/no/n/off. Returns false if @p raw is
34// not a recognized token, leaving @p out untouched.
35bool parse_bool_token(const std::string& raw, bool& out) {
36 std::string v = raw;
37 std::transform(v.begin(), v.end(), v.begin(),
38 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
39 if (v == "true" || v == "1" || v == "yes" || v == "y" || v == "on") { out = true; return true; }
40 if (v == "false" || v == "0" || v == "no" || v == "n" || v == "off") { out = false; return true; }
41 return false;
42}
43
44}
45
46// See goptions.h for full constructor API documentation.
47/*
48 * Parsing precedence implemented here:
49 * 1. YAML file(s), applied in argv order
50 * 2. Command-line tokens override YAML values
51 *
52 * Notes:
53 * - "help <option>" is treated as an immediate action and exits after printing.
54 * - Dot-notation routes structured updates to the owning option via GOption::set_sub_option_value().
55 */
56GOptions::GOptions(int argc, char* argv[], const GOptions& user_defined_options) {
57 executableName = gutilities::getFileFromPath(argv[0]);
58 executableCallingDir = gutilities::getDirFromPath(argv[0]);
59 installDir = gutilities::executable_path().parent_path().string();
60 cout << endl;
61
62 // Add user-defined options.
63 addGOptions(user_defined_options);
64
65 // switches for all everyone
66 defineSwitch("gui", "run with the graphical user interface (Qt window)");
67 defineSwitch("i", "drop into the interactive Geant4 terminal session (non-GUI mode)");
69 GVariable("conf_yaml", "saved_configuration", "infix for the YAML file that records the resolved options"),
70 "On exit the resolved configuration is written to <executable>.<conf_yaml>.yaml,\n"
71 "so the default value produces, for example, gemc.saved_configuration.yaml.\n \n"
72 "Example: -conf_yaml=run12 -> saves to gemc.run12.yaml\n \n");
73
74 // add test timeout for the tests
75 defineOption(GVariable("tt", 500, "GUI test timeout (ms)"),
76 "Milliseconds a GUI-based test waits before it auto-closes, so the module\n"
77 "example/test programs can run unattended in CI.\n \n"
78 "Example: -tt=1000\n \n");
79
80 // version is a special option, not settable by the user
81 // it is set by the gversion.h file
82 // we add it here so it can be saved to the yaml file
83 vector<GVariable> version = {
84 {"release", gversion, "release version number"},
85 {"release_date", grelease_date, "release date"},
86 {"Reference", greference, "article reference"},
87 {"Homepage", gweb, "homepage"},
88 {"Author", gauthor, "author"}
89 };
90 defineOption(goptions::GVERSION_STRING, "version information", version,
91 "Version information. Not settable by user.");
92
93 // verbosity option: convention used across modules consuming verbosity levels
94 string help = "Levels: \n \n";
95 help += " - 0: (default) = shush\n";
96 help += " - 1: log detailed information\n";
97 help += " - 2: log extra detailed information\n \n";
98 help += "Each key names a class or module; run 'help verbosity' to list the available keys.\n \n";
99 help += "Example (one key): -verbosity.gemc=1\n";
100 help += "Example (several keys): -verbosity=\"[{gemc: 1}, {<another_key>: 2}]\"\n \n";
101 help += "Equivalent YAML:\n";
102 help += " verbosity:\n";
103 help += " - gemc: 1\n";
104 help += " - <another_key>: 2\n \n";
105 defineOption("verbosity", "Sets the log verbosity for various classes", option_verbosity_names, help);
106
107 // debug option: boolean or integer, depending on consumer expectations
108 help = "Debug information Types: \n \n";
109 help += " - false: (default): do not print debug information\n";
110 help += " - true: print debug information\n\n";
111 help += "Each key names a class or module; run 'help debug' to list the available keys.\n \n";
112 help += "Example (on/off): -debug.gemc=true\n";
113 help += "Example (several keys): -debug=\"[{gemc: true}, {<another_key>: 1}]\"\n \n";
114 help += "Equivalent YAML:\n";
115 help += " debug:\n";
116 help += " - gemc: true\n \n";
117 defineOption("debug", "Sets the debug level for various classes", option_verbosity_names, help);
118
119 // Process help/version command-line arguments.
120 // These are handled early and exit immediately (they do not proceed to parse YAML/options).
121 for (int i = 1; i < argc; i++) {
122 if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--h") == 0 ||
123 strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0) {
124 printHelp();
125 }
126 else if (strcmp(argv[i], "-hweb") == 0) {
127 printWebHelp();
128 }
129 else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--v") == 0 ||
130 strcmp(argv[i], "-version") == 0 || strcmp(argv[i], "--version") == 0) {
131 print_version();
132 exit(EXIT_SUCCESS);
133 }
134 else if (strcmp(argv[i], "help") == 0) {
135 // "gemc help <topic>" shows topic help; bare "gemc help" shows the general help index.
136 if (i + 1 < argc) { printOptionOrSwitchHelp(argv[i + 1]); }
137 else { printHelp(); }
138 exit(EXIT_SUCCESS);
139 }
140 else if (strcmp(argv[i], "search") == 0) {
141 // "gemc search <value>" lists options/switches whose name or description contains <value>.
142 if (i + 1 < argc) { printSearch(argv[i + 1]); }
143 else { printHelp(); }
144 exit(EXIT_SUCCESS);
145 }
146 }
147
148 // finds and parse the yaml files
149 // YAML file tokens are treated as inputs, not as "invalid command-line arguments".
150 yaml_files = findYamls(argc, argv);
151 for (auto& yaml_file : yaml_files) {
152 cout << " Parsing " << yaml_file << endl;
153 setOptionsValuesFromYamlFile(yaml_file);
154 }
155
156 // Parse command-line arguments (supports both standard YAML–style and dot–notation).
157 for (int i = 1; i < argc; i++) {
158 string candidate = argv[i];
159 if (candidate.empty()) continue;
160
161 // Skip YAML file tokens: they were already handled above.
162 if (find(yaml_files.begin(), yaml_files.end(), candidate) != yaml_files.end()) continue;
163
164 if (candidate[0] == '-') {
165 string argStr = candidate.substr(1);
166 size_t eqPos = argStr.find('=');
167
168 if (eqPos != string::npos) {
169 string keyPart = argStr.substr(0, eqPos);
170 string valuePart = argStr.substr(eqPos + 1);
171
172 // Switch with an explicit boolean value: -gui=false / -gui=true. This gives the
173 // CLI a way to turn a switch off, honoring the documented CLI-over-YAML precedence.
174 if (switches.find(keyPart) != switches.end()) {
175 bool on = false;
176 if (parse_bool_token(valuePart, on)) { on ? switches[keyPart].turnOn() : switches[keyPart].turnOff(); }
177 else {
178 cerr << "The switch " << keyPart << " accepts only true/false/yes/no/on/off/1/0." << endl;
180 }
181 continue;
182 }
183
184 // Strip outer quotes if present (e.g., -gstreamer="[...]")
185 if (!valuePart.empty() && valuePart.front() == '"' && valuePart.back() == '"') {
186 valuePart = valuePart.substr(1, valuePart.length() - 2);
187 }
188
189 // Dot-notation targets a subkey in a structured option (e.g., verbosity.gemc).
190 size_t dotPos = keyPart.find('.');
191 if (dotPos != string::npos) {
192 string mainOption = keyPart.substr(0, dotPos);
193 string subOption = keyPart.substr(dotPos + 1);
194
195 if (doesOptionExist(mainOption)) {
196 auto it = getOptionIterator(mainOption);
197 it->set_sub_option_value(subOption, valuePart);
198 }
199 else {
200 cerr << "The option " << mainOption << " is not known to this system." << endl;
202 }
203 }
204 else {
205 // Standard option syntax: -name=value
206 if (doesOptionExist(keyPart)) {
207 setOptionValuesFromCommandLineArgument(keyPart, valuePart);
208 }
209 else {
210 cerr << "The option " << keyPart << " is not known to this system." << endl;
212 }
213 }
214 }
215 else {
216 // Treat as a switch: -gui, -i, etc.
217 const string& possibleSwitch = argStr;
218 if (switches.find(possibleSwitch) != switches.end()) {
219 switches[possibleSwitch].turnOn();
220 }
221 else {
222 cerr << "The switch " << possibleSwitch << " is not known to this system." << endl;
224 }
225 }
226 }
227 else {
228 cerr << "The command-line argument \"" << candidate << "\" is not valid." << endl;
230 }
231 }
232
233 // Always print version information.
234 print_version();
235
236 // Save the final configuration to a YAML file.
237 string yamlConf_filename = executableName + "." + getRequiredScalarString("conf_yaml") + ".yaml";
238 cout << " Saving options to " << yamlConf_filename << endl << endl;
239 yamlConf = new std::ofstream(yamlConf_filename);
240 saveOptions();
241}
242
243// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
244void GOptions::defineSwitch(const std::string& name, const std::string& description, bool default_status) {
245 if (switches.find(name) == switches.end()) {
246 switches[name] = GSwitch(description, default_status);
247 }
248 else {
249 std::cerr << guts::FATALERRORL << "The " << guts::YELLOWHHL << name << guts::RSTHHR
250 << " switch is already present." << std::endl;
252 }
253}
254
255// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
256void GOptions::defineOption(const GVariable& gvar, const std::string& help) {
257 if (doesOptionExist(gvar.name)) {
258 std::cerr << guts::FATALERRORL << "The " << guts::YELLOWHHL << gvar.name << guts::RSTHHR
259 << " option is already present." << std::endl;
261 }
262 else {
263 goptions.emplace_back(gvar, help);
264 }
265}
266
267// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
268void GOptions::defineOption(const std::string& name, const std::string& description,
269 const std::vector<GVariable>& gvars,
270 const std::string& help) {
271 if (doesOptionExist(name)) {
272 std::cerr << guts::FATALERRORL << "The " << guts::YELLOWHHL << name << guts::RSTHHR
273 << " option is already present." << std::endl;
275 }
276 else {
277 goptions.emplace_back(name, description, gvars, help);
278 }
279}
280
281// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
282int GOptions::getRequiredScalarInt(const std::string& tag) const {
283 auto it = getOptionIterator(tag);
284 if (it == goptions.end()) {
285 cerr << guts::FATALERRORL << "The option " << guts::YELLOWHHL << tag << guts::RSTHHR
286 << " was not found." << endl;
288 }
289 const YAML::Node node = it->value.begin()->second;
290 if (!node.IsDefined() || node.IsNull()) {
291 cerr << guts::FATALERRORL << "The required option " << guts::YELLOWHHL << tag << guts::RSTHHR
292 << " was not provided." << endl;
294 }
295 return node.as<int>();
296}
297
298std::optional<int> GOptions::getOptionalScalarInt(const std::string& tag) const {
299 auto it = getOptionIterator(tag);
300 if (it == goptions.end()) {
301 cerr << guts::FATALERRORL << "The option " << guts::YELLOWHHL << tag << guts::RSTHHR
302 << " was not found." << endl;
304 }
305 const YAML::Node node = it->value.begin()->second;
306 if (!node.IsDefined() || node.IsNull()) return std::nullopt;
307 return node.as<int>();
308}
309
310// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
311double GOptions::getRequiredScalarDouble(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<double>();
325}
326
327std::optional<double> GOptions::getOptionalScalarDouble(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<double>();
337}
338
339std::string GOptions::getRequiredScalarString(const std::string& tag) const {
340 auto it = getOptionIterator(tag);
341 if (it == goptions.end()) {
342 cerr << guts::FATALERRORL << "The option " << guts::YELLOWHHL << tag << guts::RSTHHR
343 << " was not found." << endl;
345 }
346 const YAML::Node node = it->value.begin()->second;
347 if (!node.IsDefined() || node.IsNull()) {
348 cerr << guts::FATALERRORL << "The required option " << guts::YELLOWHHL << tag << guts::RSTHHR
349 << " was not provided." << endl;
351 }
352 return node.as<std::string>();
353}
354
355// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
356std::optional<std::string> GOptions::getOptionalScalarString(const std::string& tag) const {
357 auto it = getOptionIterator(tag);
358 if (it == goptions.end()) {
359 std::cerr << guts::FATALERRORL << "The option " << guts::YELLOWHHL << tag << guts::RSTHHR
360 << " was not found." << std::endl;
362 }
363 const YAML::Node node = it->value.begin()->second;
364 if (!node.IsDefined() || node.IsNull()) return std::nullopt;
365 return node.as<std::string>();
366}
367
368
369// Private method: see header. Kept undocumented here to avoid duplicate param docs.
370void GOptions::printOptionOrSwitchHelp(const std::string& tag) const {
371 auto switchIt = switches.find(tag);
372 if (switchIt != switches.end()) {
373 cout << guts::KGRN << "-" << tag << guts::RST << ": " << switchIt->second.getDescription() << endl << endl;
374 cout << guts::TPOINTITEM << "Default value is " << (switchIt->second.getStatus() ? "on" : "off") << endl
375 << endl;
376 exit(EXIT_SUCCESS);
377 }
378 for (const auto& goption : goptions) {
379 if (goption.name == tag) {
380 goption.printHelp(true);
381 exit(EXIT_SUCCESS);
382 }
383 }
384 cerr << guts::FATALERRORL << "The " << guts::YELLOWHHL << tag << guts::RSTHHR
385 << " option is not known to this system." << endl;
387}
388
389// Private method: see header. Kept undocumented here to avoid duplicate param docs.
390void GOptions::printSearch(const std::string& tag) const {
391 // Case-insensitive substring match against switch/option names and descriptions.
392 auto to_lower = [](string s) {
393 transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
394 return s;
395 };
396 const string needle = to_lower(tag);
397 auto matches = [&](const string& a, const string& b) {
398 return to_lower(a).find(needle) != string::npos || to_lower(b).find(needle) != string::npos;
399 };
400
401 long int fill_width = string(goptions::HELPFILLSPACE).size() + 1;
402 cout.fill('.');
403 cout << guts::KGRN << guts::KBOLD << " Options and switches matching \"" << tag << "\":" << guts::RST
404 << endl
405 << endl;
406
407 bool found = false;
408 for (auto& s : switches) {
409 if (matches(s.first, s.second.getDescription())) {
410 found = true;
411 cout << guts::KGRN << " " << left;
412 cout.width(fill_width);
413 cout << "-" + s.first + guts::RST + " " << ": " << s.second.getDescription() << endl;
414 }
415 }
416 for (auto& option : goptions) {
417 if (option.name != goptions::GVERSION_STRING && matches(option.name, option.description)) {
418 found = true;
419 option.printHelp(false);
420 }
421 }
422 if (!found) { cout << guts::TPOINTITEM << "no match found." << endl; }
423 cout << endl
424 << " Use " << guts::KGRN << "help <value>" << guts::RST << " for the detailed help of a single option."
425 << endl
426 << endl;
427}
428
429// Private method: see header. Kept undocumented here to avoid duplicate param docs.
430vector<string> GOptions::findYamls(int argc, char* argv[]) {
431 vector<string> yaml_files;
432 auto ends_with = [](const string& s, const string& suffix) {
433 return s.size() >= suffix.size() &&
434 s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
435 };
436 for (int i = 1; i < argc; i++) {
437 string arg = argv[i];
438 // Match a trailing .yaml/.yml extension, not any substring, so option values such as
439 // -prefix=run.yaml.bak are not mistaken for input files (and silently dropped).
440 if (ends_with(arg, ".yaml") || ends_with(arg, ".yml")) yaml_files.push_back(arg);
441 }
442 return yaml_files;
443}
444
445// checks if the option exists
446// Public API docs are in goptions.h; doxygen param docs are kept only there.
447bool GOptions::doesOptionExist(const std::string& tag) const {
448 // [&tag] ensures we're referencing the original tag passed to the function
449 return std::any_of(goptions.begin(), goptions.end(),
450 [&tag](const auto& option) {
451 return option.name == tag;
452 });
453}
454
455// Private method: behavior described in header and top-of-file overview.
456void GOptions::setOptionsValuesFromYamlFile(const std::string& yaml) {
457 YAML::Node config;
458 try {
459 config = YAML::LoadFile(yaml);
460 }
461 catch (YAML::BadFile& e) {
462 cerr << guts::FATALERRORL << "Cannot open yaml file " << guts::YELLOWHHL << yaml << guts::RSTHHR
463 << ". Check the path and spelling." << endl;
465 }
466 catch (YAML::ParserException& e) {
467 cerr << guts::FATALERRORL << "Error parsing " << guts::YELLOWHHL << yaml << guts::RSTHHR
468 << " yaml file." << endl;
469 cerr << e.what() << endl;
470 cerr << "Try validating the yaml file with an online yaml validator, e.g., https://www.yamllint.com" << endl;
472 }
473
474 for (auto it = config.begin(); it != config.end(); ++it) {
475 auto option_name = it->first.as<std::string>();
476 auto option_it = getOptionIterator(option_name);
477
478 // If it is not an option, it may still be a switch.
479 if (option_it == goptions.end()) {
480 if (switches.find(option_name) == switches.end()) {
481 cerr << guts::FATALERRORL << "The option or switch " << guts::YELLOWHHL << option_name << guts::RSTHHR
482 << " is not known to this system." << endl;
484 }
485 else {
486 // A bare key (null value) means "present" -> on; an explicit boolean value is honored,
487 // so a default-on switch can be turned off from YAML (e.g. print_summary: false).
488 bool on = true;
489 if (it->second.IsScalar() && !parse_bool_token(it->second.as<std::string>(), on)) {
490 cerr << guts::FATALERRORL << "The switch " << guts::YELLOWHHL << option_name << guts::RSTHHR
491 << " accepts only true/false/yes/no/on/off/1/0." << endl;
493 }
494 on ? switches[option_name].turnOn() : switches[option_name].turnOff();
495 }
496 }
497 else {
498 YAML::NodeType::value type = it->second.Type();
499 switch (type) {
500 case YAML::NodeType::Scalar:
501 option_it->set_scalar_value(it->second.as<std::string>());
502 break;
503 case YAML::NodeType::Sequence:
504 option_it->set_value(it->second);
505 break;
506 case YAML::NodeType::Map:
507 option_it->set_value(it->second);
508 break;
509 default:
510 break;
511 }
512 }
513 }
514}
515
516// Private method: behavior described in header and top-of-file overview.
517void GOptions::setOptionValuesFromCommandLineArgument(const std::string& optionName,
518 const std::string& possibleYamlNode) {
519 auto option_it = getOptionIterator(optionName);
520 if (possibleYamlNode.empty()) {
521 option_it->set_scalar_value("");
522 return;
523 }
524
525 YAML::Node node = YAML::Load(possibleYamlNode);
526
527 if (node.Type() == YAML::NodeType::Scalar) {
528 option_it->set_scalar_value(possibleYamlNode);
529 }
530 else {
531 option_it->set_value(node);
532 }
533}
534
535void GOptions::setOptionValueFromString(const std::string& optionName,
536 const std::string& possibleYamlNode) {
537 setOptionValuesFromCommandLineArgument(optionName, possibleYamlNode);
538}
539
540// Private method (private API): do not \ref; use \c getOptionIterator() in docs if needed.
541std::vector<GOption>::iterator GOptions::getOptionIterator(const std::string& name) {
542 return std::find_if(goptions.begin(), goptions.end(),
543 [&name](GOption& option) { return option.name == name; });
544}
545
546// Private method (private API): do not \ref; use \c getOptionIterator() in docs if needed.
547std::vector<GOption>::const_iterator GOptions::getOptionIterator(const std::string& name) const {
548 return std::find_if(goptions.begin(), goptions.end(),
549 [&name](const GOption& option) { return option.name == name; });
550}
551
552// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
553bool GOptions::getSwitch(const std::string& tag) const {
554 auto it = switches.find(tag);
555 if (it != switches.end()) {
556 return it->second.getStatus();
557 }
558 else {
559 std::cerr << guts::FATALERRORL << "The switch " << guts::YELLOWHHL << tag << guts::RSTHHR
560 << " was not found." << std::endl;
562 }
563}
564
565// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
566YAML::Node GOptions::getOptionMapInNode(const string& option_name, const string& map_key) const {
567 auto sequence_node = getOptionNode(option_name);
568
569 for (auto seq_item : sequence_node) {
570 for (auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
571 if (map_item->first.as<string>() == map_key) {
572 return map_item->second;
573 }
574 }
575 }
576
577 cerr << guts::FATALERRORL << "The key " << guts::YELLOWHHL << map_key << guts::RSTHHR
578 << " was not found in " << guts::YELLOWHHL << option_name << guts::RSTHHR << endl;
580}
581
582// Template documentation lives in the header to avoid duplicate \param blocks.
583template <typename T>
584T GOptions::get_variable_in_option(const YAML::Node& node, const std::string& variable_name, const T& default_value) {
585 if (node[variable_name]) {
586 return node[variable_name].as<T>();
587 }
588 return default_value;
589}
590
591template <typename T>
592T GOptions::get_required_variable_in_option(const YAML::Node& node, const std::string& variable_name) {
593 if (!node[variable_name] || node[variable_name].IsNull()) {
594 std::cerr << guts::FATALERRORL << "The mandatory key " << guts::YELLOWHHL << variable_name
595 << guts::RSTHHR << " was not provided." << std::endl;
597 }
598 return node[variable_name].as<T>();
599}
600
601template <typename T>
603 const YAML::Node& node, const std::string& variable_name) {
604 if (!node[variable_name] || node[variable_name].IsNull()) return std::nullopt;
605 return node[variable_name].as<T>();
606}
607
608// Explicit template instantiations.
609template int GOptions::get_variable_in_option<int>(const YAML::Node& node, const std::string& variable_name,
610 const int& default_value);
611template double GOptions::get_variable_in_option<double>(const YAML::Node& node, const std::string& variable_name,
612 const double& default_value);
613template string GOptions::get_variable_in_option<string>(const YAML::Node& node, const std::string& variable_name,
614 const string& default_value);
615template bool GOptions::get_variable_in_option<bool>(const YAML::Node& node, const std::string& variable_name,
616 const bool& default_value);
617template int GOptions::get_required_variable_in_option<int>(const YAML::Node&, const std::string&);
618template double GOptions::get_required_variable_in_option<double>(const YAML::Node&, const std::string&);
619template string GOptions::get_required_variable_in_option<string>(const YAML::Node&, const std::string&);
620template bool GOptions::get_required_variable_in_option<bool>(const YAML::Node&, const std::string&);
621template std::optional<int> GOptions::get_optional_variable_in_option<int>(
622 const YAML::Node&, const std::string&);
623template std::optional<double> GOptions::get_optional_variable_in_option<double>(const YAML::Node&,
624 const std::string&);
625template std::optional<string> GOptions::get_optional_variable_in_option<string>(const YAML::Node&,
626 const std::string&);
627
628// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
629int GOptions::getVerbosityFor(const std::string& tag) const {
630 YAML::Node verbosity_node = getOptionNode("verbosity");
631 for (auto v : verbosity_node) {
632 if (v.begin()->first.as<string>() == tag) {
633 return v.begin()->second.as<int>();
634 }
635 }
636
637 // not found. error
638 std::cerr << guts::KRED << " Invalid verbosity or debug requested: " << tag << guts::RST << std::endl;
640}
641
642// Implementation note: public API docs are in goptions.h (avoid duplicate \param blocks).
643int GOptions::getDebugFor(const std::string& tag) const {
644 YAML::Node debug_node = getOptionNode("debug");
645 for (auto d : debug_node) {
646 if (d.begin()->first.as<string>() == tag) {
647 YAML::Node valNode = d.begin()->second;
648 if (valNode.IsScalar()) {
649 auto s = valNode.as<string>();
650 if (s == "true") return 1;
651 if (s == "false") return 0;
652 }
653 try {
654 return valNode.as<int>();
655 }
656 catch (const YAML::BadConversion&) {
657 std::cerr << "Invalid debug value for " << tag << std::endl;
659 }
660 }
661 }
662 // not found. error
663 std::cerr << guts::KRED << " Invalid verbosity or debug requested: " << tag << guts::RST << std::endl;
665}
666
667// Private method: no Doxygen block here to avoid duplicate \param docs.
668void GOptions::printHelp() const {
669 long int fill_width = string(goptions::HELPFILLSPACE).size() + 1;
670 cout.fill('.');
671 cout << guts::KGRN << guts::KBOLD << " " << executableName << guts::RST << " [options] [yaml files]" << endl
672 << endl;
673 cout << " Switches: " << endl << endl;
674 for (auto& s : switches) {
675 string help = "-" + s.first + guts::RST + " ";
676 cout << guts::KGRN << " " << left;
677 cout.width(fill_width);
678 cout << help;
679 cout << ": " << s.second.getDescription() << endl;
680 }
681 cout << endl;
682 cout << " Options: " << endl << endl;
683 for (auto& option : goptions) {
684 option.printHelp(false);
685 }
686 cout << endl;
687 cout << endl << " Help / Search / Introspection: " << endl << endl;
688 vector<string> helps = {
689 string("-h, --h, -help, --help") + guts::RST,
690 string("print this help and exit"),
691 string("-hweb") + guts::RST,
692 string("print this help in web format and exit"),
693 string("-v, --v, -version, --version") + guts::RST,
694 string("print the version and exit\n"),
695 string("help <value>") + guts::RST,
696 string("print detailed help for option <value> and exit"),
697 string("search <value>") + guts::RST,
698 string("list all options/switches whose name or description contains <value> and exit\n")
699 };
700 unsigned half_help = helps.size() / 2;
701 for (unsigned i = 0; i < half_help; i++) {
702 cout << guts::KGRN << " " << left;
703 cout.width(fill_width);
704 cout << helps[i * 2] << ": " << helps[i * 2 + 1] << endl;
705 }
706 cout << endl;
707 cout << " Note: command line options overwrite YAML file(s)." << endl << endl;
708 exit(EXIT_SUCCESS);
709}
710
711// Private method: no Doxygen block here to avoid duplicate \param docs.
712void GOptions::printWebHelp() const {
713 exit(EXIT_SUCCESS);
714}
715
716// Private method: no Doxygen block here to avoid duplicate \param docs.
717void GOptions::saveOptions() const {
718 for (auto& s : switches) {
719 string status = s.second.getStatus() ? "true" : "false";
720 *yamlConf << s.first + ": " + status << "," << endl;
721 }
722 for (const auto& option : goptions) {
723 option.saveOption(yamlConf);
724 }
725 yamlConf->close();
726}
727
728// Private method: no Doxygen block here to avoid duplicate \param docs.
729void GOptions::print_version() {
730 string asterisks = "*******************************************************************";
731 cout << endl << asterisks << endl;
732 cout << " " << guts::KGRN << guts::KBOLD << executableName << guts::RST << " version: " << guts::KGRN
733 << gversion << guts::RST << endl;
734 cout << " Called from: " << guts::KGRN << executableCallingDir << guts::RST << endl;
735 cout << " Install: " << guts::KGRN << installDir << guts::RST << endl;
736 cout << " Released on: " << guts::KGRN << grelease_date << guts::RST << endl;
737
738 // Report the plugin search path when one was provided, either through the
739 // -plugin_path option (or a plugin_path: YAML key) or the GEMC_PLUGIN_PATH
740 // environment variable.
741 string plugin_path = doesOptionExist("plugin_path")
742 ? getOptionalScalarString("plugin_path").value_or("")
743 : "";
744 const char* plugin_env = std::getenv("GEMC_PLUGIN_PATH");
745 if (!plugin_path.empty() || (plugin_env != nullptr && plugin_env[0] != '\0')) {
746 string combined = plugin_path;
747 if (plugin_env != nullptr && plugin_env[0] != '\0') {
748 if (!combined.empty()) combined += ':';
749 combined += plugin_env;
750 }
751 cout << " Plugin path: " << guts::KGRN << combined << guts::RST << endl;
752 }
753
754 cout << " GEMC Reference: " << guts::KGRN << greference << guts::RST << endl;
755 cout << " GEMC Homepage: " << guts::KGRN << gweb << guts::RST << endl;
756 cout << " Author: " << guts::KGRN << gauthor << guts::RST << endl << endl;
757 cout << asterisks << endl << endl;
758}
759
760// Operator documentation is provided in goptions.h; do not duplicate it here.
761GOptions& operator+=(GOptions& gopts, const GOptions& goptions_to_add) {
762 gopts.addGOptions(goptions_to_add);
763 return gopts;
764}
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:553
double getRequiredScalarDouble(const std::string &tag) const
Retrieves the required value of a scalar double option.
Definition goptions.cc:311
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:535
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:256
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:566
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:584
bool doesOptionExist(const std::string &tag) const
Checks if an option exists.
Definition goptions.cc:447
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:356
std::optional< double > getOptionalScalarDouble(const std::string &tag) const
Definition goptions.cc:327
std::optional< T > get_optional_variable_in_option(const YAML::Node &node, const std::string &variable_name)
Definition goptions.cc:602
std::string getRequiredScalarString(const std::string &tag) const
Retrieves the required value of a scalar string option.
Definition goptions.cc:339
int getDebugFor(const std::string &tag) const
Retrieves the debug level for the specified tag.
Definition goptions.cc:643
std::optional< int > getOptionalScalarInt(const std::string &tag) const
Definition goptions.cc:298
int getVerbosityFor(const std::string &tag) const
Retrieves the verbosity level for the specified tag.
Definition goptions.cc:629
T get_required_variable_in_option(const YAML::Node &node, const std::string &variable_name)
Definition goptions.cc:592
void defineSwitch(const std::string &name, const std::string &description, bool default_status=false)
Defines and adds a command-line switch.
Definition goptions.cc:244
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:282
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:761
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()
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