diff --git a/include/components/config.hpp b/include/components/config.hpp index 0b2d9501..4bcf9f57 100644 --- a/include/components/config.hpp +++ b/include/components/config.hpp @@ -74,7 +74,7 @@ class config { T get(const string& section, const string& key) const { auto it = m_sections.find(section); if (it == m_sections.end() || it->second.find(key) == it->second.end()) { - throw key_error("Missing parameter [" + section + "." + key + "]"); + throw key_error("Missing parameter \"" + section + "." + key + "\""); } return dereference(section, key, it->second.at(key), convert(string{it->second.at(key)})); } @@ -125,7 +125,7 @@ class config { } if (results.empty()) { - throw key_error("Missing parameter [" + section + "." + key + "-0]"); + throw key_error("Missing parameter \"" + section + "." + key + "-0\""); } return results; @@ -220,7 +220,7 @@ class config { } else if ((pos = path.find(".")) != string::npos) { return dereference_local(path.substr(0, pos), path.substr(pos + 1), section); } else { - throw value_error("Invalid reference defined at [" + section + "." + key + "]"); + throw value_error("Invalid reference defined at \"" + section + "." + key + "\""); } } @@ -251,7 +251,8 @@ class config { size_t pos; if ((pos = key.find(':')) != string::npos) { string fallback = key.substr(pos + 1); - m_log.info("The reference ${%s.%s} does not exist, using defined fallback value \"%s\"", section, key.substr(0, pos), fallback); + m_log.info("The reference ${%s.%s} does not exist, using defined fallback value \"%s\"", section, + key.substr(0, pos), fallback); return convert(move(fallback)); } throw value_error("The reference ${" + section + "." + key + "} does not exist (no fallback set)"); diff --git a/include/utils/string.hpp b/include/utils/string.hpp index 5fb73976..331c8ac1 100644 --- a/include/utils/string.hpp +++ b/include/utils/string.hpp @@ -77,9 +77,9 @@ namespace string_util { string strip(const string& haystack, char needle); string strip_trailing_newline(const string& haystack); - string ltrim(string&& value, const char& needle); - string rtrim(string&& value, const char& needle); - string trim(string&& value, const char& needle); + string ltrim(string&& value, const char& needle = ' '); + string rtrim(string&& value, const char& needle = ' '); + string trim(string&& value, const char& needle = ' '); string join(const vector& strs, const string& delim); vector& split_into(const string& s, char delim, vector& container); diff --git a/src/components/config.cpp b/src/components/config.cpp index 28189c2b..d5d547d6 100644 --- a/src/components/config.cpp +++ b/src/components/config.cpp @@ -77,21 +77,58 @@ void config::warn_deprecated(const string& section, const string& key, string re * Parse key/value pairs from the configuration file */ void config::parse_file() { - std::ifstream in(m_file); - string line; - string section; - unsigned int lineno{0}; - - while (std::getline(in, line)) { - lineno++; - - line = string_util::replace_all(line, "\t", ""); + vector> lines; + vector files{m_file}; + std::function pushline = [&](int lineno, string&& line) { // Ignore empty lines and comments if (line.empty() || line[0] == ';' || line[0] == '#') { - continue; + return; } + string key, value; + string::size_type pos; + + // Filter lines by: + // - key/value pairs + // - section headers + if ((pos = line.find('=')) != string::npos) { + key = forward(string_util::trim(forward(line.substr(0, pos)))); + value = forward(string_util::trim(line.substr(pos + 1))); + } else if (line[0] != '[' || line[line.length() - 1] != ']') { + return; + } + + if (key == "include-file") { + if (value.empty() || !file_util::exists(value)) { + throw value_error("Invalid include file \"" + value + "\" defined on line " + to_string(lineno)); + } + if (std::find(files.begin(), files.end(), value) != files.end()) { + throw value_error("Recursive include file \"" + value + "\""); + } + files.push_back(value); + m_log.trace("config: Including file \"%s\"", value); + for (auto&& l : string_util::split(file_util::contents(value), '\n')) { + pushline(lineno, forward(l)); + } + files.pop_back(); + } else { + lines.emplace_back(make_pair(lineno, line)); + } + }; + + int lineno{0}; + string line; + std::ifstream in(m_file); + while (std::getline(in, line)) { + pushline(++lineno, string_util::replace_all(line, "\t", "")); + } + + string section; + for (auto&& l : lines) { + auto& lineno = l.first; + auto& line = l.second; + // New section if (line[0] == '[' && line[line.length() - 1] == ']') { section = line.substr(1, line.length() - 2); @@ -107,7 +144,7 @@ void config::parse_file() { continue; } - string key{forward(string_util::trim(forward(line.substr(0, equal_pos)), ' '))}; + string key{forward(string_util::trim(forward(line.substr(0, equal_pos))))}; string value; auto it = m_sections[section].find(key); @@ -116,7 +153,7 @@ void config::parse_file() { } if (equal_pos + 1 < line.size()) { - value = forward(string_util::trim(line.substr(equal_pos + 1), ' ')); + value = forward(string_util::trim(line.substr(equal_pos + 1))); size_t len{value.size()}; if (len > 2 && value[0] == '"' && value[len - 1] == '"') { value.erase(len - 1, 1).erase(0, 1); @@ -149,13 +186,14 @@ void config::copy_inherited() { // Get name of base section auto inherit = param.second; if ((inherit = dereference(section.first, param.first, inherit, inherit)).empty()) { - throw value_error("[" + section.first + "." + KEY_INHERIT + "] requires a value"); + throw value_error("Invalid section \"\" defined for \"" + section.first + "." + KEY_INHERIT + "\""); } // Find and validate base section auto base_section = m_sections.find(inherit); if (base_section == m_sections.end()) { - throw value_error("[" + section.first + "." + KEY_INHERIT + "] invalid reference \"" + inherit + "\""); + throw value_error( + "Invalid section \"" + inherit + "\" defined for \"" + section.first + "." + KEY_INHERIT + "\""); } m_log.trace("config: Copying missing params (sub=\"%s\", base=\"%s\")", section.first, inherit); diff --git a/src/utils/file.cpp b/src/utils/file.cpp index c2d73881..5e6e58e0 100644 --- a/src/utils/file.cpp +++ b/src/utils/file.cpp @@ -190,7 +190,11 @@ namespace file_util { string contents(const string& filename) { try { string contents; - std::getline(std::ifstream(filename, std::ifstream::in), contents); + string line; + std::ifstream in(filename, std::ifstream::in); + while (std::getline(in, line)) { + contents += line + '\n'; + } return contents; } catch (const std::ifstream::failure& e) { return ""; diff --git a/tests/unit_tests/utils/string.cpp b/tests/unit_tests/utils/string.cpp index 49688122..7ac71a84 100644 --- a/tests/unit_tests/utils/string.cpp +++ b/tests/unit_tests/utils/string.cpp @@ -43,6 +43,7 @@ int main() { }; "trim"_test = [] { + expect(string_util::trim(" x x ") == "x x"); expect(string_util::ltrim("xxtestxx", 'x') == "testxx"); expect(string_util::rtrim("xxtestxx", 'x') == "xxtest"); expect(string_util::trim("xxtestxx", 'x') == "test");