35bool parse_bool_token(
const std::string& raw,
bool& out) {
37 std::transform(v.begin(), v.end(), v.begin(),
38 [](
unsigned char c) { return static_cast<char>(std::tolower(c)); });
39 if (v ==
"true" || v ==
"1" || v ==
"yes" || v ==
"y" || v ==
"on") { out =
true;
return true; }
40 if (v ==
"false" || v ==
"0" || v ==
"no" || v ==
"n" || v ==
"off") { out =
false;
return true; }
65 defineSwitch(
"gui",
"run with the graphical user interface (Qt window)");
66 defineSwitch(
"i",
"drop into the interactive Geant4 terminal session (non-GUI mode)");
68 GVariable(
"conf_yaml",
"saved_configuration",
"infix for the YAML file that records the resolved options"),
69 "On exit the resolved configuration is written to <executable>.<conf_yaml>.yaml,\n"
70 "so the default value produces, for example, gemc.saved_configuration.yaml.\n \n"
71 "Example: -conf_yaml=run12 -> saves to gemc.run12.yaml\n \n");
75 "Milliseconds a GUI-based test waits before it auto-closes, so the module\n"
76 "example/test programs can run unattended in CI.\n \n"
77 "Example: -tt=1000\n \n");
82 vector<GVariable> version = {
83 {
"release", gversion,
"release version number"},
84 {
"release_date", grelease_date,
"release date"},
85 {
"Reference", greference,
"article reference"},
86 {
"Homepage", gweb,
"homepage"},
87 {
"Author", gauthor,
"author"}
92 string help =
"Levels: \n \n";
93 help +=
" - 0: (default) = shush\n";
94 help +=
" - 1: log detailed information\n";
95 help +=
" - 2: log extra detailed information\n \n";
96 help +=
"Each key names a class or module; run 'help verbosity' to list the available keys.\n \n";
97 help +=
"Example (one key): -verbosity.gemc=1\n";
98 help +=
"Example (several keys): -verbosity=\"[{gemc: 1}, {<another_key>: 2}]\"\n \n";
99 help +=
"Equivalent YAML:\n";
100 help +=
" verbosity:\n";
101 help +=
" - gemc: 1\n";
102 help +=
" - <another_key>: 2\n \n";
106 help =
"Debug information Types: \n \n";
107 help +=
" - false: (default): do not print debug information\n";
108 help +=
" - true: print debug information\n\n";
109 help +=
"Each key names a class or module; run 'help debug' to list the available keys.\n \n";
110 help +=
"Example (on/off): -debug.gemc=true\n";
111 help +=
"Example (several keys): -debug=\"[{gemc: true}, {<another_key>: 1}]\"\n \n";
112 help +=
"Equivalent YAML:\n";
114 help +=
" - gemc: true\n \n";
119 for (
int i = 1; i < argc; i++) {
120 if (strcmp(argv[i],
"-h") == 0 || strcmp(argv[i],
"--h") == 0 ||
121 strcmp(argv[i],
"-help") == 0 || strcmp(argv[i],
"--help") == 0) {
124 else if (strcmp(argv[i],
"-hweb") == 0) {
127 else if (strcmp(argv[i],
"-v") == 0 || strcmp(argv[i],
"--v") == 0 ||
128 strcmp(argv[i],
"-version") == 0 || strcmp(argv[i],
"--version") == 0) {
132 else if (strcmp(argv[i],
"help") == 0) {
134 if (i + 1 < argc) { printOptionOrSwitchHelp(argv[i + 1]); }
135 else { printHelp(); }
138 else if (strcmp(argv[i],
"search") == 0) {
140 if (i + 1 < argc) { printSearch(argv[i + 1]); }
141 else { printHelp(); }
148 yaml_files = findYamls(argc, argv);
149 for (
auto& yaml_file : yaml_files) {
150 cout <<
" Parsing " << yaml_file << endl;
151 setOptionsValuesFromYamlFile(yaml_file);
155 for (
int i = 1; i < argc; i++) {
156 string candidate = argv[i];
157 if (candidate.empty())
continue;
160 if (find(yaml_files.begin(), yaml_files.end(), candidate) != yaml_files.end())
continue;
162 if (candidate[0] ==
'-') {
163 string argStr = candidate.substr(1);
164 size_t eqPos = argStr.find(
'=');
166 if (eqPos != string::npos) {
167 string keyPart = argStr.substr(0, eqPos);
168 string valuePart = argStr.substr(eqPos + 1);
172 if (switches.find(keyPart) != switches.end()) {
174 if (parse_bool_token(valuePart, on)) { on ? switches[keyPart].turnOn() : switches[keyPart].turnOff(); }
176 cerr <<
"The switch " << keyPart <<
" accepts only true/false/yes/no/on/off/1/0." << endl;
183 if (!valuePart.empty() && valuePart.front() ==
'"' && valuePart.back() ==
'"') {
184 valuePart = valuePart.substr(1, valuePart.length() - 2);
188 size_t dotPos = keyPart.find(
'.');
189 if (dotPos != string::npos) {
190 string mainOption = keyPart.substr(0, dotPos);
191 string subOption = keyPart.substr(dotPos + 1);
194 auto it = getOptionIterator(mainOption);
195 it->set_sub_option_value(subOption, valuePart);
198 cerr <<
"The option " << mainOption <<
" is not known to this system." << endl;
205 setOptionValuesFromCommandLineArgument(keyPart, valuePart);
208 cerr <<
"The option " << keyPart <<
" is not known to this system." << endl;
215 const string& possibleSwitch = argStr;
216 if (switches.find(possibleSwitch) != switches.end()) {
217 switches[possibleSwitch].turnOn();
220 cerr <<
"The switch " << possibleSwitch <<
" is not known to this system." << endl;
226 cerr <<
"The command-line argument \"" << candidate <<
"\" is not valid." << endl;
235 string yamlConf_filename = executableName +
"." +
getScalarString(
"conf_yaml") +
".yaml";
236 cout <<
" Saving options to " << yamlConf_filename << endl << endl;
237 yamlConf =
new std::ofstream(yamlConf_filename);
243 if (switches.find(name) == switches.end()) {
244 switches[name] =
GSwitch(description, default_status);
248 <<
" switch is already present." << std::endl;
257 <<
" option is already present." << std::endl;
267 const std::vector<GVariable>& gvars,
268 const std::string& help) {
271 <<
" option is already present." << std::endl;
275 goptions.emplace_back(name, description, gvars, help);
281 auto it = getOptionIterator(tag);
284 <<
" was not found." << endl;
287 return it->value.begin()->second.as<
int>();
292 auto it = getOptionIterator(tag);
295 <<
" was not found." << endl;
298 return it->value.begin()->second.as<
double>();
303 auto it = getOptionIterator(tag);
306 <<
" was not found." << std::endl;
309 const YAML::Node node = it->value.begin()->second;
310 if (node.IsNull())
return "NULL";
311 return node.as<std::string>();
316void GOptions::printOptionOrSwitchHelp(
const std::string& tag)
const {
317 auto switchIt = switches.find(tag);
318 if (switchIt != switches.end()) {
319 cout <<
KGRN <<
"-" << tag <<
RST <<
": " << switchIt->second.getDescription() << endl << endl;
320 cout <<
TPOINTITEM <<
"Default value is " << (switchIt->second.getStatus() ?
"on" :
"off") << endl << endl;
323 for (
const auto& goption :
goptions) {
324 if (goption.name == tag) {
325 goption.printHelp(
true);
330 <<
" option is not known to this system." << endl;
335void GOptions::printSearch(
const std::string& tag)
const {
337 auto to_lower = [](
string s) {
338 transform(s.begin(), s.end(), s.begin(), [](
unsigned char c) { return std::tolower(c); });
341 const string needle = to_lower(tag);
342 auto matches = [&](
const string& a,
const string& b) {
343 return to_lower(a).find(needle) != string::npos || to_lower(b).find(needle) != string::npos;
348 cout <<
KGRN <<
KBOLD <<
" Options and switches matching \"" << tag <<
"\":" <<
RST << endl << endl;
351 for (
auto& s : switches) {
352 if (matches(s.first, s.second.getDescription())) {
354 cout <<
KGRN <<
" " << left;
355 cout.width(fill_width);
356 cout <<
"-" + s.first +
RST +
" " <<
": " << s.second.getDescription() << endl;
360 if (option.name !=
GVERSION_STRING && matches(option.name, option.description)) {
362 option.printHelp(
false);
365 if (!found) { cout <<
TPOINTITEM <<
"no match found." << endl; }
366 cout << endl <<
" Use " <<
KGRN <<
"help <value>" <<
RST <<
" for the detailed help of a single option." << endl
371vector<string> GOptions::findYamls(
int argc,
char* argv[]) {
372 vector<string> yaml_files;
373 auto ends_with = [](
const string& s,
const string& suffix) {
374 return s.size() >= suffix.size() &&
375 s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
377 for (
int i = 1; i < argc; i++) {
378 string arg = argv[i];
381 if (ends_with(arg,
".yaml") || ends_with(arg,
".yml")) yaml_files.push_back(arg);
391 [&tag](
const auto& option) {
392 return option.name == tag;
397void GOptions::setOptionsValuesFromYamlFile(
const std::string& yaml) {
400 config = YAML::LoadFile(yaml);
402 catch (YAML::BadFile& e) {
404 <<
". Check the path and spelling." << endl;
407 catch (YAML::ParserException& e) {
409 <<
" yaml file." << endl;
410 cerr << e.what() << endl;
411 cerr <<
"Try validating the yaml file with an online yaml validator, e.g., https://www.yamllint.com" << endl;
415 for (
auto it = config.begin(); it != config.end(); ++it) {
416 auto option_name = it->first.as<std::string>();
417 auto option_it = getOptionIterator(option_name);
421 if (switches.find(option_name) == switches.end()) {
423 <<
" is not known to this system." << endl;
430 if (it->second.IsScalar() && !parse_bool_token(it->second.as<std::string>(), on)) {
432 <<
" accepts only true/false/yes/no/on/off/1/0." << endl;
435 on ? switches[option_name].turnOn() : switches[option_name].turnOff();
439 YAML::NodeType::value type = it->second.Type();
441 case YAML::NodeType::Scalar:
442 option_it->set_scalar_value(it->second.as<std::string>());
444 case YAML::NodeType::Sequence:
445 option_it->set_value(it->second);
447 case YAML::NodeType::Map:
448 option_it->set_value(it->second);
458void GOptions::setOptionValuesFromCommandLineArgument(
const std::string& optionName,
459 const std::string& possibleYamlNode) {
460 YAML::Node node = YAML::Load(possibleYamlNode);
461 auto option_it = getOptionIterator(optionName);
463 if (node.Type() == YAML::NodeType::Scalar) {
464 option_it->set_scalar_value(possibleYamlNode);
467 option_it->set_value(node);
472 const std::string& possibleYamlNode) {
473 setOptionValuesFromCommandLineArgument(optionName, possibleYamlNode);
477std::vector<GOption>::iterator GOptions::getOptionIterator(
const std::string& name) {
479 [&name](
GOption& option) { return option.name == name; });
483std::vector<GOption>::const_iterator GOptions::getOptionIterator(
const std::string& name)
const {
485 [&name](
const GOption& option) { return option.name == name; });
490 auto it = switches.find(tag);
491 if (it != switches.end()) {
492 return it->second.getStatus();
496 <<
" was not found." << std::endl;
505 for (
auto seq_item : sequence_node) {
506 for (
auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
507 if (map_item->first.as<
string>() == map_key) {
508 return map_item->second;
521 if (node[variable_name]) {
522 return node[variable_name].as<T>();
524 return default_value;
529 const int& default_value);
531 const double& default_value);
533 const string& default_value);
535 const bool& default_value);
541 if (
v.begin()->first.as<
string>() ==
tag) {
542 return v.begin()->second.as<
int>();
547 std::cerr <<
KRED <<
" Invalid verbosity or debug requested: " <<
tag <<
RST << std::endl;
555 if (
d.begin()->first.as<
string>() ==
tag) {
556 YAML::Node
valNode =
d.begin()->second;
559 if (
s ==
"true")
return 1;
560 if (
s ==
"false")
return 0;
565 catch (
const YAML::BadConversion&) {
566 std::cerr <<
"Invalid debug value for " <<
tag << std::endl;
572 std::cerr <<
KRED <<
" Invalid verbosity or debug requested: " <<
tag <<
RST << std::endl;
577void GOptions::printHelp()
const {
580 cout <<
KGRN <<
KBOLD <<
" " << executableName <<
RST <<
" [options] [yaml files]" << endl << endl;
581 cout <<
" Switches: " << endl << endl;
582 for (
auto&
s : switches) {
583 string help =
"-" +
s.first +
RST +
" ";
584 cout <<
KGRN <<
" " << left;
587 cout <<
": " <<
s.second.getDescription() << endl;
590 cout <<
" Options: " << endl << endl;
595 cout << endl <<
" Help / Search / Introspection: " << endl << endl;
596 vector<string>
helps = {
597 string(
"-h, --h, -help, --help") +
RST,
598 string(
"print this help and exit"),
599 string(
"-hweb") +
RST,
600 string(
"print this help in web format and exit"),
601 string(
"-v, --v, -version, --version") +
RST,
602 string(
"print the version and exit\n"),
603 string(
"help <value>") +
RST,
604 string(
"print detailed help for option <value> and exit"),
605 string(
"search <value>") +
RST,
606 string(
"list all options/switches whose name or description contains <value> and exit\n")
610 cout <<
KGRN <<
" " << left;
612 cout <<
helps[
i * 2] <<
": " <<
helps[
i * 2 + 1] << endl;
615 cout <<
" Note: command line options overwrite YAML file(s)." << endl << endl;
620void GOptions::printWebHelp()
const {
625void GOptions::saveOptions()
const {
626 for (
auto&
s : switches) {
627 string status =
s.second.getStatus() ?
"true" :
"false";
628 *yamlConf <<
s.first +
": " + status <<
"," << endl;
631 option.saveOption(yamlConf);
637void GOptions::print_version() {
638 string asterisks =
"*******************************************************************";
641 cout <<
" Called from: " <<
KGRN << executableCallingDir <<
RST << endl;
642 cout <<
" Install: " <<
KGRN << installDir <<
"/bin" <<
RST << endl;
650 const char*
plugin_env = std::getenv(
"GEMC_PLUGIN_PATH");
661 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.
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.
void defineSwitch(const std::string &name, const std::string &description, bool default_status=false)
Defines and adds a command-line switch.
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).