diff --git a/src/mrmc.cpp b/src/mrmc.cpp index 179877e78..243f1e0cc 100644 --- a/src/mrmc.cpp +++ b/src/mrmc.cpp @@ -87,11 +87,6 @@ int main(const int argc, const char* argv[]) { delete s; return 0; } - if (s->isSet("help-config")) { - std::cout << mrmc::settings::helpConfigfile; - delete s; - return 0; - } if (s->isSet("test-prctl")) { mrmc::parser::PRCTLParser parser(s->getString("test-prctl").c_str()); delete s; diff --git a/src/utility/settings.cpp b/src/utility/settings.cpp index 429ffa6a9..748bc931a 100644 --- a/src/utility/settings.cpp +++ b/src/utility/settings.cpp @@ -11,6 +11,8 @@ #include "log4cplus/loggingmacros.h" extern log4cplus::Logger logger; +#include <boost/algorithm/string/join.hpp> + namespace mrmc { namespace settings { @@ -19,11 +21,12 @@ namespace bpo = boost::program_options; /* * static initializers */ -std::unique_ptr<bpo::options_description> mrmc::settings::Settings::cli; -std::unique_ptr<bpo::options_description> mrmc::settings::Settings::conf = nullptr; +std::unique_ptr<bpo::options_description> mrmc::settings::Settings::desc = nullptr; std::string mrmc::settings::Settings::binaryName = ""; mrmc::settings::Settings* mrmc::settings::Settings::inst = nullptr; +std::map< std::pair<std::string, std::string>, bpo::options_description* > mrmc::settings::Settings::modules; + /*! * The constructor fills the option descriptions, parses the * command line and the config file and puts the option values to @@ -38,46 +41,63 @@ mrmc::settings::Settings* mrmc::settings::Settings::inst = nullptr; * @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") { Settings::binaryName = std::string(argv[0]); try { - //! Initially fill description objects and call register callbacks + // Initially fill description objects this->initDescriptions(); - //! Take care of positional arguments + // Take care of positional arguments Settings::positional.add("trafile", 1); Settings::positional.add("labfile", 1); + + // Check module triggers, add corresponding options + std::map< std::string, std::list< std::string > > options; - //! Create and fill collecting options descriptions - Settings::cli = std::unique_ptr<bpo::options_description>(new bpo::options_description()); - Settings::cli->add(Settings::commandline).add(generic); - Settings::conf = std::unique_ptr<bpo::options_description>(new bpo::options_description()); - Settings::conf->add(Settings::configfile).add(generic); + for (auto it : Settings::modules) + { + options[it.first.first].push_back(it.first.second); + } + for (auto it : options) + { + std::stringstream str; + str << "select " << it.first << " module (" << boost::algorithm::join(it.second, ", ") << ")"; + + Settings::desc->add_options() + (it.first.c_str(), bpo::value<std::string>(), str.str().c_str()) + ; + } - //! Perform first parse run and call intermediate callbacks + // Perform first parse run this->firstRun(argc, argv, filename); - //! Rebuild collecting options descriptions - Settings::cli = std::unique_ptr<bpo::options_description>(new bpo::options_description()); - Settings::cli->add(Settings::commandline).add(generic); - - Settings::conf = std::unique_ptr<bpo::options_description>(new bpo::options_description()); - Settings::conf->add(Settings::configfile).add(generic); + // Check module triggers + for (auto it : Settings::modules) + { + std::pair< std::string, std::string > trigger = it.first; + if (this->vm.count(trigger.first)) + { + if (this->vm[trigger.first].as<std::string>().compare(trigger.second) == 0) + { + Settings::desc->add(*it.second); + Settings::modules.erase(trigger); + } + } + + } - //! Stop if help is set - if ((this->vm.count("help") > 0) || (this->vm.count("help-config") > 0)) + // Stop if help is set + if (this->vm.count("help") > 0) { return; } - //! Perform second run and call checker callbacks + // Perform second run this->secondRun(argc, argv, filename); - //! Finalize parsed options, check for specified requirements + // Finalize parsed options, check for specified requirements bpo::notify(this->vm); - mrmc::settings::Callbacks::instance()->disabled = true; LOG4CPLUS_DEBUG(logger, "Finished loading config."); } catch (bpo::reading_file e) @@ -118,100 +138,38 @@ Settings::Settings(const int argc, const char* argv[], const char* filename) void Settings::initDescriptions() { LOG4CPLUS_DEBUG(logger, "Initializing descriptions."); - this->commandline.add_options() + Settings::desc = std::unique_ptr<bpo::options_description>(new bpo::options_description("Generic Options")); + Settings::desc->add_options() ("help,h", "produce help message") ("verbose,v", "be verbose") - ("help-config", "produce help message about config file") ("configfile,c", bpo::value<std::string>(), "name of config file") ("test-prctl", bpo::value<std::string>(), "name of prctl file") - ; - this->generic.add_options() ("trafile", bpo::value<std::string>()->required(), "name of the .tra file") ("labfile", bpo::value<std::string>()->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::instance(); - 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. - * - * Call all intermediate callbacks afterwards. */ void Settings::firstRun(const int argc, const char* argv[], const char* filename) { LOG4CPLUS_DEBUG(logger, "Performing first run."); - //! parse command line - bpo::store(bpo::command_line_parser(argc, argv).options(*(Settings::cli)).allow_unregistered().run(), this->vm); + // parse command line + bpo::store(bpo::command_line_parser(argc, argv).options(*(Settings::desc)).allow_unregistered().run(), this->vm); /* * load config file if specified */ if (this->vm.count("configfile")) { - bpo::store(bpo::parse_config_file<char>(this->vm["configfile"].as<std::string>().c_str(), *(Settings::conf)), this->vm, true); + bpo::store(bpo::parse_config_file<char>(this->vm["configfile"].as<std::string>().c_str(), *(Settings::desc)), this->vm, true); } else if (filename != NULL) { - bpo::store(bpo::parse_config_file<char>(filename, *(Settings::conf)), this->vm, true); - } - - /* - * Call intermediate callbacks. - */ - Callbacks* cb = mrmc::settings::Callbacks::instance(); - while (cb->intermediateList.size() > 0) - { - CallbackType type = cb->intermediateList.front().first; - IntermediateCallback fptr = cb->intermediateList.front().second; - cb->intermediateList.pop_front(); - - try - { - switch (type) - { - case CB_CONFIG: - (*fptr)(&this->configfile, this->vm); - break; - case CB_CLI: - (*fptr)(&this->commandline, this->vm); - break; - case CB_GENERIC: - (*fptr)(&this->generic, this->vm); - break; - } - } - catch (boost::bad_any_cast e) - { - std::cerr << "An intermediate callback failed." << std::endl; - LOG4CPLUS_ERROR(logger, "An intermediate callback failed.\n" << e.what()); - } + bpo::store(bpo::parse_config_file<char>(filename, *(Settings::desc)), this->vm, true); } } @@ -219,42 +177,22 @@ void Settings::firstRun(const int argc, const char* argv[], const char* filename * 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) { LOG4CPLUS_DEBUG(logger, "Performing second run."); - //! Parse command line - bpo::store(bpo::command_line_parser(argc, argv).options(*(Settings::cli)).positional(this->positional).run(), this->vm); + // Parse command line + bpo::store(bpo::command_line_parser(argc, argv).options(*(Settings::desc)).positional(this->positional).run(), this->vm); /* * load config file if specified */ if (this->vm.count("configfile")) { - bpo::store(bpo::parse_config_file<char>(this->vm["configfile"].as<std::string>().c_str(), *(Settings::conf)), this->vm, true); + bpo::store(bpo::parse_config_file<char>(this->vm["configfile"].as<std::string>().c_str(), *(Settings::desc)), this->vm, true); } else if (filename != NULL) { - bpo::store(bpo::parse_config_file<char>(filename, *(Settings::conf)), this->vm, true); - } - - - /* - * Call checker callbacks. - */ - Callbacks* cb = mrmc::settings::Callbacks::instance(); - while (cb->checkerList.size() > 0) - { - CheckerCallback fptr = cb->checkerList.front(); - cb->checkerList.pop_front(); - - if (! (*fptr)(this->vm)) - { - std::cerr << "Custom option checker failed." << std::endl; - LOG4CPLUS_ERROR(logger, "A checker callback returned false."); - throw mrmc::exceptions::InvalidSettings(); - } + bpo::store(bpo::parse_config_file<char>(filename, *(Settings::desc)), this->vm, true); } } @@ -269,19 +207,11 @@ void Settings::secondRun(const int argc, const char* argv[], const char* filenam std::ostream& help(std::ostream& os) { os << "Usage: " << mrmc::settings::Settings::binaryName << " [options] <transition file> <label file>" << std::endl; - os << *(mrmc::settings::Settings::cli) << std::endl; - return os; -} - -/*! - * Print a list of available options for the config file. - * - * Use it like this: - * @code std::cout << mrmc::settings::helpConfigfile; @endcode - */ -std::ostream& helpConfigfile(std::ostream& os) -{ - os << *(mrmc::settings::Settings::conf) << std::endl;; + os << *(mrmc::settings::Settings::desc) << std::endl; + for (auto it : Settings::modules) + { + os << *(it.second) << std::endl; + } return os; } diff --git a/src/utility/settings.h b/src/utility/settings.h index 08e56736d..842074655 100644 --- a/src/utility/settings.h +++ b/src/utility/settings.h @@ -9,6 +9,7 @@ #define SETTINGS_H_ #include <iostream> +#include <sstream> #include <list> #include <utility> #include <memory> @@ -25,14 +26,9 @@ namespace mrmc { namespace settings { namespace bpo = boost::program_options; - /* - * Sorry for very long comment at this point (for the class), but all - * methods are private, hence there is no other place to explain the - * inner workings of this class that are necessary to understand the - * callback concept... - */ + /*! - * @brief Simple wrapper around boost::program_options to handle configuration options. + * @brief Wrapper around boost::program_options to handle configuration options. * * This class uses boost::program_options to read options from the * commandline and additionally load options from a file. @@ -44,36 +40,15 @@ namespace settings { * @code mrmc::settings::instance() @endcode * * This class can be customized by other parts of the software using - * callbacks. There are three types of callbacks: register, - * intermediate and checker. - * - * The (private) constructor will start with filling the internal - * options_description object. There are a few generic options like - * --help or --verbose. Then if calls all register callbacks that may - * add more options. - * - * Then, it will start with a sloppy parsing run, allowing unregistered - * options and ignoring further constraints from the options_description - * objects. - * - * After that, it will call all intermediate callbacks. They can - * inspect the options from the first run and add more options, e.g. - * enable more options for a specific component that has been enabled. - * - * Using the new options_description objects, the constructor performs - * a second run. This time, it will not allow unregistered options and - * will check for required and positional arguments. - * - * Finally, all checker callbacks will be called. They can check the - * final options for more complex requirements. If any of those checker - * callbacks returns false, a InvalidSettings exception will be thrown. + * option modules. An option module can be anything that implements the + * interface specified by registerModule(). */ class Settings { public: /*! - * @brief Get value of a generic option. + * @brief Get value of a generic option. */ template <typename T> const T& get(const std::string &name) const { @@ -82,18 +57,68 @@ namespace settings { } /*! - * @brief Get value of string option + * @brief Get value of string option */ const std::string& getString(const std::string &name) const { return this->get<std::string>(name); } /*! - * @brief Check if an option is set + * @brief Check if an option is set */ const bool isSet(const std::string &name) const { return this->vm.count(name) > 0; } + + /*! + * @brief Register a new module. + * + * A new settings module can be registered via + * @code + * mrmc::settings::Settings::registerModule<mrmc::ModuleClass>(); + * @endcode + * This has to be done before any parsing takes place, i.e. before newInstance() is called. + * + * This function implicitly defines the following interface for any SettingsModule: + * @code + * static std::string getModuleName(); + * static std::pair< std::string, std::string > getOptionTrigger(); + * static void putOptions(boost::program_options::options_description*); + * @endcode + * + * The semantic is the following: + * If the trigger <a,b> is true, i.e. if option a is set to b, + * the options_description object will be added to the internal + * option object. + * + * Furthermore, it will generate the option specified by the + * trigger. + * + * The functions could look like this: + * @code + * static std::string getModuleName() { return "Backend A"; } + * static std::pair<std::string, std::string> getOptionTrigger() { + * return std::pair<std::string, std::string>("backend", "a"); + * } + * static void putOptions(boost::program_options::options_description* desc) { + * desc->add_options()("foo", "bar"); + * } + * @endcode + */ + template <typename T> + static void registerModule() + { + // get trigger + std::pair< std::string, std::string > trigger = T::getOptionTrigger(); + // build description name + std::stringstream str; + str << T::getModuleName() << " (" << trigger.first << " = " << trigger.second << ")"; + bpo::options_description* desc = new bpo::options_description(str.str()); + // but options + T::putOptions(desc); + // store + Settings::modules[ trigger ] = desc; + } friend std::ostream& help(std::ostream& os); friend std::ostream& helpConfigfile(std::ostream& os); @@ -121,31 +146,20 @@ namespace settings { */ void secondRun(const int argc, const char* argv[], const char* filename); - /*! - * @brief Option descriptions for config file. - */ - bpo::options_description configfile; - /*! - * @brief Option descriptions for config file and command line. - */ - bpo::options_description generic; - /*! - * @brief Option descriptions for command line. - */ - bpo::options_description commandline; /*! * @brief Option description for positional arguments on command line. */ bpo::positional_options_description positional; /*! - * @brief Collecting option descriptions for command line. + * @brief Collecting option descriptions. */ - static std::unique_ptr<bpo::options_description> cli; + static std::unique_ptr<bpo::options_description> desc; + /*! - * @brief Collecting option descriptions for config file. + * @brief Contains option descriptions for all modules. */ - static std::unique_ptr<bpo::options_description> conf; + static std::map< std::pair< std::string, std::string >, bpo::options_description* > modules; /*! * @brief option mapping. @@ -160,7 +174,7 @@ namespace settings { /*! * @brief actual instance of this class. */ - static Settings* inst; + static Settings* inst; }; /*! @@ -168,11 +182,6 @@ namespace settings { */ std::ostream& help(std::ostream& os); - /*! - * @brief Print help for config file options. - */ - std::ostream& helpConfigfile(std::ostream& os); - /*! * @brief Return current instance. * @@ -199,195 +208,7 @@ namespace settings { Settings::inst = new Settings(argc, argv, filename); return Settings::inst; } - - /*! - * @brief Function type for functions registering new options. - */ - typedef void(*RegisterCallback)(bpo::options_description&); - - /*! - * @brief Function type for functions changing the parser state - * between the first and second run. - */ - typedef void(*IntermediateCallback)(bpo::options_description*, bpo::variables_map&); - - /*! - * @brief Function type for function checking constraints on settings. - */ - typedef bool(*CheckerCallback)(bpo::variables_map&); - - /*! - * @brief This enums specifies the three types of options. - */ - enum CallbackType { - //! Option can be set in config file - CB_CONFIG, - //! Option can be set on command line - CB_CLI, - //! Option can be set in config file and command line - CB_GENERIC - }; - - /*! - * @brief This class handles callbacks for registering new options and - * checking constraints on them afterwards. - * - * As it should never be used directly, but only through the Register - * class, it does not provide any public methods. - * - * This class is also a singleton (like Settings) and is implemented much - * simpler as we don't need any custom initialization code. - */ - class Callbacks - { - public: - inline void put(const CallbackType type, const RegisterCallback ptr) - { - if (this->disabled) throw mrmc::exceptions::InvalidSettings(); - this->registerList.push_back(std::pair<CallbackType, RegisterCallback>(type, ptr)); - } - inline void put(const CallbackType type, const IntermediateCallback ptr) - { - if (this->disabled) throw mrmc::exceptions::InvalidSettings(); - this->intermediateList.push_back(std::pair<CallbackType, IntermediateCallback>(type, ptr)); - } - inline void put(const CheckerCallback ptr) - { - if (this->disabled) throw mrmc::exceptions::InvalidSettings(); - this->checkerList.push_back(ptr); - } - - private: - /*! - * @brief Stores register callbacks. - */ - std::list<std::pair<CallbackType, RegisterCallback>> registerList; - - /*! - * @brief Stores intermediate callbacks. - */ - std::list<std::pair<CallbackType, IntermediateCallback>> intermediateList; - - /*! - * @brief Stores check callbacks. - */ - std::list<CheckerCallback> checkerList; - - /*! - * @brief Stores if we already loaded the settings. - */ - bool disabled; - - /*! - * @brief Private constructor. - */ - Callbacks() : disabled(false) {} - /*! - * @brief Private copy constructor. - */ - Callbacks(const Callbacks&) {} - /*! - * @brief Private destructor. - */ - ~Callbacks() {} - - /*! - * @brief Returns current instance to create singleton. - * @return current instance - */ - inline static Callbacks* instance() - { - static Callbacks inst; - return &inst; - } - - /*! - * @brief Register class needs access to lists. - */ - friend class Register; - /*! - * @brief Settings class need access to lists. - */ - friend class Settings; - }; - - /*! - * @brief Wrapper class to allow for registering callbacks during - * static initialization. - * - * To use this class, use the following includes: - * @code - * #include "src/utility/settings.h" - * #include <boost/program_options.hpp> - * namespace bpo = boost::program_options; - * @endcode - */ - class Register - { - public: - /*! - * @brief Registers given function as register callback. - * - * This constructor registers a callback routine that might add - * new options for the Settings class. It should be used like - * this: - * @code - * void register(bpo::options_description& desc) { - * // do something with desc here - * } - * mrmc::settings::Register reg(mrmc::settings::CB_CLI, ®ister); - * @endcode - * This code should be executed during static initialization, i.e. - * it should be somewhere in the cpp-file. - */ - Register(const CallbackType type, const RegisterCallback ptr) - { - mrmc::settings::Callbacks::instance()->put(type, ptr); - } - - /*! - * @brief Registers given function as intermediate callback. - * - * This constructor registers a callback routine that can check - * the option assignment after the first run and change the - * options description before the second run. - * It should be used like this: - * @code - * void intermediate(bpo::options_description& desc, bpo::variables_map& map) { - * // check contents of map and maybe change desc - * } - * mrmc::settings::Register reg(mrmc::settings::CB_CLI, &intermediate); - * @endcode - * This code should be executed during static initialization, i.e. - * it should be somewhere in the cpp-file. - */ - Register(const CallbackType type, const IntermediateCallback ptr) - { - mrmc::settings::Callbacks::instance()->put(type, ptr); - } - - /*! - * @brief Registers given function as check callback. - * - * This constructor registers a callback routine that can check - * the option assignment after the Settings class has loaded - * them. It should be used like this: - * @code - * void check(bpo::variables_map& map) { - * // check contents of map - * } - * mrmc::settings::Register reg(&check); - * @endcode - * This code should be executed during static initialization, i.e. - * it should be somewhere in the cpp-file. - */ - Register(const CheckerCallback ptr) - { - mrmc::settings::Callbacks::instance()->put(ptr); - } - }; - } // namespace settings } // namespace mrmc