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; }
66 defineSwitch(
"gui",
"run with the graphical user interface (Qt window)");
67 defineSwitch(
"i",
"drop into the interactive Geant4 terminal session (non-GUI mode)");
69 GVariable(
"conf_yaml",
"saved_configuration",
"infix for the YAML file that records the resolved options"),
70 "On exit the resolved configuration is written to <executable>.<conf_yaml>.yaml,\n"
71 "so the default value produces, for example, gemc.saved_configuration.yaml.\n \n"
72 "Example: -conf_yaml=run12 -> saves to gemc.run12.yaml\n \n");
76 "Milliseconds a GUI-based test waits before it auto-closes, so the module\n"
77 "example/test programs can run unattended in CI.\n \n"
78 "Example: -tt=1000\n \n");
83 vector<GVariable> version = {
84 {
"release", gversion,
"release version number"},
85 {
"release_date", grelease_date,
"release date"},
86 {
"Reference", greference,
"article reference"},
87 {
"Homepage", gweb,
"homepage"},
88 {
"Author", gauthor,
"author"}
91 "Version information. Not settable by user.");
94 string help =
"Levels: \n \n";
95 help +=
" - 0: (default) = shush\n";
96 help +=
" - 1: log detailed information\n";
97 help +=
" - 2: log extra detailed information\n \n";
98 help +=
"Each key names a class or module; run 'help verbosity' to list the available keys.\n \n";
99 help +=
"Example (one key): -verbosity.gemc=1\n";
100 help +=
"Example (several keys): -verbosity=\"[{gemc: 1}, {<another_key>: 2}]\"\n \n";
101 help +=
"Equivalent YAML:\n";
102 help +=
" verbosity:\n";
103 help +=
" - gemc: 1\n";
104 help +=
" - <another_key>: 2\n \n";
108 help =
"Debug information Types: \n \n";
109 help +=
" - false: (default): do not print debug information\n";
110 help +=
" - true: print debug information\n\n";
111 help +=
"Each key names a class or module; run 'help debug' to list the available keys.\n \n";
112 help +=
"Example (on/off): -debug.gemc=true\n";
113 help +=
"Example (several keys): -debug=\"[{gemc: true}, {<another_key>: 1}]\"\n \n";
114 help +=
"Equivalent YAML:\n";
116 help +=
" - gemc: true\n \n";
121 for (
int i = 1; i < argc; i++) {
122 if (strcmp(argv[i],
"-h") == 0 || strcmp(argv[i],
"--h") == 0 ||
123 strcmp(argv[i],
"-help") == 0 || strcmp(argv[i],
"--help") == 0) {
126 else if (strcmp(argv[i],
"-hweb") == 0) {
129 else if (strcmp(argv[i],
"-v") == 0 || strcmp(argv[i],
"--v") == 0 ||
130 strcmp(argv[i],
"-version") == 0 || strcmp(argv[i],
"--version") == 0) {
134 else if (strcmp(argv[i],
"help") == 0) {
136 if (i + 1 < argc) { printOptionOrSwitchHelp(argv[i + 1]); }
137 else { printHelp(); }
140 else if (strcmp(argv[i],
"search") == 0) {
142 if (i + 1 < argc) { printSearch(argv[i + 1]); }
143 else { printHelp(); }
150 yaml_files = findYamls(argc, argv);
151 for (
auto& yaml_file : yaml_files) {
152 cout <<
" Parsing " << yaml_file << endl;
153 setOptionsValuesFromYamlFile(yaml_file);
157 for (
int i = 1; i < argc; i++) {
158 string candidate = argv[i];
159 if (candidate.empty())
continue;
162 if (find(yaml_files.begin(), yaml_files.end(), candidate) != yaml_files.end())
continue;
164 if (candidate[0] ==
'-') {
165 string argStr = candidate.substr(1);
166 size_t eqPos = argStr.find(
'=');
168 if (eqPos != string::npos) {
169 string keyPart = argStr.substr(0, eqPos);
170 string valuePart = argStr.substr(eqPos + 1);
174 if (switches.find(keyPart) != switches.end()) {
176 if (parse_bool_token(valuePart, on)) { on ? switches[keyPart].turnOn() : switches[keyPart].turnOff(); }
178 cerr <<
"The switch " << keyPart <<
" accepts only true/false/yes/no/on/off/1/0." << endl;
185 if (!valuePart.empty() && valuePart.front() ==
'"' && valuePart.back() ==
'"') {
186 valuePart = valuePart.substr(1, valuePart.length() - 2);
190 size_t dotPos = keyPart.find(
'.');
191 if (dotPos != string::npos) {
192 string mainOption = keyPart.substr(0, dotPos);
193 string subOption = keyPart.substr(dotPos + 1);
196 auto it = getOptionIterator(mainOption);
197 it->set_sub_option_value(subOption, valuePart);
200 cerr <<
"The option " << mainOption <<
" is not known to this system." << endl;
207 setOptionValuesFromCommandLineArgument(keyPart, valuePart);
210 cerr <<
"The option " << keyPart <<
" is not known to this system." << endl;
217 const string& possibleSwitch = argStr;
218 if (switches.find(possibleSwitch) != switches.end()) {
219 switches[possibleSwitch].turnOn();
222 cerr <<
"The switch " << possibleSwitch <<
" is not known to this system." << endl;
228 cerr <<
"The command-line argument \"" << candidate <<
"\" is not valid." << endl;
238 cout <<
" Saving options to " << yamlConf_filename << endl << endl;
239 yamlConf =
new std::ofstream(yamlConf_filename);
245 if (switches.find(name) == switches.end()) {
246 switches[name] =
GSwitch(description, default_status);
250 <<
" switch is already present." << std::endl;
259 <<
" option is already present." << std::endl;
269 const std::vector<GVariable>& gvars,
270 const std::string& help) {
273 <<
" option is already present." << std::endl;
277 goptions.emplace_back(name, description, gvars, help);
283 auto it = getOptionIterator(tag);
286 <<
" was not found." << endl;
289 const YAML::Node node = it->value.begin()->second;
290 if (!node.IsDefined() || node.IsNull()) {
292 <<
" was not provided." << endl;
295 return node.as<
int>();
299 auto it = getOptionIterator(tag);
302 <<
" was not found." << endl;
305 const YAML::Node node = it->value.begin()->second;
306 if (!node.IsDefined() || node.IsNull())
return std::nullopt;
307 return node.as<
int>();
312 auto it = getOptionIterator(tag);
315 <<
" was not found." << endl;
318 const YAML::Node node = it->value.begin()->second;
319 if (!node.IsDefined() || node.IsNull()) {
321 <<
" was not provided." << endl;
324 return node.as<
double>();
328 auto it = getOptionIterator(tag);
331 <<
" was not found." << endl;
334 const YAML::Node node = it->value.begin()->second;
335 if (!node.IsDefined() || node.IsNull())
return std::nullopt;
336 return node.as<
double>();
340 auto it = getOptionIterator(tag);
343 <<
" was not found." << endl;
346 const YAML::Node node = it->value.begin()->second;
347 if (!node.IsDefined() || node.IsNull()) {
349 <<
" was not provided." << endl;
352 return node.as<std::string>();
357 auto it = getOptionIterator(tag);
360 <<
" was not found." << std::endl;
363 const YAML::Node node = it->value.begin()->second;
364 if (!node.IsDefined() || node.IsNull())
return std::nullopt;
365 return node.as<std::string>();
370void GOptions::printOptionOrSwitchHelp(
const std::string& tag)
const {
371 auto switchIt = switches.find(tag);
372 if (switchIt != switches.end()) {
373 cout <<
guts::KGRN <<
"-" << tag <<
guts::RST <<
": " << switchIt->second.getDescription() << endl << endl;
374 cout <<
guts::TPOINTITEM <<
"Default value is " << (switchIt->second.getStatus() ?
"on" :
"off") << endl
378 for (
const auto& goption : goptions) {
379 if (goption.name == tag) {
380 goption.printHelp(
true);
385 <<
" option is not known to this system." << endl;
390void GOptions::printSearch(
const std::string& tag)
const {
392 auto to_lower = [](
string s) {
393 transform(s.begin(), s.end(), s.begin(), [](
unsigned char c) { return std::tolower(c); });
396 const string needle = to_lower(tag);
397 auto matches = [&](
const string& a,
const string& b) {
398 return to_lower(a).find(needle) != string::npos || to_lower(b).find(needle) != string::npos;
408 for (
auto& s : switches) {
409 if (matches(s.first, s.second.getDescription())) {
412 cout.width(fill_width);
413 cout <<
"-" + s.first +
guts::RST +
" " <<
": " << s.second.getDescription() << endl;
416 for (
auto& option : goptions) {
419 option.printHelp(
false);
424 <<
" Use " <<
guts::KGRN <<
"help <value>" <<
guts::RST <<
" for the detailed help of a single option."
430vector<string> GOptions::findYamls(
int argc,
char* argv[]) {
431 vector<string> yaml_files;
432 auto ends_with = [](
const string& s,
const string& suffix) {
433 return s.size() >= suffix.size() &&
434 s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
436 for (
int i = 1; i < argc; i++) {
437 string arg = argv[i];
440 if (ends_with(arg,
".yaml") || ends_with(arg,
".yml")) yaml_files.push_back(arg);
450 [&tag](
const auto& option) {
451 return option.name == tag;
456void GOptions::setOptionsValuesFromYamlFile(
const std::string& yaml) {
459 config = YAML::LoadFile(yaml);
461 catch (YAML::BadFile& e) {
463 <<
". Check the path and spelling." << endl;
466 catch (YAML::ParserException& e) {
468 <<
" yaml file." << endl;
469 cerr << e.what() << endl;
470 cerr <<
"Try validating the yaml file with an online yaml validator, e.g., https://www.yamllint.com" << endl;
474 for (
auto it = config.begin(); it != config.end(); ++it) {
475 auto option_name = it->first.as<std::string>();
476 auto option_it = getOptionIterator(option_name);
479 if (option_it == goptions.end()) {
480 if (switches.find(option_name) == switches.end()) {
482 <<
" is not known to this system." << endl;
489 if (it->second.IsScalar() && !parse_bool_token(it->second.as<std::string>(), on)) {
491 <<
" accepts only true/false/yes/no/on/off/1/0." << endl;
494 on ? switches[option_name].turnOn() : switches[option_name].turnOff();
498 YAML::NodeType::value type = it->second.Type();
500 case YAML::NodeType::Scalar:
501 option_it->set_scalar_value(it->second.as<std::string>());
503 case YAML::NodeType::Sequence:
504 option_it->set_value(it->second);
506 case YAML::NodeType::Map:
507 option_it->set_value(it->second);
517void GOptions::setOptionValuesFromCommandLineArgument(
const std::string& optionName,
518 const std::string& possibleYamlNode) {
519 auto option_it = getOptionIterator(optionName);
520 if (possibleYamlNode.empty()) {
521 option_it->set_scalar_value(
"");
525 YAML::Node node = YAML::Load(possibleYamlNode);
527 if (node.Type() == YAML::NodeType::Scalar) {
528 option_it->set_scalar_value(possibleYamlNode);
531 option_it->set_value(node);
536 const std::string& possibleYamlNode) {
537 setOptionValuesFromCommandLineArgument(optionName, possibleYamlNode);
541std::vector<GOption>::iterator GOptions::getOptionIterator(
const std::string& name) {
543 [&name](
GOption& option) { return option.name == name; });
547std::vector<GOption>::const_iterator GOptions::getOptionIterator(
const std::string& name)
const {
548 return std::find_if(goptions.begin(), goptions.end(),
549 [&name](
const GOption& option) { return option.name == name; });
554 auto it = switches.find(tag);
555 if (it != switches.end()) {
556 return it->second.getStatus();
560 <<
" was not found." << std::endl;
569 for (
auto seq_item : sequence_node) {
570 for (
auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
571 if (map_item->first.as<
string>() == map_key) {
572 return map_item->second;
585 if (node[variable_name]) {
586 return node[variable_name].as<T>();
588 return default_value;
593 if (!node[variable_name] || node[variable_name].IsNull()) {
598 return node[variable_name].as<T>();
603 const YAML::Node& node,
const std::string& variable_name) {
604 if (!node[variable_name] || node[variable_name].IsNull())
return std::nullopt;
605 return node[variable_name].as<T>();
610 const int& default_value);
612 const double& default_value);
614 const string& default_value);
616 const bool& default_value);
622 const YAML::Node&,
const std::string&);
631 for (
auto v : verbosity_node) {
632 if (v.begin()->first.as<
string>() == tag) {
633 return v.begin()->second.as<
int>();
638 std::cerr <<
guts::KRED <<
" Invalid verbosity or debug requested: " << tag <<
guts::RST << std::endl;
645 for (
auto d : debug_node) {
646 if (d.begin()->first.as<
string>() == tag) {
647 YAML::Node valNode = d.begin()->second;
648 if (valNode.IsScalar()) {
649 auto s = valNode.as<
string>();
650 if (s ==
"true")
return 1;
651 if (s ==
"false")
return 0;
654 return valNode.as<
int>();
656 catch (
const YAML::BadConversion&) {
657 std::cerr <<
"Invalid debug value for " << tag << std::endl;
663 std::cerr <<
guts::KRED <<
" Invalid verbosity or debug requested: " << tag <<
guts::RST << std::endl;
668void GOptions::printHelp()
const {
673 cout <<
" Switches: " << endl << endl;
674 for (
auto& s : switches) {
675 string help =
"-" + s.first +
guts::RST +
" ";
677 cout.width(fill_width);
679 cout <<
": " << s.second.getDescription() << endl;
682 cout <<
" Options: " << endl << endl;
683 for (
auto& option : goptions) {
684 option.printHelp(
false);
687 cout << endl <<
" Help / Search / Introspection: " << endl << endl;
688 vector<string> helps = {
689 string(
"-h, --h, -help, --help") +
guts::RST,
690 string(
"print this help and exit"),
692 string(
"print this help in web format and exit"),
693 string(
"-v, --v, -version, --version") +
guts::RST,
694 string(
"print the version and exit\n"),
696 string(
"print detailed help for option <value> and exit"),
698 string(
"list all options/switches whose name or description contains <value> and exit\n")
700 unsigned half_help = helps.size() / 2;
701 for (
unsigned i = 0; i < half_help; i++) {
703 cout.width(fill_width);
704 cout << helps[i * 2] <<
": " << helps[i * 2 + 1] << endl;
707 cout <<
" Note: command line options overwrite YAML file(s)." << endl << endl;
712void GOptions::printWebHelp()
const {
717void GOptions::saveOptions()
const {
718 for (
auto& s : switches) {
719 string status = s.second.getStatus() ?
"true" :
"false";
720 *yamlConf << s.first +
": " + status <<
"," << endl;
722 for (
const auto& option : goptions) {
723 option.saveOption(yamlConf);
729void GOptions::print_version() {
730 string asterisks =
"*******************************************************************";
731 cout << endl << asterisks << endl;
744 const char* plugin_env = std::getenv(
"GEMC_PLUGIN_PATH");
745 if (!plugin_path.empty() || (plugin_env !=
nullptr && plugin_env[0] !=
'\0')) {
746 string combined = plugin_path;
747 if (plugin_env !=
nullptr && plugin_env[0] !=
'\0') {
748 if (!combined.empty()) combined +=
':';
749 combined += plugin_env;
757 cout << asterisks << endl << 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.
double getRequiredScalarDouble(const std::string &tag) const
Retrieves the required value of a scalar double option.
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.
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.
bool doesOptionExist(const std::string &tag) const
Checks if an option exists.
void addGOptions(const GOptions &src)
Merges options and switches from another GOptions : into this one.
std::optional< std::string > getOptionalScalarString(const std::string &tag) const
Retrieves the optional value of a scalar string option.
std::optional< double > getOptionalScalarDouble(const std::string &tag) const
std::optional< T > get_optional_variable_in_option(const YAML::Node &node, const std::string &variable_name)
std::string getRequiredScalarString(const std::string &tag) const
Retrieves the required value of a scalar string option.
int getDebugFor(const std::string &tag) const
Retrieves the debug level for the specified tag.
std::optional< int > getOptionalScalarInt(const std::string &tag) const
int getVerbosityFor(const std::string &tag) const
Retrieves the verbosity level for the specified tag.
T get_required_variable_in_option(const YAML::Node &node, const std::string &variable_name)
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.
int getRequiredScalarInt(const std::string &tag) const
Retrieves the required value of a scalar integer option.
Represents a boolean command-line switch with a description and a status.
Conventions, constants, and error codes for the GOptions : / GOption : subsystem.
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.
constexpr int EC__NOOPTIONFOUND
Option/switch/key not found, or invalid command-line token.
constexpr int EC__DEFINED_SWITCHALREADYPRESENT
Attempted to define a switch name more than once.
constexpr int EC__BAD_CONVERSION
YAML value could not be converted to requested type.
constexpr int EC__YAML_PARSING_ERROR
YAML file failed to parse (syntax error or parser failure).
constexpr int EC__MANDATORY_NOT_FILLED
Mandatory structured option key missing.
constexpr char GVERSION_STRING[]
Reserved option tag used to store version information.
constexpr int EC__DEFINED_OPTION_ALREADY_PRESENT
Attempted to define an option name more than once.
constexpr char HELPFILLSPACE[]
Padding used when printing option/switch help.
std::filesystem::path executable_path()
string getDirFromPath(const std::string &path)
string getFileFromPath(const std::string &path)
constexpr char YELLOWHHL[]
constexpr char FATALERRORL[]
constexpr char TPOINTITEM[]
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).