// ************************************************************************** /** @class Configuration @brief Commandline parsing, resource file parsing and database access @section User For the user The Configuration class will process the following steps: Check the command-line for --default=default.rc (If no configuration filename is given on the command-line use \e program_name.rc instead. (Note that the name is retrieved from \b argv[0] and might change if you start the program through a symbolic link with a different name) Read the "database=user:password@database:port/database" entry from the file. (For details about the syntax see Configuration::parse_database) The retrieved entry can be overwritten by "--database=user:passwd@server:port/database" from the command line. If neither option is given no configuration data will be read from the database. To suppress any database access use \b --no-database. Check the command-line for -C priority.rc The configuration data is now evaluated from the following sources in the following order. Note that options from earlier source have priority. - (1) Commandline options - (2) Options from the high prioroty configuration-file (given by \b -C or \b --config) - (3) Database entries - (4) Options from the default configuration-file (given by \b --default, defaults to \b program_name.rc) - (5) Options from the global configuration-file (constrctor path + \b fact++.rc) - (6) Environment variables Which options are accepted is defined by the program. To get a list of all command-line option use \b --help. This also lists all other available options to list for exmaple the options available in the configuration files or from the databse. In addition some default options are available which allow to debug parsing of the options, by either printing the options retrieval or after parsing. Options in the configuration files must be given in the form - key = value which is equivalent to the command-line option --key=value. If there are sections in the configuration file like \code [section1] key = value \endcode the key is transformed into section1.key (which would be equivalent to --section1.key) @attention In principle it is possible that an exception is thrown before options like \b --help are properly parsed and evaluated. In this case it is necessary to first resolve the problem. Usually, this mean that there is a design flaw in the program rather than a mistake of usage. For more details on the order in which configuration is read, check Configuration::Parse. For more details on the parsing itself see the documentation of boost::program_options. @section API For the programmer The Configuration class heavily uses the C++ boost library and makes heavy use of the boost::program_options The databse access is based on the MySQL++ library. The basic idea is to have an easy to use, but powerfull setup. The setup on all options is based on a special syntax of options_description. Here is an example: \code int opt = 0; po::options_description config("Section"); config.add_options() ("option1", var(), "This is option1") ("option2", var(22), "This is option2") ("option3,o", var->required(), "This option is mandatory") ("option4", var(&opt), "This is option 4") ("option5", vars(), "A list of strings") ("option6", vars(), "A list of strings") ("option7", vars, "A list of strings") ("option8", var()->implicit_value("val"), "Just a string") ("option9", var()->default_value("def"), "Just a string") ("optionA", var("def"), "Just a string") ("bool", po_bool(), "A special switch") ; \endcode This will setup, e.g., the commandline option '--option1 arg' (which is identical to '--option1=arg'. Option 3 can also be expressed in a short form as '-o arg' or '-o=arg'. Option 2 defaults to 22 if no explicit value is given. Option 3 is mandatory and an exceptionb is thrown if not specified. Option 4 will, apart from the usual access to the option values, also store its value in the variable opt. The used functions po_*() are defined in configuration.h and are abbreviations. Generally speaking also other variable types are possible. If the options are displayed, e.g. by \b --help the corresponding section will by titled \e Section, also untitled sections are possible. If an option can be given more than once then a std::vector can be used. Abbreviations po_ints(), po_doubles() and po_strings() are available. There are several ways to define the behaviour of the options. In the example above Parse will throw an exception if the "--option3" or "-o" option is not given. "option9" will evaluate to "def" if it is not given on the command line. The syntax of "optionA" is just an abbreviation. "option8" will evaluate to "val" if just "--option5" but no argument is given. Note, that these modifiers can be concatenated. A special type po_bool() is provided which is an abbreviation of var()->implicit_value(true)->default_value(false). In contradiction to po_switch() this allows to set a true and false value in the setup file. In addition to options introduced by a minus or double minus, so called positional options can be given on the command line. To describe these options use \code po::positional_options_description p; p.add("option5", 2); // The first 2 positional options p.add("option6", 3); // The next three positional options // p.add("option7", -1); // All others, if wanted \endcode This assigns option-keys to the positional options (arguments) in the command-line. Note that the validity of the given commandline is checked. Hence, this way of defining the options makes sense. As needed options_descriptions can be grouped together \code po::options_description config1("Section1"); po::options_description config2("Section2"); po::options_description configall; configall.add(config1); configall.add(config2); \endcode The member functions of Configurations allow to define for which option source these options are valid. The member functions are: \code Configuration conf; conf.AddOptionsCommandline(configall, true); conf.AddOptionsConfigfile(config1, true); conf.AddOptionsDatabase(config2, true); // To enable the mapping of the position arguments call this conf.SetArgumentPositions(p); \endcode If the second option is false, the options will not be displayed in any \b --help directive, but are available to the user. Each of the functions can be called more than once. If an option should be available from all kind of inputs AddOptions() can be used which will call all four other AddOptions() functions. A special case are the options from environment variables. Since you might want to use the same option-key for the command-line and the environment, a mapping is needed (e.g. from \b PATH to \b --path). This mapping can be implemented by a mapping function or by the build in mapping and be initialized like this: \code conf.AddEnv("path", "PATH"); \endcode or \code const string name_mapper(const string str) { return str=="PATH" ? "path" : ""; } conf.SetNameMapper(name_mapper); \endcode Assuming all the above is done in a function calles SetupConfiguration(), a simple program to demonstrate the power of the class could look like this: \code int main(int argc, char **argv) { int opt; Configuration conf(argv[0]); SetupConfiguration(conf, opt); po::variables_map vm; try { vm = conf.Parse(argc, argv); } catch (std::exception &e) { po::multiple_occurrences *MO = dynamic_cast(&e); if (MO) cout << "Error: " << e.what() << " of '" << MO->get_option_name() << "' option." << endl; else cout << "Error: " << e.what() << endl; cout << endl; return -1; } cout << "Opt1: " << conf.GetString("option1") << endl; cout << "Opt2: " << conf.GetInt("option2") << endl; cout << "Opt3: " << conf.GetDouble("option3") << endl; cout << "Opt4: " << opt << endl; return 0; } \endcode Another possibility to access the result is the direct approach, for example: \code vector i = vm["option2"].as(); vector vec = vm["option6"].as>(); \endcode Note that accessing an option which was not given will throw an exception. Therefor its availability should first be checked in one of the following ways: \code bool has_option1 = vm.count("option1"); bool has_option2 = conf.Has("option2"); \endcode @section Extensions The configuration interpreter can be easily extended to new types, for example: \code template // Just for the output std::ostream &operator<<(std::ostream &out, const pair &f) { out << f.first << "|" << f.second; return out; } template // Needed to convert the option std::istream &operator>>(std::istream &in, pair &f) { char c; in >> f.first; in >> c; if (c!=':') return in; in >> f.second; return in; } typedef pair mytype; // Type definition void main(int argc, char **argv) { po::options_description config("Configuration"); config.add_options() ("mytype", var(), "my new type") ; Configuration conf; conf.AddOptionsCommandline(config); conf.Parse(argc, argv); cout << conf.Get("mytype") << endl; } \endcode @section Examples - An example can be found in \ref argv.cc @todo - Maybe we should remove the necessity to propagate argv[0] in the constructor? - Add an option to the constructor to switch of database/file access */ // ************************************************************************** #include "Configuration.h" #include #include #include #include #include #define HAS_SQL #ifdef HAS_SQL #include #endif using namespace std; namespace style = boost::program_options::command_line_style; // -------------------------------------------------------------------------- // //! The purpose of this function is basically to connect to the database, //! and retrieve all the options entries from the 'Configuration' table. //! //! @param database //! The URL of the database from which the configuration data is //! retrieved. It should be given in the form //! \li [user[:password]@]server.com[:port]/database //! //! with //! - user: user name (default is the current user) //! - password: necessary if required by the database rights //! - server: the URL of the server (can be 'localhost') //! - port: the port to which to connect (usually obsolete) //! - database: The name of the database containing the table //! //! @param desc //! A reference to the object with the description of the options //! which should be retrieved. //! //! @param allow_unregistered //! If this is true also unregistered, i.e. options unknown to desc, //! are returned. Otherwise an exception is thrown if such an option //! was retrieved. //! //! @return //! Return an object of type basic_parsed_options containing all //! the entries retrieved from the database. Options not found in //! desc are flagged as unregistered. //! //! @throws //! Two types of exceptions are thrown //! - It thows an unnamed exception if the options could not be //! retrieved properly from the databse. //! - If an option is not registered within the given descriptions //! and \b allow_unregistered is \b false, an exception of type //! \b po::unknown_option is thrown. //! //! @todo //! - The exceptions handling should be improved. //! - The final database layout is missing in the description //! - Shell we allow options to be given more than once? // #ifdef HAS_SQL po::basic_parsed_options Configuration::parse_database(const string &prgname, const string &database, const po::options_description& desc, bool allow_unregistered) { //static const boost::regex expr("(([[:word:].-]+)(:(.+))?@)?([[:word:].-]+)(:([[:digit:]]+))?(/([[:word:].-]+))?"); static const boost::regex expr("(([[:word:].-]+)(:(.+))?@)?([[:word:].-]+)(:([[:digit:]]+))?(/([[:word:].-]+))"); // 2: user // 4: pass // 5: server // 7: port // 9: db boost::smatch what; if (!boost::regex_match(database, what, expr, boost::match_extra)) throw runtime_error("Couldn't parse '"+database+"'."); if (what.size()!=10) throw runtime_error("Error parsing '"+database+"'."); const string user = what[2]; const string passwd = what[4]; const string server = what[5]; const string db = what[9]; const int port = atoi(string(what[7]).c_str()); cout << "Connecting to '"; if (!user.empty()) cout << user << "@"; cout << server; if (port) cout << ":" << port; if (!db.empty()) cout << "/" << db; cout << "' for " << prgname << endl; mysqlpp::Connection conn(db.c_str(), server.c_str(), user.c_str(), passwd.c_str(), port); /* throws exceptions if (!conn.connected()) { cout << "MySQL connection error: " << conn.error() << endl; throw; }*/ const mysqlpp::StoreQueryResult res = conn.query("SELECT CONCAT(fKey1,fKey2), fValue " "FROM ProgramOption " "WHERE fCounter=(SELECT MAX(fCounter) FROM History) " "AND NOT ISNULL(fValue) " "AND (fProgram='"+prgname+"' OR fProgram='*')").store(); /* throws exceptions if (!res) { cout << "MySQL query failed: " << query.error() << endl; throw; }*/ set allowed_options; const vector> &options = desc.options(); for (unsigned i=0; i::const_iterator v=res.begin(); v Throw exception continue; // Check if we are allowed to accept unregistered options, // i.e. options which are not in options_description &desc. const bool unregistered = allowed_options.find(key)==allowed_options.end(); if (unregistered && allow_unregistered) boost::throw_exception(po::unknown_option(key)); // Create a key/value-pair and store whether it is a // registered option of not po::option n; n.string_key = key; // This is now identical to file parsing. What if we want // to concatenate options like on the command line? n.value.clear(); // Fixme: composing? n.value.push_back((*v)[1].c_str()); //n.unregistered = unregistered; // If any parsing will be done in the future... //n.value().original_tokens.clear(); //n.value().original_tokens.push_back(name); //n.value().original_tokens.push_back(value); result.options.push_back(n); } return result; } #else po::basic_parsed_options Configuration::parse_database(const string &, const string &, const po::options_description &desc, bool) { return po::parsed_options(&desc); } #endif // -------------------------------------------------------------------------- // //! // Configuration::Configuration(const string &prgname) : fName(UnLibToolize(prgname)), fNameMapper(bind1st(mem_fun(&Configuration::DefaultMapper), this)), fPrintUsage(bind(&Configuration::PrintUsage, this)) { po::options_description generic("Generic options"); generic.add_options() ("version,V", "Print version information.") ("help", "Print available commandline options.") ("help-environment", "Print available environment variables.") ("help-database", "Print available options retreived from the database.") ("help-config", "Print available configuration file options.") ("print-all", "Print all options as parsed from all the different sources.") ("print", "Print options as parsed from the commandline.") ("print-default", "Print options as parsed from default configuration file.") ("print-database", "Print options as retrieved from the database.") ("print-config", "Print options as parsed from the high priority configuration file.") ("print-environment", "Print options as parsed from the environment.") ("print-unknown", "Print unrecognized options.") ("print-options", "Print options as passed to program.") ("print-wildcards", "Print all options registered with wildcards.") ("dont-check", "Do not check validity of options from files and database.") ("dont-check-files", "Do not check validity of options from files.") ("dont-check-database", "Do not check validity of options from database.") ; po::options_description def_config; def_config.add_options() ("default", var(fName+string(".rc")), "Default configuration file.") ; po::options_description config("Configuration options"); config.add_options() ("config,C", var(), "Configuration file overwriting options retrieved from the database.") ("database", var(), "Database link as in\n\t[user[:password]@]server.com[:port]/database\nOverwrites options from the default configuration file.") ("no-database", "Suppress any access to the database even if a database URL was set.") ; fOptionsCommandline[kVisible].add(generic); fOptionsCommandline[kVisible].add(config); fOptionsCommandline[kVisible].add(def_config); fOptionsConfigfile[kVisible].add(config); } // -------------------------------------------------------------------------- // //! // void Configuration::PrintParsed(const po::parsed_options &parsed) const { const vector< po::basic_option >& options = parsed.options; // .description -> Pointer to opt_commandline // const std::vector< shared_ptr >& options() const; //const std::string& key(const std::string& option) const; //const std::string& long_name() const; //const std::string& description() const; //shared_ptr semantic() const; int maxlen = 0; for (unsigned i=0; i &opt = options[i]; if (opt.value.size()>0 && opt.string_key[0]!='-') Max(maxlen, opt.string_key.length()); } cout.setf(ios_base::left); // =============> Implement prining of parsed options for(unsigned i=0; i &opt = options[i]; if (opt.value.size()==0 && !opt.string_key[0]=='-') cout << "--"; cout << setw(maxlen) << opt.string_key; if (opt.value.size()>0) cout << " = " << opt.value[0]; //for (int j=0; j=0) com << " [position=" << opt.position_key << "]"; if (opt.unregistered) com << " [unregistered]"; if (!com.str().empty()) cout << " # " << com.str(); cout << endl; } } template string Configuration::VecAsStr(const po::variable_value &v) const { ostringstream str; const vector vec = v.as>(); for (typename std::vector::const_iterator s=vec.begin(); s() ? "yes ": "no"; if (v.value().type()==typeid(string)) return v.as(); if (v.value().type()==typeid(int16_t)) return to_string((long long int)v.as()); if (v.value().type()==typeid(int32_t)) return to_string((long long int)v.as()); if (v.value().type()==typeid(int64_t)) return to_string((long long int)v.as()); if (v.value().type()==typeid(uint16_t)) return to_string((long long unsigned int)v.as()); if (v.value().type()==typeid(uint32_t)) return to_string((long long unsigned int)v.as()); if (v.value().type()==typeid(uint64_t)) return to_string((long long unsigned int)v.as()); if (v.value().type()==typeid(float)) return to_string((long double)v.as()); if (v.value().type()==typeid(double)) return to_string((long double)v.as()); if (v.value().type()==typeid(vector)) return VecAsStr(v); if (v.value().type()==typeid(vector)) return VecAsStr(v); if (v.value().type()==typeid(vector)) return VecAsStr(v); if (v.value().type()==typeid(vector)) return VecAsStr(v); if (v.value().type()==typeid(vector)) return VecAsStr(v); if (v.value().type()==typeid(vector)) return VecAsStr(v); if (v.value().type()==typeid(vector)) return VecAsStr(v); if (v.value().type()==typeid(vector)) return VecAsStr(v); if (v.value().type()==typeid(vector)) return VecAsStr(v); ostringstream str; str << hex << setfill('0') << "0x"; if (v.value().type()==typeid(Hex)) str << setw(4) << v.as>(); if (v.value().type()==typeid(Hex)) str << setw(8) << v.as>(); if (v.value().type()==typeid(Hex)) str << setw(16) << v.as>(); return str.str(); } // -------------------------------------------------------------------------- // //! // void Configuration::PrintOptions() const { cout << "Options propagated to program:" << endl; int maxlen = 0; for (map::const_iterator m=fVariables.begin(); m!=fVariables.end(); m++) Max(maxlen, m->first.length()); cout.setf(ios_base::left); // =============> Implement prining of options in use for (map::const_iterator m=fVariables.begin(); m!=fVariables.end(); m++) { const po::variable_value &v = m->second; ostringstream str; if (v.value().type()==typeid(bool)) str << " bool"; if (v.value().type()==typeid(string)) str << " string"; if (v.value().type()==typeid(int16_t)) str << " int16_t"; if (v.value().type()==typeid(int32_t)) str << " int32_t"; if (v.value().type()==typeid(int64_t)) str << " int64_t"; if (v.value().type()==typeid(uint16_t)) str << " uint16_t"; if (v.value().type()==typeid(uint32_t)) str << " uint32_t"; if (v.value().type()==typeid(uint64_t)) str << " uint64_t"; if (v.value().type()==typeid(float)) str << " float"; if (v.value().type()==typeid(double)) str << " double"; if (v.value().type()==typeid(Hex)) str << " Hex"; if (v.value().type()==typeid(Hex)) str << " Hex"; if (v.value().type()==typeid(Hex)) str << " Hex"; if (v.value().type()==typeid(vector)) str << " vector"; if (v.value().type()==typeid(vector)) str << " vector"; if (v.value().type()==typeid(vector)) str << " vector"; if (v.value().type()==typeid(vector)) str << " vector"; if (v.value().type()==typeid(vector)) str << " vector"; if (v.value().type()==typeid(vector)) str << " vector"; if (v.value().type()==typeid(vector)) str << " vector"; if (v.value().type()==typeid(vector)) str << " vector"; if (v.value().type()==typeid(vector)) str << " vector"; if (str.str().empty()) str << " unknown[" << v.value().type().name() << "]"; const string var = VarAsStr(v); cout << setw(maxlen) << m->first; if (!var.empty()) cout << " = "; cout << var << " #" << str.str(); if (v.defaulted()) cout << " [default]"; if (v.empty()) cout << " [empty]"; cout << endl; } cout << endl; } // -------------------------------------------------------------------------- // //! // void Configuration::PrintUnknown(const vector &vec, int steps) const { for (vector::const_iterator v=vec.begin(); v Configuration::GetOptions() const { multimap rc; for (map::const_iterator m=fVariables.begin(); m!=fVariables.end(); m++) rc.insert(make_pair(m->first, VarAsStr(m->second))); return rc; } // -------------------------------------------------------------------------- // //! // void Configuration::PrintUnknown() const { if (fUnknownCommandline.size()) { cout << "Unknown commandline options:" << endl; PrintUnknown(fUnknownCommandline); } if (fUnknownConfigfile.size()) { cout << "Unknown options in configfile:" << endl; PrintUnknown(fUnknownConfigfile, 2); } if (fUnknownEnvironment.size()) { cout << "Unknown environment variables:" << endl; PrintUnknown(fUnknownEnvironment); } if (fUnknownDatabase.size()) { cout << "Unknown database entry:" << endl; PrintUnknown(fUnknownDatabase); } } // -------------------------------------------------------------------------- // //! // void Configuration::AddOptionsCommandline(const po::options_description &cl, bool visible) { fOptionsCommandline[visible].add(cl); } // -------------------------------------------------------------------------- // //! // void Configuration::AddOptionsConfigfile(const po::options_description &cf, bool visible) { fOptionsConfigfile[visible].add(cf); } // -------------------------------------------------------------------------- // //! // void Configuration::AddOptionsEnvironment(const po::options_description &env, bool visible) { fOptionsEnvironment[visible].add(env); } // -------------------------------------------------------------------------- // //! // void Configuration::AddOptionsDatabase(const po::options_description &db, bool visible) { fOptionsDatabase[visible].add(db); } // -------------------------------------------------------------------------- // //! // void Configuration::SetArgumentPositions(const po::positional_options_description &desc) { fArgumentPositions = desc; } // -------------------------------------------------------------------------- // //! // void Configuration::SetNameMapper(const function &func) { fNameMapper = func; } void Configuration::SetNameMapper() { fNameMapper = bind1st(mem_fun(&Configuration::DefaultMapper), this); } void Configuration::SetPrintUsage(const function &func) { fPrintUsage = func; } void Configuration::SetPrintUsage() { fPrintUsage = bind(&Configuration::PrintUsage, this); } void Configuration::SetPrintVersion(const function &func) { fPrintVersion = func; } void Configuration::SetPrintVersion() { fPrintVersion = function(); } // -------------------------------------------------------------------------- // //! //! The idea of the Parse() memeber-function is to parse the command-line, //! the configuration files, the databse and the environment and return //! a proper combined result. //! //! In details the following actions are performed in the given order: //! //! - (0) Init local variables with the list of options described by the //! data members. //! - (1) Reset the data members fPriorityFile, fDefaultFile, fDatabase //! - (2) Parse the command line //! - (3) Check for \b --help* command-line options and performe //! corresponding action //! - (4) Check for \b --print and \b --print-all and perform corresponding //! action //! - (5) Read and parse the global configuration file, which is compiled //! from the path corresponding to the argument given in the //! constructor + "/fact++.rc", unrecognized options are always //! allowed. Note that in contradiction to all other options //! the options in this file are not checked at all. Hence, //! typos might stay unnoticed. //! - (6) Read and parse the default configuration file, which is either //! given by the default name or the \b --default command-line //! option. The default name is compiled from the argument //! given to the constructor and ".rc". If the file-name is //! identical to the default (no command-line option given) //! a missing configuration file is no error. Depending on //! the \b --dont-check and \b --dont-check-files options, //! unrecognized options in the file throw an exception or not. //! - (7) Check for \b --print-default and \b --print-all and perform //! corresponding action //! - (8) Read and parse the priority configuration file, which must be given //! by the \b --config or \b -C command-line option or a //! corresponding entry in the default-configuration file. //! If an option on the command-line and the in the configuration //! file exists, the command-line option has priority. //! If none is given, no priority file is read. Depending on //! the \b --dont-check and \b --dont-check-files options, //! unrecognized options in the file throw an exception or not. //! - (9) Check for \b --print-config and \b --print-all and perform //! corresponding action //! - (10) Retrieve options from the database according to the //! options \b --database and \b --no-database. Note that //! options given on the command-line have highest priority. //! The second priority is the priority-configuration file. //! The options from the default configuration-file have //! lowest priority. //! - (11) Check for \b --print-database and \b --print-all and perform //! corresponding action //! - (12) Parse the environment options. //! - (13) Check for \b --print-environment and \b --print-all and perform //! corresponding action //! - (14) Compile the final result. The priority of the options is (in //! decreasing order): command-line options, options from the //! priority configuration file, options from the database, //! options from the default configuration-file and options //! from the environment. //! - (15) Find all options which were found and flagged as unrecognized, //! because they are not in the user-defined list of described //! options, are collected and stored in the corresponding //! data-members. //! - (16) Find all options which where registered with wildcards and //! store the list in fWildcardOptions. //! - (17) Before the function returns it check for \b --print-options //! and \b --print-unknown and performs the corresponding actions. //! //! //! @param argc,argv //! arguments passed to main(int argc, char **argv) //! //! @returns //! A reference to the list with the resulting options with their //! values. //! //! @todo //! - describe the exceptions //! - describe what happens in a more general way //! - print a waring when no default coonfig file is read //! - proper handling and error messages if files not available // const po::variables_map &Configuration::Parse(int argc, const char **argv, const std::function &PrintHelp) { const po::positional_options_description &opt_positional = fArgumentPositions; // ------------------------ (0) -------------------------- #ifdef DEBUG cout << "--0--" << endl; #endif po::options_description opt_commandline; po::options_description opt_configfile; po::options_description opt_environment; po::options_description opt_database; for (int i=0; i<2; i++) { opt_commandline.add(fOptionsCommandline[i]); opt_configfile.add(fOptionsConfigfile[i]); opt_environment.add(fOptionsEnvironment[i]); opt_database.add(fOptionsDatabase[i]); } // ------------------------ (1) -------------------------- #ifdef DEBUG cout << "--1--" << endl; #endif fPriorityFile = ""; fDefaultFile = ""; fDatabase = ""; // ------------------------ (2) -------------------------- #ifdef DEBUG cout << "--2--" << endl; #endif po::command_line_parser parser(argc, const_cast(argv)); parser.options(opt_commandline); parser.positional(opt_positional); parser.style(style::unix_style&~style::allow_guessing); //parser.allow_unregistered(); const po::parsed_options parsed_commandline = parser.run(); // ------------------------ (3) -------------------------- #ifdef DEBUG cout << "--3--" << endl; #endif po::variables_map getfiles; po::store(parsed_commandline, getfiles); if (getfiles.count("version")) PrintVersion(); if (getfiles.count("help")) { fPrintUsage(); cout << "Options:\n" "The following describes the available commandline options. " "For further details on how command line option are parsed " "and in which order which configuration sources are accessed " "please refer to the class reference of the Configuration class." << endl; cout << fOptionsCommandline[kVisible] << endl; } if (getfiles.count("help-config")) cout << fOptionsConfigfile[kVisible] << endl; if (getfiles.count("help-env")) cout << fOptionsEnvironment[kVisible] << endl; if (getfiles.count("help-database")) cout << fOptionsDatabase[kVisible] << endl; // ------------------------ (4) -------------------------- #ifdef DEBUG cout << "--4--" << endl; #endif if (getfiles.count("print") || getfiles.count("print-all")) { cout << endl << "Parsed commandline options:" << endl; PrintParsed(parsed_commandline); cout << endl; } if (getfiles.count("help") || getfiles.count("help-config") || getfiles.count("help-env") || getfiles.count("help-database")) { if (PrintHelp) PrintHelp(); } // ------------------------ (5) -------------------------- #ifdef DEBUG cout << "--5--" << endl; #endif const boost::filesystem::path path(GetName()); const string globalfile = (path.parent_path()/boost::filesystem::path("fact++.rc")).string(); cerr << "Reading global options from '" << globalfile << "'." << endl; ifstream gfile(globalfile.c_str()); // ===> FIXME: Proper handling of missing file or wrong file name const po::parsed_options parsed_globalfile = !gfile ? po::parsed_options(&opt_configfile) : po::parse_config_file(gfile, opt_configfile, true); // ------------------------ (6) -------------------------- #ifdef DEBUG cout << "--6--" << endl; #endif // Get default file from command line if (getfiles.count("default")) { fDefaultFile = getfiles["default"].as(); cerr << "Reading default options from '" << fDefaultFile << "'." << endl; } const bool checkf = !getfiles.count("dont-check-files") && !getfiles.count("dont-check"); const bool defaulted = getfiles.count("default") && getfiles["default"].defaulted(); //const bool exists = boost::filesystem::exists(fDefaultFile); ifstream indef(fDefaultFile.c_str()); // ===> FIXME: Proper handling of missing file or wrong file name const po::parsed_options parsed_defaultfile = !indef && defaulted ? po::parsed_options(&opt_configfile) : po::parse_config_file(indef, opt_configfile, !checkf); // ------------------------ (7) -------------------------- #ifdef DEBUG cout << "--7--" << endl; #endif if (getfiles.count("print-default") || getfiles.count("print-all")) { if (!indef.is_open() && defaulted) cout << "No configuration file by --default option specified." << endl; else { cout << endl << "Parsed options from '" << fDefaultFile << "':" << endl; PrintParsed(parsed_defaultfile); cout << endl; } } po::store(parsed_defaultfile, getfiles); // ------------------------ (8) -------------------------- #ifdef DEBUG cout << "--8--" << endl; #endif // Get priority from commandline(1), defaultfile(2) if (getfiles.count("config")) { fPriorityFile = getfiles["config"].as(); cerr << "Reading config options from '" << fPriorityFile << "'." << endl; } ifstream inpri(fPriorityFile.c_str()); // ===> FIXME: Proper handling of missing file or wrong file name const po::parsed_options parsed_priorityfile = fPriorityFile.empty() ? po::parsed_options(&opt_configfile) : po::parse_config_file(inpri, opt_configfile, !checkf); // ------------------------ (9) -------------------------- #ifdef DEBUG cout << "--9--" << endl; #endif if (getfiles.count("print-config") || getfiles.count("print-all")) { if (fPriorityFile.empty()) cout << "No configuration file by --config option specified." << endl; else { cout << endl << "Parsed options from '" << fPriorityFile << "':" << endl; PrintParsed(parsed_priorityfile); cout << endl; } } // ------------------------ (10) ------------------------- #ifdef DEBUG cout << "--10--" << endl; #endif po::variables_map getdatabase; po::store(parsed_commandline, getdatabase); po::store(parsed_priorityfile, getdatabase); po::store(parsed_defaultfile, getdatabase); po::store(parsed_globalfile, getdatabase); if (getdatabase.count("database") && !getdatabase.count("no-database")) { fDatabase = getdatabase["database"].as(); cerr << "Requesting options from database for '" << fName << "'" << endl; } const bool checkdb = !getdatabase.count("dont-check-database") && !getdatabase.count("dont-check"); const po::parsed_options parsed_database = fDatabase.empty() ? po::parsed_options(&opt_database) : #if BOOST_VERSION < 104600 parse_database(path.filename(), fDatabase, opt_database, !checkdb); #else parse_database(path.filename().string(), fDatabase, opt_database, !checkdb); #endif // ------------------------ (11) ------------------------- #ifdef DEBUG cout << "--11--" << endl; #endif if (getfiles.count("print-database") || getfiles.count("print-all")) { if (fDatabase.empty()) cout << "No database access requested." << endl; else { cout << endl << "Options received from '" << fDatabase << "':" << endl; PrintParsed(parsed_database); cout << endl; } } // ------------------------ (12) ------------------------- #ifdef DEBUG cout << "--12--" << endl; #endif const po::parsed_options parsed_environment = po::parse_environment(opt_environment, fNameMapper); // ------------------------ (13) ------------------------- #ifdef DEBUG cout << "--13--" << endl; #endif if (getfiles.count("print-environment")) { cout << "Parsed options from environment:" << endl; PrintParsed(parsed_environment); cout << endl; } // ------------------------ (14) ------------------------- #ifdef DEBUG cout << "--14--" << endl; #endif po::variables_map result; po::store(parsed_commandline, result); po::store(parsed_priorityfile, result); po::store(parsed_database, result); po::store(parsed_defaultfile, result); po::store(parsed_globalfile, result); po::store(parsed_environment, result); po::notify(result); fVariables = result; // ------------------------ (15) ------------------------- #ifdef DEBUG cout << "--15--" << endl; #endif const vector unknown0 = collect_unrecognized(parsed_globalfile.options, po::exclude_positional); const vector unknown1 = collect_unrecognized(parsed_defaultfile.options, po::exclude_positional); const vector unknown2 = collect_unrecognized(parsed_priorityfile.options, po::exclude_positional); fUnknownConfigfile.clear(); fUnknownConfigfile.insert(fUnknownConfigfile.end(), unknown0.begin(), unknown0.end()); fUnknownConfigfile.insert(fUnknownConfigfile.end(), unknown1.begin(), unknown1.end()); fUnknownConfigfile.insert(fUnknownConfigfile.end(), unknown2.begin(), unknown2.end()); fUnknownCommandline = collect_unrecognized(parsed_commandline.options, po::exclude_positional); fUnknownEnvironment = collect_unrecognized(parsed_environment.options, po::exclude_positional); fUnknownDatabase = collect_unrecognized(parsed_database.options, po::exclude_positional); // ------------------------ (16) ------------------------- #ifdef DEBUG cout << "--16--" << endl; #endif CreateWildcardOptions(); // ------------------------ (17) ------------------------- #ifdef DEBUG cout << "--17--" << endl; #endif if (result.count("print-options")) PrintOptions(); if (result.count("print-wildcards")) PrintWildcardOptions(); if (result.count("print-unknown")) PrintUnknown(); #ifdef DEBUG cout << "------" << endl; #endif return fVariables; } bool Configuration::DoParse(int argc, const char **argv, const std::function &PrintHelp) { try { Parse(argc, argv, PrintHelp); } #if BOOST_VERSION > 104000 catch (po::multiple_occurrences &e) { cerr << "Program options invalid due to: " << e.what() << " of '" << e.get_option_name() << "'." << endl; return false; } #endif catch (exception& e) { cerr << "Program options invalid due to: " << e.what() << endl; return false; } return !HasVersion() && !HasPrint() && !HasHelp(); } // -------------------------------------------------------------------------- // //! Create a list of all options which were registered using wildcards //! void Configuration::CreateWildcardOptions() { po::options_description opts; for (int i=0; i<2; i++) { opts.add(fOptionsCommandline[i]); opts.add(fOptionsConfigfile[i]); opts.add(fOptionsEnvironment[i]); opts.add(fOptionsDatabase[i]); } fWildcardOptions.clear(); typedef map Vars; typedef vector> Descs; const Descs &desc = opts.options(); for (Vars::const_iterator io=fVariables.begin(); io!=fVariables.end(); io++) { for (Descs::const_iterator id=desc.begin(); id!=desc.end(); id++) #if BOOST_VERSION > 104000 if ((*id)->match(io->first, false, false, false)==po::option_description::approximate_match) #else if ((*id)->match(io->first, false)==po::option_description::approximate_match) #endif fWildcardOptions[io->first] = (*id)->long_name(); } } // -------------------------------------------------------------------------- // //! Print a list of all options which were registered using wildcards and //! have not be registered subsequently by access. //! void Configuration::PrintWildcardOptions() const { cout << "Options registered with wildcards and not yet accessed:" << endl; size_t max = 0; for (auto it=fWildcardOptions.begin(); it!=fWildcardOptions.end(); it++) if (it->second.length()>max) max = it->second.length(); cout.setf(ios_base::left); for (auto it=fWildcardOptions.begin(); it!=fWildcardOptions.end(); it++) cout << setw(max+1) << it->second << " : " << it->first < Configuration::GetWildcardOptions(const std::string &opt) const { vector rc; for (auto it=fWildcardOptions.begin(); it!=fWildcardOptions.end(); it++) { if (it->second == opt) rc.push_back(it->first); } return rc; } // -------------------------------------------------------------------------- // //! Removes /.libs/lt- from a path or just lt- from the filename. //! //! @param src //! input path with filename //! @returns //! path cleaned from libtool extensions //! string Configuration::UnLibToolize(const string &src) const { const boost::filesystem::path path(src); string pname = path.parent_path().string(); #if BOOST_VERSION < 104600 string fname = path.filename(); #else string fname = path.filename().string(); #endif // If the filename starts with "lt-" remove it from the name if (fname.substr(0, 3)=="lt-") fname = fname.substr(3); string pwd; // If no directory is contained determine the current directory if (pname.empty()) pname = boost::filesystem::current_path().string(); // If the directory is relative and just ".libs" forget about it if (pname==".libs") return fname; // Check if the directory is long enough to contain "/.libs" if (pname.length()>=6) { // If the directory ends with "/.libs", remove it const size_t pos = pname.length()-6; if (pname.substr(pos)=="/.libs") pname = pname.substr(0, pos); } // If the path is the local path do not return the path-name if (pname==boost::filesystem::current_path().string()) return fname; return pname+'/'+fname; } // -------------------------------------------------------------------------- // //! Print version information about the program and package. //! //! The program name is taken from fName. If a leading "lt-" is found, //! it is removed. This is useful if the program was build and run //! using libtool. //! //! The package name is taken from the define PACKAGE_STRING. If it is //! not defined (like automatically done by autoconf) no package information //! is printed. The same is true for PACKAGE_URL and PACKAGE_BUGREPORT. //! //! From help2man: //! //! The first line of the --version information is assumed to be in one //! of the following formats: //! //! \verbatim //! - //! - //! - {GNU,Free} //! - ({GNU,Free} ) //! - - {GNU,Free} //! \endverbatim //! //! and separated from any copyright/author details by a blank line. //! //! Handle multi-line bug reporting sections of the form: //! //! \verbatim //! - Report bugs to //! - GNU home page: //! - ... //! \endverbatim //! //! @param name //! name of the program (usually argv[0]). //! void Configuration::PrintVersion() const { #ifndef PACKAGE_STRING #define PACKAGE_STRING "" #endif #ifndef PACKAGE_URL #define PACKAGE_URL "" #endif #ifndef PACKAGE_BUGREPORT #define PACKAGE_BUGREPORT "" #endif if (fPrintVersion) { fPrintVersion(fName); return; } #if BOOST_VERSION < 104600 const std::string n = boost::filesystem::path(fName).filename(); #else const std::string n = boost::filesystem::path(fName).filename().string(); #endif const string name = PACKAGE_STRING; const string bugs = PACKAGE_BUGREPORT; const string url = PACKAGE_URL; cout << n; if (!name.empty()) cout << " - " << name; cout << "\n\n" "Written by Thomas Bretz et al.\n" "\n"; if (!bugs.empty()) cout << "Report bugs to <" << bugs << ">\n"; if (!url.empty()) cout << "Home page: " << url << "\n"; cout << "\n" "Copyright (C) 2011 by the FACT Collaboration.\n" "This is free software; see the source for copying conditions.\n" << std::endl; }