Commit 86f785c1 authored by Simon Spannagel's avatar Simon Spannagel
Browse files

Update ConfigManager, add OptionParser

parent a8eb9a9e
......@@ -12,6 +12,7 @@ ADD_LIBRARY(CorryvreckanCore SHARED
config/ConfigReader.cpp
config/Configuration.cpp
config/exceptions.cpp
config/OptionParser.cpp
module/Module.cpp
module/ModuleManager.cpp
)
......
/**
* @file
* @brief Implementation of config manager
* @copyright Copyright (c) 2017 CERN and the Allpix Squared authors.
* @copyright Copyright (c) 2017 CERN and the Corryvreckan authors.
* This software is distributed under the terms of the MIT License, copied verbatim in the file "LICENSE.md".
* In applying this license, CERN does not waive the privileges and immunities granted to it by virtue of its status as an
* Intergovernmental Organization or submit itself to any jurisdiction.
......@@ -24,117 +24,159 @@ using namespace corryvreckan;
/**
* @throws ConfigFileUnavailableError If the main configuration file cannot be accessed
*/
ConfigManager::ConfigManager(std::string file_name) : file_name_(std::move(file_name)) {
LOG(TRACE) << "Using " << file_name_ << " as main configuration file";
ConfigManager::ConfigManager(std::string file_name,
std::initializer_list<std::string> global,
std::initializer_list<std::string> ignore) {
// Check if the file exists
std::ifstream file(file_name_);
std::ifstream file(file_name);
if(!file) {
throw ConfigFileUnavailableError(file_name_);
throw ConfigFileUnavailableError(file_name);
}
// Convert main file to absolute path
file_name_ = corryvreckan::get_canonical_path(file_name_);
// Initialize global base configuration with absolute file name
// FIXME do not hard-code name!
global_base_config_ = Configuration("Corryvreckan", file_name_);
file_name = corryvreckan::get_canonical_path(file_name);
LOG(TRACE) << "Reading main configuration";
// Read the file
reader_.add(file, file_name_);
ConfigReader reader(file, file_name);
// Convert all global and ignored names to lower case and store them
auto lowercase = [](const std::string& in) {
std::string out(in);
std::transform(out.begin(), out.end(), out.begin(), ::tolower);
return out;
};
std::transform(global.begin(), global.end(), std::inserter(global_names_, global_names_.end()), lowercase);
std::transform(ignore.begin(), ignore.end(), std::inserter(ignore_names_, ignore_names_.end()), lowercase);
// Initialize global base configuration
global_config_ = reader.getHeaderConfiguration();
// Store all the configurations read
for(auto& config : reader.getConfigurations()) {
// Skip all ignored sections
std::string config_name = config.getName();
std::transform(config_name.begin(), config_name.end(), config_name.begin(), ::tolower);
if(ignore_names_.find(config_name) != ignore_names_.end()) {
continue;
}
// Merge all global section with the global config
if(global_names_.find(config_name) != global_names_.end()) {
global_config_.merge(config);
continue;
}
module_configs_.push_back(config);
}
// Reading detector file
std::string detector_file_name = global_config_.getPath("detectors_file", true);
LOG(TRACE) << "Reading detector configuration";
std::ifstream detector_file(detector_file_name);
ConfigReader detector_reader(detector_file, detector_file_name);
auto detector_configs = detector_reader.getConfigurations();
detector_configs_ = std::list<Configuration>(detector_configs.begin(), detector_configs.end());
}
/**
* @warning Only one header can be added in this way to define its main name
* The global configuration is the combination of all sections with a global header.
*/
void ConfigManager::setGlobalHeaderName(std::string name) {
global_default_name_ = name;
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
global_names_.emplace(std::move(name));
}
void ConfigManager::addGlobalHeaderName(std::string name) {
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
global_names_.emplace(std::move(name));
Configuration& ConfigManager::getGlobalConfiguration() {
return global_config_;
}
/**
* The global configuration is the combination of all sections with a global header.
* Load all extra options that should be added on top of the configuration in the file. The options loaded here are
* automatically applied to the module instance when these are added later.
*/
Configuration ConfigManager::getGlobalConfiguration() {
// Copy base config and set name
Configuration global_config = global_base_config_;
// FIXME needs to be done when loading
// global_config.setName(global_default_name_);
// Add all other global configuration
for(auto& global_name : global_names_) {
auto configs = reader_.getConfigurations(global_name);
for(auto& config : configs) {
global_config.merge(config);
}
bool ConfigManager::loadModuleOptions(const std::vector<std::string>& options) {
bool optionsApplied = false;
// Parse the options
for(auto& option : options) {
module_option_parser_.parseOption(option);
}
return global_config;
}
void ConfigManager::addIgnoreHeaderName(std::string name) {
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
ignore_names_.emplace(std::move(name));
// Apply global options
optionsApplied = module_option_parser_.applyGlobalOptions(global_config_) || optionsApplied;
// Apply module options
for(auto& config : module_configs_) {
optionsApplied = module_option_parser_.applyOptions(config.getName(), config) || optionsApplied;
}
return optionsApplied;
}
/**
* Option is split in a key / value pair, an error is thrown if that is not possible. When the key contains at least one dot
* it is interpreted as a relative configuration with the module identified by the first dot. In that case the option is
* applied during module loading when either the unique or the configuration name match. Otherwise the key is interpreted as
* global key and is added to the global header.
* Load all extra options that should be added on top of the detector configuration in the file. The options loaded here are
* automatically applied to the detector instance when these are added later and will be taken into account when possibly
* loading customized detector models.
*/
void ConfigManager::parseOption(std::string line) {
line = corryvreckan::trim(line);
auto key_value = ConfigReader::parseKeyValue(line);
auto key = key_value.first;
auto value = key_value.second;
auto dot_pos = key.find('.');
if(dot_pos == std::string::npos) {
// Global option, add to the global base config
global_base_config_.setText(key, value);
} else {
// Other identifier bound option is passed
auto identifier = key.substr(0, dot_pos);
key = key.substr(dot_pos + 1);
identifier_options_[identifier].push_back(std::make_pair(key, value));
}
}
bool ConfigManager::loadDetectorOptions(const std::vector<std::string>& options) {
bool optionsApplied = false;
// Create the parser
OptionParser detector_option_parser;
bool ConfigManager::applyOptions(const std::string& identifier, Configuration& config) {
if(identifier_options_.find(identifier) == identifier_options_.end()) {
return false;
// Parse the options
for(auto& option : options) {
detector_option_parser.parseOption(option);
}
for(auto& key_value : identifier_options_[identifier]) {
config.setText(key_value.first, key_value.second);
// Apply detector options
for(auto& config : detector_configs_) {
optionsApplied = detector_option_parser.applyOptions(config.getName(), config) || optionsApplied;
}
return true;
return optionsApplied;
}
bool ConfigManager::hasConfiguration(const std::string& name) {
return reader_.hasConfiguration(name);
/**
* All special global and ignored sections are not included in the list of module configurations.
*/
std::list<Configuration>& ConfigManager::getModuleConfigurations() {
return module_configs_;
}
/**
* The list of detector configurations is read from the configuration defined in 'detector_file'
*/
std::list<Configuration>& ConfigManager::getDetectorConfigurations() {
return detector_configs_;
}
/**
* All special global and ignored sections are removed before returning the rest of the configurations. The list of normal
* sections is used by the ModuleManager to instantiate all the required modules.
* @warning A previously stored configuration is directly invalidated if the same unique name is used again
*
* An instance configuration is a specialized configuration for a particular module instance. If an unique name already
* exists the previous record is deleted and a new configuration record corresponding to the replaced instance is added.
*/
std::vector<Configuration> ConfigManager::getConfigurations() const {
std::vector<Configuration> result;
for(auto& config : reader_.getConfigurations()) {
// ignore all global and ignores names
std::string config_name = config.getName();
std::transform(config_name.begin(), config_name.end(), config_name.begin(), ::tolower);
if(global_names_.find(config_name) != global_names_.end() ||
ignore_names_.find(config_name) != ignore_names_.end()) {
continue;
}
result.push_back(config);
Configuration& ConfigManager::addInstanceConfiguration(const ModuleIdentifier& identifier, const Configuration& config) {
std::string unique_name = identifier.getUniqueName();
// Check uniqueness
if(instance_name_to_config_.find(unique_name) != instance_name_to_config_.end()) {
instance_configs_.erase(instance_name_to_config_[unique_name]);
}
return result;
// Add configuration
instance_configs_.push_back(config);
Configuration& ret_config = instance_configs_.back();
instance_name_to_config_[unique_name] = --instance_configs_.end();
// Add identifier key to config
ret_config.set<std::string>("identifier", identifier.getIdentifier());
// Apply instance options
module_option_parser_.applyOptions(unique_name, ret_config);
return ret_config;
}
/**
* The list of instance configurations can contain configurations with duplicate names, but the instance configuration is
* guaranteed to have a configuration value 'identifier' that contains an unique identifier for every same config name
*/
std::list<Configuration>& ConfigManager::getInstanceConfigurations() {
return instance_configs_;
}
/**
* @file
* @brief Interface to the main configuration and its normal and special sections
* @copyright Copyright (c) 2017 CERN and the Allpix Squared authors.
* @copyright Copyright (c) 2017 CERN and the Corryvreckan authors.
* This software is distributed under the terms of the MIT License, copied verbatim in the file "LICENSE.md".
* In applying this license, CERN does not waive the privileges and immunities granted to it by virtue of its status as an
* Intergovernmental Organization or submit itself to any jurisdiction.
......@@ -16,6 +16,8 @@
#include "ConfigReader.hpp"
#include "Configuration.hpp"
#include "OptionParser.hpp"
#include "core/module/ModuleIdentifier.hpp"
namespace corryvreckan {
......@@ -35,8 +37,12 @@ namespace corryvreckan {
/**
* @brief Construct the configuration manager
* @param file_name Path to the main configuration file
* @param global List of sections representing the global configuration (excluding the empty header section)
* @param ignore List of sections that should be ignored
*/
explicit ConfigManager(std::string file_name);
explicit ConfigManager(std::string file_name,
std::initializer_list<std::string> global = {},
std::initializer_list<std::string> ignore = {"Ignore"});
/**
* @brief Use default destructor
*/
......@@ -59,70 +65,63 @@ namespace corryvreckan {
/// @}
/**
* @brief Set the name of the global header and add to the global names
* @param name Name of a global header that should be used as the name
* @brief Get the global configuration
* @return Reference to global configuration
*/
// TODO [doc] Should only set the name and do not add it
void setGlobalHeaderName(std::string name);
Configuration& getGlobalConfiguration();
/**
* @brief Add a global header name
* @param name Name of a global header section
* @brief Get all the module configurations
* @return Reference to list of module configurations
*/
// TODO [doc] Rename to addGlobalHeader
void addGlobalHeaderName(std::string name);
std::list<Configuration>& getModuleConfigurations();
/**
* @brief Get the global configuration
* @return Global configuration
* @brief Add a new module instance configuration and applies instance options
* @param identifier Identifier for this module instance
* @param config Instance configuration to store
* @return Reference to stored instance configuration
*/
Configuration getGlobalConfiguration();
Configuration& addInstanceConfiguration(const ModuleIdentifier& identifier, const Configuration& config);
/**
* @brief Add a header name to fully ignore
* @param name Name of a header to ignore
* @brief Get all the instance configurations
* @return Reference to list of instance configurations
*/
// TODO [doc] Rename to ignoreHeader
void addIgnoreHeaderName(std::string name);
std::list<Configuration>& getInstanceConfigurations();
/**
* @brief Parse an extra configuration option
* @param line Line with the option
* @brief Load module options and directly apply them to the global configuration and the module configurations
* @param options List of options to load and apply
* @return True if any actual options where applied
*
* @note Instance configuration options are applied in \ref ConfigManager::addInstanceConfiguration instead
*/
void parseOption(std::string line);
bool loadModuleOptions(const std::vector<std::string>& options);
/**
* @brief Apply all relevant options to the passed configuration
* @param identifier Identifier to select the options to apply
* @param config Configuration option where the options should be applied to
* @return True if the configuration was changed because of applied options
* @brief Get all the detector configurations
* @return Reference to list of detector configurations
*/
bool applyOptions(const std::string& identifier, Configuration& config);
std::list<Configuration>& getDetectorConfigurations();
/**
* @brief Return if section with given name exists
* @param name Name of the section
* @return True if at least one section with that name exists, false otherwise
* @brief Load detector specific options
* @param options List of options to load and apply
* @return True if any actual options where applied
*/
bool hasConfiguration(const std::string&);
/**
* @brief Get all configurations that are not global or ignored
* @return List of all normal configurations
*/
std::vector<Configuration> getConfigurations() const;
bool loadDetectorOptions(const std::vector<std::string>& options);
private:
std::string file_name_;
std::set<std::string> global_names_{};
std::set<std::string> ignore_names_{};
ConfigReader reader_;
OptionParser module_option_parser_;
Configuration global_base_config_;
std::string global_default_name_{};
std::set<std::string> global_names_{};
std::list<Configuration> module_configs_;
Configuration global_config_;
std::set<std::string> ignore_names_{};
std::list<Configuration> detector_configs_;
std::map<std::string, std::vector<std::pair<std::string, std::string>>> identifier_options_{};
std::list<Configuration> instance_configs_;
std::map<std::string, std::list<Configuration>::iterator> instance_name_to_config_;
};
} // namespace corryvreckan
......
/**
* @file
* @brief Implementation of option parser
* @copyright Copyright (c) 2017 CERN and the Corryvreckan authors.
* This software is distributed under the terms of the MIT License, copied verbatim in the file "LICENSE.md".
* In applying this license, CERN does not waive the privileges and immunities granted to it by virtue of its status as an
* Intergovernmental Organization or submit itself to any jurisdiction.
*/
#include "OptionParser.hpp"
#include "ConfigReader.hpp"
using namespace corryvreckan;
/**
* Option is split in a key / value pair, an error is thrown if that is not possible. When the key contains at least one dot
* it is interpreted as a relative configuration with the module / detector identified by the first dot. In that case the
* option is applied during loading when either the unique or the configuration name match. Otherwise the key is interpreted
* as global key and is added to the global header.
*/
void OptionParser::parseOption(std::string line) {
line = corryvreckan::trim(line);
auto key_value = ConfigReader::parseKeyValue(line);
auto key = key_value.first;
auto value = key_value.second;
auto dot_pos = key.find('.');
if(dot_pos == std::string::npos) {
// Global option, add to the global options list
global_options_.emplace_back(key, value);
} else {
// Other identifier bound option is passed
auto identifier = key.substr(0, dot_pos);
key = key.substr(dot_pos + 1);
identifier_options_[identifier].push_back(std::make_pair(key, value));
}
}
bool OptionParser::applyGlobalOptions(Configuration& config) {
for(auto& key_value : global_options_) {
config.setText(key_value.first, key_value.second);
}
return !global_options_.empty();
}
bool OptionParser::applyOptions(const std::string& identifier, Configuration& config) {
if(identifier_options_.find(identifier) == identifier_options_.end()) {
return false;
}
for(auto& key_value : identifier_options_[identifier]) {
config.setText(key_value.first, key_value.second);
}
return true;
}
/**
* @file
* @brief Option parser for additional command line options
* @copyright Copyright (c) 2017 CERN and the Corryvreckan authors.
* This software is distributed under the terms of the MIT License, copied verbatim in the file "LICENSE.md".
* In applying this license, CERN does not waive the privileges and immunities granted to it by virtue of its status as an
* Intergovernmental Organization or submit itself to any jurisdiction.
*/
#ifndef CORRYVRECKAN_OPTION_PARSER_H
#define CORRYVRECKAN_OPTION_PARSER_H
#include <set>
#include <string>
#include <vector>
#include "Configuration.hpp"
namespace corryvreckan {
/**
* @ingroup Managers
* @brief Option parser responsible for parsing and caching command line arguments
*
* The option parser stores additional configuration items provided via the command line interface for later reference,
* since most of the parameters can only be applied once all modules have been instantiated.
*/
class OptionParser {
public:
/**
* @brief Use default constructor
*/
OptionParser() = default;
/**
* @brief Use default destructor
*/
~OptionParser() = default;
/// @{
/**
* @brief Copying the option parser is not allowed
*/
OptionParser(const OptionParser&) = delete;
OptionParser& operator=(const OptionParser&) = delete;
/// @}
/// @{
/**
* @brief Use default move behaviour
*/
OptionParser(OptionParser&&) noexcept = default;
OptionParser& operator=(OptionParser&&) noexcept = default;
/// @}
/**
* @brief Parse an extra configuration option
* @param line Line with the option
*/
void parseOption(std::string line);
/**
* @brief Apply all global options to a given global configuration object
* @param config Global configuration option to which the options should be applied to
* @return True if new global configuration options were applied
*/
bool applyGlobalOptions(Configuration& config);
/**
* @brief Apply all relevant options to the given configuration object
* @param identifier Identifier to select the options to apply
* @param config Configuration option to which the options should be applied to
* @return True if the configuration was changed because of applied options
*/
bool applyOptions(const std::string& identifier, Configuration& config);
private:
std::vector<std::pair<std::string, std::string>> global_options_{};
std::map<std::string, std::vector<std::pair<std::string, std::string>>> identifier_options_{};
};
} // namespace corryvreckan
#endif /* CORRYVRECKAN_OPTION_PARSER_H */
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment