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 // c style string
12 #include <cstring>
13 
14 using namespace std;
15 
16 
17 // constructor:
18 // - load user defined options, add goptions options
19 // - parse the yaml files
20 // - parse the command line options
21 GOptions::GOptions(int argc, char *argv[], const GOptions &user_defined_options) {
22 
23  executableName = gutilities::getFileFromPath(argv[0]);
24 
25  cout << endl;
26 
27  // copy the user defined options maps and switches onto ours
28  addGOptions(user_defined_options);
29 
30  // switches for all everyone
31  defineSwitch("gui", "use Graphical User Interface");
32  defineOption(GVariable("conf_yaml", "saved_configuration", "the yaml filename prefix where all used options are saved"),
33  "The default value appends \"_saved_configuration\" to the executable name.");
34 
35  // version is a special option, not settable by the user
36  // it is set by the gversion.h file
37  // we add it here so it can be saved to the yaml file
38  vector <GVariable> version = {
39  {"release", gversion, "release version number"},
40  {"release_date", grelease_date, "release date"},
41  {"Reference", greference, "article reference"},
42  {"Homepage", gweb, "homepage"},
43  {"Author", gauthor, "author"}
44  };
45  defineOption(GVERSION_STRING, "version information", version, "Version information. Not settable by user.");
46 
47 
48  vector <GVariable> verbosity = {
49  {"ghits", 0, "ghits verbosity"},
50  {"gmaterials", 0, "gmaterials verbosity"},
51  {"gevent_dispenser", 0, "event dispenser verbosity"},
52  {"grun", 0, "run verbosity"},
53  {"g4display", 0, "g4display verbositythe "},
54  {"gsystem", 0, "gsystem verbositythe "},
55  {"gfield", 0, "general fields verbosity"},
56  {"g4system", 0, "g4system verbosity"},
57  {"gparticle", 0, "gparticle verbosity"},
58  {"gphysics", 0, "gphysics verbosity"},
59  {"gstreamer_ev", 0, "gstreamer event verbosity"},
60  {"gstreamer_fr", 0, "gstreamer frame verbosity"},
61  {"gsensitivity", 0, "sensitivity verbosity"},
62  {"general", 0, "general verbosity"},
63  {"event", 0, "event verbosity"},
64  };
65 
66  string help = "Levels: \n \n";
67  help += "0: shush\n";
68  help += "1: summaryt\n";
69  help += "2: details\n";
70  help += "3: everything\n \n";
71  help += "Example: -verbosity=\"[{gsystem: 3}, {grun: 1}]\" \n";
72  defineOption("verbosity", "Sets the log verbosity for various categories", verbosity, help);
73 
74 
75  // parsing command line to check for help
76  for (int i = 1; i < argc; i++) {
77  if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--h") == 0 || strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0) {
78  printHelp();
79  } else if (strcmp(argv[i], "-hweb") == 0) {
80  printWebHelp();
81  } else if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--v") == 0 || strcmp(argv[i], "-version") == 0 || strcmp(argv[i], "--version") == 0) {
82  print_version();
83  exit(EXIT_SUCCESS);
84  } else if (strcmp(argv[i], "help") == 0) {
85  printOptionOrSwitchHelp(argv[i + 1]);
86  exit(EXIT_SUCCESS);
87  }
88  }
89 
90  // finds the yaml files
91  yaml_files = findYamls(argc, argv);
92 
93  // parse the yaml files
94  for (auto &yaml_file: yaml_files) {
95  cout << " Parsing " << yaml_file << endl;
96  setOptionsValuesFromYamlFile(yaml_file);
97  }
98 
99 
100  // parse command lines
101  // check that every option passed is either a switch, an option or a yaml file
102  for (int i = 1; i < argc; i++) {
103  string candidate = string(argv[i]);
104  // empty string
105  if (candidate == "") continue;
106 
107  if (find(yaml_files.begin(), yaml_files.end(), candidate) == yaml_files.end()) {
108 
109  // if the command line is not a yaml file, check that is a valid
110  // - switch: starts with a dash
111  // - option: a dash followed by a string and an equal sign
112  if (candidate[0] == '-') {
113 
114  // checking for a switch
115  string possible_switch = candidate.substr(1, candidate.size() - 1);
116 
117  // switch found, turn it on
118  if (switches.find(possible_switch) != switches.end()) {
119  switches[possible_switch].turnOn();
120  } else {
121  // not a switch, check if it is an option
122  // checking if '-' is present
123  if (possible_switch.find("=") != string::npos) {
124 
125  string possible_option = possible_switch.substr(0, candidate.find("=") - 1);
126 
127  // option found, parse it
128  if (doesOptionExist(possible_option)) {
129  string possible_yaml_node = possible_switch.substr(candidate.find("="), candidate.size() - 1);
130  setOptionValuesFromCommandLineArgument(possible_option, possible_yaml_node);
131  } else {
132  // option not found
133  cerr << FATALERRORL << "the " << YELLOWHHL << candidate << RSTHHR << " option is not known to this system. " << endl;
134  cerr << endl << " " << executableName << " -h for help." << endl << endl;
135  gexit(EC__NOOPTIONFOUND);
136 
137  }
138  } else {
139  // not a switch, not an option
140  cerr << FATALERRORL << YELLOWHHL << candidate << RSTHHR << " is not a valid command line option or switch. " << endl;
141  cerr << " Note: switches start with a dash; options start with a dash, and are followed by an equal sign and their desired value."
142  << endl;
143  cerr << endl << " Usage: " << endl << endl;
144  cerr << " " << executableName << " [options] [yaml files]" << endl;
145  cerr << " " << executableName << " -h for help." << endl << endl;
146  gexit(EC__NOOPTIONFOUND);
147  }
148  }
149  } else {
150  // not a file, not a switch, not an option
151  cerr << FATALERRORL << "the " << YELLOWHHL << candidate << RSTHHR << " command line is not known to this system. " << endl;
152  cerr << endl << " Usage: " << endl << endl;
153  cerr << " " << executableName << " [options] [yaml files]" << endl;
154  cerr << " " << executableName << " -h for help." << endl << endl;
155  gexit(EC__NOOPTIONFOUND);
156  }
157  }
158  }
159 
160  // print version no matter what
161  print_version();
162 
163  // save options to yaml
164  string yamlConf_filename = executableName + "." + getScalarString("conf_yaml") + ".yaml";
165  cout << " Saving options to " << yamlConf_filename << endl << endl;
166  yamlConf = new std::ofstream(yamlConf_filename);
167 
168  saveOptions();
169 }
170 
171 // define and add a command line switch to the map of switches
172 void 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 << " switch is already present." << std::endl;
178  }
179 }
180 
181 // add a simple option to the map of options
182 void GOptions::defineOption(const GVariable &gvar, const std::string &help) {
183 
184  if (doesOptionExist(gvar.name)) {
185  std::cerr << FATALERRORL << "the " << YELLOWHHL << gvar.name << RSTHHR << " option is already present." << std::endl;
187  } else {
188  goptions.push_back(GOption(gvar, help));
189  }
190 }
191 
192 // add a map option to the map of options
193 void GOptions::defineOption(const std::string &name, const std::string &description, const std::vector <GVariable> &g_vars, const std::string &help) {
194 
195  if (doesOptionExist(name)) {
196  std::cerr << FATALERRORL << "the " << YELLOWHHL << name << RSTHHR << " option is already present." << std::endl;
198  } else {
199  goptions.push_back(GOption(name, description, g_vars, help));
200  }
201 }
202 
203 int GOptions::getScalarInt(const std::string &tag) const {
204  auto it = getOptionIterator(tag);
205 
206  // if the option is not found, exit with error
207  if (it == goptions.end()) {
208  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR << " was not found." << endl;
209  gexit(EC__NOOPTIONFOUND);
210  }
211 
212  return it->value.begin()->second.as<int>();
213 }
214 
215 float GOptions::getScalarFloat(const std::string &tag) const {
216  auto it = getOptionIterator(tag);
217 
218  // if the option is not found, exit with error
219  if (it == goptions.end()) {
220  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR << " was not found." << endl;
221  gexit(EC__NOOPTIONFOUND);
222  }
223 
224  return it->value.begin()->second.as<float>();
225 }
226 
227 double GOptions::getScalarDouble(const std::string &tag) const {
228  auto it = getOptionIterator(tag);
229 
230  // if the option is not found, exit with error
231  if (it == goptions.end()) {
232  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR << " was not found." << endl;
233  gexit(EC__NOOPTIONFOUND);
234  }
235 
236  return it->value.begin()->second.as<double>();
237 }
238 
239 string GOptions::getScalarString(const std::string &tag) const {
240  auto it = getOptionIterator(tag);
241 
242  // if the option is not found, exit with error
243  if (it == goptions.end()) {
244  cerr << FATALERRORL << "The option " << YELLOWHHL << tag << RSTHHR << " was not found." << endl;
245  gexit(EC__NOOPTIONFOUND);
246  }
247 
248  return it->value.begin()->second.as<string>();
249 }
250 
251 void GOptions::printOptionOrSwitchHelp(const std::string& tag) const {
252  // Check if the tag is a switch
253  auto switchIt = switches.find(tag);
254  if (switchIt != switches.end()) {
255  cout << KGRN << "-" << tag << RST << ": " << switchIt->second.getDescription() << endl << endl;
256  cout << TPOINTITEM << "Default value is " << (switchIt->second.getStatus() ? "on" : "off") << endl << endl;
257  exit(EXIT_SUCCESS);
258  }
259 
260  // Check if the tag is an option
261  for (const auto& goption : goptions) { // Use const auto& to avoid copying and ensure const-correctness
262  if (goption.name == tag) {
263  goption.printHelp(true); // Assuming printHelp() is const-correct
264  exit(EXIT_SUCCESS);
265  }
266  }
267 
268  // If not found, print error and exit
269  cerr << FATALERRORL << "The " << YELLOWHHL << tag << RSTHHR << " option is not known to this system." << endl;
270  gexit(EC__NOOPTIONFOUND);
271 }
272 
273 
274 // Finds the (first) configuration file (yaml or yml extensions).
275 vector <string> GOptions::findYamls(int argc, char *argv[]) {
276  vector <string> yaml_files;
277 
278  for (int i = 1; i < argc; i++) {
279  string arg = argv[i];
280 
281  size_t pos = arg.find(".yaml");
282  if (pos != string::npos) yaml_files.push_back(arg);
283  pos = arg.find(".yml");
284  if (pos != string::npos) yaml_files.push_back(arg);
285  }
286 
287  return yaml_files;
288 }
289 
290 
291 // checks if the option exists
292 bool GOptions::doesOptionExist(const std::string& tag) const {
293  for (const auto& goption : goptions) {
294  // Use const auto& to avoid copying and to ensure const-correctness
295  if (goption.name == tag) {
296  return true;
297  }
298  }
299  return false;
300 }
301 
302 void GOptions::setOptionsValuesFromYamlFile(const std::string& yaml) {
303 
304  YAML::Node config;
305  try {
306  config = YAML::LoadFile(yaml);
307 
308  } catch (YAML::ParserException &e) {
309  cerr << FATALERRORL << "Error parsing " << YELLOWHHL << yaml << RSTHHR << " yaml file." << endl;
310  cerr << e.what() << endl;
311  cerr << "Try validating the yaml file with an online yaml validator, for example: https://www.yamllint.com" << endl;
312  gexit(EC__YAML_PARSING_ERROR);
313  }
314 
315  for (YAML::const_iterator it = config.begin(); it != config.end(); ++it) {
316 
317  string option_name = it->first.as<std::string>();
318  auto option_it = getOptionIterator(option_name);
319 
320  if (option_it == goptions.end()) {
321  if (switches.find(option_name) == switches.end()) {
322  cerr << FATALERRORL << "The option or switch " << YELLOWHHL << option_name << RSTHHR << " is not known to this system." << endl;
323  gexit(EC__NOOPTIONFOUND);
324  } else {
325  switches[option_name].turnOn();
326  }
327  } else {
328  YAML::NodeType::value type = it->second.Type(); // Cache the type to avoid repeated calls
329 
330  switch (type) {
331  case YAML::NodeType::Scalar:
332  option_it->set_scalar_value(it->second.as<std::string>());
333  break;
334  case YAML::NodeType::Sequence:
335  option_it->set_value(it->second);
336  break;
337  case YAML::NodeType::Map:
338  option_it->set_value(it->second);
339  break;
340  default:
341  break;
342 
343  }
344  }
345  }
346 }
347 
348 // parse a command line
349 void GOptions::setOptionValuesFromCommandLineArgument(const std::string& optionName, const std::string& possibleYamlNode) {
350  YAML::Node node = YAML::Load(possibleYamlNode);
351 
352  auto option_it = getOptionIterator(optionName);
353 
354  if (node.Type() == YAML::NodeType::Scalar) {
355  option_it->set_scalar_value(possibleYamlNode);
356  } else {
357  option_it->set_value(node);
358  }
359 }
360 
361 // Non-const version
362 std::vector<GOption>::iterator GOptions::getOptionIterator(const std::string& name) {
363  return std::find_if(goptions.begin(), goptions.end(),
364  [&name](GOption& option) { return option.name == name; });
365 }
366 
367 // Const version
368 std::vector<GOption>::const_iterator GOptions::getOptionIterator(const std::string& name) const {
369  return std::find_if(goptions.begin(), goptions.end(),
370  [&name](const GOption& option) { return option.name == name; });
371 }
372 
373 
374 bool GOptions::getSwitch(const std::string &tag) const {
375  // Use the find method to get an iterator to the switch
376  auto it = switches.find(tag);
377 
378  // Check if the iterator is not at the end, indicating the switch was found
379  if (it != switches.end()) {
380  return it->second.getStatus();
381  } else {
382  std::cerr << FATALERRORL << "The switch " << YELLOWHHL << tag << RSTHHR << " was not found." << std::endl;
383  gexit(EC__NOOPTIONFOUND);
384  }
385  return false; // This will never be reached due to gexit, but included for completeness
386 }
387 
388 YAML::Node GOptions::getOptionMapInNode(string option_name, string map_key) {
389 
390  auto sequence_node = getOptionNode(option_name);
391 
392  for (auto seq_item: sequence_node) {
393  for (auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
394  if (map_item->first.as<string>() == map_key) {
395  return map_item->second;
396  }
397  }
398  }
399 
400  // if the key is not found, exit with error
401  cerr << FATALERRORL << "The key " << YELLOWHHL << map_key << RSTHHR << " was not found in " << YELLOWHHL << option_name << RSTHHR << endl;
402  gexit(EC__NOOPTIONFOUND);
403 
404  return sequence_node;
405 }
406 
407 template<typename T>
408 T GOptions::get_variable_in_option(const YAML::Node &node, const string &variable_name, const T &default_value) {
409  if (node[variable_name]) {
410  return node[variable_name].as<T>();
411  }
412  return default_value;
413 }
414 
415 // Explicit instantiations
416 template int GOptions::get_variable_in_option<int>(const YAML::Node &node, const std::string &variable_name, const int &default_value);
417 
418 template float GOptions::get_variable_in_option<float>(const YAML::Node &node, const std::string &variable_name, const float &default_value);
419 
420 template double GOptions::get_variable_in_option<double>(const YAML::Node &node, const std::string &variable_name, const double &default_value);
421 
422 template string GOptions::get_variable_in_option<string>(const YAML::Node &node, const std::string &variable_name, const string &default_value);
423 
424 template bool GOptions::get_variable_in_option<bool>(const YAML::Node &node, const std::string &variable_name, const bool &default_value);
425 
426 int GOptions::getVerbosityFor(const std::string& tag) const{
427  auto verbosity_node = getOptionNode("verbosity");
428 
429  for (auto v: verbosity_node) {
430  if (v.begin()->first.as<string>() == tag) {
431  return v.begin()->second.as<int>();
432  }
433  }
434 
435  return 0;
436 }
437 
438 
439 // print only the non default settings set by users
440 void GOptions::printHelp() const {
441 
442  long int fill_width = string(HELPFILLSPACE).size() + 1;
443  cout.fill('.');
444 
445  cout << KGRN << KBOLD << " " << executableName << RST << " [options] [yaml files]" << endl << endl;
446  cout << " Switches: " << endl << endl;
447 
448  // switches help, belongs here cause of the map key
449  for (auto &s: switches) {
450  string help = "-" + s.first + RST + " ";
451  cout << KGRN << " " << left;
452  cout.width(fill_width);
453  cout << help;
454  cout << ": " << s.second.getDescription() << endl;
455  }
456  cout << endl;
457 
458  cout << " Options: " << endl << endl;
459 
460  for (auto &option: goptions) {
461  option.printHelp(false);
462  }
463 
464  cout << endl;
465 
466  cout << endl << " Help / Search / Introspection: " << endl << endl;
467 
468  vector <string> helps = {
469  string("-h, --h, -help, --help") + RST,
470  string("print this help and exit"),
471  string("-hweb") + RST,
472  string("print this help in web format and exit"),
473  string("-v, --v, -version, --version") + RST,
474  string("print the version and exit\n"), string("help <value>") + RST,
475  string("print detailed help for option <value> and exit"),
476  string("search <value>") + RST,
477  string("list all options containing <value> in the description and exit\n")
478  };
479  unsigned half_help = helps.size() / 2;
480  for (unsigned i = 0; i < half_help; i++) {
481  cout << KGRN << " " << left;
482  cout.width(fill_width);
483  cout << helps[i * 2] << ": " << helps[i * 2 + 1] << endl;
484  }
485  cout << endl;
486 
487 
488  cout << " Note: command line options overwrite yaml file(s). " << endl << endl;
489 
490  exit(EXIT_SUCCESS);
491 }
492 
493 
494 // print only the non default settings set by users
495 void GOptions::printWebHelp() const {
496 
497  exit(EXIT_SUCCESS);
498 }
499 
500 
501 // print options and switches values
502 void GOptions::saveOptions() const {
503 
504  for (auto &s: switches) {
505  string status = "false";
506  if (s.second.getStatus()) status = "true";
507  *yamlConf << s.first + ": " + status << "," << endl;
508  }
509 
510  for (const auto& option: goptions) {
511  option.saveOption(yamlConf);
512  }
513 
514  yamlConf->close();
515 }
516 
517 // introspection, add file option
518 void GOptions::print_version() {
519  string asterisks = "**************************************************************";
520  cout << endl << asterisks << endl;
521  cout << " " << KGRN << KBOLD << executableName << RST << " version: " << KGRN << gversion << RST << endl;
522  cout << " Released on: " << KGRN << grelease_date << RST << endl;
523  cout << " Reference: " << KGRN << greference << RST << endl;
524  cout << " Homepage: " << KGRN << gweb << RST << endl;
525  cout << " Author: " << KGRN << gauthor << RST << endl << endl;
526  cout << asterisks << endl << endl;
527 
528 }
529 
530 // overloaded operator to add option vectors and switch maps
531 GOptions &operator+=(GOptions &gopts, const GOptions& goptions_to_add) {
532  gopts.addGOptions(goptions_to_add);
533  return gopts;
534 }
Represents a configurable option with a name, value, description, and help text.
Definition: goption.h:86
The GOptions class manages command-line options and switches.
Definition: goptions.h:22
std::string getScalarString(const std::string &tag) const
Retrieves the value of a scalar string option.
Definition: goptions.cc:239
bool getSwitch(const std::string &tag) const
Retrieves the status of a switch.
Definition: goptions.cc:374
T get_variable_in_option(const YAML::Node &node, const string &variable_name, const T &default_value)
Retrieves a variable from a YAML::Node.
Definition: goptions.cc:408
void defineSwitch(const std::string &name, const std::string &description)
Defines and adds a command-line switch to the map of switches.
Definition: goptions.cc:172
float getScalarFloat(const std::string &tag) const
Retrieves the value of a scalar float option.
Definition: goptions.cc:215
GOptions()
Default constructor.
Definition: goptions.h:31
void defineOption(const GVariable &gvar, const std::string &help)
Defines and adds a scalar option to the map of options.
Definition: goptions.cc:182
double getScalarDouble(const std::string &tag) const
Retrieves the value of a scalar double option.
Definition: goptions.cc:227
void addGOptions(const GOptions &goptions_to_add)
Adds a set of GOptions to the current options.
Definition: goptions.h:158
int getScalarInt(const std::string &tag) const
Retrieves the value of a scalar integer option.
Definition: goptions.cc:203
int getVerbosityFor(const std::string &tag) const
Retrieves the verbosity level for a given tag.
Definition: goptions.cc:426
YAML::Node getOptionMapInNode(string option_name, string map_key)
Retrieves a map option within a YAML::Node.
Definition: goptions.cc:388
Represents a switch with a description and a status.
Definition: gswitch.h:14
#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)
Definition: goptions.cc:531
Encapsulates a variable with a name, value, and description.
Definition: goption.h:32
string name
The name of the variable.
Definition: goption.h:34