Commit 78edf115 authored by Simon Spannagel's avatar Simon Spannagel
Browse files

Add Configuration and utilities from Allpix Squared

parent b24abf15
/**
* @file
* @brief Implementation of config manager
* @copyright Copyright (c) 2017 CERN and the Allpix Squared 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 "ConfigManager.hpp"
#include <algorithm>
#include <fstream>
#include <string>
#include <vector>
#include "Configuration.hpp"
#include "core/utils/file.h"
#include "core/utils/log.h"
#include "exceptions.h"
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";
// Check if the file exists
std::ifstream file(file_name_);
if(!file) {
throw ConfigFileUnavailableError(file_name_);
}
// Convert main file to absolute path
file_name_ = corryvreckan::get_absolute_path(file_name_);
// Read the file
reader_.add(file, file_name_);
}
/**
* @warning Only one header can be added in this way to define its main name
*/
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));
}
/**
* The global configuration is the combination of all sections with a global header.
*/
Configuration ConfigManager::getGlobalConfiguration() {
Configuration global_config(global_default_name_, file_name_);
for(auto& global_name : global_names_) {
auto configs = reader_.getConfigurations(global_name);
for(auto& config : configs) {
global_config.merge(config);
}
}
return global_config;
}
void ConfigManager::addIgnoreHeaderName(std::string name) {
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
ignore_names_.emplace(std::move(name));
}
bool ConfigManager::hasConfiguration(const std::string& name) {
return reader_.hasConfiguration(name);
}
/**
* 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.
*/
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);
}
return result;
}
/**
* @file
* @brief Interface to the main configuration and its normal and special sections
* @copyright Copyright (c) 2017 CERN and the Allpix Squared 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_CONFIG_MANAGER_H
#define CORRYVRECKAN_CONFIG_MANAGER_H
#include <set>
#include <string>
#include <vector>
#include "ConfigReader.hpp"
#include "Configuration.hpp"
namespace corryvreckan {
/**
* @ingroup Managers
* @brief Manager responsible for loading and providing access to the main configuration
*
* The main configuration is the single most important source of configuration. It is split up in:
* - Global headers that are combined into a single global (not module specific) configuration
* - Ignored headers that are not used at all (mainly useful for debugging)
* - All other headers representing all modules that have to be instantiated by the ModuleManager
*
* Configuration sections are always case-sensitive.
*/
class ConfigManager {
public:
/**
* @brief Construct the configuration manager
* @param file_name Path to the main configuration file
*/
explicit ConfigManager(std::string file_name);
/**
* @brief Use default destructor
*/
~ConfigManager() = default;
/// @{
/**
* @brief Copying the manager is not allowed
*/
ConfigManager(const ConfigManager&) = delete;
ConfigManager& operator=(const ConfigManager&) = delete;
/// @}
/// @{
/**
* @brief Use default move behaviour
*/
ConfigManager(ConfigManager&&) noexcept = default;
ConfigManager& operator=(ConfigManager&&) noexcept = default;
/// @}
/**
* @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
*/
// TODO [doc] Should only set the name and do not add it
void setGlobalHeaderName(std::string name);
/**
* @brief Add a global header name
* @param name Name of a global header section
*/
// TODO [doc] Rename to addGlobalHeader
void addGlobalHeaderName(std::string name);
/**
* @brief Get the global configuration
* @return Global configuration
*/
Configuration getGlobalConfiguration();
/**
* @brief Add a header name to fully ignore
* @param name Name of a header to ignore
*/
// TODO [doc] Rename to ignoreHeader
void addIgnoreHeaderName(std::string name);
/**
* @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
*/
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;
private:
std::string file_name_;
ConfigReader reader_;
std::string global_default_name_;
std::set<std::string> global_names_;
std::set<std::string> ignore_names_;
};
} // namespace corryvreckan
#endif /* CORRYVRECKAN_CONFIG_MANAGER_H */
/**
* @file
* @brief Implementation of config reader
* @copyright Copyright (c) 2017 CERN and the Allpix Squared 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 "ConfigReader.hpp"
#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <string>
#include <vector>
#include "core/utils/file.h"
#include "core/utils/log.h"
#include "exceptions.h"
using namespace corryvreckan;
ConfigReader::ConfigReader() = default;
ConfigReader::ConfigReader(std::istream& stream, std::string file_name) : ConfigReader() {
add(stream, std::move(file_name));
}
ConfigReader::ConfigReader(const ConfigReader& other) : conf_array_(other.conf_array_) {
copy_init_map();
}
ConfigReader& ConfigReader::operator=(const ConfigReader& other) {
conf_array_ = other.conf_array_;
copy_init_map();
return *this;
}
void ConfigReader::copy_init_map() {
conf_map_.clear();
for(auto iter = conf_array_.begin(); iter != conf_array_.end(); ++iter) {
conf_map_[iter->getName()].push_back(iter);
}
}
/**
* @throws ConfigParseError If an error occurred during the parsing of the stream
*
* The configuration is immediately parsed and all of its configurations are available after the functions returns.
*/
void ConfigReader::add(std::istream& stream, std::string file_name) {
LOG(TRACE) << "Parsing configuration file " << file_name;
// Convert file name to absolute path (if given)
if(!file_name.empty()) {
file_name = corryvreckan::get_absolute_path(file_name);
}
// Build first empty configuration
std::string section_name;
Configuration conf(section_name, file_name);
int line_num = 0;
while(true) {
// Process config line by line
std::string line;
if(stream.eof()) {
break;
}
std::getline(stream, line);
++line_num;
// Find equal sign
size_t equals_pos = line.find('=');
if(equals_pos == std::string::npos) {
line = corryvreckan::trim(line);
// Ignore empty lines or comments
if(line == "" || line[0] == '#') {
continue;
}
// Parse new section
if(line[0] == '[' && line[line.length() - 1] == ']') {
// Ignore empty sections if they contain no configurations
if(!conf.getName().empty() || conf.countSettings() > 0) {
// Add previous section
addConfiguration(conf);
}
// Begin new section
section_name = std::string(line, 1, line.length() - 2);
conf = Configuration(section_name, file_name);
} else {
// FIXME: should be a bit more helpful...
throw ConfigParseError(file_name, line_num);
}
} else {
std::string key = trim(std::string(line, 0, equals_pos));
std::string value = trim(std::string(line, equals_pos + 1));
char ins = 0;
for(size_t i = 0; i < value.size(); ++i) {
if(value[i] == '\'' || value[i] == '\"') {
if(ins == 0) {
ins = value[i];
} else if(ins == value[i]) {
ins = 0;
}
}
if(ins == 0 && value[i] == '#') {
value = std::string(value, 0, i);
break;
}
}
// Add the config key
conf.setText(key, trim(value));
}
}
// Add last section
addConfiguration(conf);
}
void ConfigReader::addConfiguration(Configuration config) {
conf_array_.push_back(std::move(config));
std::string section_name = conf_array_.back().getName();
std::transform(section_name.begin(), section_name.end(), section_name.begin(), ::tolower);
conf_map_[section_name].push_back(--conf_array_.end());
}
void ConfigReader::clear() {
conf_map_.clear();
conf_array_.clear();
}
bool ConfigReader::hasConfiguration(std::string name) const {
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
return conf_map_.find(name) != conf_map_.end();
}
unsigned int ConfigReader::countConfigurations(std::string name) const {
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
if(!hasConfiguration(name)) {
return 0;
}
return static_cast<unsigned int>(conf_map_.at(name).size());
}
/**
* @warning This will have the file path of the first header section
* @note An empty configuration is returned if no empty section is found
*/
Configuration ConfigReader::getHeaderConfiguration() const {
// Get empty configurations
std::vector<Configuration> configurations = getConfigurations("");
if(configurations.empty()) {
// Use all configurations to find the file name if no empty
configurations = getConfigurations();
std::string file_name;
if(!configurations.empty()) {
file_name = configurations.at(0).getFilePath();
}
return Configuration("", file_name);
}
// Merge all configurations
Configuration header_config = configurations.at(0);
for(auto& config : configurations) {
// NOTE: Merging first configuration again has no effect
header_config.merge(config);
}
return header_config;
}
std::vector<Configuration> ConfigReader::getConfigurations(std::string name) const {
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
if(!hasConfiguration(name)) {
return std::vector<Configuration>();
}
std::vector<Configuration> result;
for(auto& iter : conf_map_.at(name)) {
result.push_back(*iter);
}
return result;
}
std::vector<Configuration> ConfigReader::getConfigurations() const {
return std::vector<Configuration>(conf_array_.begin(), conf_array_.end());
}
/**
* @file
* @brief Provides a reader for configuration files
* @copyright Copyright (c) 2017 CERN and the Allpix Squared 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_CONFIG_READER_H
#define CORRYVRECKAN_CONFIG_READER_H
#include <istream>
#include <list>
#include <map>
#include <string>
#include <vector>
#include "Configuration.hpp"
namespace corryvreckan {
/**
* @brief Reader of configuration files
*
* Read the internal configuration file format used in the framework. The format contains
* - A set of section header between [ and ] brackets
* - Key/value pairs linked to the last defined section (or the empty section if none has been defined yet)
*/
class ConfigReader {
public:
/**
* @brief Constructs a config reader without any attached streams
*/
ConfigReader();
/**
* @brief Constructs a config reader with a single attached stream
* @param stream Stream to read configuration from
* @param file_name Name of the file related to the stream or empty if not linked to a file
*/
explicit ConfigReader(std::istream& stream, std::string file_name = "");
/**
* @brief Adds a configuration stream to read
* @param stream Stream to read configuration from
* @param file_name Name of the file related to the stream or empty if not linked to a file
*/
void add(std::istream&, std::string file_name = "");
/**
* @brief Directly add a configuration object to the reader
* @param config Configuration object to add
*/
void addConfiguration(Configuration config);
/// @{
/**
* @brief Implement correct copy behaviour
*/
ConfigReader(const ConfigReader&);
ConfigReader& operator=(const ConfigReader&);
/// @}
/// @{
/**
* @brief Use default move behaviour
*/
ConfigReader(ConfigReader&&) noexcept = default;
ConfigReader& operator=(ConfigReader&&) noexcept = default;
/// @}
/**
* @brief Removes all streams and all configurations
*/
void clear();
/**
* @brief Check if a configuration exists
* @param name Name of a configuration header to search for
* @return True if at least a single configuration with this name exists, false otherwise
*/
bool hasConfiguration(std::string name) const;
/**
* @brief Count the number of configurations with a particular name
* @param name Name of a configuration header
* @return The number of configurations with the given name
*/
unsigned int countConfigurations(std::string name) const;
/**
* @brief Get cmobined configuration of all empty sections (usually the header)
* @note Typically this is only the section at the top of the file
* @return Configuration object for the empty section
*/
Configuration getHeaderConfiguration() const;
/**
* @brief Get all configurations with a particular header
* @param name Header name of the configurations to return
* @return List of configurations with the given name
*/
std::vector<Configuration> getConfigurations(std::string name) const;
/**
* @brief Get all configurations
* @return List of all configurations
*/
std::vector<Configuration> getConfigurations() const;
private:
/**
* @brief Initialize the configuration map after copy of the class
*/
void copy_init_map();
std::map<std::string, std::vector<std::list<Configuration>::iterator>> conf_map_;
std::list<Configuration> conf_array_;
};
} // namespace corryvreckan
#endif /* CORRYVRECKAN_CONFIG_MANAGER_H */
/**
* @file
* @brief Implementation of configuration
* @copyright Copyright (c) 2017 CERN and the Allpix Squared 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 "Configuration.hpp"
#include <cassert>
#include <ostream>
#include <stdexcept>
#include <string>
#include "core/utils/file.h"
#include "exceptions.h"
#include <iostream>
using namespace corryvreckan;
Configuration::Configuration(std::string name, std::string path) : name_(std::move(name)), path_(std::move(path)) {}
bool Configuration::has(const std::string& key) const {
return config_.find(key) != config_.cend();
}
std::string Configuration::getName() const {
return name_;
}
std::string Configuration::getFilePath() const {
return path_;
}
std::string Configuration::getText(const std::string& key) const {
try {
// NOTE: returning literally including ""
return config_.at(key);
} catch(std::out_of_range& e) {
throw MissingKeyError(key, getName());
}
}
std::string Configuration::getText(const std::string& key, const std::string& def) const {
if(!has(key)) {
return def;
}
return getText(key);
}
/**
* @throws InvalidValueError If the path did not exists while the check_exists parameter is given
*
* For a relative path the absolute path of the configuration file is preprended. Absolute paths are not changed.
*/
// TODO [doc] Document canonicalizing behaviour
std::string Configuration::getPath(const std::string& key, bool check_exists) const {
try {
return path_to_absolute(get<std::string>(key), check_exists);
} catch(std::invalid_argument& e) {
throw InvalidValueError(*this, key, e.what());
}
}
/**
* @throws InvalidValueError If the path did not exists while the check_exists parameter is given
*
* For all relative paths the absolute path of the configuration file is preprended. Absolute paths are not changed.
*/
// TODO [doc] Document canonicalizing behaviour
std::vector<std::string> Configuration::getPathArray(const std::string& key, bool check_exists) const {
std::vector<std::string> path_array = getArray<std::string>(key);
// Convert all paths to absolute
try {
for(auto& path : path_array) {
path = path_to_absolute(path, check_exists);
}
return path_array;
} catch(std::invalid_argument& e) {
throw InvalidValueError(*this, key, e.what());
}
}
/**
* @throws std::invalid_argument If the path does not exists
*/
std::string Configuration::path_to_absolute(std::string path, bool canonicalize_path) const {