src/core/config/Configuration.cpp

Implementation of configuration. More…

Detailed Description

Implementation of configuration.

Copyright: Copyright (c) 2016-2024 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. SPDX-License-Identifier: MIT

Source code


#include "Configuration.hpp"

#include <cassert>
#include <filesystem>
#include <ostream>
#include <stdexcept>
#include <string>

#include "core/config/exceptions.h"
#include "core/utils/log.h"

using namespace allpix;

Configuration::AccessMarker::AccessMarker(const Configuration::AccessMarker& rhs) {
    for(const auto& [key, value] : rhs.markers_) {
        registerMarker(key);
        markers_.at(key).store(value.load());
    }
}

Configuration::AccessMarker& Configuration::AccessMarker::operator=(const Configuration::AccessMarker& rhs) {
    for(const auto& [key, value] : rhs.markers_) {
        registerMarker(key);
        markers_.at(key).store(value.load());
    }
    return *this;
}

void Configuration::AccessMarker::registerMarker(const std::string& key) {
    markers_.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple());
}

Configuration::Configuration(std::string name, std::filesystem::path path)
    : name_(std::move(name)), path_(std::move(path)) {}

bool Configuration::has(const std::string& key) const { return config_.find(key) != config_.cend(); }

unsigned int Configuration::count(std::initializer_list<std::string> keys) const {
    if(keys.size() == 0) {
        throw std::invalid_argument("list of keys cannot be empty");
    }

    unsigned int found = 0;
    for(const auto& key : keys) {
        if(has(key)) {
            found++;
        }
    }
    return found;
}

std::string Configuration::getText(const std::string& key) const {
    try {
        // NOTE: returning literally including ""
        used_keys_.markUsed(key);
        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);
}

// TODO [doc] Document canonicalizing behaviour
std::filesystem::path 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());
    }
}
std::filesystem::path
Configuration::getPathWithExtension(const std::string& key, const std::string& extension, bool check_exists) const {
    try {
        return path_to_absolute(std::filesystem::path(get<std::string>(key)).replace_extension(extension), check_exists);
    } catch(std::invalid_argument& e) {
        throw InvalidValueError(*this, key, e.what());
    }
}
// TODO [doc] Document canonicalizing behaviour
std::vector<std::filesystem::path> Configuration::getPathArray(const std::string& key, bool check_exists) const {
    std::vector<std::filesystem::path> path_array;

    // Convert all paths to absolute
    try {
        for(auto& path : getArray<std::string>(key)) {
            path_array.emplace_back(path_to_absolute(path, check_exists));
        }
        return path_array;
    } catch(std::invalid_argument& e) {
        throw InvalidValueError(*this, key, e.what());
    }
}
std::filesystem::path Configuration::path_to_absolute(std::filesystem::path path, bool canonicalize_path) const {
    // If not a absolute path, make it an absolute path
    if(!path.is_absolute()) {
        // Get base directory of config file and append the relative path
        path = path_.parent_path() / path;
    }

    // Normalize path only if we have to check if it exists
    // NOTE: This throws an error if the path does not exist
    if(canonicalize_path) {
        try {
            path = std::filesystem::canonical(path);
        } catch(std::filesystem::filesystem_error&) {
            throw std::invalid_argument("path " + path.string() + " not found");
        }
    }
    return path;
}

void Configuration::setText(const std::string& key, const std::string& val) {
    config_[key] = val;
    used_keys_.registerMarker(key);
}

void Configuration::setAlias(const std::string& new_key, const std::string& old_key, bool warn) {
    if(!has(old_key) || has(new_key)) {
        return;
    }
    try {
        config_[new_key] = config_.at(old_key);
        used_keys_.registerMarker(new_key);
        used_keys_.markUsed(old_key);
    } catch(std::out_of_range& e) {
        throw MissingKeyError(old_key, getName());
    }

    if(warn) {
        LOG(WARNING) << "Parameter \"" << old_key << "\" is deprecated and superseded by \"" << new_key << "\"";
    }
}

void Configuration::merge(const Configuration& other) {
    for(const auto& [key, value] : other.config_) {
        // Only merge values that do not yet exist
        if(!has(key)) {
            setText(key, value);
        }
    }
}

std::vector<std::pair<std::string, std::string>> Configuration::getAll() const {
    std::vector<std::pair<std::string, std::string>> result;

    // Loop over all configuration keys
    for(const auto& key_value : config_) {
        // Skip internal keys starting with an underscore
        if(!key_value.first.empty() && key_value.first.front() == '_') {
            continue;
        }

        result.emplace_back(key_value);
    }

    return result;
}

std::vector<std::string> Configuration::getUnusedKeys() const {
    std::vector<std::string> result;

    // Loop over all configuration keys, excluding internal ones
    for(const auto& key_value : getAll()) {
        // Add those to result that have not been accessed:
        if(!used_keys_.isUsed(key_value.first)) {
            result.emplace_back(key_value.first);
        }
    }

    return result;
}

std::unique_ptr<Configuration::parse_node> Configuration::parse_value(std::string str, int depth) { // NOLINT

    auto node = std::make_unique<parse_node>();
    str = allpix::trim(str);
    if(str.empty()) {
        throw std::invalid_argument("element is empty");
    }

    // Initialize variables for non-zero levels
    size_t beg = 1, lst = 1, in_dpt = 0;
    bool in_dpt_chg = false;

    // Implicitly add pair of brackets on zero level
    if(depth == 0) {
        beg = lst = 0;
        in_dpt = 1;
    }

    for(size_t i = 0; i < str.size(); ++i) {
        // Skip over quotation marks
        if(str[i] == '\'' || str[i] == '\"') {
            i = str.find(str[i], i + 1);
            if(i == std::string::npos) {
                throw std::invalid_argument("quotes are not balanced");
            }
            continue;
        }

        // Handle brackets
        if(str[i] == '[') {
            ++in_dpt;
            if(!in_dpt_chg && i != 0) {
                throw std::invalid_argument("invalid start bracket");
            }
            in_dpt_chg = true;
        } else if(str[i] == ']') {
            if(in_dpt == 0) {
                throw std::invalid_argument("brackets are not matched");
            }
            --in_dpt;
            in_dpt_chg = true;
        }

        // Make subitems at the zero level
        if(in_dpt == 1 && (str[i] == ',' || (isspace(str[i]) != 0 && (isspace(str[i - 1]) == 0 && str[i - 1] != ',')))) {
            node->children.push_back(parse_value(str.substr(lst, i - lst), depth + 1));
            lst = i + 1;
        }
    }

    if((depth > 0 && in_dpt != 0) || (depth == 0 && in_dpt != 1)) {
        throw std::invalid_argument("brackets are not balanced");
    }

    // Determine if array or value
    if(in_dpt_chg || depth == 0) {
        // Handle last array item
        size_t end = str.size();
        if(depth != 0) {
            if(str.back() != ']') {
                throw std::invalid_argument("invalid end bracket");
            }
            end = str.size() - 1;
        }
        node->children.push_back(parse_value(str.substr(lst, end - lst), depth + 1));
        node->value = str.substr(beg, end - beg);
    } else {
        // Not an array, handle as value instead
        node->value = std::move(str);
    }

    // Handle zero level where brackets where explicitly added
    if(depth == 0 && node->children.size() == 1 && !node->children.front()->children.empty()) {
        node = std::move(node->children.front());
    }

    return node;
}

Updated on 2024-12-13 at 08:31:37 +0000