goptions
goptions.cc
Go to the documentation of this file.
1 // goptions
2 #include "goptions.h"
3 #include "goptionsConventions.h"
4 #include "gversion.h"
5 
6 // gemc
7 #include "gutilities.h"
8 
9 // c++
10 #include <iostream>
11 #include <cstring>
12 
13 using namespace std;
14 
29 GOptions::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");
42  defineOption(
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: normal information\n";
65  help += " - 2: 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  size_t dotPos = keyPart.find('.');
114  if (dotPos != string::npos) {
115  // Dot–notation detected (e.g. "debug.general=true")
116  string mainOption = keyPart.substr(0, dotPos);
117  string subOption = keyPart.substr(dotPos + 1);
118  if (doesOptionExist(mainOption)) {
119  auto it = getOptionIterator(mainOption);
120  it->set_sub_option_value(subOption, valuePart);
121  } else {
122  cerr << "The option " << mainOption << " is not known to this system." << endl;
123  exit(EC__NOOPTIONFOUND);
124  }
125  } else {
126  // Standard option syntax.
127  if (doesOptionExist(keyPart)) {
128  setOptionValuesFromCommandLineArgument(keyPart, valuePart);
129  } else {
130  cerr << "The option " << keyPart << " is not known to this system." << endl;
131  exit(EC__NOOPTIONFOUND);
132  }
133  }
134  } else {
135  // Treat as a switch.
136  const string& possibleSwitch = argStr;
137  if (switches.find(possibleSwitch) != switches.end()) {
138  switches[possibleSwitch].turnOn();
139  } else {
140  cerr << "The switch " << possibleSwitch << " is not known to this system." << endl;
141  exit(EC__NOOPTIONFOUND);
142  }
143  }
144  } else {
145  cerr << "The command-line argument \"" << candidate << "\" is not valid." << endl;
146  exit(EC__NOOPTIONFOUND);
147  }
148  }
149 
150  // Always print version information.
151  print_version();
152 
153  // Save the final configuration to a YAML file.
154  string yamlConf_filename = executableName + "." + getScalarString("conf_yaml") + ".yaml";
155  cout << " Saving options to " << yamlConf_filename << endl << endl;
156  yamlConf = new std::ofstream(yamlConf_filename);
157  saveOptions();
158 }
159 
166 void GOptions::defineSwitch(const std::string &name, const std::string &description) {
167  if (switches.find(name) == switches.end()) {
168  switches[name] = GSwitch(description);
169  } else {
170  std::cerr << FATALERRORL << "The " << YELLOWHHL << name << RSTHHR
171  << " switch is already present." << std::endl;
173  }
174 }
175 
182 void GOptions::defineOption(const GVariable &gvar, const std::string &help) {
183  if (doesOptionExist(gvar.name)) {
184  std::cerr << FATALERRORL << "The " << YELLOWHHL << gvar.name << RSTHHR
185  << " option is already present." << std::endl;
187  } else {
188  goptions.emplace_back(gvar, help);
189  }
190 }
191 
200 void GOptions::defineOption(const std::string &name, const std::string &description, const std::vector <GVariable> &g_vars,
201  const std::string &help) {
202  if (doesOptionExist(name)) {
203  std::cerr << FATALERRORL << "The " << YELLOWHHL << name << RSTHHR
204  << " option is already present." << std::endl;
206  } else {
207  goptions.emplace_back(name, description, g_vars, help);
208  }
209 }
210 
217 int GOptions::getScalarInt(const std::string &tag) const {
218  auto it = getOptionIterator(tag);
219  if (it == goptions.end()) {
220  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
221  << " was not found." << endl;
222  exit(EC__NOOPTIONFOUND);
223  }
224  return it->value.begin()->second.as<int>();
225 }
226 
233 double GOptions::getScalarDouble(const std::string &tag) const {
234  auto it = getOptionIterator(tag);
235  if (it == goptions.end()) {
236  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
237  << " was not found." << endl;
238  exit(EC__NOOPTIONFOUND);
239  }
240  return it->value.begin()->second.as<double>();
241 }
242 
249 string GOptions::getScalarString(const std::string &tag) const {
250  auto it = getOptionIterator(tag);
251  if (it == goptions.end()) {
252  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
253  << " was not found." << endl;
254  exit(EC__NOOPTIONFOUND);
255  }
256  return it->value.begin()->second.as<string>();
257 }
258 
264 void GOptions::printOptionOrSwitchHelp(const std::string &tag) const {
265  auto switchIt = switches.find(tag);
266  if (switchIt != switches.end()) {
267  cout << KGRN << "-" << tag << RST << ": " << switchIt->second.getDescription() << endl << endl;
268  cout << TPOINTITEM << "Default value is " << (switchIt->second.getStatus() ? "on" : "off") << endl << endl;
269  exit(EXIT_SUCCESS);
270  }
271  for (const auto &goption: goptions) {
272  if (goption.name == tag) {
273  goption.printHelp(true);
274  exit(EXIT_SUCCESS);
275  }
276  }
277  cerr << FATALERRORL << "The " << YELLOWHHL << tag << RSTHHR
278  << " option is not known to this system." << endl;
279  exit(EC__NOOPTIONFOUND);
280 }
281 
289 vector <string> GOptions::findYamls(int argc, char *argv[]) {
290  vector <string> yaml_files;
291  for (int i = 1; i < argc; i++) {
292  string arg = argv[i];
293  size_t pos = arg.find(".yaml");
294  if (pos != string::npos) yaml_files.push_back(arg);
295  pos = arg.find(".yml");
296  if (pos != string::npos) yaml_files.push_back(arg);
297  }
298  return yaml_files;
299 }
300 
301 // checks if the option exists
302 bool GOptions::doesOptionExist(const std::string &tag) const {
303 
304  // [&tag] ensures we're referencing the original tag passed to the function
305  return std::any_of(goptions.begin(), goptions.end(),
306  [&tag](const auto& option) {
307  return option.name == tag;
308  });
309 }
310 
316 void GOptions::setOptionsValuesFromYamlFile(const std::string &yaml) {
317  YAML::Node config;
318  try {
319  config = YAML::LoadFile(yaml);
320  } catch (YAML::ParserException &e) {
321  cerr << FATALERRORL << "Error parsing " << YELLOWHHL << yaml << RSTHHR
322  << " yaml file." << endl;
323  cerr << e.what() << endl;
324  cerr << "Try validating the yaml file with an online yaml validator, e.g., https://www.yamllint.com" << endl;
326  }
327 
328  for (auto it = config.begin(); it != config.end(); ++it) {
329  auto option_name = it->first.as<std::string>();
330  auto option_it = getOptionIterator(option_name);
331  if (option_it == goptions.end()) {
332  if (switches.find(option_name) == switches.end()) {
333  cerr << FATALERRORL << "The option or switch " << YELLOWHHL << option_name << RSTHHR
334  << " is not known to this system." << endl;
335  exit(EC__NOOPTIONFOUND);
336  } else {
337  switches[option_name].turnOn();
338  }
339  } else {
340  YAML::NodeType::value type = it->second.Type();
341  switch (type) {
342  case YAML::NodeType::Scalar:
343  option_it->set_scalar_value(it->second.as<std::string>());
344  break;
345  case YAML::NodeType::Sequence:
346  option_it->set_value(it->second);
347  break;
348  case YAML::NodeType::Map:
349  option_it->set_value(it->second);
350  break;
351  default:
352  break;
353  }
354  }
355  }
356 }
357 
364 void GOptions::setOptionValuesFromCommandLineArgument(const std::string &optionName, const std::string &possibleYamlNode) {
365  YAML::Node node = YAML::Load(possibleYamlNode);
366  auto option_it = getOptionIterator(optionName);
367  if (node.Type() == YAML::NodeType::Scalar) {
368  option_it->set_scalar_value(possibleYamlNode);
369  } else {
370  option_it->set_value(node);
371  }
372 }
373 
380 std::vector<GOption>::iterator GOptions::getOptionIterator(const std::string &name) {
381  return std::find_if(goptions.begin(), goptions.end(),
382  [&name](GOption &option) { return option.name == name; });
383 }
384 
385 
392 std::vector<GOption>::const_iterator GOptions::getOptionIterator(const std::string &name) const {
393  return std::find_if(goptions.begin(), goptions.end(),
394  [&name](const GOption &option) { return option.name == name; });
395 }
396 
403 bool GOptions::getSwitch(const std::string &tag) const {
404  auto it = switches.find(tag);
405  if (it != switches.end()) {
406  return it->second.getStatus();
407  } else {
408  std::cerr << FATALERRORL << "The switch " << YELLOWHHL << tag << RSTHHR
409  << " was not found." << std::endl;
410  exit(EC__NOOPTIONFOUND);
411  }
412 }
413 
421 YAML::Node GOptions::getOptionMapInNode(const string& option_name, const string& map_key) const {
422  auto sequence_node = getOptionNode(option_name);
423  for (auto seq_item : sequence_node) {
424  for (auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
425  if (map_item->first.as<string>() == map_key) {
426  return map_item->second;
427  }
428  }
429  }
430  cerr << FATALERRORL << "The key " << YELLOWHHL << map_key << RSTHHR
431  << " was not found in " << YELLOWHHL << option_name << RSTHHR << endl;
432  exit(EC__NOOPTIONFOUND);
433 }
434 
444 template<typename T>
445 T GOptions::get_variable_in_option(const YAML::Node &node, const std::string &variable_name, const T &default_value) {
446  if (node[variable_name]) {
447  return node[variable_name].as<T>();
448  }
449  return default_value;
450 }
451 
452 // Explicit template instantiations.
453 template int GOptions::get_variable_in_option<int>(const YAML::Node &node, const std::string &variable_name, const int &default_value);
454 template double GOptions::get_variable_in_option<double>(const YAML::Node &node, const std::string &variable_name, const double &default_value);
455 template string GOptions::get_variable_in_option<string>(const YAML::Node &node, const std::string &variable_name, const string &default_value);
456 template bool GOptions::get_variable_in_option<bool>(const YAML::Node &node, const std::string &variable_name, const bool &default_value);
457 
464 int GOptions::getVerbosityFor(const std::string &tag) const {
465  YAML::Node verbosity_node = getOptionNode("verbosity");
466  for (auto v : verbosity_node) {
467  if (v.begin()->first.as<string>() == tag) {
468  return v.begin()->second.as<int>();
469  }
470  }
471 
472  // not found. error
473  std::cerr << KRED << " Invalid verbosity or debug requested: " << tag << RST << std::endl;
474  exit(EC__NOOPTIONFOUND);
475 }
476 
485 int GOptions::getDebugFor(const std::string &tag) const {
486  YAML::Node debug_node = getOptionNode("debug");
487  for (auto d : debug_node) {
488  if (d.begin()->first.as<string>() == tag) {
489  YAML::Node valNode = d.begin()->second;
490  if (valNode.IsScalar()) {
491  auto s = valNode.as<string>();
492  if (s == "true") return 1;
493  if (s == "false") return 0;
494  }
495  try {
496  return valNode.as<int>();
497  } catch (const YAML::BadConversion &) {
498  std::cerr << "Invalid debug value for " << tag << std::endl;
499  exit(EC__BAD_CONVERSION);
500  }
501  }
502  }
503  // not found. error
504  std::cerr << KRED << " Invalid verbosity or debug requested: " << tag << RST << std::endl;
505  exit(EC__NOOPTIONFOUND);}
506 
510 void GOptions::printHelp() const {
511  long int fill_width = string(HELPFILLSPACE).size() + 1;
512  cout.fill('.');
513  cout << KGRN << KBOLD << " " << executableName << RST << " [options] [yaml files]" << endl << endl;
514  cout << " Switches: " << endl << endl;
515  for (auto &s : switches) {
516  string help = "-" + s.first + RST + " ";
517  cout << KGRN << " " << left;
518  cout.width(fill_width);
519  cout << help;
520  cout << ": " << s.second.getDescription() << endl;
521  }
522  cout << endl;
523  cout << " Options: " << endl << endl;
524  for (auto &option : goptions) {
525  option.printHelp(false);
526  }
527  cout << endl;
528  cout << endl << " Help / Search / Introspection: " << endl << endl;
529  vector<string> helps = {
530  string("-h, --h, -help, --help") + RST,
531  string("print this help and exit"),
532  string("-hweb") + RST,
533  string("print this help in web format and exit"),
534  string("-v, --v, -version, --version") + RST,
535  string("print the version and exit\n"),
536  string("help <value>") + RST,
537  string("print detailed help for option <value> and exit"),
538  string("search <value>") + RST,
539  string("list all options containing <value> in the description and exit\n")
540  };
541  unsigned half_help = helps.size() / 2;
542  for (unsigned i = 0; i < half_help; i++) {
543  cout << KGRN << " " << left;
544  cout.width(fill_width);
545  cout << helps[i * 2] << ": " << helps[i * 2 + 1] << endl;
546  }
547  cout << endl;
548  cout << " Note: command line options overwrite YAML file(s)." << endl << endl;
549  exit(EXIT_SUCCESS);
550 }
551 
552 
553 
557 void GOptions::printWebHelp() const {
558  exit(EXIT_SUCCESS);
559 }
560 
564 void GOptions::saveOptions() const {
565  for (auto &s : switches) {
566  string status = s.second.getStatus() ? "true" : "false";
567  *yamlConf << s.first + ": " + status << "," << endl;
568  }
569  for (const auto &option : goptions) {
570  option.saveOption(yamlConf);
571  }
572  yamlConf->close();
573 }
574 
578 void GOptions::print_version() {
579  string asterisks = "*******************************************************************";
580  cout << endl << asterisks << endl;
581  cout << " " << KGRN << KBOLD << executableName << RST << " version: " << KGRN << gversion << RST << endl;
582  cout << " Called from: " << KGRN << executableCallingDir << RST << endl;
583  cout << " Executed from: " << KGRN << installDir << "/bin" << RST << endl;
584  cout << " Released on: " << KGRN << grelease_date << RST << endl;
585  cout << " GEMC Reference: " << KGRN << greference << RST << endl;
586  cout << " GEMC Homepage: " << KGRN << gweb << RST << endl;
587  cout << " Author: " << KGRN << gauthor << RST << endl << endl;
588  cout << asterisks << endl << endl;
589 }
590 
598 GOptions &operator+=(GOptions &gopts, const GOptions &goptions_to_add) {
599  gopts.addGOptions(goptions_to_add);
600  return gopts;
601 }
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
std::string getScalarString(const std::string &tag) const
Retrieves the value of a scalar string option.
Definition: goptions.cc:249
bool getSwitch(const std::string &tag) const
Retrieves the status of a switch.
Definition: goptions.cc:403
void defineSwitch(const std::string &name, const std::string &description)
Defines and adds a command–line switch.
Definition: goptions.cc:166
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:182
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:421
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:445
double getScalarDouble(const std::string &tag) const
Retrieves the value of a scalar double option.
Definition: goptions.cc:233
void addGOptions(const GOptions &goptions_to_add)
Adds options from another GOptions object.
Definition: goptions.h:170
bool doesOptionExist(const std::string &tag) const
Checks if the specified option exists.
Definition: goptions.cc:302
int getScalarInt(const std::string &tag) const
Retrieves the value of a scalar integer option.
Definition: goptions.cc:217
int getDebugFor(const std::string &tag) const
Retrieves the debug level for the specified tag.
Definition: goptions.cc:485
int getVerbosityFor(const std::string &tag) const
Retrieves the verbosity level for the specified tag.
Definition: goptions.cc:464
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:598
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