goptions
Loading...
Searching...
No Matches
goptions.cc
Go to the documentation of this file.
1
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 <iostream>
24#include <cstring>
25
26using namespace std;
27
28// See goptions.h for full constructor API documentation.
29/*
30 * Parsing precedence implemented here:
31 * 1. YAML file(s), applied in argv order
32 * 2. Command-line tokens override YAML values
33 *
34 * Notes:
35 * - "help <option>" is treated as an immediate action and exits after printing.
36 * - Dot-notation routes structured updates to the owning option via GOption::set_sub_option_value().
37 */
38GOptions::GOptions(int argc, char* argv[], const GOptions& user_defined_options) {
39 executableName = gutilities::getFileFromPath(argv[0]);
40 executableCallingDir = gutilities::getDirFromPath(argv[0]);
41 installDir = gutilities::gemc_root();
42 cout << endl;
43
44 // Add user-defined options.
45 addGOptions(user_defined_options);
46
47 // switches for all everyone
48 defineSwitch("gui", "use Graphical User Interface");
49 defineSwitch("i", "use interactive batch mode");
51 GVariable("conf_yaml", "saved_configuration", "the prefix for filename that store the used options"),
52 "The default value appends \"_saved_configuration\" to the executable name.");
53
54 // add test timeout for the tests
55 defineOption(GVariable("tt", 500, "tests timeout (ms)"),
56 "Timeout in milliseconds for the code tests that have GUI. ");
57
58 // version is a special option, not settable by the user
59 // it is set by the gversion.h file
60 // we add it here so it can be saved to the yaml file
61 vector<GVariable> version = {
62 {"release", gversion, "release version number"},
63 {"release_date", grelease_date, "release date"},
64 {"Reference", greference, "article reference"},
65 {"Homepage", gweb, "homepage"},
66 {"Author", gauthor, "author"}
67 };
68 defineOption(GVERSION_STRING, "version information", version, "Version information. Not settable by user.");
69
70 // verbosity option: convention used across modules consuming verbosity levels
71 string help = "Levels: \n \n";
72 help += " - 0: (default) = shush\n";
73 help += " - 1: log detailed information\n";
74 help += " - 2: log extra detailed information\n \n";
75 help += "Example: -verbosity.general=1 \n \n";
76 help += "This option can be repeated.\n \n";
77 defineOption("verbosity", "Sets the log verbosity for various classes", option_verbosity_names, help);
78
79 // debug option: boolean or integer, depending on consumer expectations
80 help = "Debug information Types: \n \n";
81 help += " - false: (default): do not print debug information\n";
82 help += " - true: print debug information\n\n";
83 help += "Example: -debug.general=true \n \n";
84 help += "This option can be repeated.\n \n";
85 defineOption("debug", "Sets the debug level for various classes", option_verbosity_names, help);
86
87 // Process help/version command-line arguments.
88 // These are handled early and exit immediately (they do not proceed to parse YAML/options).
89 for (int i = 1; i < argc; i++) {
90 if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--h") == 0 ||
91 strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0) {
92 printHelp();
93 }
94 else if (strcmp(argv[i], "-hweb") == 0) {
95 printWebHelp();
96 }
97 else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--v") == 0 ||
98 strcmp(argv[i], "-version") == 0 || strcmp(argv[i], "--version") == 0) {
99 print_version();
100 exit(EXIT_SUCCESS);
101 }
102 else if (strcmp(argv[i], "help") == 0) {
103 printOptionOrSwitchHelp(argv[i + 1]);
104 exit(EXIT_SUCCESS);
105 }
106 }
107
108 // finds and parse the yaml files
109 // YAML file tokens are treated as inputs, not as "invalid command-line arguments".
110 yaml_files = findYamls(argc, argv);
111 for (auto& yaml_file : yaml_files) {
112 cout << " Parsing " << yaml_file << endl;
113 setOptionsValuesFromYamlFile(yaml_file);
114 }
115
116 // Parse command-line arguments (supports both standard YAML–style and dot–notation).
117 for (int i = 1; i < argc; i++) {
118 string candidate = argv[i];
119 if (candidate.empty()) continue;
120
121 // Skip YAML file tokens: they were already handled above.
122 if (find(yaml_files.begin(), yaml_files.end(), candidate) != yaml_files.end()) continue;
123
124 if (candidate[0] == '-') {
125 string argStr = candidate.substr(1);
126 size_t eqPos = argStr.find('=');
127
128 if (eqPos != string::npos) {
129 string keyPart = argStr.substr(0, eqPos);
130 string valuePart = argStr.substr(eqPos + 1);
131
132 // Strip outer quotes if present (e.g., -gstreamer="[...]")
133 if (!valuePart.empty() && valuePart.front() == '"' && valuePart.back() == '"') {
134 valuePart = valuePart.substr(1, valuePart.length() - 2);
135 }
136
137 // Dot-notation targets a subkey in a structured option (e.g., verbosity.general).
138 size_t dotPos = keyPart.find('.');
139 if (dotPos != string::npos) {
140 string mainOption = keyPart.substr(0, dotPos);
141 string subOption = keyPart.substr(dotPos + 1);
142
143 if (doesOptionExist(mainOption)) {
144 auto it = getOptionIterator(mainOption);
145 it->set_sub_option_value(subOption, valuePart);
146 }
147 else {
148 cerr << "The option " << mainOption << " is not known to this system." << endl;
149 exit(EC__NOOPTIONFOUND);
150 }
151 }
152 else {
153 // Standard option syntax: -name=value
154 if (doesOptionExist(keyPart)) {
155 setOptionValuesFromCommandLineArgument(keyPart, valuePart);
156 }
157 else {
158 cerr << "The option " << keyPart << " is not known to this system." << endl;
159 exit(EC__NOOPTIONFOUND);
160 }
161 }
162 }
163 else {
164 // Treat as a switch: -gui, -i, etc.
165 const string& possibleSwitch = argStr;
166 if (switches.find(possibleSwitch) != switches.end()) {
167 switches[possibleSwitch].turnOn();
168 }
169 else {
170 cerr << "The switch " << possibleSwitch << " is not known to this system." << endl;
171 exit(EC__NOOPTIONFOUND);
172 }
173 }
174 }
175 else {
176 cerr << "The command-line argument \"" << candidate << "\" is not valid." << endl;
177 exit(EC__NOOPTIONFOUND);
178 }
179 }
180
181 // Always print version information.
182 print_version();
183
184 // Save the final configuration to a YAML file.
185 string yamlConf_filename = executableName + "." + getScalarString("conf_yaml") + ".yaml";
186 cout << " Saving options to " << yamlConf_filename << endl << endl;
187 yamlConf = new std::ofstream(yamlConf_filename);
188 saveOptions();
189}
190
191// Implementation note: public API docs are in goptions.h (avoid duplicate @param blocks).
192void GOptions::defineSwitch(const std::string& name, const std::string& description) {
193 if (switches.find(name) == switches.end()) {
194 switches[name] = GSwitch(description);
195 }
196 else {
197 std::cerr << FATALERRORL << "The " << YELLOWHHL << name << RSTHHR
198 << " switch is already present." << std::endl;
200 }
201}
202
203// Implementation note: public API docs are in goptions.h (avoid duplicate @param blocks).
204void GOptions::defineOption(const GVariable& gvar, const std::string& help) {
205 if (doesOptionExist(gvar.name)) {
206 std::cerr << FATALERRORL << "The " << YELLOWHHL << gvar.name << RSTHHR
207 << " option is already present." << std::endl;
209 }
210 else {
211 goptions.emplace_back(gvar, help);
212 }
213}
214
215// Implementation note: public API docs are in goptions.h (avoid duplicate @param blocks).
216void GOptions::defineOption(const std::string& name, const std::string& description,
217 const std::vector<GVariable>& gvars,
218 const std::string& help) {
219 if (doesOptionExist(name)) {
220 std::cerr << FATALERRORL << "The " << YELLOWHHL << name << RSTHHR
221 << " option is already present." << std::endl;
223 }
224 else {
225 goptions.emplace_back(name, description, gvars, help);
226 }
227}
228
229// Implementation note: public API docs are in goptions.h (avoid duplicate @param blocks).
230int GOptions::getScalarInt(const std::string& tag) const {
231 auto it = getOptionIterator(tag);
232 if (it == goptions.end()) {
233 cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
234 << " was not found." << endl;
235 exit(EC__NOOPTIONFOUND);
236 }
237 return it->value.begin()->second.as<int>();
238}
239
240// Implementation note: public API docs are in goptions.h (avoid duplicate @param blocks).
241double GOptions::getScalarDouble(const std::string& tag) const {
242 auto it = getOptionIterator(tag);
243 if (it == goptions.end()) {
244 cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
245 << " was not found." << endl;
246 exit(EC__NOOPTIONFOUND);
247 }
248 return it->value.begin()->second.as<double>();
249}
250
251// Implementation note: public API docs are in goptions.h (avoid duplicate @param blocks).
252std::string GOptions::getScalarString(const std::string& tag) const {
253 auto it = getOptionIterator(tag);
254 if (it == goptions.end()) {
255 std::cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
256 << " was not found." << std::endl;
257 std::exit(EC__NOOPTIONFOUND);
258 }
259 const YAML::Node node = it->value.begin()->second;
260 if (node.IsNull()) return "NULL"; // force the exact sentinel you prefer
261 return node.as<std::string>();
262}
263
264
265// Private method: see header. Kept undocumented here to avoid duplicate param docs.
266void GOptions::printOptionOrSwitchHelp(const std::string& tag) const {
267 auto switchIt = switches.find(tag);
268 if (switchIt != switches.end()) {
269 cout << KGRN << "-" << tag << RST << ": " << switchIt->second.getDescription() << endl << endl;
270 cout << TPOINTITEM << "Default value is " << (switchIt->second.getStatus() ? "on" : "off") << endl << endl;
271 exit(EXIT_SUCCESS);
272 }
273 for (const auto& goption : goptions) {
274 if (goption.name == tag) {
275 goption.printHelp(true);
276 exit(EXIT_SUCCESS);
277 }
278 }
279 cerr << FATALERRORL << "The " << YELLOWHHL << tag << RSTHHR
280 << " option is not known to this system." << endl;
281 exit(EC__NOOPTIONFOUND);
282}
283
284// Private method: see header. Kept undocumented here to avoid duplicate param docs.
285vector<string> GOptions::findYamls(int argc, char* argv[]) {
286 vector<string> yaml_files;
287 for (int i = 1; i < argc; i++) {
288 string arg = argv[i];
289 size_t pos = arg.find(".yaml");
290 if (pos != string::npos) yaml_files.push_back(arg);
291 pos = arg.find(".yml");
292 if (pos != string::npos) yaml_files.push_back(arg);
293 }
294 return yaml_files;
295}
296
297// checks if the option exists
298// Public API docs are in goptions.h; doxygen param docs are kept only there.
299bool GOptions::doesOptionExist(const std::string& tag) const {
300 // [&tag] ensures we're referencing the original tag passed to the function
301 return std::any_of(goptions.begin(), goptions.end(),
302 [&tag](const auto& option) {
303 return option.name == tag;
304 });
305}
306
307// Private method: behavior described in header and top-of-file overview.
308void GOptions::setOptionsValuesFromYamlFile(const std::string& yaml) {
309 YAML::Node config;
310 try {
311 config = YAML::LoadFile(yaml);
312 }
313 catch (YAML::ParserException& e) {
314 cerr << FATALERRORL << "Error parsing " << YELLOWHHL << yaml << RSTHHR
315 << " yaml file." << endl;
316 cerr << e.what() << endl;
317 cerr << "Try validating the yaml file with an online yaml validator, e.g., https://www.yamllint.com" << endl;
319 }
320
321 for (auto it = config.begin(); it != config.end(); ++it) {
322 auto option_name = it->first.as<std::string>();
323 auto option_it = getOptionIterator(option_name);
324
325 // If it is not an option, it may still be a switch.
326 if (option_it == goptions.end()) {
327 if (switches.find(option_name) == switches.end()) {
328 cerr << FATALERRORL << "The option or switch " << YELLOWHHL << option_name << RSTHHR
329 << " is not known to this system." << endl;
330 exit(EC__NOOPTIONFOUND);
331 }
332 else {
333 switches[option_name].turnOn();
334 }
335 }
336 else {
337 YAML::NodeType::value type = it->second.Type();
338 switch (type) {
339 case YAML::NodeType::Scalar:
340 option_it->set_scalar_value(it->second.as<std::string>());
341 break;
342 case YAML::NodeType::Sequence:
343 option_it->set_value(it->second);
344 break;
345 case YAML::NodeType::Map:
346 option_it->set_value(it->second);
347 break;
348 default:
349 break;
350 }
351 }
352 }
353}
354
355// Private method: behavior described in header and top-of-file overview.
356void GOptions::setOptionValuesFromCommandLineArgument(const std::string& optionName,
357 const std::string& possibleYamlNode) {
358 YAML::Node node = YAML::Load(possibleYamlNode);
359 auto option_it = getOptionIterator(optionName);
360
361 if (node.Type() == YAML::NodeType::Scalar) {
362 option_it->set_scalar_value(possibleYamlNode);
363 }
364 else {
365 option_it->set_value(node);
366 }
367}
368
369// Private method (private API): do not \ref; use \c getOptionIterator() in docs if needed.
370std::vector<GOption>::iterator GOptions::getOptionIterator(const std::string& name) {
371 return std::find_if(goptions.begin(), goptions.end(),
372 [&name](GOption& option) { return option.name == name; });
373}
374
375// Private method (private API): do not \ref; use \c getOptionIterator() in docs if needed.
376std::vector<GOption>::const_iterator GOptions::getOptionIterator(const std::string& name) const {
377 return std::find_if(goptions.begin(), goptions.end(),
378 [&name](const GOption& option) { return option.name == name; });
379}
380
381// Implementation note: public API docs are in goptions.h (avoid duplicate @param blocks).
382bool GOptions::getSwitch(const std::string& tag) const {
383 auto it = switches.find(tag);
384 if (it != switches.end()) {
385 return it->second.getStatus();
386 }
387 else {
388 std::cerr << FATALERRORL << "The switch " << YELLOWHHL << tag << RSTHHR
389 << " was not found." << std::endl;
390 exit(EC__NOOPTIONFOUND);
391 }
392}
393
394// Implementation note: public API docs are in goptions.h (avoid duplicate @param blocks).
395YAML::Node GOptions::getOptionMapInNode(const string& option_name, const string& map_key) const {
396 auto sequence_node = getOptionNode(option_name);
397
398 for (auto seq_item : sequence_node) {
399 for (auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
400 if (map_item->first.as<string>() == map_key) {
401 return map_item->second;
402 }
403 }
404 }
405
406 cerr << FATALERRORL << "The key " << YELLOWHHL << map_key << RSTHHR
407 << " was not found in " << YELLOWHHL << option_name << RSTHHR << endl;
408 exit(EC__NOOPTIONFOUND);
409}
410
411// Template documentation lives in the header to avoid duplicate @param blocks.
412template <typename T>
413T GOptions::get_variable_in_option(const YAML::Node& node, const std::string& variable_name, const T& default_value) {
414 if (node[variable_name]) {
415 return node[variable_name].as<T>();
416 }
417 return default_value;
418}
419
420// Explicit template instantiations.
421template int GOptions::get_variable_in_option<int>(const YAML::Node& node, const std::string& variable_name,
422 const int& default_value);
423template double GOptions::get_variable_in_option<double>(const YAML::Node& node, const std::string& variable_name,
424 const double& default_value);
425template string GOptions::get_variable_in_option<string>(const YAML::Node& node, const std::string& variable_name,
426 const string& default_value);
427template bool GOptions::get_variable_in_option<bool>(const YAML::Node& node, const std::string& variable_name,
428 const bool& default_value);
429
430// Implementation note: public API docs are in goptions.h (avoid duplicate @param blocks).
431int GOptions::getVerbosityFor(const std::string& tag) const {
432 YAML::Node verbosity_node = getOptionNode("verbosity");
433 for (auto v : verbosity_node) {
434 if (v.begin()->first.as<string>() == tag) {
435 return v.begin()->second.as<int>();
436 }
437 }
438
439 // not found. error
440 std::cerr << KRED << " Invalid verbosity or debug requested: " << tag << RST << std::endl;
442}
443
444// Implementation note: public API docs are in goptions.h (avoid duplicate @param blocks).
445int GOptions::getDebugFor(const std::string& tag) const {
446 YAML::Node debug_node = getOptionNode("debug");
447 for (auto d : debug_node) {
448 if (d.begin()->first.as<string>() == tag) {
449 YAML::Node valNode = d.begin()->second;
450 if (valNode.IsScalar()) {
451 auto s = valNode.as<string>();
452 if (s == "true") return 1;
453 if (s == "false") return 0;
454 }
455 try {
456 return valNode.as<int>();
457 }
458 catch (const YAML::BadConversion&) {
459 std::cerr << "Invalid debug value for " << tag << std::endl;
461 }
462 }
463 }
464 // not found. error
465 std::cerr << KRED << " Invalid verbosity or debug requested: " << tag << RST << std::endl;
467}
468
469// Private method: no Doxygen block here to avoid duplicate @param docs.
470void GOptions::printHelp() const {
471 long int fill_width = string(HELPFILLSPACE).size() + 1;
472 cout.fill('.');
473 cout << KGRN << KBOLD << " " << executableName << RST << " [options] [yaml files]" << endl << endl;
474 cout << " Switches: " << endl << endl;
475 for (auto& s : switches) {
476 string help = "-" + s.first + RST + " ";
477 cout << KGRN << " " << left;
478 cout.width(fill_width);
479 cout << help;
480 cout << ": " << s.second.getDescription() << endl;
481 }
482 cout << endl;
483 cout << " Options: " << endl << endl;
484 for (auto& option : goptions) {
485 option.printHelp(false);
486 }
487 cout << endl;
488 cout << endl << " Help / Search / Introspection: " << endl << endl;
489 vector<string> helps = {
490 string("-h, --h, -help, --help") + RST,
491 string("print this help and exit"),
492 string("-hweb") + RST,
493 string("print this help in web format and exit"),
494 string("-v, --v, -version, --version") + RST,
495 string("print the version and exit\n"),
496 string("help <value>") + RST,
497 string("print detailed help for option <value> and exit"),
498 string("search <value>") + RST,
499 string("list all options containing <value> in the description and exit\n")
500 };
501 unsigned half_help = helps.size() / 2;
502 for (unsigned i = 0; i < half_help; i++) {
503 cout << KGRN << " " << left;
504 cout.width(fill_width);
505 cout << helps[i * 2] << ": " << helps[i * 2 + 1] << endl;
506 }
507 cout << endl;
508 cout << " Note: command line options overwrite YAML file(s)." << endl << endl;
510}
511
512// Private method: no Doxygen block here to avoid duplicate @param docs.
513void GOptions::printWebHelp() const {
515}
516
517// Private method: no Doxygen block here to avoid duplicate @param docs.
518void GOptions::saveOptions() const {
519 for (auto& s : switches) {
520 string status = s.second.getStatus() ? "true" : "false";
521 *yamlConf << s.first + ": " + status << "," << endl;
522 }
523 for (const auto& option : goptions) {
524 option.saveOption(yamlConf);
525 }
526 yamlConf->close();
527}
528
529// Private method: no Doxygen block here to avoid duplicate @param docs.
530void GOptions::print_version() {
531 string asterisks = "*******************************************************************";
532 cout << endl << asterisks << endl;
533 cout << " " << KGRN << KBOLD << executableName << RST << " version: " << KGRN << gversion << RST << endl;
534 cout << " Called from: " << KGRN << executableCallingDir << RST << endl;
535 cout << " Install: " << KGRN << installDir << "/bin" << RST << endl; //
536 cout << " Released on: " << KGRN << grelease_date << RST << endl;
537 cout << " GEMC Reference: " << KGRN << greference << RST << endl;
538 cout << " GEMC Homepage: " << KGRN << gweb << RST << endl;
539 cout << " Author: " << KGRN << gauthor << RST << endl << endl;
540 cout << asterisks << endl << endl;
541}
542
543// Operator documentation is provided in goptions.h; do not duplicate it here.
544GOptions& operator+=(GOptions& gopts, const GOptions& goptions_to_add) {
545 gopts.addGOptions(goptions_to_add);
546 return gopts;
547}
Stores one configuration option (scalar or structured), including schema defaults and current value.
Definition goption.h:105
Parses, stores, and exposes command-line options and YAML configuration values.
Definition goptions.h:46
bool getSwitch(const std::string &tag) const
Retrieves the status of a switch.
Definition goptions.cc:382
YAML::Node getOptionNode(const std::string &tag) const
Retrieves the YAML node for the specified option.
Definition goptions.h:206
void defineSwitch(const std::string &name, const std::string &description)
Defines and adds a command-line switch.
Definition goptions.cc:192
std::string getScalarString(const std::string &tag) const
Retrieves the value of a scalar string option.
Definition goptions.cc:252
GOptions()
Default constructor.
Definition goptions.h:56
void defineOption(const GVariable &gvar, const std::string &help)
Defines and adds a scalar option.
Definition goptions.cc:204
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:395
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:413
double getScalarDouble(const std::string &tag) const
Retrieves the value of a scalar double option.
Definition goptions.cc:241
bool doesOptionExist(const std::string &tag) const
Checks if an option exists.
Definition goptions.cc:299
int getScalarInt(const std::string &tag) const
Retrieves the value of a scalar integer option.
Definition goptions.cc:230
void addGOptions(const GOptions &src)
Merges options and switches from another GOptions : into this one.
Definition goptions.h:286
int getDebugFor(const std::string &tag) const
Retrieves the debug level for the specified tag.
Definition goptions.cc:445
int getVerbosityFor(const std::string &tag) const
Retrieves the verbosity level for the specified tag.
Definition goptions.cc:431
std::vector< GVariable > option_verbosity_names
Schema entries used to define the verbosity and debug structured options.
Definition goptions.h:329
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.
#define EC__BAD_CONVERSION
YAML value could not be converted to requested type.
#define EC__NOOPTIONFOUND
Option/switch/key not found, or invalid command-line token.
#define EC__YAML_PARSING_ERROR
YAML file failed to parse (syntax error or parser failure).
#define HELPFILLSPACE
Padding used when printing option/switch help.
#define EC__DEFINED_SWITCHALREADYPRESENT
Attempted to define a switch name more than once.
#define EC__DEFINED_OPTION_ALREADY_PRESENT
Attempted to define an option name more than once.
#define GVERSION_STRING
Reserved option tag used to store version information.
GOptions & operator+=(GOptions &gopts, const GOptions &goptions_to_add)
Overloaded operator to add options and switches from one GOptions : to another.
Definition goptions.cc:544
Public interface for GOptions : the YAML + command-line configuration manager.
#define RSTHHR
#define YELLOWHHL
#define KRED
#define KBOLD
#define KGRN
#define FATALERRORL
#define RST
#define TPOINTITEM
std::filesystem::path gemc_root()
string getDirFromPath(const std::string &path)
string getFileFromPath(const std::string &path)
Describes a schema entry: key name, default value, and user-facing description.
Definition goption.h:34
std::string name
Variable name (option name for scalar options, schema key name for structured options).
Definition goption.h:35