Configuration.cpp 7.55 KB
Newer Older
1
2
3
/**
 * @file
 * @brief Implementation of configuration
4
 * @copyright Copyright (c) 2017 CERN and the Corryvreckan authors.
5
6
 * 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
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 * 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"

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();
}

28
29
30
31
32
33
34
35
36
37
38
39
40
41
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(auto& key : keys) {
        if(has(key)) {
            found++;
        }
    }
    return found;
}

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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);
}

/**
65
 * @throws InvalidValueError If the path did not exists while the check_exists parameter is given
66
 *
67
 * For a relative path the absolute path of the configuration file is preprended. Absolute paths are not changed.
68
69
70
71
72
73
74
75
76
77
 */
// 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());
    }
}
/**
78
 * @throws InvalidValueError If the path did not exists while the check_exists parameter is given
79
 *
80
 * For all relative paths the absolute path of the configuration file is preprended. Absolute paths are not changed.
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
 */
// 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 {
    // If not a absolute path, make it an absolute path
101
    if(path.front() != '/') {
102
103
104
105
106
        // Get base directory of config file
        std::string directory = path_.substr(0, path_.find_last_of('/'));

        // Set new path
        path = directory + "/" + path;
107
    }
108

109
110
111
112
    // 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) {
        path = corryvreckan::get_canonical_path(path);
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
    }
    return path;
}

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

/**
 *  The alias is only used if new key does not exist but old key does
 */
void Configuration::setAlias(const std::string& new_key, const std::string& old_key) {
    if(!has(old_key) || has(new_key)) {
        return;
    }
    try {
        config_[new_key] = config_.at(old_key);
    } catch(std::out_of_range& e) {
        throw MissingKeyError(old_key, getName());
    }
}

unsigned int Configuration::countSettings() const {
    return static_cast<unsigned int>(config_.size());
}

/**
140
 * All keys that are already defined earlier in this configuration are not changed.
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
 */
void Configuration::merge(const Configuration& other) {
    for(auto config_pair : other.config_) {
        // Only merge values that do not yet exist
        if(!has(config_pair.first)) {
            setText(config_pair.first, config_pair.second);
        }
    }
}

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

    // Loop over all configuration keys
    for(auto& key_value : config_) {
        // Skip internal keys starting with an underscore
157
        if(!key_value.first.empty() && key_value.first.front() == '_') {
158
159
160
161
162
163
164
165
            continue;
        }

        result.emplace_back(key_value);
    }

    return result;
}
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249

/**
 * String is recursively parsed for all pair of [ and ] brackets. All parts between single or double quotation marks are
 * skipped.
 */
std::unique_ptr<Configuration::parse_node> Configuration::parse_value(std::string str, int depth) {

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

    // Initialize variables for non-zero levels
    size_t beg = 1, lst = 1;
    int 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 = 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;
}