/* * This file is part of smolbote. It's copyrighted by the contributors recorded * in the version control history of the file, available from its original * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote * * SPDX-License-Identifier: GPL-3.0 */ #include "configuration.h" #include #include #include #include #ifndef NO_QT_SPEC #include #endif static const Configuration *s_conf = nullptr; Configuration::Configuration() : use_global(true) { if(s_conf == nullptr) { throw std::runtime_error("Trying to use default Configuration, but none has been set!"); } } Configuration::Configuration(const std::initializer_list> &list) noexcept #ifndef NO_QT_SPEC : m_homePath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation).toStdString()) #endif { for(const auto &i : list) { insert_or_assign(i.first, i.second); } } void Configuration::read_file(const std::string &location) { std::fstream fs(location, std::fstream::in); if(fs.is_open()) { read(fs); fs.close(); } } inline auto strip(std::string &value) { value.erase(value.begin(), std::find_if(value.begin(), value.end(), std::bind1st(std::not_equal_to(), ' '))); value.erase(std::find_if(value.rbegin(), value.rend(), std::bind1st(std::not_equal_to(), ' ')).base(), value.end()); return value; } enum parse_result { None, Pair, Section, Include, }; inline auto parse(const std::string &line, const std::string §ion) { struct { parse_result result = None; std::string key; std::string value; } ret; if(line.front() == '#' || line.length() == 0) { return ret; } if(line.starts_with("@@")) { if(line.starts_with("@@include ")) { ret.result = parse_result::Include; ret.key = line.substr(10); } return ret; } if(line.front() == '[' && line.back() == ']') { ret.result = parse_result::Section; ret.key = line.substr(1, line.length() - 2) + '/'; return ret; } const auto pos = line.find_first_of('='); if(pos == std::string::npos) { return ret; } ret.key = line.substr(0, pos); strip(ret.key); if(ret.key.empty()) { return ret; } ret.key = section + ret.key; ret.value = line.substr(pos + 1); strip(ret.value); ret.result = parse_result::Pair; return ret; } #ifdef FUZZER extern "C" int LLVMFuzzerTestOneInput(const char *Data, long long Size) { std::string section; parse(std::string(Data, static_cast(Size)), section); return 0; } #endif void Configuration::read(std::basic_istream &input) { std::string line, section; while(std::getline(input, line)) { const auto p = parse(line, section); switch(p.result) { case parse_result::None: break; case parse_result::Section: section = p.key; break; case parse_result::Include: read_file(p.key); break; case parse_result::Pair: setValue(p.key, p.value); break; } } } void Configuration::setValue(const std::string &key, const std::string &value) { if(use_global) { throw std::runtime_error("Global configuration is read-only!"); } if(this->count(key) == 0) { insert(std::make_pair(key, value)); } else { auto v = at(key); if(std::holds_alternative(v)) { at(key) = value; } else if(std::holds_alternative(v)) { at(key) = std::stoi(value); } else if(std::holds_alternative(v)) { at(key) = (value == "true"); } } } bool Configuration::make_global() { if(use_global || s_conf != nullptr) { return false; } s_conf = this; return true; } const Configuration *Configuration::instance() { return s_conf; } std::ostream &operator<<(std::ostream &out, const Configuration &obj param_typestate(unconsumed)) { if(obj.use_global) { out << *s_conf; return out; } // unordered_map is, well, unordered, so grab the keys and sort them before printing them std::vector keys; for(const auto &pair : obj) { keys.emplace_back(pair.first); } std::sort(keys.begin(), keys.end()); for(const auto &key : keys) { out << key << "=" << obj.value(key.c_str()).value() << "\n"; } return out; }