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  cout << endl;
34 
35  // Add user-defined options.
36  addGOptions(user_defined_options);
37 
38  // switches for all everyone
39  defineSwitch("gui", "use Graphical User Interface");
40  defineSwitch("i", "use interactive batch mode");
41  defineOption(
42  GVariable("conf_yaml", "saved_configuration", "the prefix for filename that store the used options"),
43  "The default value appends \"_saved_configuration\" to the executable name.");
44 
45  // add test timeout for the tests
46  defineOption(GVariable("tt", 500, "tests timeout (ms)"),
47  "Timeout in milliseconds for the code tests that have GUI. ");
48 
49  // version is a special option, not settable by the user
50  // it is set by the gversion.h file
51  // we add it here so it can be saved to the yaml file
52  vector <GVariable> version = {
53  {"release", gversion, "release version number"},
54  {"release_date", grelease_date, "release date"},
55  {"Reference", greference, "article reference"},
56  {"Homepage", gweb, "homepage"},
57  {"Author", gauthor, "author"}
58  };
59  defineOption(GVERSION_STRING, "version information", version, "Version information. Not settable by user.");
60 
61  string help = "Levels: \n \n";
62  help += " - 0: (default) shush\n";
63  help += " - 1: normal information\n";
64  help += " - 2: detailed information\n \n";
65  help += "Example: -verbosity.general=1 \n \n";
66  help += "This option can be repeated.\n \n";
67  defineOption("verbosity", "Sets the log verbosity for various classes", option_verbosity_names, help);
68 
69 
70  help = "Debug information Types: \n \n";
71  help += " - false: (default): do not print debug information\n";
72  help += " - true: print debug information\n\n";
73  help += "Example: -debug.general=true \n \n";
74  help += "This option can be repeated.\n \n";
75  defineOption("debug", "Sets the debug level for various classes", option_verbosity_names, help);
76 
77  // Process help/version command-line arguments.
78  for (int i = 1; i < argc; i++) {
79  if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--h") == 0 ||
80  strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0) {
81  printHelp();
82  } else if (strcmp(argv[i], "-hweb") == 0) {
83  printWebHelp();
84  } else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--v") == 0 ||
85  strcmp(argv[i], "-version") == 0 || strcmp(argv[i], "--version") == 0) {
86  print_version();
87  exit(EXIT_SUCCESS);
88  } else if (strcmp(argv[i], "help") == 0) {
89  printOptionOrSwitchHelp(argv[i + 1]);
90  exit(EXIT_SUCCESS);
91  }
92  }
93 
94  // finds and parse the yaml files
95  yaml_files = findYamls(argc, argv);
96  for (auto &yaml_file: yaml_files) {
97  cout << " Parsing " << yaml_file << endl;
98  setOptionsValuesFromYamlFile(yaml_file);
99  }
100 
101  // Parse command-line arguments (supports both standard YAML–style and dot–notation).
102  for (int i = 1; i < argc; i++) {
103  string candidate = argv[i];
104  if (candidate.empty()) continue;
105  if (find(yaml_files.begin(), yaml_files.end(), candidate) != yaml_files.end()) continue;
106  if (candidate[0] == '-') {
107  string argStr = candidate.substr(1);
108  size_t eqPos = argStr.find('=');
109  if (eqPos != string::npos) {
110  string keyPart = argStr.substr(0, eqPos);
111  string valuePart = argStr.substr(eqPos + 1);
112  size_t dotPos = keyPart.find('.');
113  if (dotPos != string::npos) {
114  // Dot–notation detected (e.g. "debug.general=true")
115  string mainOption = keyPart.substr(0, dotPos);
116  string subOption = keyPart.substr(dotPos + 1);
117  if (doesOptionExist(mainOption)) {
118  auto it = getOptionIterator(mainOption);
119  it->set_sub_option_value(subOption, valuePart);
120  } else {
121  cerr << "The option " << mainOption << " is not known to this system." << endl;
122  exit(EC__NOOPTIONFOUND);
123  }
124  } else {
125  // Standard option syntax.
126  if (doesOptionExist(keyPart)) {
127  setOptionValuesFromCommandLineArgument(keyPart, valuePart);
128  } else {
129  cerr << "The option " << keyPart << " is not known to this system." << endl;
130  exit(EC__NOOPTIONFOUND);
131  }
132  }
133  } else {
134  // Treat as a switch.
135  const string& possibleSwitch = argStr;
136  if (switches.find(possibleSwitch) != switches.end()) {
137  switches[possibleSwitch].turnOn();
138  } else {
139  cerr << "The switch " << possibleSwitch << " is not known to this system." << endl;
140  exit(EC__NOOPTIONFOUND);
141  }
142  }
143  } else {
144  cerr << "The command-line argument \"" << candidate << "\" is not valid." << endl;
145  exit(EC__NOOPTIONFOUND);
146  }
147  }
148 
149  // Always print version information.
150  print_version();
151 
152  // Save the final configuration to a YAML file.
153  string yamlConf_filename = executableName + "." + getScalarString("conf_yaml") + ".yaml";
154  cout << " Saving options to " << yamlConf_filename << endl << endl;
155  yamlConf = new std::ofstream(yamlConf_filename);
156  saveOptions();
157 }
158 
165 void GOptions::defineSwitch(const std::string &name, const std::string &description) {
166  if (switches.find(name) == switches.end()) {
167  switches[name] = GSwitch(description);
168  } else {
169  std::cerr << FATALERRORL << "The " << YELLOWHHL << name << RSTHHR
170  << " switch is already present." << std::endl;
172  }
173 }
174 
181 void GOptions::defineOption(const GVariable &gvar, const std::string &help) {
182  if (doesOptionExist(gvar.name)) {
183  std::cerr << FATALERRORL << "The " << YELLOWHHL << gvar.name << RSTHHR
184  << " option is already present." << std::endl;
186  } else {
187  goptions.emplace_back(gvar, help);
188  }
189 }
190 
199 void GOptions::defineOption(const std::string &name, const std::string &description, const std::vector <GVariable> &g_vars,
200  const std::string &help) {
201  if (doesOptionExist(name)) {
202  std::cerr << FATALERRORL << "The " << YELLOWHHL << name << RSTHHR
203  << " option is already present." << std::endl;
205  } else {
206  goptions.emplace_back(name, description, g_vars, help);
207  }
208 }
209 
216 int GOptions::getScalarInt(const std::string &tag) const {
217  auto it = getOptionIterator(tag);
218  if (it == goptions.end()) {
219  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
220  << " was not found." << endl;
221  exit(EC__NOOPTIONFOUND);
222  }
223  return it->value.begin()->second.as<int>();
224 }
225 
232 double GOptions::getScalarDouble(const std::string &tag) const {
233  auto it = getOptionIterator(tag);
234  if (it == goptions.end()) {
235  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
236  << " was not found." << endl;
237  exit(EC__NOOPTIONFOUND);
238  }
239  return it->value.begin()->second.as<double>();
240 }
241 
248 string GOptions::getScalarString(const std::string &tag) const {
249  auto it = getOptionIterator(tag);
250  if (it == goptions.end()) {
251  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
252  << " was not found." << endl;
253  exit(EC__NOOPTIONFOUND);
254  }
255  return it->value.begin()->second.as<string>();
256 }
257 
263 void GOptions::printOptionOrSwitchHelp(const std::string &tag) const {
264  auto switchIt = switches.find(tag);
265  if (switchIt != switches.end()) {
266  cout << KGRN << "-" << tag << RST << ": " << switchIt->second.getDescription() << endl << endl;
267  cout << TPOINTITEM << "Default value is " << (switchIt->second.getStatus() ? "on" : "off") << endl << endl;
268  exit(EXIT_SUCCESS);
269  }
270  for (const auto &goption: goptions) {
271  if (goption.name == tag) {
272  goption.printHelp(true);
273  exit(EXIT_SUCCESS);
274  }
275  }
276  cerr << FATALERRORL << "The " << YELLOWHHL << tag << RSTHHR
277  << " option is not known to this system." << endl;
278  exit(EC__NOOPTIONFOUND);
279 }
280 
288 vector <string> GOptions::findYamls(int argc, char *argv[]) {
289  vector <string> yaml_files;
290  for (int i = 1; i < argc; i++) {
291  string arg = argv[i];
292  size_t pos = arg.find(".yaml");
293  if (pos != string::npos) yaml_files.push_back(arg);
294  pos = arg.find(".yml");
295  if (pos != string::npos) yaml_files.push_back(arg);
296  }
297  return yaml_files;
298 }
299 
300 // checks if the option exists
301 bool GOptions::doesOptionExist(const std::string &tag) const {
302 
303  // [&tag] ensures we're referencing the original tag passed to the function
304  return std::any_of(goptions.begin(), goptions.end(),
305  [&tag](const auto& option) {
306  return option.name == tag;
307  });
308 }
309 
315 void GOptions::setOptionsValuesFromYamlFile(const std::string &yaml) {
316  YAML::Node config;
317  try {
318  config = YAML::LoadFile(yaml);
319  } catch (YAML::ParserException &e) {
320  cerr << FATALERRORL << "Error parsing " << YELLOWHHL << yaml << RSTHHR
321  << " yaml file." << endl;
322  cerr << e.what() << endl;
323  cerr << "Try validating the yaml file with an online yaml validator, e.g., https://www.yamllint.com" << endl;
325  }
326 
327  for (auto it = config.begin(); it != config.end(); ++it) {
328  auto option_name = it->first.as<std::string>();
329  auto option_it = getOptionIterator(option_name);
330  if (option_it == goptions.end()) {
331  if (switches.find(option_name) == switches.end()) {
332  cerr << FATALERRORL << "The option or switch " << YELLOWHHL << option_name << RSTHHR
333  << " is not known to this system." << endl;
334  exit(EC__NOOPTIONFOUND);
335  } else {
336  switches[option_name].turnOn();
337  }
338  } else {
339  YAML::NodeType::value type = it->second.Type();
340  switch (type) {
341  case YAML::NodeType::Scalar:
342  option_it->set_scalar_value(it->second.as<std::string>());
343  break;
344  case YAML::NodeType::Sequence:
345  option_it->set_value(it->second);
346  break;
347  case YAML::NodeType::Map:
348  option_it->set_value(it->second);
349  break;
350  default:
351  break;
352  }
353  }
354  }
355 }
356 
363 void GOptions::setOptionValuesFromCommandLineArgument(const std::string &optionName, const std::string &possibleYamlNode) {
364  YAML::Node node = YAML::Load(possibleYamlNode);
365  auto option_it = getOptionIterator(optionName);
366  if (node.Type() == YAML::NodeType::Scalar) {
367  option_it->set_scalar_value(possibleYamlNode);
368  } else {
369  option_it->set_value(node);
370  }
371 }
372 
379 std::vector<GOption>::iterator GOptions::getOptionIterator(const std::string &name) {
380  return std::find_if(goptions.begin(), goptions.end(),
381  [&name](GOption &option) { return option.name == name; });
382 }
383 
384 
391 std::vector<GOption>::const_iterator GOptions::getOptionIterator(const std::string &name) const {
392  return std::find_if(goptions.begin(), goptions.end(),
393  [&name](const GOption &option) { return option.name == name; });
394 }
395 
402 bool GOptions::getSwitch(const std::string &tag) const {
403  auto it = switches.find(tag);
404  if (it != switches.end()) {
405  return it->second.getStatus();
406  } else {
407  std::cerr << FATALERRORL << "The switch " << YELLOWHHL << tag << RSTHHR
408  << " was not found." << std::endl;
409  exit(EC__NOOPTIONFOUND);
410  }
411 }
412 
420 YAML::Node GOptions::getOptionMapInNode(const string& option_name, const string& map_key) const {
421  auto sequence_node = getOptionNode(option_name);
422  for (auto seq_item : sequence_node) {
423  for (auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
424  if (map_item->first.as<string>() == map_key) {
425  return map_item->second;
426  }
427  }
428  }
429  cerr << FATALERRORL << "The key " << YELLOWHHL << map_key << RSTHHR
430  << " was not found in " << YELLOWHHL << option_name << RSTHHR << endl;
431  exit(EC__NOOPTIONFOUND);
432 }
433 
443 template<typename T>
444 T GOptions::get_variable_in_option(const YAML::Node &node, const std::string &variable_name, const T &default_value) {
445  if (node[variable_name]) {
446  return node[variable_name].as<T>();
447  }
448  return default_value;
449 }
450 
451 // Explicit template instantiations.
452 template int GOptions::get_variable_in_option<int>(const YAML::Node &node, const std::string &variable_name, const int &default_value);
453 template double GOptions::get_variable_in_option<double>(const YAML::Node &node, const std::string &variable_name, const double &default_value);
454 template string GOptions::get_variable_in_option<string>(const YAML::Node &node, const std::string &variable_name, const string &default_value);
455 template bool GOptions::get_variable_in_option<bool>(const YAML::Node &node, const std::string &variable_name, const bool &default_value);
456 
463 int GOptions::getVerbosityFor(const std::string &tag) const {
464  YAML::Node verbosity_node = getOptionNode("verbosity");
465  for (auto v : verbosity_node) {
466  if (v.begin()->first.as<string>() == tag) {
467  return v.begin()->second.as<int>();
468  }
469  }
470 
471  // not found. error
472  std::cerr << KRED << " Invalid verbosity or debug requested: " << tag << RST << std::endl;
473  exit(EC__NOOPTIONFOUND);
474 }
475 
484 int GOptions::getDebugFor(const std::string &tag) const {
485  YAML::Node debug_node = getOptionNode("debug");
486  for (auto d : debug_node) {
487  if (d.begin()->first.as<string>() == tag) {
488  YAML::Node valNode = d.begin()->second;
489  if (valNode.IsScalar()) {
490  auto s = valNode.as<string>();
491  if (s == "true") return 1;
492  if (s == "false") return 0;
493  }
494  try {
495  return valNode.as<int>();
496  } catch (const YAML::BadConversion &) {
497  std::cerr << "Invalid debug value for " << tag << std::endl;
498  exit(EC__BAD_CONVERSION);
499  }
500  }
501  }
502  // not found. error
503  std::cerr << KRED << " Invalid verbosity or debug requested: " << tag << RST << std::endl;
504  exit(EC__NOOPTIONFOUND);}
505 
509 void GOptions::printHelp() const {
510  long int fill_width = string(HELPFILLSPACE).size() + 1;
511  cout.fill('.');
512  cout << KGRN << KBOLD << " " << executableName << RST << " [options] [yaml files]" << endl << endl;
513  cout << " Switches: " << endl << endl;
514  for (auto &s : switches) {
515  string help = "-" + s.first + RST + " ";
516  cout << KGRN << " " << left;
517  cout.width(fill_width);
518  cout << help;
519  cout << ": " << s.second.getDescription() << endl;
520  }
521  cout << endl;
522  cout << " Options: " << endl << endl;
523  for (auto &option : goptions) {
524  option.printHelp(false);
525  }
526  cout << endl;
527  cout << endl << " Help / Search / Introspection: " << endl << endl;
528  vector<string> helps = {
529  string("-h, --h, -help, --help") + RST,
530  string("print this help and exit"),
531  string("-hweb") + RST,
532  string("print this help in web format and exit"),
533  string("-v, --v, -version, --version") + RST,
534  string("print the version and exit\n"),
535  string("help <value>") + RST,
536  string("print detailed help for option <value> and exit"),
537  string("search <value>") + RST,
538  string("list all options containing <value> in the description and exit\n")
539  };
540  unsigned half_help = helps.size() / 2;
541  for (unsigned i = 0; i < half_help; i++) {
542  cout << KGRN << " " << left;
543  cout.width(fill_width);
544  cout << helps[i * 2] << ": " << helps[i * 2 + 1] << endl;
545  }
546  cout << endl;
547  cout << " Note: command line options overwrite YAML file(s)." << endl << endl;
548  exit(EXIT_SUCCESS);
549 }
550 
551 
552 
556 void GOptions::printWebHelp() const {
557  exit(EXIT_SUCCESS);
558 }
559 
563 void GOptions::saveOptions() const {
564  for (auto &s : switches) {
565  string status = s.second.getStatus() ? "true" : "false";
566  *yamlConf << s.first + ": " + status << "," << endl;
567  }
568  for (const auto &option : goptions) {
569  option.saveOption(yamlConf);
570  }
571  yamlConf->close();
572 }
573 
577 void GOptions::print_version() {
578  string asterisks = "**************************************************************";
579  cout << endl << asterisks << endl;
580  cout << " " << KGRN << KBOLD << executableName << RST << " version: " << KGRN << gversion << RST;
581  cout << ", executed from: " << KGRN << executableCallingDir << RST << endl;
582  cout << " Released on: " << KGRN << grelease_date << RST << endl;
583  cout << " GEMC Reference: " << KGRN << greference << RST << endl;
584  cout << " GEMC Homepage: " << KGRN << gweb << RST << endl;
585  cout << " Author: " << KGRN << gauthor << RST << endl << endl;
586  cout << asterisks << endl << endl;
587 }
588 
596 GOptions &operator+=(GOptions &gopts, const GOptions &goptions_to_add) {
597  gopts.addGOptions(goptions_to_add);
598  return gopts;
599 }
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:248
bool getSwitch(const std::string &tag) const
Retrieves the status of a switch.
Definition: goptions.cc:402
void defineSwitch(const std::string &name, const std::string &description)
Defines and adds a command–line switch.
Definition: goptions.cc:165
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:181
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:420
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:444
double getScalarDouble(const std::string &tag) const
Retrieves the value of a scalar double option.
Definition: goptions.cc:232
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:301
int getScalarInt(const std::string &tag) const
Retrieves the value of a scalar integer option.
Definition: goptions.cc:216
int getDebugFor(const std::string &tag) const
Retrieves the debug level for the specified tag.
Definition: goptions.cc:484
int getVerbosityFor(const std::string &tag) const
Retrieves the verbosity level for the specified tag.
Definition: goptions.cc:463
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:596
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