diff --git a/src/mrmc.cpp b/src/mrmc.cpp index ec9ea7832..7f5d54c34 100644 --- a/src/mrmc.cpp +++ b/src/mrmc.cpp @@ -74,7 +74,7 @@ int main(const int argc, const char* argv[]) { LOG4CPLUS_INFO(logger, "MRMC command invoked " << commandStream.str()); try { - s = mrmc::settings::Settings::instance(argc, argv, nullptr); + s = mrmc::settings::newInstance(argc, argv, nullptr); } catch (mrmc::exceptions::InvalidSettings&) { LOG4CPLUS_FATAL(logger, "Could not recover from settings error, terminating."); std::cout << std::endl << mrmc::settings::help << std::endl; diff --git a/src/utility/settings.cpp b/src/utility/settings.cpp index a268ec684..e216134b1 100644 --- a/src/utility/settings.cpp +++ b/src/utility/settings.cpp @@ -7,24 +7,17 @@ #include "src/utility/settings.h" -#include -//#include -//#include "src/exceptions/InvalidSettings.h" - namespace mrmc { namespace settings { namespace bpo = boost::program_options; -bpo::options_description mrmc::settings::Settings::configfile("Config Options"); -bpo::options_description mrmc::settings::Settings::generic("Generic Options"); -bpo::options_description mrmc::settings::Settings::commandline("Commandline Options"); -bpo::positional_options_description mrmc::settings::Settings::positional; - -bpo::options_description mrmc::settings::Settings::cli; -bpo::options_description mrmc::settings::Settings::conf; - -mrmc::settings::Settings* mrmc::settings::Settings::inst = NULL; +/* + * static initializers + */ +bpo::options_description* mrmc::settings::Settings::cli = nullptr; +bpo::options_description* mrmc::settings::Settings::conf = nullptr; +mrmc::settings::Settings* mrmc::settings::Settings::inst = nullptr; /*! * The constructor fills the option descriptions, parses the @@ -37,163 +30,220 @@ mrmc::settings::Settings* mrmc::settings::Settings::inst = NULL; * * @param argc should be argc passed to main function * @param argv should be argv passed to main function - * @param filename either NULL or name of config file + * @param filename either nullptr or name of config file */ Settings::Settings(const int argc, const char* argv[], const char* filename) + : configfile("Config Options"), generic("Generic Options"), commandline("Commandline Options") { try { - /* - * load command line - */ - bpo::store(bpo::command_line_parser(argc, argv).options(this->cli).positional(this->positional).run(), this->vm); - /* - * load config file if specified - */ - if (this->vm.count("configfile")) - { - bpo::store(bpo::parse_config_file(this->vm["configfile"].as().c_str(), this->conf), this->vm, true); - } - else if (filename != NULL) - { - bpo::store(bpo::parse_config_file(filename, this->conf), this->vm, true); - } - /* - * If a required option is missing, this will throw a bpo::required_option. - * This exception is catched below. - */ - bpo::notify(this->vm); + //! Initially fill description objects and call register callbacks + this->initDescriptions(); + + //! Take care of positional arguments + Settings::positional.add("trafile", 1); + Settings::positional.add("labfile", 1); + + //! Create and fill collecting options descriptions + Settings::cli = new bpo::options_description(); + Settings::cli->add(Settings::commandline).add(generic); + Settings::conf = new bpo::options_description(); + Settings::conf->add(Settings::configfile).add(generic); + + //! Perform first parse run and call intermediate callbacks + this->firstRun(argc, argv, filename); - /* - * Call custom option checker. - */ - Callbacks* cb = mrmc::settings::Callbacks::getInstance(); - while (cb->checkerList.size() > 0) + //! Rebuild collecting options descriptions + delete Settings::cli; + Settings::cli = new bpo::options_description(); + Settings::cli->add(Settings::commandline).add(generic); + + delete Settings::conf; + Settings::conf = new bpo::options_description(); + Settings::conf->add(Settings::configfile).add(generic); + + //! Stop if help is set + if ((this->vm.count("help") > 0) || (this->vm.count("help-config") > 0)) { - CheckerCallback fptr = cb->checkerList.front(); - cb->checkerList.pop_front(); - - if (! (*fptr)(this->vm)) - { - std::cerr << "Custom option checker failed." << std::endl; - throw mrmc::exceptions::InvalidSettings(); - } + return; } + + //! Perform second run and call checker callbacks + this->secondRun(argc, argv, filename); + + //! Finalize parsed options, check for specified requirements + bpo::notify(this->vm); } - /* - * catch errors... - */ catch (bpo::reading_file e) { std::cerr << "Could not read config file " << filename << std::endl; } catch (bpo::required_option e) { - if (! (this->vm.count("help") || this->vm.count("help-config"))) - { - std::cerr << e.what() << std::endl; - throw mrmc::exceptions::InvalidSettings(); - } + std::cerr << "required option: " << e.what() << std::endl; + throw mrmc::exceptions::InvalidSettings(); + } + catch (bpo::validation_error e) + { + std::cerr << "Validation failed: " << e.what() << std::endl; + throw mrmc::exceptions::InvalidSettings(); + } + catch (bpo::invalid_command_line_syntax e) + { + std::cerr << "Invalid command line syntax: " << e.what() << std::endl; + throw mrmc::exceptions::InvalidSettings(); } catch (bpo::error e) { - std::cerr << "Some error occurred: " << e.what() << std::endl; + std::cerr << e.what() << std::endl; + throw mrmc::exceptions::InvalidSettings(); } } /*! - * Creates a new instance if necessary. When the instance is actually - * created for the first time, the internal options_description objects are - * initialized. - * - * If this function was already called and another instance is - * already present, the existing instance will be returned. In this - * case, better use the routine mrmc::settings::instance(). - * - * The constructor fills the option descriptions, parses the - * command line and the config file and puts the option values to - * our option mapping. It also calls all register callbacks that were - * registered via Callbacks and Register classes. - * - * If a configfile is set in the commandline, we load this one. - * Otherwise, if filename is not NULL, we load this one. Otherwise, - * we load no config file. + * Initially fill options_description objects. + * First puts some generic options, then calls all register Callbacks. + */ +void Settings::initDescriptions() +{ + this->commandline.add_options() + ("help,h", "produce help message") + ("verbose,v", "be verbose") + ("help-config", "produce help message about config file") + ("configfile,c", bpo::value(), "name of config file") + ("test-prctl", bpo::value(), "name of prctl file") + ; + this->generic.add_options() + ("trafile", bpo::value()->required(), "name of the .tra file") + ("labfile", bpo::value()->required(), "name of the .lab file") + ; + this->configfile.add_options() + ; + + /* + * Get Callbacks object, then iterate over and call all register callbacks. + */ + Callbacks* cb = mrmc::settings::Callbacks::getInstance(); + while (cb->registerList.size() > 0) + { + CallbackType type = cb->registerList.front().first; + RegisterCallback fptr = cb->registerList.front().second; + cb->registerList.pop_front(); + + switch (type) + { + case CB_CONFIG: + (*fptr)(this->configfile); + break; + case CB_CLI: + (*fptr)(this->commandline); + break; + case CB_GENERIC: + (*fptr)(this->generic); + break; + } + } +} + +/*! + * Perform a sloppy parsing run: parse command line and config file (if + * given), but allow for unregistered options, do not check requirements + * from options_description objects, do not check positional arguments. * - * @param argc should be argc passed to main function - * @param argv should be argv passed to main function - * @param filename either NULL or name of config file - * @return instance of Settings + * Call all intermediate callbacks afterwards. */ -Settings* Settings::instance(const int argc, const char* argv[], const char* filename) +void Settings::firstRun(const int argc, const char* argv[], const char* filename) { - if (Settings::inst == NULL) { - /* - * fill option descriptions - */ - Settings::commandline.add_options() - ("help,h", "produce help message") - ("verbose,v", "be verbose") - ("help-config", "produce help message about config file") - ("configfile,c", bpo::value(), "name of config file") - ("test-prctl", bpo::value(), "name of prctl file") - ; - Settings::generic.add_options() - ("trafile", bpo::value()->required(), "name of the .tra file") - ("labfile", bpo::value()->required(), "name of the .lab file") - ; - Settings::configfile.add_options() - ; + //! parse command line + bpo::store(bpo::command_line_parser(argc, argv).options(*(Settings::cli)).allow_unregistered().run(), this->vm); + + /* + * load config file if specified + */ + if (this->vm.count("configfile")) + { + bpo::store(bpo::parse_config_file(this->vm["configfile"].as().c_str(), *(Settings::conf)), this->vm, true); + } + else if (filename != NULL) + { + bpo::store(bpo::parse_config_file(filename, *(Settings::conf)), this->vm, true); + } + + /* + * Call intermediate callbacks. + */ + Callbacks* cb = mrmc::settings::Callbacks::getInstance(); + while (cb->intermediateList.size() > 0) + { + CallbackType type = cb->intermediateList.front().first; + IntermediateCallback fptr = cb->intermediateList.front().second; + cb->intermediateList.pop_front(); - /* - * Call all custom register callbacks. - */ - Callbacks* cb = mrmc::settings::Callbacks::getInstance(); - while (cb->registerList.size() > 0) + try { - CallbackType type = cb->registerList.front().first; - RegisterCallback fptr = cb->registerList.front().second; - cb->registerList.pop_front(); - - /* - * Allow modules to specify the type of an option. - */ switch (type) { case CB_CONFIG: - (*fptr)(Settings::configfile); + (*fptr)(&this->configfile, this->vm); break; case CB_CLI: - (*fptr)(Settings::commandline); + (*fptr)(&this->commandline, this->vm); break; case CB_GENERIC: - (*fptr)(Settings::generic); - break; - default: - // hm. is this an error? can this actually happen with an enum? + (*fptr)(&this->generic, this->vm); break; } } + catch (boost::bad_any_cast e) + { + std::cerr << "An intermediate callback failed." << std::endl; + std::cerr << e.what() << std::endl; + } + } +} - /* - * construct option descriptions for commandline and config file - */ - Settings::cli.add(Settings::commandline).add(generic); - Settings::conf.add(Settings::configfile).add(generic); +/*! + * Perform the second parser run: parse command line and config file (if + * given) and check for unregistered options, requirements from + * options_description objects and positional arguments. + * + * Call all checker callbacks afterwards. + */ +void Settings::secondRun(const int argc, const char* argv[], const char* filename) +{ + //! Parse command line + bpo::store(bpo::command_line_parser(argc, argv).options(*(Settings::cli)).positional(this->positional).run(), this->vm); + /* + * load config file if specified + */ + if (this->vm.count("configfile")) + { + bpo::store(bpo::parse_config_file(this->vm["configfile"].as().c_str(), *(Settings::conf)), this->vm, true); + } + else if (filename != NULL) + { + bpo::store(bpo::parse_config_file(filename, *(Settings::conf)), this->vm, true); + } - /* - * Take care of positional arguments - */ - Settings::positional.add("trafile", 1); - Settings::positional.add("labfile", 1); + + /* + * Call checker callbacks. + */ + Callbacks* cb = mrmc::settings::Callbacks::getInstance(); + while (cb->checkerList.size() > 0) + { + CheckerCallback fptr = cb->checkerList.front(); + cb->checkerList.pop_front(); - /* - * Actually create new instance - */ - Settings::inst = new Settings(argc, argv, filename); + if (! (*fptr)(this->vm)) + { + std::cerr << "Custom option checker failed." << std::endl; + throw mrmc::exceptions::InvalidSettings(); + } } - return Settings::inst; } + /*! * Print a short general usage information consisting of the positional * options and the list of available command line options. @@ -204,7 +254,7 @@ Settings* Settings::instance(const int argc, const char* argv[], const char* fil std::ostream& help(std::ostream& os) { os << "Usage: [options]