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  cout << endl;
33 
34  // Add user-defined options.
35  addGOptions(user_defined_options);
36 
37  // switches for all everyone
38  defineSwitch("gui", "use Graphical User Interface");
39  defineSwitch("i", "use interactive batch mode");
40  defineOption(
41  GVariable("conf_yaml", "saved_configuration", "the yaml filename prefix where all used options are saved"),
42  "The default value appends \"_saved_configuration\" to the executable name.");
43 
44  // version is a special option, not settable by the user
45  // it is set by the gversion.h file
46  // we add it here so it can be saved to the yaml file
47  vector <GVariable> version = {
48  {"release", gversion, "release version number"},
49  {"release_date", grelease_date, "release date"},
50  {"Reference", greference, "article reference"},
51  {"Homepage", gweb, "homepage"},
52  {"Author", gauthor, "author"}
53  };
54  defineOption(GVERSION_STRING, "version information", version, "Version information. Not settable by user.");
55 
56  string help = "Levels: \n \n";
57  help += " - 0: (default) shush\n";
58  help += " - 1: normal information\n";
59  help += " - 2: detailed information\n \n";
60  help += "Example: -verbosity.general=1 \n \n";
61  help += "This option can be repeated.\n \n";
62  defineOption("verbosity", "Sets the log verbosity for various classes", option_verbosity_names, help);
63 
64 
65  help = "Debug information Types: \n \n";
66  help += " - false: (default): do not print debug information\n";
67  help += " - true: print debug information\n\n";
68  help += "Example: -debug.general=true \n \n";
69  help += "This option can be repeated.\n \n";
70  defineOption("debug", "Sets the debug level for various classes", option_verbosity_names, help);
71 
72  // Process help/version command-line arguments.
73  for (int i = 1; i < argc; i++) {
74  if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--h") == 0 ||
75  strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0) {
76  printHelp();
77  } else if (strcmp(argv[i], "-hweb") == 0) {
78  printWebHelp();
79  } else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--v") == 0 ||
80  strcmp(argv[i], "-version") == 0 || strcmp(argv[i], "--version") == 0) {
81  print_version();
82  exit(EXIT_SUCCESS);
83  } else if (strcmp(argv[i], "help") == 0) {
84  printOptionOrSwitchHelp(argv[i + 1]);
85  exit(EXIT_SUCCESS);
86  }
87  }
88 
89  // finds and parse the yaml files
90  yaml_files = findYamls(argc, argv);
91  for (auto &yaml_file: yaml_files) {
92  cout << " Parsing " << yaml_file << endl;
93  setOptionsValuesFromYamlFile(yaml_file);
94  }
95 
96  // Parse command-line arguments (supports both standard YAML–style and dot–notation).
97  for (int i = 1; i < argc; i++) {
98  string candidate = argv[i];
99  if (candidate.empty()) continue;
100  if (find(yaml_files.begin(), yaml_files.end(), candidate) != yaml_files.end()) continue;
101  if (candidate[0] == '-') {
102  string argStr = candidate.substr(1);
103  size_t eqPos = argStr.find('=');
104  if (eqPos != string::npos) {
105  string keyPart = argStr.substr(0, eqPos);
106  string valuePart = argStr.substr(eqPos + 1);
107  size_t dotPos = keyPart.find('.');
108  if (dotPos != string::npos) {
109  // Dot–notation detected (e.g. "debug.general=true")
110  string mainOption = keyPart.substr(0, dotPos);
111  string subOption = keyPart.substr(dotPos + 1);
112  if (doesOptionExist(mainOption)) {
113  auto it = getOptionIterator(mainOption);
114  it->set_sub_option_value(subOption, valuePart);
115  } else {
116  cerr << "The option " << mainOption << " is not known to this system." << endl;
117  exit(EC__NOOPTIONFOUND);
118  }
119  } else {
120  // Standard option syntax.
121  if (doesOptionExist(keyPart)) {
122  setOptionValuesFromCommandLineArgument(keyPart, valuePart);
123  } else {
124  cerr << "The option " << keyPart << " is not known to this system." << endl;
125  exit(EC__NOOPTIONFOUND);
126  }
127  }
128  } else {
129  // Treat as a switch.
130  const string& possibleSwitch = argStr;
131  if (switches.find(possibleSwitch) != switches.end()) {
132  switches[possibleSwitch].turnOn();
133  } else {
134  cerr << "The switch " << possibleSwitch << " is not known to this system." << endl;
135  exit(EC__NOOPTIONFOUND);
136  }
137  }
138  } else {
139  cerr << "The command-line argument \"" << candidate << "\" is not valid." << endl;
140  exit(EC__NOOPTIONFOUND);
141  }
142  }
143 
144  // Always print version information.
145  print_version();
146 
147  // Save the final configuration to a YAML file.
148  string yamlConf_filename = executableName + "." + getScalarString("conf_yaml") + ".yaml";
149  cout << " Saving options to " << yamlConf_filename << endl << endl;
150  yamlConf = new std::ofstream(yamlConf_filename);
151  saveOptions();
152 }
153 
160 void GOptions::defineSwitch(const std::string &name, const std::string &description) {
161  if (switches.find(name) == switches.end()) {
162  switches[name] = GSwitch(description);
163  } else {
164  std::cerr << FATALERRORL << "The " << YELLOWHHL << name << RSTHHR
165  << " switch is already present." << std::endl;
167  }
168 }
169 
176 void GOptions::defineOption(const GVariable &gvar, const std::string &help) {
177  if (doesOptionExist(gvar.name)) {
178  std::cerr << FATALERRORL << "The " << YELLOWHHL << gvar.name << RSTHHR
179  << " option is already present." << std::endl;
181  } else {
182  goptions.emplace_back(gvar, help);
183  }
184 }
185 
194 void GOptions::defineOption(const std::string &name, const std::string &description, const std::vector <GVariable> &g_vars,
195  const std::string &help) {
196  if (doesOptionExist(name)) {
197  std::cerr << FATALERRORL << "The " << YELLOWHHL << name << RSTHHR
198  << " option is already present." << std::endl;
200  } else {
201  goptions.emplace_back(name, description, g_vars, help);
202  }
203 }
204 
211 int GOptions::getScalarInt(const std::string &tag) const {
212  auto it = getOptionIterator(tag);
213  if (it == goptions.end()) {
214  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
215  << " was not found." << endl;
216  exit(EC__NOOPTIONFOUND);
217  }
218  return it->value.begin()->second.as<int>();
219 }
220 
227 float GOptions::getScalarFloat(const std::string &tag) const {
228  auto it = getOptionIterator(tag);
229  if (it == goptions.end()) {
230  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
231  << " was not found." << endl;
232  exit(EC__NOOPTIONFOUND);
233  }
234  return it->value.begin()->second.as<float>();
235 }
236 
243 double GOptions::getScalarDouble(const std::string &tag) const {
244  auto it = getOptionIterator(tag);
245  if (it == goptions.end()) {
246  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
247  << " was not found." << endl;
248  exit(EC__NOOPTIONFOUND);
249  }
250  return it->value.begin()->second.as<double>();
251 }
252 
259 string GOptions::getScalarString(const std::string &tag) const {
260  auto it = getOptionIterator(tag);
261  if (it == goptions.end()) {
262  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR
263  << " was not found." << endl;
264  exit(EC__NOOPTIONFOUND);
265  }
266  return it->value.begin()->second.as<string>();
267 }
268 
274 void GOptions::printOptionOrSwitchHelp(const std::string &tag) const {
275  auto switchIt = switches.find(tag);
276  if (switchIt != switches.end()) {
277  cout << KGRN << "-" << tag << RST << ": " << switchIt->second.getDescription() << endl << endl;
278  cout << TPOINTITEM << "Default value is " << (switchIt->second.getStatus() ? "on" : "off") << endl << endl;
279  exit(EXIT_SUCCESS);
280  }
281  for (const auto &goption: goptions) {
282  if (goption.name == tag) {
283  goption.printHelp(true);
284  exit(EXIT_SUCCESS);
285  }
286  }
287  cerr << FATALERRORL << "The " << YELLOWHHL << tag << RSTHHR
288  << " option is not known to this system." << endl;
289  exit(EC__NOOPTIONFOUND);
290 }
291 
299 vector <string> GOptions::findYamls(int argc, char *argv[]) {
300  vector <string> yaml_files;
301  for (int i = 1; i < argc; i++) {
302  string arg = argv[i];
303  size_t pos = arg.find(".yaml");
304  if (pos != string::npos) yaml_files.push_back(arg);
305  pos = arg.find(".yml");
306  if (pos != string::npos) yaml_files.push_back(arg);
307  }
308  return yaml_files;
309 }
310 
311 // checks if the option exists
312 bool GOptions::doesOptionExist(const std::string &tag) const {
313 
314  // [&tag] ensures we're referencing the original tag passed to the function
315  return std::any_of(goptions.begin(), goptions.end(),
316  [&tag](const auto& option) {
317  return option.name == tag;
318  });
319 }
320 
326 void GOptions::setOptionsValuesFromYamlFile(const std::string &yaml) {
327  YAML::Node config;
328  try {
329  config = YAML::LoadFile(yaml);
330  } catch (YAML::ParserException &e) {
331  cerr << FATALERRORL << "Error parsing " << YELLOWHHL << yaml << RSTHHR
332  << " yaml file." << endl;
333  cerr << e.what() << endl;
334  cerr << "Try validating the yaml file with an online yaml validator, e.g., https://www.yamllint.com" << endl;
336  }
337 
338  for (auto it = config.begin(); it != config.end(); ++it) {
339  auto option_name = it->first.as<std::string>();
340  auto option_it = getOptionIterator(option_name);
341  if (option_it == goptions.end()) {
342  if (switches.find(option_name) == switches.end()) {
343  cerr << FATALERRORL << "The option or switch " << YELLOWHHL << option_name << RSTHHR
344  << " is not known to this system." << endl;
345  exit(EC__NOOPTIONFOUND);
346  } else {
347  switches[option_name].turnOn();
348  }
349  } else {
350  YAML::NodeType::value type = it->second.Type();
351  switch (type) {
352  case YAML::NodeType::Scalar:
353  option_it->set_scalar_value(it->second.as<std::string>());
354  break;
355  case YAML::NodeType::Sequence:
356  option_it->set_value(it->second);
357  break;
358  case YAML::NodeType::Map:
359  option_it->set_value(it->second);
360  break;
361  default:
362  break;
363  }
364  }
365  }
366 }
367 
374 void GOptions::setOptionValuesFromCommandLineArgument(const std::string &optionName, const std::string &possibleYamlNode) {
375  YAML::Node node = YAML::Load(possibleYamlNode);
376  auto option_it = getOptionIterator(optionName);
377  if (node.Type() == YAML::NodeType::Scalar) {
378  option_it->set_scalar_value(possibleYamlNode);
379  } else {
380  option_it->set_value(node);
381  }
382 }
383 
390 std::vector<GOption>::iterator GOptions::getOptionIterator(const std::string &name) {
391  return std::find_if(goptions.begin(), goptions.end(),
392  [&name](GOption &option) { return option.name == name; });
393 }
394 
395 
402 std::vector<GOption>::const_iterator GOptions::getOptionIterator(const std::string &name) const {
403  return std::find_if(goptions.begin(), goptions.end(),
404  [&name](const GOption &option) { return option.name == name; });
405 }
406 
413 bool GOptions::getSwitch(const std::string &tag) const {
414  auto it = switches.find(tag);
415  if (it != switches.end()) {
416  return it->second.getStatus();
417  } else {
418  std::cerr << FATALERRORL << "The switch " << YELLOWHHL << tag << RSTHHR
419  << " was not found." << std::endl;
420  exit(EC__NOOPTIONFOUND);
421  }
422 }
423 
431 YAML::Node GOptions::getOptionMapInNode(string option_name, string map_key) const {
432  auto sequence_node = getOptionNode(option_name);
433  for (auto seq_item : sequence_node) {
434  for (auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
435  if (map_item->first.as<string>() == map_key) {
436  return map_item->second;
437  }
438  }
439  }
440  cerr << FATALERRORL << "The key " << YELLOWHHL << map_key << RSTHHR
441  << " was not found in " << YELLOWHHL << option_name << RSTHHR << endl;
442  exit(EC__NOOPTIONFOUND);
443  return sequence_node;
444 }
445 
455 template<typename T>
456 T GOptions::get_variable_in_option(const YAML::Node &node, const std::string &variable_name, const T &default_value) {
457  if (node[variable_name]) {
458  return node[variable_name].as<T>();
459  }
460  return default_value;
461 }
462 
463 // Explicit template instantiations.
464 template int GOptions::get_variable_in_option<int>(const YAML::Node &node, const std::string &variable_name, const int &default_value);
465 template float GOptions::get_variable_in_option<float>(const YAML::Node &node, const std::string &variable_name, const float &default_value);
466 template double GOptions::get_variable_in_option<double>(const YAML::Node &node, const std::string &variable_name, const double &default_value);
467 template string GOptions::get_variable_in_option<string>(const YAML::Node &node, const std::string &variable_name, const string &default_value);
468 template bool GOptions::get_variable_in_option<bool>(const YAML::Node &node, const std::string &variable_name, const bool &default_value);
469 
476 int GOptions::getVerbosityFor(const std::string &tag) const {
477  YAML::Node verbosity_node = getOptionNode("verbosity");
478  for (auto v : verbosity_node) {
479  if (v.begin()->first.as<string>() == tag) {
480  return v.begin()->second.as<int>();
481  }
482  }
483 
484  // not found. error
485  std::cerr << KRED << " Invalid verbosity or debug requested: " << tag << RST << std::endl;
486  exit(EC__NOOPTIONFOUND);
487 }
488 
497 int GOptions::getDebugFor(const std::string &tag) const {
498  YAML::Node debug_node = getOptionNode("debug");
499  for (auto d : debug_node) {
500  if (d.begin()->first.as<string>() == tag) {
501  YAML::Node valNode = d.begin()->second;
502  if (valNode.IsScalar()) {
503  string s = valNode.as<string>();
504  if (s == "true") return 1;
505  if (s == "false") return 0;
506  }
507  try {
508  return valNode.as<int>();
509  } catch (const YAML::BadConversion &) {
510  std::cerr << "Invalid debug value for " << tag << std::endl;
511  exit(EC__BAD_CONVERSION);
512  }
513  }
514  }
515  // not found. error
516  std::cerr << KRED << " Invalid verbosity or debug requested: " << tag << RST << std::endl;
517  exit(EC__NOOPTIONFOUND);}
518 
522 void GOptions::printHelp() const {
523  long int fill_width = string(HELPFILLSPACE).size() + 1;
524  cout.fill('.');
525  cout << KGRN << KBOLD << " " << executableName << RST << " [options] [yaml files]" << endl << endl;
526  cout << " Switches: " << endl << endl;
527  for (auto &s : switches) {
528  string help = "-" + s.first + RST + " ";
529  cout << KGRN << " " << left;
530  cout.width(fill_width);
531  cout << help;
532  cout << ": " << s.second.getDescription() << endl;
533  }
534  cout << endl;
535  cout << " Options: " << endl << endl;
536  for (auto &option : goptions) {
537  option.printHelp(false);
538  }
539  cout << endl;
540  cout << endl << " Help / Search / Introspection: " << endl << endl;
541  vector<string> helps = {
542  string("-h, --h, -help, --help") + RST,
543  string("print this help and exit"),
544  string("-hweb") + RST,
545  string("print this help in web format and exit"),
546  string("-v, --v, -version, --version") + RST,
547  string("print the version and exit\n"),
548  string("help <value>") + RST,
549  string("print detailed help for option <value> and exit"),
550  string("search <value>") + RST,
551  string("list all options containing <value> in the description and exit\n")
552  };
553  unsigned half_help = helps.size() / 2;
554  for (unsigned i = 0; i < half_help; i++) {
555  cout << KGRN << " " << left;
556  cout.width(fill_width);
557  cout << helps[i * 2] << ": " << helps[i * 2 + 1] << endl;
558  }
559  cout << endl;
560  cout << " Note: command line options overwrite YAML file(s)." << endl << endl;
561  exit(EXIT_SUCCESS);
562 }
563 
564 
565 
569 void GOptions::printWebHelp() const {
570  exit(EXIT_SUCCESS);
571 }
572 
576 void GOptions::saveOptions() const {
577  for (auto &s : switches) {
578  string status = s.second.getStatus() ? "true" : "false";
579  *yamlConf << s.first + ": " + status << "," << endl;
580  }
581  for (const auto &option : goptions) {
582  option.saveOption(yamlConf);
583  }
584  yamlConf->close();
585 }
586 
590 void GOptions::print_version() {
591  string asterisks = "**************************************************************";
592  cout << endl << asterisks << endl;
593  cout << " " << KGRN << KBOLD << executableName << RST << " version: " << KGRN << gversion << RST << endl;
594  cout << " Released on: " << KGRN << grelease_date << RST << endl;
595  cout << " Reference: " << KGRN << greference << RST << endl;
596  cout << " Homepage: " << KGRN << gweb << RST << endl;
597  cout << " Author: " << KGRN << gauthor << RST << endl << endl;
598  cout << asterisks << endl << endl;
599 }
600 
608 GOptions &operator+=(GOptions &gopts, const GOptions &goptions_to_add) {
609  gopts.addGOptions(goptions_to_add);
610  return gopts;
611 }
Represents a configurable option with a name, value(s), description, and help text.
Definition: goption.h:89
The GOptions class manages command-line options and switches.
Definition: goptions.h:20
void defineOption(const GVariable &gvar, const string &help)
Defines and adds a scalar option.
Definition: goptions.cc:176
bool getSwitch(const string &tag) const
Retrieves the status of a switch.
Definition: goptions.cc:413
int getDebugFor(const string &tag) const
Retrieves the debug level for the specified tag.
Definition: goptions.cc:497
T get_variable_in_option(const YAML::Node &node, const string &variable_name, const T &default_value)
Retrieves a variable from a YAML node within an option.
Definition: goptions.cc:456
int getVerbosityFor(const string &tag) const
Retrieves the verbosity level for the specified tag.
Definition: goptions.cc:476
YAML::Node getOptionMapInNode(string option_name, string map_key) const
Retrieves a map option’s value from within a YAML node.
Definition: goptions.cc:431
GOptions()
Default constructor.
Definition: goptions.h:27
int getScalarInt(const string &tag) const
Retrieves the value of a scalar integer option.
Definition: goptions.cc:211
void addGOptions(const GOptions &goptions_to_add)
Adds options from another GOptions object.
Definition: goptions.h:162
bool doesOptionExist(const string &tag) const
Checks if the specified option exists.
Definition: goptions.cc:312
float getScalarFloat(const string &tag) const
Retrieves the value of a scalar float option.
Definition: goptions.cc:227
void defineSwitch(const string &name, const string &description)
Defines and adds a command–line switch.
Definition: goptions.cc:160
string getScalarString(const string &tag) const
Retrieves the value of a scalar string option.
Definition: goptions.cc:259
double getScalarDouble(const string &tag) const
Retrieves the value of a scalar double option.
Definition: goptions.cc:243
Represents a switch with a description and a status.
Definition: gswitch.h:14
#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:608
Encapsulates a variable with a name, value, and description.
Definition: goption.h:30
string name
The name of the variable.
Definition: goption.h:31