50 defineSwitch(
"gui",
"run with the graphical user interface (Qt window)");
51 defineSwitch(
"i",
"drop into the interactive Geant4 terminal session (non-GUI mode)");
53 GVariable(
"conf_yaml",
"saved_configuration",
"infix for the YAML file that records the resolved options"),
54 "On exit the resolved configuration is written to <executable>.<conf_yaml>.yaml,\n"
55 "so the default value produces, for example, gemc.saved_configuration.yaml.\n \n"
56 "Example: -conf_yaml=run12 -> saves to gemc.run12.yaml\n \n");
60 "Milliseconds a GUI-based test waits before it auto-closes, so the module\n"
61 "example/test programs can run unattended in CI.\n \n"
62 "Example: -tt=1000\n \n");
67 vector<GVariable> version = {
68 {
"release", gversion,
"release version number"},
69 {
"release_date", grelease_date,
"release date"},
70 {
"Reference", greference,
"article reference"},
71 {
"Homepage", gweb,
"homepage"},
72 {
"Author", gauthor,
"author"}
77 string help =
"Levels: \n \n";
78 help +=
" - 0: (default) = shush\n";
79 help +=
" - 1: log detailed information\n";
80 help +=
" - 2: log extra detailed information\n \n";
81 help +=
"Each key names a class or module; run 'help verbosity' to list the available keys.\n \n";
82 help +=
"Example (one key): -verbosity.gemc=1\n";
83 help +=
"Example (several keys): -verbosity=\"[{gemc: 1}, {<another_key>: 2}]\"\n \n";
84 help +=
"Equivalent YAML:\n";
85 help +=
" verbosity:\n";
86 help +=
" - gemc: 1\n";
87 help +=
" - <another_key>: 2\n \n";
91 help =
"Debug information Types: \n \n";
92 help +=
" - false: (default): do not print debug information\n";
93 help +=
" - true: print debug information\n\n";
94 help +=
"Each key names a class or module; run 'help debug' to list the available keys.\n \n";
95 help +=
"Example (on/off): -debug.gemc=true\n";
96 help +=
"Example (several keys): -debug=\"[{gemc: true}, {<another_key>: 1}]\"\n \n";
97 help +=
"Equivalent YAML:\n";
99 help +=
" - gemc: true\n \n";
104 for (
int i = 1; i < argc; i++) {
105 if (strcmp(argv[i],
"-h") == 0 || strcmp(argv[i],
"--h") == 0 ||
106 strcmp(argv[i],
"-help") == 0 || strcmp(argv[i],
"--help") == 0) {
109 else if (strcmp(argv[i],
"-hweb") == 0) {
112 else if (strcmp(argv[i],
"-v") == 0 || strcmp(argv[i],
"--v") == 0 ||
113 strcmp(argv[i],
"-version") == 0 || strcmp(argv[i],
"--version") == 0) {
117 else if (strcmp(argv[i],
"help") == 0) {
119 if (i + 1 < argc) { printOptionOrSwitchHelp(argv[i + 1]); }
120 else { printHelp(); }
123 else if (strcmp(argv[i],
"search") == 0) {
125 if (i + 1 < argc) { printSearch(argv[i + 1]); }
126 else { printHelp(); }
133 yaml_files = findYamls(argc, argv);
134 for (
auto& yaml_file : yaml_files) {
135 cout <<
" Parsing " << yaml_file << endl;
136 setOptionsValuesFromYamlFile(yaml_file);
140 for (
int i = 1; i < argc; i++) {
141 string candidate = argv[i];
142 if (candidate.empty())
continue;
145 if (find(yaml_files.begin(), yaml_files.end(), candidate) != yaml_files.end())
continue;
147 if (candidate[0] ==
'-') {
148 string argStr = candidate.substr(1);
149 size_t eqPos = argStr.find(
'=');
151 if (eqPos != string::npos) {
152 string keyPart = argStr.substr(0, eqPos);
153 string valuePart = argStr.substr(eqPos + 1);
157 if (switches.find(keyPart) != switches.end()) {
158 if (valuePart ==
"true" || valuePart ==
"1") { switches[keyPart].turnOn(); }
159 else if (valuePart ==
"false" || valuePart ==
"0") { switches[keyPart].turnOff(); }
161 cerr <<
"The switch " << keyPart <<
" accepts only true/false." << endl;
168 if (!valuePart.empty() && valuePart.front() ==
'"' && valuePart.back() ==
'"') {
169 valuePart = valuePart.substr(1, valuePart.length() - 2);
173 size_t dotPos = keyPart.find(
'.');
174 if (dotPos != string::npos) {
175 string mainOption = keyPart.substr(0, dotPos);
176 string subOption = keyPart.substr(dotPos + 1);
179 auto it = getOptionIterator(mainOption);
180 it->set_sub_option_value(subOption, valuePart);
183 cerr <<
"The option " << mainOption <<
" is not known to this system." << endl;
190 setOptionValuesFromCommandLineArgument(keyPart, valuePart);
193 cerr <<
"The option " << keyPart <<
" is not known to this system." << endl;
200 const string& possibleSwitch = argStr;
201 if (switches.find(possibleSwitch) != switches.end()) {
202 switches[possibleSwitch].turnOn();
205 cerr <<
"The switch " << possibleSwitch <<
" is not known to this system." << endl;
211 cerr <<
"The command-line argument \"" << candidate <<
"\" is not valid." << endl;
220 string yamlConf_filename = executableName +
"." +
getScalarString(
"conf_yaml") +
".yaml";
221 cout <<
" Saving options to " << yamlConf_filename << endl << endl;
222 yamlConf =
new std::ofstream(yamlConf_filename);
228 if (switches.find(name) == switches.end()) {
229 switches[name] =
GSwitch(description);
233 <<
" switch is already present." << std::endl;
242 <<
" option is already present." << std::endl;
252 const std::vector<GVariable>& gvars,
253 const std::string& help) {
256 <<
" option is already present." << std::endl;
260 goptions.emplace_back(name, description, gvars, help);
266 auto it = getOptionIterator(tag);
269 <<
" was not found." << endl;
272 return it->value.begin()->second.as<
int>();
277 auto it = getOptionIterator(tag);
280 <<
" was not found." << endl;
283 return it->value.begin()->second.as<
double>();
288 auto it = getOptionIterator(tag);
291 <<
" was not found." << std::endl;
294 const YAML::Node node = it->value.begin()->second;
295 if (node.IsNull())
return "NULL";
296 return node.as<std::string>();
301void GOptions::printOptionOrSwitchHelp(
const std::string& tag)
const {
302 auto switchIt = switches.find(tag);
303 if (switchIt != switches.end()) {
304 cout <<
KGRN <<
"-" << tag <<
RST <<
": " << switchIt->second.getDescription() << endl << endl;
305 cout <<
TPOINTITEM <<
"Default value is " << (switchIt->second.getStatus() ?
"on" :
"off") << endl << endl;
308 for (
const auto& goption :
goptions) {
309 if (goption.name == tag) {
310 goption.printHelp(
true);
315 <<
" option is not known to this system." << endl;
320void GOptions::printSearch(
const std::string& tag)
const {
322 auto to_lower = [](
string s) {
323 transform(s.begin(), s.end(), s.begin(), [](
unsigned char c) { return std::tolower(c); });
326 const string needle = to_lower(tag);
327 auto matches = [&](
const string& a,
const string& b) {
328 return to_lower(a).find(needle) != string::npos || to_lower(b).find(needle) != string::npos;
333 cout <<
KGRN <<
KBOLD <<
" Options and switches matching \"" << tag <<
"\":" <<
RST << endl << endl;
336 for (
auto& s : switches) {
337 if (matches(s.first, s.second.getDescription())) {
339 cout <<
KGRN <<
" " << left;
340 cout.width(fill_width);
341 cout <<
"-" + s.first +
RST +
" " <<
": " << s.second.getDescription() << endl;
345 if (option.name !=
GVERSION_STRING && matches(option.name, option.description)) {
347 option.printHelp(
false);
350 if (!found) { cout <<
TPOINTITEM <<
"no match found." << endl; }
351 cout << endl <<
" Use " <<
KGRN <<
"help <value>" <<
RST <<
" for the detailed help of a single option." << endl
356vector<string> GOptions::findYamls(
int argc,
char* argv[]) {
357 vector<string> yaml_files;
358 auto ends_with = [](
const string& s,
const string& suffix) {
359 return s.size() >= suffix.size() &&
360 s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
362 for (
int i = 1; i < argc; i++) {
363 string arg = argv[i];
366 if (ends_with(arg,
".yaml") || ends_with(arg,
".yml")) yaml_files.push_back(arg);
376 [&tag](
const auto& option) {
377 return option.name == tag;
382void GOptions::setOptionsValuesFromYamlFile(
const std::string& yaml) {
385 config = YAML::LoadFile(yaml);
387 catch (YAML::BadFile& e) {
389 <<
". Check the path and spelling." << endl;
392 catch (YAML::ParserException& e) {
394 <<
" yaml file." << endl;
395 cerr << e.what() << endl;
396 cerr <<
"Try validating the yaml file with an online yaml validator, e.g., https://www.yamllint.com" << endl;
400 for (
auto it = config.begin(); it != config.end(); ++it) {
401 auto option_name = it->first.as<std::string>();
402 auto option_it = getOptionIterator(option_name);
406 if (switches.find(option_name) == switches.end()) {
408 <<
" is not known to this system." << endl;
412 switches[option_name].turnOn();
416 YAML::NodeType::value type = it->second.Type();
418 case YAML::NodeType::Scalar:
419 option_it->set_scalar_value(it->second.as<std::string>());
421 case YAML::NodeType::Sequence:
422 option_it->set_value(it->second);
424 case YAML::NodeType::Map:
425 option_it->set_value(it->second);
435void GOptions::setOptionValuesFromCommandLineArgument(
const std::string& optionName,
436 const std::string& possibleYamlNode) {
437 YAML::Node node = YAML::Load(possibleYamlNode);
438 auto option_it = getOptionIterator(optionName);
440 if (node.Type() == YAML::NodeType::Scalar) {
441 option_it->set_scalar_value(possibleYamlNode);
444 option_it->set_value(node);
449 const std::string& possibleYamlNode) {
450 setOptionValuesFromCommandLineArgument(optionName, possibleYamlNode);
454std::vector<GOption>::iterator GOptions::getOptionIterator(
const std::string& name) {
456 [&name](
GOption& option) { return option.name == name; });
460std::vector<GOption>::const_iterator GOptions::getOptionIterator(
const std::string& name)
const {
462 [&name](
const GOption& option) { return option.name == name; });
467 auto it = switches.find(tag);
468 if (it != switches.end()) {
469 return it->second.getStatus();
473 <<
" was not found." << std::endl;
482 for (
auto seq_item : sequence_node) {
483 for (
auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
484 if (map_item->first.as<
string>() == map_key) {
485 return map_item->second;
498 if (node[variable_name]) {
499 return node[variable_name].as<T>();
501 return default_value;
506 const int& default_value);
508 const double& default_value);
510 const string& default_value);
512 const bool& default_value);
518 if (
v.begin()->first.as<
string>() ==
tag) {
519 return v.begin()->second.as<
int>();
524 std::cerr <<
KRED <<
" Invalid verbosity or debug requested: " <<
tag <<
RST << std::endl;
532 if (
d.begin()->first.as<
string>() ==
tag) {
533 YAML::Node
valNode =
d.begin()->second;
536 if (
s ==
"true")
return 1;
537 if (
s ==
"false")
return 0;
542 catch (
const YAML::BadConversion&) {
543 std::cerr <<
"Invalid debug value for " <<
tag << std::endl;
549 std::cerr <<
KRED <<
" Invalid verbosity or debug requested: " <<
tag <<
RST << std::endl;
554void GOptions::printHelp()
const {
557 cout <<
KGRN <<
KBOLD <<
" " << executableName <<
RST <<
" [options] [yaml files]" << endl << endl;
558 cout <<
" Switches: " << endl << endl;
559 for (
auto&
s : switches) {
560 string help =
"-" +
s.first +
RST +
" ";
561 cout <<
KGRN <<
" " << left;
564 cout <<
": " <<
s.second.getDescription() << endl;
567 cout <<
" Options: " << endl << endl;
572 cout << endl <<
" Help / Search / Introspection: " << endl << endl;
573 vector<string>
helps = {
574 string(
"-h, --h, -help, --help") +
RST,
575 string(
"print this help and exit"),
576 string(
"-hweb") +
RST,
577 string(
"print this help in web format and exit"),
578 string(
"-v, --v, -version, --version") +
RST,
579 string(
"print the version and exit\n"),
580 string(
"help <value>") +
RST,
581 string(
"print detailed help for option <value> and exit"),
582 string(
"search <value>") +
RST,
583 string(
"list all options/switches whose name or description contains <value> and exit\n")
587 cout <<
KGRN <<
" " << left;
589 cout <<
helps[
i * 2] <<
": " <<
helps[
i * 2 + 1] << endl;
592 cout <<
" Note: command line options overwrite YAML file(s)." << endl << endl;
597void GOptions::printWebHelp()
const {
602void GOptions::saveOptions()
const {
603 for (
auto&
s : switches) {
604 string status =
s.second.getStatus() ?
"true" :
"false";
605 *yamlConf <<
s.first +
": " + status <<
"," << endl;
608 option.saveOption(yamlConf);
614void GOptions::print_version() {
615 string asterisks =
"*******************************************************************";
618 cout <<
" Called from: " <<
KGRN << executableCallingDir <<
RST << endl;
619 cout <<
" Install: " <<
KGRN << installDir <<
"/bin" <<
RST << endl;
627 const char*
plugin_env = std::getenv(
"GEMC_PLUGIN_PATH");
638 cout <<
" GEMC Homepage: " <<
KGRN <<
gweb <<
RST << endl;
Stores one configuration option (scalar or structured), including schema defaults and current value.
Parses, stores, and exposes command-line options and YAML configuration values.
bool getSwitch(const std::string &tag) const
Retrieves the status of a switch.
YAML::Node getOptionNode(const std::string &tag) const
Retrieves the YAML node for the specified option.
void setOptionValueFromString(const std::string &optionName, const std::string &possibleYamlNode)
Updates an option value from a YAML-formatted string.
void defineSwitch(const std::string &name, const std::string &description)
Defines and adds a command-line switch.
std::string getScalarString(const std::string &tag) const
Retrieves the value of a scalar string option.
GOptions()
Default constructor.
void defineOption(const GVariable &gvar, const std::string &help)
Defines and adds a scalar option.
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.
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.
double getScalarDouble(const std::string &tag) const
Retrieves the value of a scalar double option.
bool doesOptionExist(const std::string &tag) const
Checks if an option exists.
int getScalarInt(const std::string &tag) const
Retrieves the value of a scalar integer option.
void addGOptions(const GOptions &src)
Merges options and switches from another GOptions : into this one.
int getDebugFor(const std::string &tag) const
Retrieves the debug level for the specified tag.
int getVerbosityFor(const std::string &tag) const
Retrieves the verbosity level for the specified tag.
std::vector< GVariable > option_verbosity_names
Schema entries used to define the verbosity and debug structured options.
Represents a boolean command-line switch with a description and a status.
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.
Public interface for GOptions : the YAML + command-line configuration manager.
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.
std::string name
Variable name (option name for scalar options, schema key name for structured options).