36bool parse_bool_token(
const std::string& raw,
bool& out) {
38 std::transform(v.begin(), v.end(), v.begin(),
39 [](
unsigned char c) { return static_cast<char>(std::tolower(c)); });
40 if (v ==
"true" || v ==
"1" || v ==
"yes" || v ==
"y" || v ==
"on") { out =
true;
return true; }
41 if (v ==
"false" || v ==
"0" || v ==
"no" || v ==
"n" || v ==
"off") { out =
false;
return true; }
45std::string geant4_installation() {
46 const char* path_environment = std::getenv(
"PATH");
47 if (path_environment ==
nullptr) {
48 return "geant4-config not found (PATH is not set)";
51 const std::string path = path_environment;
52 std::size_t start = 0;
53 while (start <= path.size()) {
54 const std::size_t end = path.find(
':', start);
55 const std::string directory = path.substr(start, end - start);
56 const auto candidate = std::filesystem::path(directory.empty() ?
"." : directory) /
"geant4-config";
57 std::error_code error;
58 if (std::filesystem::is_regular_file(candidate, error)) {
59 auto resolved = std::filesystem::canonical(candidate, error);
63 return resolved.parent_path().parent_path().string();
65 if (end == std::string::npos) {
71 return "geant4-config not found in PATH";
95 defineSwitch(
"gui",
"run with the graphical user interface (Qt window)");
96 defineSwitch(
"i",
"drop into the interactive Geant4 terminal session (non-GUI mode)");
98 GVariable(
"conf_yaml",
"saved_configuration",
"infix for the YAML file that records the resolved options"),
99 "On exit the resolved configuration is written to <executable>.<conf_yaml>.yaml,\n"
100 "so the default value produces, for example, gemc.saved_configuration.yaml.\n \n"
101 "Example: -conf_yaml=run12 -> saves to gemc.run12.yaml\n \n");
105 "Milliseconds a GUI-based test waits before it auto-closes, so the module\n"
106 "example/test programs can run unattended in CI.\n \n"
107 "Example: -tt=1000\n \n");
112 vector<GVariable> version = {
113 {
"release", gversion,
"release version number"},
114 {
"release_date", grelease_date,
"release date"},
115 {
"Reference", greference,
"article reference"},
116 {
"Homepage", gweb,
"homepage"},
117 {
"Author", gauthor,
"author"}
120 "Version information. Not settable by user.");
123 string help =
"Levels: \n \n";
124 help +=
" - 0: (default) = shush\n";
125 help +=
" - 1: log detailed information\n";
126 help +=
" - 2: log extra detailed information\n \n";
127 help +=
"Each key names a class or module; run 'help verbosity' to list the available keys.\n \n";
128 help +=
"Example (one key): -verbosity.gemc=1\n";
129 help +=
"Example (several keys): -verbosity=\"[{gemc: 1}, {<another_key>: 2}]\"\n \n";
130 help +=
"Equivalent YAML:\n";
131 help +=
" verbosity:\n";
132 help +=
" - gemc: 1\n";
133 help +=
" - <another_key>: 2\n \n";
137 help =
"Debug information Types: \n \n";
138 help +=
" - false: (default): do not print debug information\n";
139 help +=
" - true: print debug information\n\n";
140 help +=
"Each key names a class or module; run 'help debug' to list the available keys.\n \n";
141 help +=
"Example (on/off): -debug.gemc=true\n";
142 help +=
"Example (several keys): -debug=\"[{gemc: true}, {<another_key>: 1}]\"\n \n";
143 help +=
"Equivalent YAML:\n";
145 help +=
" - gemc: true\n \n";
150 for (
int i = 1; i < argc; i++) {
151 if (strcmp(argv[i],
"-h") == 0 || strcmp(argv[i],
"--h") == 0 ||
152 strcmp(argv[i],
"-help") == 0 || strcmp(argv[i],
"--help") == 0) {
155 else if (strcmp(argv[i],
"-hweb") == 0) {
158 else if (strcmp(argv[i],
"-v") == 0 || strcmp(argv[i],
"--v") == 0 ||
159 strcmp(argv[i],
"-version") == 0 || strcmp(argv[i],
"--version") == 0) {
163 else if (strcmp(argv[i],
"help") == 0) {
165 if (i + 1 < argc) { printOptionOrSwitchHelp(argv[i + 1]); }
166 else { printHelp(); }
169 else if (strcmp(argv[i],
"search") == 0) {
171 if (i + 1 < argc) { printSearch(argv[i + 1]); }
172 else { printHelp(); }
179 yaml_files = findYamls(argc, argv);
180 for (
auto& yaml_file : yaml_files) {
181 cout <<
" Parsing " << yaml_file << endl;
182 setOptionsValuesFromYamlFile(yaml_file);
186 for (
int i = 1; i < argc; i++) {
187 string candidate = argv[i];
188 if (candidate.empty())
continue;
191 if (find(yaml_files.begin(), yaml_files.end(), candidate) != yaml_files.end())
continue;
193 if (candidate[0] ==
'-') {
194 string argStr = candidate.substr(1);
195 size_t eqPos = argStr.find(
'=');
197 if (eqPos != string::npos) {
198 string keyPart = argStr.substr(0, eqPos);
199 string valuePart = argStr.substr(eqPos + 1);
203 if (switches.find(keyPart) != switches.end()) {
205 if (parse_bool_token(valuePart, on)) { on ? switches[keyPart].turnOn() : switches[keyPart].turnOff(); }
207 cerr <<
"The switch " << keyPart <<
" accepts only true/false/yes/no/on/off/1/0." << endl;
214 if (!valuePart.empty() && valuePart.front() ==
'"' && valuePart.back() ==
'"') {
215 valuePart = valuePart.substr(1, valuePart.length() - 2);
219 size_t dotPos = keyPart.find(
'.');
220 if (dotPos != string::npos) {
221 string mainOption = keyPart.substr(0, dotPos);
222 string subOption = keyPart.substr(dotPos + 1);
225 auto it = getOptionIterator(mainOption);
226 it->set_sub_option_value(subOption, valuePart);
229 cerr <<
"The option " << mainOption <<
" is not known to this system." << endl;
236 setOptionValuesFromCommandLineArgument(keyPart, valuePart);
239 cerr <<
"The option " << keyPart <<
" is not known to this system." << endl;
246 const string& possibleSwitch = argStr;
247 if (switches.find(possibleSwitch) != switches.end()) {
248 switches[possibleSwitch].turnOn();
251 cerr <<
"The switch " << possibleSwitch <<
" is not known to this system." << endl;
257 cerr <<
"The command-line argument \"" << candidate <<
"\" is not valid." << endl;
267 cout <<
" Saving options to " << yamlConf_filename << endl << endl;
268 yamlConf =
new std::ofstream(yamlConf_filename);
274 if (switches.find(name) == switches.end()) {
275 switches[name] =
GSwitch(description, default_status);
279 <<
" switch is already present." << std::endl;
288 <<
" option is already present." << std::endl;
298 const std::vector<GVariable>& gvars,
299 const std::string& help) {
302 <<
" option is already present." << std::endl;
306 goptions.emplace_back(name, description, gvars, help);
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<
int>();
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<
int>();
341 auto it = getOptionIterator(tag);
344 <<
" was not found." << endl;
347 const YAML::Node node = it->value.begin()->second;
348 if (!node.IsDefined() || node.IsNull()) {
350 <<
" was not provided." << endl;
353 return node.as<
double>();
357 auto it = getOptionIterator(tag);
360 <<
" was not found." << endl;
363 const YAML::Node node = it->value.begin()->second;
364 if (!node.IsDefined() || node.IsNull())
return std::nullopt;
365 return node.as<
double>();
369 auto it = getOptionIterator(tag);
372 <<
" was not found." << endl;
375 const YAML::Node node = it->value.begin()->second;
376 if (!node.IsDefined() || node.IsNull()) {
378 <<
" was not provided." << endl;
381 return node.as<std::string>();
386 auto it = getOptionIterator(tag);
389 <<
" was not found." << std::endl;
392 const YAML::Node node = it->value.begin()->second;
393 if (!node.IsDefined() || node.IsNull())
return std::nullopt;
394 return node.as<std::string>();
399void GOptions::printOptionOrSwitchHelp(
const std::string& tag)
const {
400 auto switchIt = switches.find(tag);
401 if (switchIt != switches.end()) {
402 cout <<
guts::KGRN <<
"-" << tag <<
guts::RST <<
": " << switchIt->second.getDescription() << endl << endl;
403 cout <<
guts::TPOINTITEM <<
"Default value is " << (switchIt->second.getStatus() ?
"on" :
"off") << endl
407 for (
const auto& goption : goptions) {
408 if (goption.name == tag) {
409 goption.printHelp(
true);
414 <<
" option is not known to this system." << endl;
419void GOptions::printSearch(
const std::string& tag)
const {
421 auto to_lower = [](
string s) {
422 transform(s.begin(), s.end(), s.begin(), [](
unsigned char c) { return std::tolower(c); });
425 const string needle = to_lower(tag);
426 auto matches = [&](
const string& a,
const string& b) {
427 return to_lower(a).find(needle) != string::npos || to_lower(b).find(needle) != string::npos;
437 for (
auto& s : switches) {
438 if (matches(s.first, s.second.getDescription())) {
441 cout.width(fill_width);
442 cout <<
"-" + s.first +
guts::RST +
" " <<
": " << s.second.getDescription() << endl;
445 for (
auto& option : goptions) {
448 option.printHelp(
false);
453 <<
" Use " <<
guts::KGRN <<
"help <value>" <<
guts::RST <<
" for the detailed help of a single option."
459vector<string> GOptions::findYamls(
int argc,
char* argv[]) {
460 vector<string> yaml_files;
461 auto ends_with = [](
const string& s,
const string& suffix) {
462 return s.size() >= suffix.size() &&
463 s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
465 for (
int i = 1; i < argc; i++) {
466 string arg = argv[i];
469 if (ends_with(arg,
".yaml") || ends_with(arg,
".yml")) yaml_files.push_back(arg);
479 [&tag](
const auto& option) {
480 return option.name == tag;
485void GOptions::setOptionsValuesFromYamlFile(
const std::string& yaml) {
488 config = YAML::LoadFile(yaml);
490 catch (YAML::BadFile& e) {
492 <<
". Check the path and spelling." << endl;
495 catch (YAML::ParserException& e) {
497 <<
" yaml file." << endl;
498 cerr << e.what() << endl;
499 cerr <<
"Try validating the yaml file with an online yaml validator, e.g., https://www.yamllint.com" << endl;
503 for (
auto it = config.begin(); it != config.end(); ++it) {
504 auto option_name = it->first.as<std::string>();
505 auto option_it = getOptionIterator(option_name);
508 if (option_it == goptions.end()) {
509 if (switches.find(option_name) == switches.end()) {
511 <<
" is not known to this system." << endl;
518 if (it->second.IsScalar() && !parse_bool_token(it->second.as<std::string>(), on)) {
520 <<
" accepts only true/false/yes/no/on/off/1/0." << endl;
523 on ? switches[option_name].turnOn() : switches[option_name].turnOff();
527 YAML::NodeType::value type = it->second.Type();
529 case YAML::NodeType::Scalar:
530 option_it->set_scalar_value(it->second.as<std::string>());
532 case YAML::NodeType::Sequence:
533 option_it->set_value(it->second);
535 case YAML::NodeType::Map:
536 option_it->set_value(it->second);
546void GOptions::setOptionValuesFromCommandLineArgument(
const std::string& optionName,
547 const std::string& possibleYamlNode) {
548 auto option_it = getOptionIterator(optionName);
549 if (possibleYamlNode.empty()) {
550 option_it->set_scalar_value(
"");
554 YAML::Node node = YAML::Load(possibleYamlNode);
556 if (node.Type() == YAML::NodeType::Scalar) {
557 option_it->set_scalar_value(possibleYamlNode);
560 option_it->set_value(node);
565 const std::string& possibleYamlNode) {
566 setOptionValuesFromCommandLineArgument(optionName, possibleYamlNode);
570std::vector<GOption>::iterator GOptions::getOptionIterator(
const std::string& name) {
572 [&name](
GOption& option) { return option.name == name; });
576std::vector<GOption>::const_iterator GOptions::getOptionIterator(
const std::string& name)
const {
577 return std::find_if(goptions.begin(), goptions.end(),
578 [&name](
const GOption& option) { return option.name == name; });
583 auto it = switches.find(tag);
584 if (it != switches.end()) {
585 return it->second.getStatus();
589 <<
" was not found." << std::endl;
598 for (
auto seq_item : sequence_node) {
599 for (
auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
600 if (map_item->first.as<
string>() == map_key) {
601 return map_item->second;
614 if (node[variable_name]) {
615 return node[variable_name].as<T>();
617 return default_value;
622 if (!node[variable_name] || node[variable_name].IsNull()) {
627 return node[variable_name].as<T>();
632 const YAML::Node& node,
const std::string& variable_name) {
633 if (!node[variable_name] || node[variable_name].IsNull())
return std::nullopt;
634 return node[variable_name].as<T>();
639 const int& default_value);
641 const double& default_value);
643 const string& default_value);
645 const bool& default_value);
651 const YAML::Node&,
const std::string&);
660 for (
auto v : verbosity_node) {
661 if (v.begin()->first.as<
string>() == tag) {
662 return v.begin()->second.as<
int>();
667 std::cerr <<
guts::KRED <<
" Invalid verbosity or debug requested: " << tag <<
guts::RST << std::endl;
674 for (
auto d : debug_node) {
675 if (d.begin()->first.as<
string>() == tag) {
676 YAML::Node valNode = d.begin()->second;
677 if (valNode.IsScalar()) {
678 auto s = valNode.as<
string>();
679 if (s ==
"true")
return 1;
680 if (s ==
"false")
return 0;
683 return valNode.as<
int>();
685 catch (
const YAML::BadConversion&) {
686 std::cerr <<
"Invalid debug value for " << tag << std::endl;
692 std::cerr <<
guts::KRED <<
" Invalid verbosity or debug requested: " << tag <<
guts::RST << std::endl;
697void GOptions::printHelp()
const {
702 cout <<
" Switches: " << endl << endl;
703 for (
auto& s : switches) {
704 string help =
"-" + s.first +
guts::RST +
" ";
706 cout.width(fill_width);
708 cout <<
": " << s.second.getDescription() << endl;
711 cout <<
" Options: " << endl << endl;
712 for (
auto& option : goptions) {
713 option.printHelp(
false);
716 cout << endl <<
" Help / Search / Introspection: " << endl << endl;
717 vector<string> helps = {
718 string(
"-h, --h, -help, --help") +
guts::RST,
719 string(
"print this help and exit"),
721 string(
"print this help in web format and exit"),
722 string(
"-v, --v, -version, --version") +
guts::RST,
723 string(
"print the version and exit\n"),
725 string(
"print detailed help for option <value> and exit"),
727 string(
"list all options/switches whose name or description contains <value> and exit\n")
729 unsigned half_help = helps.size() / 2;
730 for (
unsigned i = 0; i < half_help; i++) {
732 cout.width(fill_width);
733 cout << helps[i * 2] <<
": " << helps[i * 2 + 1] << endl;
736 cout <<
" Note: command line options overwrite YAML file(s)." << endl << endl;
741void GOptions::printWebHelp()
const {
746void GOptions::saveOptions()
const {
747 for (
auto& s : switches) {
748 string status = s.second.getStatus() ?
"true" :
"false";
749 *yamlConf << s.first +
": " + status <<
"," << endl;
751 for (
const auto& option : goptions) {
752 option.saveOption(yamlConf);
758void GOptions::print_version() {
759 string asterisks =
"*******************************************************************";
760 cout << endl << asterisks << endl;
765 cout <<
" Geant4 Installation: " <<
guts::KGRN << geant4_installation() <<
guts::RST << endl;
774 const char* plugin_env = std::getenv(
"GEMC_PLUGIN_PATH");
775 if (!plugin_path.empty() || (plugin_env !=
nullptr && plugin_env[0] !=
'\0')) {
776 string combined = plugin_path;
777 if (plugin_env !=
nullptr && plugin_env[0] !=
'\0') {
778 if (!combined.empty()) combined +=
':';
779 combined += plugin_env;
787 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).