goptions
Loading...
Searching...
No Matches
goptions.cc
Go to the documentation of this file.
1// goptions
2#include "goptions.h"
4#include "gversion.h"
5
6// gemc
7#include "gutilities.h"
8
9// c++
10#include <iostream>
11#include <cstring>
12
13using namespace std;
14
29GOptions::GOptions(int argc, char *argv[], const GOptions &user_defined_options) {
30
31 executableName = gutilities::getFileFromPath(argv[0]);
32 executableCallingDir = gutilities::getDirFromPath(argv[0]);
33 installDir = gutilities::gemc_root();
34 cout << endl;
35
36 // Add user-defined options.
37 addGOptions(user_defined_options);
38
39 // switches for all everyone
40 defineSwitch("gui", "use Graphical User Interface");
41 defineSwitch("i", "use interactive batch mode");
43 GVariable("conf_yaml", "saved_configuration", "the prefix for filename that store the used options"),
44 "The default value appends \"_saved_configuration\" to the executable name.");
45
46 // add test timeout for the tests
47 defineOption(GVariable("tt", 500, "tests timeout (ms)"),
48 "Timeout in milliseconds for the code tests that have GUI. ");
49
50 // version is a special option, not settable by the user
51 // it is set by the gversion.h file
52 // we add it here so it can be saved to the yaml file
53 vector <GVariable> version = {
54 {"release", gversion, "release version number"},
55 {"release_date", grelease_date, "release date"},
56 {"Reference", greference, "article reference"},
57 {"Homepage", gweb, "homepage"},
58 {"Author", gauthor, "author"}
59 };
60 defineOption(GVERSION_STRING, "version information", version, "Version information. Not settable by user.");
61
62 string help = "Levels: \n \n";
63 help += " - 0: (default) = shush\n";
64 help += " - 1: log detailed information\n";
65 help += " - 2: log extra detailed information\n \n";
66 help += "Example: -verbosity.general=1 \n \n";
67 help += "This option can be repeated.\n \n";
68 defineOption("verbosity", "Sets the log verbosity for various classes", option_verbosity_names, help);
69
70
71 help = "Debug information Types: \n \n";
72 help += " - false: (default): do not print debug information\n";
73 help += " - true: print debug information\n\n";
74 help += "Example: -debug.general=true \n \n";
75 help += "This option can be repeated.\n \n";
76 defineOption("debug", "Sets the debug level for various classes", option_verbosity_names, help);
77
78 // Process help/version command-line arguments.
79 for (int i = 1; i < argc; i++) {
80 if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--h") == 0 ||
81 strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0) {
82 printHelp();
83 } else if (strcmp(argv[i], "-hweb") == 0) {
84 printWebHelp();
85 } else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--v") == 0 ||
86 strcmp(argv[i], "-version") == 0 || strcmp(argv[i], "--version") == 0) {
87 print_version();
88 exit(EXIT_SUCCESS);
89 } else if (strcmp(argv[i], "help") == 0) {
90 printOptionOrSwitchHelp(argv[i + 1]);
91 exit(EXIT_SUCCESS);
92 }
93 }
94
95 // finds and parse the yaml files
96 yaml_files = findYamls(argc, argv);
97 for (auto &yaml_file: yaml_files) {
98 cout << " Parsing " << yaml_file << endl;
99 setOptionsValuesFromYamlFile(yaml_file);
100 }
101
102 // Parse command-line arguments (supports both standard YAML–style and dot–notation).
103 for (int i = 1; i < argc; i++) {
104 string candidate = argv[i];
105 if (candidate.empty()) continue;
106 if (find(yaml_files.begin(), yaml_files.end(), candidate) != yaml_files.end()) continue;
107 if (candidate[0] == '-') {
108 string argStr = candidate.substr(1);
109 size_t eqPos = argStr.find('=');
110 if (eqPos != string::npos) {
111 string keyPart = argStr.substr(0, eqPos);
112 string valuePart = argStr.substr(eqPos + 1);
113
114 // Strip outer quotes if present (e.g., -gstreamer="[...]")
115 if (!valuePart.empty() && valuePart.front() == '"' && valuePart.back() == '"') {
116 valuePart = valuePart.substr(1, valuePart.length() - 2);
117 }
118
119 size_t dotPos = keyPart.find('.');
120 if (dotPos != string::npos) {
121 // Dot–notation detected (e.g. "debug.general=true")
122 string mainOption = keyPart.substr(0, dotPos);
123 string subOption = keyPart.substr(dotPos + 1);
124 if (doesOptionExist(mainOption)) {
125 auto it = getOptionIterator(mainOption);
126 it->set_sub_option_value(subOption, valuePart);
127 } else {
128 cerr << "The option " << mainOption << " is not known to this system." << endl;
129 exit(EC__NOOPTIONFOUND);
130 }
131 } else {
132 // Standard option syntax.
133 if (doesOptionExist(keyPart)) {
134 setOptionValuesFromCommandLineArgument(keyPart, valuePart);
135 } else {
136 cerr << "The option " << keyPart << " is not known to this system." << endl;
137 exit(EC__NOOPTIONFOUND);
138 }
139 }
140 } else {
141 // Treat as a switch.
142 const string& possibleSwitch = argStr;
143 if (switches.find(possibleSwitch) != switches.end()) {
144 switches[possibleSwitch].turnOn();
145 } else {
146 cerr << "The switch " << possibleSwitch << " is not known to this system." << endl;
147 exit(EC__NOOPTIONFOUND);
148 }
149 }
150 } else {
151 cerr << "The command-line argument \"" << candidate << "\" is not valid." << endl;
152 exit(EC__NOOPTIONFOUND);
153 }
154 }
155
156 // Always print version information.
157 print_version();
158
159 // Save the final configuration to a YAML file.
160 string yamlConf_filename = executableName + "." + getScalarString("conf_yaml") + ".yaml";
161 cout << " Saving options to " << yamlConf_filename << endl << endl;
162 yamlConf = new std::ofstream(yamlConf_filename);
163 saveOptions();
164}
165
172void GOptions::defineSwitch(const std::string &name, const std::string &description) {
173 if (switches.find(name) == switches.end()) {
174 switches[name] = GSwitch(description);
175 } else {
176 std::cerr << FATALERRORL << "The " << YELLOWHHL << name << RSTHHR
177 << " switch is already present." << std::endl;
179 }
180}
181
188void GOptions::defineOption(const GVariable &gvar, const std::string &help) {
189 if (doesOptionExist(gvar.name)) {
190 std::cerr << FATALERRORL << "The " << YELLOWHHL << gvar.name << RSTHHR
191 << " option is already present." << std::endl;
193 } else {
194 goptions.emplace_back(gvar, help);
195 }
196}
197
206void GOptions::defineOption(const std::string &name, const std::string &description, const std::vector <GVariable> &g_vars,
207 const std::string &help) {
208 if (doesOptionExist(name)) {
209 std::cerr << FATALERRORL << "The " << YELLOWHHL << name << RSTHHR
210 << " option is already present." << std::endl;
212 } else {
213 goptions.emplace_back(name, description, g_vars, help);
214 }
215}
216
223int GOptions::getScalarInt(const std::string &tag) const {
224 auto it = getOptionIterator(tag);
225 if (it == goptions.end()) {
226 cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
227 << " was not found." << endl;
228 exit(EC__NOOPTIONFOUND);
229 }
230 return it->value.begin()->second.as<int>();
231}
232
239double GOptions::getScalarDouble(const std::string &tag) const {
240 auto it = getOptionIterator(tag);
241 if (it == goptions.end()) {
242 cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
243 << " was not found." << endl;
244 exit(EC__NOOPTIONFOUND);
245 }
246 return it->value.begin()->second.as<double>();
247}
248
255std::string GOptions::getScalarString(const std::string &tag) const {
256 auto it = getOptionIterator(tag);
257 if (it == goptions.end()) {
258 std::cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
259 << " was not found." << std::endl;
260 std::exit(EC__NOOPTIONFOUND);
261 }
262 const YAML::Node node = it->value.begin()->second;
263 if (node.IsNull()) return "NULL"; // force the exact sentinel you prefer
264 return node.as<std::string>();
265}
266
267
273void GOptions::printOptionOrSwitchHelp(const std::string &tag) const {
274 auto switchIt = switches.find(tag);
275 if (switchIt != switches.end()) {
276 cout << KGRN << "-" << tag << RST << ": " << switchIt->second.getDescription() << endl << endl;
277 cout << TPOINTITEM << "Default value is " << (switchIt->second.getStatus() ? "on" : "off") << endl << endl;
278 exit(EXIT_SUCCESS);
279 }
280 for (const auto &goption: goptions) {
281 if (goption.name == tag) {
282 goption.printHelp(true);
283 exit(EXIT_SUCCESS);
284 }
285 }
286 cerr << FATALERRORL << "The " << YELLOWHHL << tag << RSTHHR
287 << " option is not known to this system." << endl;
288 exit(EC__NOOPTIONFOUND);
289}
290
298vector <string> GOptions::findYamls(int argc, char *argv[]) {
299 vector <string> yaml_files;
300 for (int i = 1; i < argc; i++) {
301 string arg = argv[i];
302 size_t pos = arg.find(".yaml");
303 if (pos != string::npos) yaml_files.push_back(arg);
304 pos = arg.find(".yml");
305 if (pos != string::npos) yaml_files.push_back(arg);
306 }
307 return yaml_files;
308}
309
310// checks if the option exists
311bool GOptions::doesOptionExist(const std::string &tag) const {
312
313 // [&tag] ensures we're referencing the original tag passed to the function
314 return std::any_of(goptions.begin(), goptions.end(),
315 [&tag](const auto& option) {
316 return option.name == tag;
317 });
318}
319
325void GOptions::setOptionsValuesFromYamlFile(const std::string &yaml) {
326 YAML::Node config;
327 try {
328 config = YAML::LoadFile(yaml);
329 } catch (YAML::ParserException &e) {
330 cerr << FATALERRORL << "Error parsing " << YELLOWHHL << yaml << RSTHHR
331 << " yaml file." << endl;
332 cerr << e.what() << endl;
333 cerr << "Try validating the yaml file with an online yaml validator, e.g., https://www.yamllint.com" << endl;
335 }
336
337 for (auto it = config.begin(); it != config.end(); ++it) {
338 auto option_name = it->first.as<std::string>();
339 auto option_it = getOptionIterator(option_name);
340 if (option_it == goptions.end()) {
341 if (switches.find(option_name) == switches.end()) {
342 cerr << FATALERRORL << "The option or switch " << YELLOWHHL << option_name << RSTHHR
343 << " is not known to this system." << endl;
344 exit(EC__NOOPTIONFOUND);
345 } else {
346 switches[option_name].turnOn();
347 }
348 } else {
349 YAML::NodeType::value type = it->second.Type();
350 switch (type) {
351 case YAML::NodeType::Scalar:
352 option_it->set_scalar_value(it->second.as<std::string>());
353 break;
354 case YAML::NodeType::Sequence:
355 option_it->set_value(it->second);
356 break;
357 case YAML::NodeType::Map:
358 option_it->set_value(it->second);
359 break;
360 default:
361 break;
362 }
363 }
364 }
365}
366
373void GOptions::setOptionValuesFromCommandLineArgument(const std::string &optionName, const std::string &possibleYamlNode) {
374 YAML::Node node = YAML::Load(possibleYamlNode);
375 auto option_it = getOptionIterator(optionName);
376 if (node.Type() == YAML::NodeType::Scalar) {
377 option_it->set_scalar_value(possibleYamlNode);
378 } else {
379 option_it->set_value(node);
380 }
381}
382
389std::vector<GOption>::iterator GOptions::getOptionIterator(const std::string &name) {
390 return std::find_if(goptions.begin(), goptions.end(),
391 [&name](GOption &option) { return option.name == name; });
392}
393
394
401std::vector<GOption>::const_iterator GOptions::getOptionIterator(const std::string &name) const {
402 return std::find_if(goptions.begin(), goptions.end(),
403 [&name](const GOption &option) { return option.name == name; });
404}
405
412bool GOptions::getSwitch(const std::string &tag) const {
413 auto it = switches.find(tag);
414 if (it != switches.end()) {
415 return it->second.getStatus();
416 } else {
417 std::cerr << FATALERRORL << "The switch " << YELLOWHHL << tag << RSTHHR
418 << " was not found." << std::endl;
419 exit(EC__NOOPTIONFOUND);
420 }
421}
422
430YAML::Node GOptions::getOptionMapInNode(const string& option_name, const string& map_key) const {
431 auto sequence_node = getOptionNode(option_name);
432 for (auto seq_item : sequence_node) {
433 for (auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
434 if (map_item->first.as<string>() == map_key) {
435 return map_item->second;
436 }
437 }
438 }
439 cerr << FATALERRORL << "The key " << YELLOWHHL << map_key << RSTHHR
440 << " was not found in " << YELLOWHHL << option_name << RSTHHR << endl;
441 exit(EC__NOOPTIONFOUND);
442}
443
453template<typename T>
454T GOptions::get_variable_in_option(const YAML::Node &node, const std::string &variable_name, const T &default_value) {
455 if (node[variable_name]) {
456 return node[variable_name].as<T>();
457 }
458 return default_value;
459}
460
461// Explicit template instantiations.
462template int GOptions::get_variable_in_option<int>(const YAML::Node &node, const std::string &variable_name, const int &default_value);
463template double GOptions::get_variable_in_option<double>(const YAML::Node &node, const std::string &variable_name, const double &default_value);
464template string GOptions::get_variable_in_option<string>(const YAML::Node &node, const std::string &variable_name, const string &default_value);
465template bool GOptions::get_variable_in_option<bool>(const YAML::Node &node, const std::string &variable_name, const bool &default_value);
466
473int GOptions::getVerbosityFor(const std::string &tag) const {
474 YAML::Node verbosity_node = getOptionNode("verbosity");
475 for (auto v : verbosity_node) {
476 if (v.begin()->first.as<string>() == tag) {
477 return v.begin()->second.as<int>();
478 }
479 }
480
481 // not found. error
482 std::cerr << KRED << " Invalid verbosity or debug requested: " << tag << RST << std::endl;
483 exit(EC__NOOPTIONFOUND);
484}
485
494int GOptions::getDebugFor(const std::string &tag) const {
495 YAML::Node debug_node = getOptionNode("debug");
496 for (auto d : debug_node) {
497 if (d.begin()->first.as<string>() == tag) {
498 YAML::Node valNode = d.begin()->second;
499 if (valNode.IsScalar()) {
500 auto s = valNode.as<string>();
501 if (s == "true") return 1;
502 if (s == "false") return 0;
503 }
504 try {
505 return valNode.as<int>();
506 } catch (const YAML::BadConversion &) {
507 std::cerr << "Invalid debug value for " << tag << std::endl;
508 exit(EC__BAD_CONVERSION);
509 }
510 }
511 }
512 // not found. error
513 std::cerr << KRED << " Invalid verbosity or debug requested: " << tag << RST << std::endl;
514 exit(EC__NOOPTIONFOUND);}
515
519void GOptions::printHelp() const {
520 long int fill_width = string(HELPFILLSPACE).size() + 1;
521 cout.fill('.');
522 cout << KGRN << KBOLD << " " << executableName << RST << " [options] [yaml files]" << endl << endl;
523 cout << " Switches: " << endl << endl;
524 for (auto &s : switches) {
525 string help = "-" + s.first + RST + " ";
526 cout << KGRN << " " << left;
527 cout.width(fill_width);
528 cout << help;
529 cout << ": " << s.second.getDescription() << endl;
530 }
531 cout << endl;
532 cout << " Options: " << endl << endl;
533 for (auto &option : goptions) {
534 option.printHelp(false);
535 }
536 cout << endl;
537 cout << endl << " Help / Search / Introspection: " << endl << endl;
538 vector<string> helps = {
539 string("-h, --h, -help, --help") + RST,
540 string("print this help and exit"),
541 string("-hweb") + RST,
542 string("print this help in web format and exit"),
543 string("-v, --v, -version, --version") + RST,
544 string("print the version and exit\n"),
545 string("help <value>") + RST,
546 string("print detailed help for option <value> and exit"),
547 string("search <value>") + RST,
548 string("list all options containing <value> in the description and exit\n")
549 };
550 unsigned half_help = helps.size() / 2;
551 for (unsigned i = 0; i < half_help; i++) {
552 cout << KGRN << " " << left;
553 cout.width(fill_width);
554 cout << helps[i * 2] << ": " << helps[i * 2 + 1] << endl;
555 }
556 cout << endl;
557 cout << " Note: command line options overwrite YAML file(s)." << endl << endl;
558 exit(EXIT_SUCCESS);
559}
560
561
562
566void GOptions::printWebHelp() const {
567 exit(EXIT_SUCCESS);
568}
569
573void GOptions::saveOptions() const {
574 for (auto &s : switches) {
575 string status = s.second.getStatus() ? "true" : "false";
576 *yamlConf << s.first + ": " + status << "," << endl;
577 }
578 for (const auto &option : goptions) {
579 option.saveOption(yamlConf);
580 }
581 yamlConf->close();
582}
583
587void GOptions::print_version() {
588 string asterisks = "*******************************************************************";
589 cout << endl << asterisks << endl;
590 cout << " " << KGRN << KBOLD << executableName << RST << " version: " << KGRN << gversion << RST << endl;
591 cout << " Called from: " << KGRN << executableCallingDir << RST << endl;
592 cout << " Install: " << KGRN << installDir << "/bin" << RST << endl; //
593 cout << " Released on: " << KGRN << grelease_date << RST << endl;
594 cout << " GEMC Reference: " << KGRN << greference << RST << endl;
595 cout << " GEMC Homepage: " << KGRN << gweb << RST << endl;
596 cout << " Author: " << KGRN << gauthor << RST << endl << endl;
597 cout << asterisks << endl << endl;
598}
599
607GOptions &operator+=(GOptions &gopts, const GOptions &goptions_to_add) {
608 gopts.addGOptions(goptions_to_add);
609 return gopts;
610}
Represents a configurable option with a name, value(s), description, and help text.
Definition goption.h:85
The GOptions class manages command-line options and switches.
Definition goptions.h:24
bool getSwitch(const std::string &tag) const
Retrieves the status of a switch.
Definition goptions.cc:412
YAML::Node getOptionNode(const std::string &tag) const
Retrieves the YAML node for the specified option.
Definition goptions.h:121
void defineSwitch(const std::string &name, const std::string &description)
Defines and adds a command–line switch.
Definition goptions.cc:172
std::string getScalarString(const std::string &tag) const
Retrieves the value of a scalar string option.
Definition goptions.cc:255
GOptions()
Default constructor.
Definition goptions.h:31
void defineOption(const GVariable &gvar, const std::string &help)
Defines and adds a scalar option.
Definition goptions.cc:188
YAML::Node getOptionMapInNode(const std::string &option_name, const std::string &map_key) const
Retrieves a map option’s value from within a YAML node.
Definition goptions.cc:430
T get_variable_in_option(const YAML::Node &node, const std::string &variable_name, const T &default_value)
Retrieves a variable from a YAML node within an option.
Definition goptions.cc:454
double getScalarDouble(const std::string &tag) const
Retrieves the value of a scalar double option.
Definition goptions.cc:239
bool doesOptionExist(const std::string &tag) const
Checks if the specified option exists.
Definition goptions.cc:311
int getScalarInt(const std::string &tag) const
Retrieves the value of a scalar integer option.
Definition goptions.cc:223
void addGOptions(const GOptions &src)
Adds options from another GOptions object.
Definition goptions.h:170
int getDebugFor(const std::string &tag) const
Retrieves the debug level for the specified tag.
Definition goptions.cc:494
int getVerbosityFor(const std::string &tag) const
Retrieves the verbosity level for the specified tag.
Definition goptions.cc:473
std::vector< GVariable > option_verbosity_names
Definition goptions.h:199
Represents a switch with a description and a status.
Definition gswitch.h:13
#define EC__BAD_CONVERSION
#define EC__NOOPTIONFOUND
#define EC__YAML_PARSING_ERROR
#define HELPFILLSPACE
#define EC__DEFINED_SWITCHALREADYPRESENT
#define EC__DEFINED_OPTION_ALREADY_PRESENT
#define GVERSION_STRING
GOptions & operator+=(GOptions &gopts, const GOptions &goptions_to_add)
Overloaded operator to add options and switches from one GOptions object to another.
Definition goptions.cc:607
Encapsulates a variable with a name, value, and description.
Definition goption.h:24
std::string name
The name of the variable.
Definition goption.h:25