|
|
@ -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 |
|
|
|
|
|
|
|