21#include <gemc/glogging/gmessageCatalog.h>
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; }
67 defineSwitch(
"gui",
"run with the graphical user interface (Qt window)");
68 defineSwitch(
"i",
"drop into the interactive Geant4 terminal session (non-GUI mode)");
70 "language for GEMC logging messages, startup banner, and GUI text"),
72 " auto: detect your language automatically (default).\n"
73 " en: English (original English messages).\n"
74 " it: italiano (Italian).\n"
75 " ja: 日本語 (Japanese).\n"
76 " fr: français (French).\n"
77 " zh: 中文 (Chinese).\n\n"
78 "Automatic selection checks GEMC_LANGUAGE, LC_ALL, LC_MESSAGES, LANG, then system settings.\n"
79 "Use -language=<code> or YAML language: <code> to override automatic selection.\n"
80 "Regional tags such as it_IT.UTF-8 are accepted; missing regional entries use the base language.\n"
81 "Missing languages or messages fall back to English. C and POSIX locales select English.\n"
82 "Numeric formatting is unchanged.\n");
84 GVariable(
"conf_yaml",
"saved_configuration",
"infix for the YAML file that records the resolved options"),
85 "On exit the resolved configuration is written to <executable>.<conf_yaml>.yaml,\n"
86 "so the default value produces, for example, gemc.saved_configuration.yaml.\n \n"
87 "Example: -conf_yaml=run12 -> saves to gemc.run12.yaml\n \n");
91 "Milliseconds a GUI-based test waits before it auto-closes, so the module\n"
92 "example/test programs can run unattended in CI.\n \n"
93 "Example: -tt=1000\n \n");
98 vector<GVariable> version = {
99 {
"release", gversion,
"release version number"},
100 {
"release_date", grelease_date,
"release date"},
101 {
"Reference", greference,
"article reference"},
102 {
"Homepage", gweb,
"homepage"},
103 {
"Author", gauthor,
"author"}
106 "Version information. Not settable by user.");
109 string help =
"Levels: \n \n";
110 help +=
" - 0: (default) = shush\n";
111 help +=
" - 1: log detailed information\n";
112 help +=
" - 2: log extra detailed information\n \n";
113 help +=
"Each key names a class or module; run 'help verbosity' to list the available keys.\n \n";
114 help +=
"Example (one key): -verbosity.gemc=1\n";
115 help +=
"Example (several keys): -verbosity=\"[{gemc: 1}, {<another_key>: 2}]\"\n \n";
116 help +=
"Equivalent YAML:\n";
117 help +=
" verbosity:\n";
118 help +=
" - gemc: 1\n";
119 help +=
" - <another_key>: 2\n \n";
123 help =
"Debug information Types: \n \n";
124 help +=
" - false: (default): do not print debug information\n";
125 help +=
" - true: print debug information\n\n";
126 help +=
"Each key names a class or module; run 'help debug' to list the available keys.\n \n";
127 help +=
"Example (on/off): -debug.gemc=true\n";
128 help +=
"Example (several keys): -debug=\"[{gemc: true}, {<another_key>: 1}]\"\n \n";
129 help +=
"Equivalent YAML:\n";
131 help +=
" - gemc: true\n \n";
135 yaml_files = findYamls(argc, argv);
140 validateCommandLineArguments(argc, argv, yaml_files);
144 for (
int i = 1; i < argc; i++) {
145 if (strcmp(argv[i],
"-h") == 0 || strcmp(argv[i],
"--h") == 0 ||
146 strcmp(argv[i],
"-help") == 0 || strcmp(argv[i],
"--help") == 0) {
149 else if (strcmp(argv[i],
"-hweb") == 0) {
152 else if (strcmp(argv[i],
"-v") == 0 || strcmp(argv[i],
"--v") == 0 ||
153 strcmp(argv[i],
"-version") == 0 || strcmp(argv[i],
"--version") == 0) {
155 for (
int argument = 1; argument < argc; ++argument) {
156 string candidate = argv[argument];
157 if (candidate.starts_with(
"--language=")) candidate.erase(0, 1);
158 if (candidate.starts_with(
"-language=")) {
159 string value = candidate.substr(
string(
"-language=").size());
160 if (value.size() >= 2 && value.front() ==
'"' && value.back() ==
'"') {
161 value = value.substr(1, value.size() - 2);
163 setOptionValuesFromCommandLineArgument(
"language", value);
169 else if (strcmp(argv[i],
"help") == 0) {
171 if (i + 1 < argc) { printOptionOrSwitchHelp(argv[i + 1]); }
172 else { printHelp(); }
175 else if (strcmp(argv[i],
"search") == 0) {
177 if (i + 1 < argc) { printSearch(argv[i + 1]); }
178 else { printHelp(); }
185 for (
auto& yaml_file : yaml_files) {
186 cout <<
" Parsing " << yaml_file << endl;
187 setOptionsValuesFromYamlFile(yaml_file);
191 for (
int i = 1; i < argc; i++) {
192 string candidate = argv[i];
193 if (candidate.empty())
continue;
196 if (find(yaml_files.begin(), yaml_files.end(), candidate) != yaml_files.end())
continue;
198 if (candidate[0] ==
'-') {
202 string argStr = candidate.substr(1);
203 if (!argStr.empty() && argStr[0] ==
'-') argStr = argStr.substr(1);
204 size_t eqPos = argStr.find(
'=');
206 if (eqPos != string::npos) {
207 string keyPart = argStr.substr(0, eqPos);
208 string valuePart = argStr.substr(eqPos + 1);
212 if (switches.contains(keyPart)) {
214 if (parse_bool_token(valuePart, on)) { on ? switches[keyPart].turnOn() : switches[keyPart].turnOff(); }
216 cerr <<
"The switch " << keyPart <<
" accepts only true/false/yes/no/on/off/1/0." << endl;
223 if (!valuePart.empty() && valuePart.front() ==
'"' && valuePart.back() ==
'"') {
224 valuePart = valuePart.substr(1, valuePart.length() - 2);
228 size_t dotPos = keyPart.find(
'.');
229 if (dotPos != string::npos) {
230 string mainOption = keyPart.substr(0, dotPos);
231 string subOption = keyPart.substr(dotPos + 1);
234 auto it = getOptionIterator(mainOption);
235 it->set_sub_option_value(subOption, valuePart);
237 else { reportUnknownNameAndExit(
"option", mainOption); }
242 setOptionValuesFromCommandLineArgument(keyPart, valuePart);
244 else { reportUnknownNameAndExit(
"option", keyPart); }
249 const string& possibleSwitch = argStr;
250 if (switches.contains(possibleSwitch)) {
251 switches[possibleSwitch].turnOn();
253 else { reportUnknownNameAndExit(
"switch", possibleSwitch); }
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.contains(name)) {
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::reportUnknownNameAndExit(
const std::string& kind,
const std::string& name)
const {
401 vector<string> known;
402 known.reserve(
goptions.size() + switches.size());
403 for (
const auto& option :
goptions) {
406 for (
const auto& s : switches) known.push_back(s.first);
409 <<
" is not known to this system." << endl;
415 <<
" for the list of options and switches." << endl;
420void GOptions::validateCommandLineArguments(
int argc,
char* argv[],
const vector<string>& yamls)
const {
422 static const vector<string> meta = {
423 "-h",
"--h",
"-help",
"--help",
"-hweb",
"-v",
"--v",
"-version",
"--version"
426 for (
int i = 1; i < argc; i++) {
427 string candidate = argv[i];
428 if (candidate.empty())
continue;
431 if (candidate ==
"help" || candidate ==
"search") {
432 if (i + 1 < argc) ++i;
435 if (find(meta.begin(), meta.end(), candidate) != meta.end())
continue;
436 if (find(yamls.begin(), yamls.end(), candidate) != yamls.end())
continue;
438 if (candidate[0] ==
'-') {
440 string argStr = candidate.substr(1);
441 if (!argStr.empty() && argStr[0] ==
'-') argStr = argStr.substr(1);
443 const size_t eqPos = argStr.find(
'=');
444 const bool hasValue = eqPos != string::npos;
445 string keyPart = hasValue ? argStr.substr(0, eqPos) : argStr;
448 const size_t dotPos = keyPart.find(
'.');
449 if (dotPos != string::npos) keyPart = keyPart.substr(0, dotPos);
452 reportUnknownNameAndExit(hasValue ?
"option" :
"switch", keyPart);
463void GOptions::printOptionOrSwitchHelp(
const std::string& tag)
const {
464 auto switchIt = switches.find(tag);
465 if (switchIt != switches.end()) {
466 cout <<
guts::KGRN <<
"-" << tag <<
guts::RST <<
": " << switchIt->second.getDescription() << endl << endl;
467 cout <<
guts::TPOINTITEM <<
"Default value is " << (switchIt->second.getStatus() ?
"on" :
"off") << endl
471 for (
const auto& goption : goptions) {
472 if (goption.name == tag) {
473 goption.printHelp(
true);
477 reportUnknownNameAndExit(
"option", tag);
481void GOptions::printSearch(
const std::string& tag)
const {
483 auto to_lower = [](
string s) {
484 transform(s.begin(), s.end(), s.begin(), [](
unsigned char c) { return std::tolower(c); });
487 const string needle = to_lower(tag);
488 auto matches = [&](
const string& a,
const string& b) {
489 return to_lower(a).find(needle) != string::npos || to_lower(b).find(needle) != string::npos;
499 for (
auto& s : switches) {
500 if (matches(s.first, s.second.getDescription())) {
503 cout.width(fill_width);
504 cout <<
"-" + s.first +
guts::RST +
" " <<
": " << s.second.getDescription() << endl;
507 for (
auto& option : goptions) {
510 option.printHelp(
false);
515 <<
" Use " <<
guts::KGRN <<
"help <value>" <<
guts::RST <<
" for the detailed help of a single option."
521vector<string> GOptions::findYamls(
int argc,
char* argv[]) {
522 vector<string> yaml_files;
523 for (
int i = 1; i < argc; i++) {
524 string arg = argv[i];
527 if (arg.ends_with(
".yaml") || arg.ends_with(
".yml")) yaml_files.push_back(arg);
537 [&tag](
const auto& option) {
538 return option.name == tag;
543void GOptions::setOptionsValuesFromYamlFile(
const std::string& yaml) {
546 config = YAML::LoadFile(yaml);
548 catch (YAML::BadFile& e) {
550 <<
". Check the path and spelling." << endl;
553 catch (YAML::ParserException& e) {
555 <<
" yaml file." << endl;
556 cerr << e.what() << endl;
557 cerr <<
"Try validating the yaml file with an online yaml validator, e.g., https://www.yamllint.com" << endl;
561 for (
auto it = config.begin(); it != config.end(); ++it) {
562 auto option_name = it->first.as<std::string>();
563 auto option_it = getOptionIterator(option_name);
566 if (option_it == goptions.end()) {
567 if (!switches.contains(option_name)) { reportUnknownNameAndExit(
"option or switch", option_name); }
572 if (it->second.IsScalar() && !parse_bool_token(it->second.as<std::string>(), on)) {
574 <<
" accepts only true/false/yes/no/on/off/1/0." << endl;
577 on ? switches[option_name].turnOn() : switches[option_name].turnOff();
581 YAML::NodeType::value type = it->second.Type();
583 case YAML::NodeType::Scalar:
584 option_it->set_scalar_value(it->second.as<std::string>());
586 case YAML::NodeType::Sequence:
587 option_it->set_value(it->second);
589 case YAML::NodeType::Map:
590 option_it->set_value(it->second);
600void GOptions::setOptionValuesFromCommandLineArgument(
const std::string& optionName,
601 const std::string& possibleYamlNode) {
602 auto option_it = getOptionIterator(optionName);
603 if (possibleYamlNode.empty()) {
604 option_it->set_scalar_value(
"");
608 YAML::Node node = YAML::Load(possibleYamlNode);
610 if (node.Type() == YAML::NodeType::Scalar) {
611 option_it->set_scalar_value(possibleYamlNode);
614 option_it->set_value(node);
619 const std::string& possibleYamlNode) {
620 setOptionValuesFromCommandLineArgument(optionName, possibleYamlNode);
624std::vector<GOption>::iterator GOptions::getOptionIterator(
const std::string& name) {
626 [&name](
GOption& option) { return option.name == name; });
630std::vector<GOption>::const_iterator GOptions::getOptionIterator(
const std::string& name)
const {
631 return std::find_if(goptions.begin(), goptions.end(),
632 [&name](
const GOption& option) { return option.name == name; });
637 auto it = switches.find(tag);
638 if (it != switches.end()) {
639 return it->second.getStatus();
643 <<
" was not found." << std::endl;
652 for (
auto seq_item : sequence_node) {
653 for (
auto map_item = seq_item.begin(); map_item != seq_item.end(); ++map_item) {
654 if (map_item->first.as<
string>() == map_key) {
655 return map_item->second;
668 if (node[variable_name]) {
669 return node[variable_name].as<T>();
671 return default_value;
676 if (!node[variable_name] || node[variable_name].IsNull()) {
681 return node[variable_name].as<T>();
686 const YAML::Node& node,
const std::string& variable_name) {
687 if (!node[variable_name] || node[variable_name].IsNull())
return std::nullopt;
688 return node[variable_name].as<T>();
693 const int& default_value);
695 const double& default_value);
697 const string& default_value);
699 const bool& default_value);
705 const YAML::Node&,
const std::string&);
714 for (
auto v : verbosity_node) {
715 if (v.begin()->first.as<
string>() == tag) {
716 return v.begin()->second.as<
int>();
721 std::cerr <<
guts::KRED <<
" Invalid verbosity or debug requested: " << tag <<
guts::RST << std::endl;
728 for (
auto d : debug_node) {
729 if (d.begin()->first.as<
string>() == tag) {
730 YAML::Node valNode = d.begin()->second;
731 if (valNode.IsScalar()) {
732 auto s = valNode.as<
string>();
733 if (s ==
"true")
return 1;
734 if (s ==
"false")
return 0;
737 return valNode.as<
int>();
739 catch (
const YAML::BadConversion&) {
740 std::cerr <<
"Invalid debug value for " << tag << std::endl;
746 std::cerr <<
guts::KRED <<
" Invalid verbosity or debug requested: " << tag <<
guts::RST << std::endl;
751void GOptions::printHelp()
const {
756 cout <<
" Switches: " << endl << endl;
757 for (
auto& s : switches) {
758 string help =
"-" + s.first +
guts::RST +
" ";
760 cout.width(fill_width);
762 cout <<
": " << s.second.getDescription() << endl;
765 cout <<
" Options: " << endl << endl;
766 for (
auto& option : goptions) {
767 option.printHelp(
false);
770 cout << endl <<
" Help / Search / Introspection: " << endl << endl;
771 vector<string> helps = {
772 string(
"-h, --h, -help, --help") +
guts::RST,
773 string(
"print this help and exit"),
775 string(
"print this help in web format and exit"),
776 string(
"-v, --v, -version, --version") +
guts::RST,
777 string(
"print the version and exit\n"),
779 string(
"print detailed help for option <value> and exit"),
781 string(
"list all options/switches whose name or description contains <value> and exit\n")
783 unsigned half_help = helps.size() / 2;
784 for (
unsigned i = 0; i < half_help; i++) {
786 cout.width(fill_width);
787 cout << helps[i * 2] <<
": " << helps[i * 2 + 1] << endl;
790 cout <<
" Note: command line options overwrite YAML file(s)." << endl << endl;
795void GOptions::printWebHelp()
const {
800void GOptions::saveOptions()
const {
801 for (
auto& s : switches) {
802 string status = s.second.getStatus() ?
"true" :
"false";
803 *yamlConf << s.first +
": " + status <<
"," << endl;
805 for (
const auto& option : goptions) {
806 option.saveOption(yamlConf);
812void GOptions::print_version() {
813 string asterisks =
"*******************************************************************";
814 cout << endl << asterisks << endl;
816 const auto colored = [](
const string& value) {
return string(
guts::KGRN) + value +
guts::RST; };
818 cout << catalog->format(
" %1 version: %2", executable, colored(gversion)) << endl;
819 cout << catalog->format(
" Called from: %1", colored(executableCallingDir)) << endl;
820 cout << catalog->format(
" Install: %1", colored(installDir)) << endl;
821 cout << catalog->format(
" Released on: %1", colored(grelease_date)) << endl;
829 const char* plugin_env = std::getenv(
"GEMC_PLUGIN_PATH");
830 if (!plugin_path.empty() || (plugin_env !=
nullptr && plugin_env[0] !=
'\0')) {
831 string combined = plugin_path;
832 if (plugin_env !=
nullptr && plugin_env[0] !=
'\0') {
833 if (!combined.empty()) combined +=
':';
834 combined += plugin_env;
836 cout << catalog->format(
" Plugin path: %1", colored(combined)) << endl;
839 cout << catalog->format(
" GEMC Reference: %1", colored(greference)) << endl;
840 cout << catalog->format(
" GEMC Homepage: %1", colored(gweb)) << endl;
841 cout << catalog->format(
" Author: %1", colored(gauthor)) << endl << endl;
842 cout << asterisks << endl << endl;
static std::shared_ptr< const GMessageCatalog > load(const GOptions *options=nullptr)
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()
std::optional< std::string > closestMatch(const std::string &query, const std::vector< std::string > &candidates, std::size_t max_distance=3)
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).