/* * 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: git://neueland.iserlohn-fortress.net/smolbote.git * * SPDX-License-Identifier: GPL-3.0 */ #include "configuration.h" #include <libconfig.h++> #include <sstream> using namespace libconfig; Configuration::Configuration() { m_userCfg = new Config(); #ifdef C_LIKE_CONFIG m_userCfg->setOptions(Config::OptionSemicolonSeparators | Config::OptionOpenBraceOnSeparateLine); #endif // fsync after writing and before closing m_userCfg->setOption(Config::OptionFsync, true); m_defaultCfg = new Config(); } Configuration::~Configuration() { if(!m_userCfgPath.empty()) { m_userCfg->writeFile(m_userCfgPath.c_str()); } delete m_userCfg; delete m_defaultCfg; } bool Configuration::readUserConfiguration(const std::string &path) { m_userCfgPath = path; try { m_userCfg->readFile(path.c_str()); } catch (FileIOException) { return false; } catch (ParseException) { return false; } return true; } bool Configuration::writeUserConfiguration(const std::string &path) { m_userCfgPath = path; try { m_userCfg->writeFile(path.c_str()); } catch (FileIOException) { return false; } return true; } bool Configuration::readDefaultConfiguration(const std::string &data) { try { m_defaultCfg->readString(data); } catch(ParseException) { return false; } return true; } std::vector<std::string> Configuration::childrenSettings(const char* name) { std::vector<std::string> groupNames; const Setting &root = m_defaultCfg->lookup(name); for(const Setting &setting : root) { if(setting.getType() != Setting::TypeGroup) { groupNames.push_back(setting.getName()); } } return groupNames; } std::vector<std::string> Configuration::childrenGroups(const char* name) { std::vector<std::string> groupNames; const Setting &root = m_defaultCfg->lookup(name); for(const Setting &setting : root) { if(setting.getType() == Setting::TypeGroup) { groupNames.push_back(setting.getName()); } } return groupNames; } template<typename T> std::optional<T> Configuration::value(const char* path) const { Config *conf = getConfig(path); if(conf == nullptr) { return std::nullopt; } // setting was found const Setting &setting = conf->lookup(path); std::optional<T> r; // cast depending on type // type checks are done during compile time switch (setting.getType()) { case Setting::TypeNone: r = std::nullopt; break; case Setting::TypeInt: // int, unsigned int, long, unsigned long if constexpr(std::is_same_v<T, std::string>) { r = std::optional<std::string>(std::to_string(static_cast<int>(setting))); } else if constexpr(std::is_same_v<T, int> || std::is_same_v<T, unsigned int> || std::is_same_v<T, long> || std::is_same_v<T, unsigned long>) { r = std::optional<T>(static_cast<T>(setting)); } else { r = std::nullopt; } break; case Setting::TypeInt64: // int, unsigned int; long long, unsigned long long if constexpr(std::is_same_v<T, std::string>) { r = std::optional<std::string>(std::to_string(static_cast<long long>(setting))); } else if constexpr(std::is_same_v<T, int> || std::is_same_v<T, unsigned int> || std::is_same_v<T, long long> || std::is_same_v<T, unsigned long long>) { r = std::optional<T>(static_cast<T>(setting)); } else { r = std::nullopt; } break; case Setting::TypeFloat: // float, double if constexpr(std::is_same_v<T, std::string>) { r = std::optional<std::string>(std::to_string(static_cast<float>(setting))); } else if constexpr(std::is_same_v<T, float> || std::is_same_v<T, double>) { r = std::optional<T>(static_cast<T>(setting)); } else { r = std::nullopt; } break; case Setting::TypeString: // const char*, std::string if constexpr(std::is_same<T, std::string>::value) { r = std::optional<std::string>(static_cast<const char*>(setting)); } else { r = std::nullopt; } break; case Setting::TypeBoolean: // bool if constexpr(std::is_same<T, std::string>::value) { r = std::optional<std::string>(static_cast<bool>(setting) ? "true" : "false"); } else if constexpr(std::is_same<T, bool>::value) { r = std::optional<bool>(static_cast<bool>(setting)); } else { r = std::nullopt; } break; case Setting::TypeGroup: r = std::nullopt; break; case Setting::TypeArray: r = std::nullopt; break; case Setting::TypeList: r = std::nullopt; break; } return r; } // tell the compiler to export these functions, otherwise you get linking errors template std::optional<int> Configuration::value<int>(const char* path) const; template std::optional<unsigned int> Configuration::value<unsigned int>(const char* path) const; template std::optional<long> Configuration::value<long>(const char* path) const; template std::optional<unsigned long> Configuration::value<unsigned long>(const char* path) const; template std::optional<long long> Configuration::value<long long>(const char* path) const; template std::optional<unsigned long long> Configuration::value<unsigned long long>(const char* path) const; template std::optional<float> Configuration::value<float>(const char* path) const; template std::optional<double> Configuration::value<double>(const char* path) const; template std::optional<bool> Configuration::value<bool>(const char* path) const; template std::optional<std::string> Configuration::value<std::string>(const char* path) const; template<typename T> void Configuration::setValue(std::string path, const T &val) { if(m_userCfg->exists(path)) { Setting &setting = m_userCfg->lookup(path); // compiler complained about operator= not taking unsinged ints, longs and long longs if constexpr(std::is_unsigned_v<T> && !std::is_same_v<T, bool>) { setting = static_cast<typename std::make_signed_t<T>>(val); } else { setting = val; } return; } // the entry doesn't exist, so we need to create it // libconfig can't create an entry from a path, we need to get its root, and then add it // this will error out if entry being added is not in the default config std::istringstream p(path); Setting *userSetting = &m_userCfg->getRoot(); Setting *defaultSetting = &m_defaultCfg->getRoot(); std::string i; while (std::getline(p, i, '.')) { defaultSetting = &defaultSetting->lookup(i); // check if user setting exists, if not, create it if(!userSetting->exists(i)) { userSetting = &userSetting->add(i, defaultSetting->getType()); } } if constexpr(std::is_unsigned_v<T> && !std::is_same_v<T, bool>) { *userSetting = static_cast<typename std::make_signed_t<T>>(val); } else { *userSetting = val; } } template void Configuration::setValue<int>(std::string path, const int &val); template void Configuration::setValue<unsigned int>(std::string path, const unsigned int &val); template void Configuration::setValue<long>(std::string path, const long &val); template void Configuration::setValue<unsigned long>(std::string path, const unsigned long &val); template void Configuration::setValue<long long>(std::string path, const long long &val); template void Configuration::setValue<unsigned long long>(std::string path, const unsigned long long &val); template void Configuration::setValue<float>(std::string path, const float &val); template void Configuration::setValue<double>(std::string path, const double &val); template void Configuration::setValue<bool>(std::string path, const bool &val); template void Configuration::setValue<std::string>(std::string path, const std::string &val); Config *Configuration::getConfig(const char* path) const { if(m_userCfg->exists(path)) { return m_userCfg; } else if(m_defaultCfg->exists(path)) { return m_defaultCfg; } else { return nullptr; } } std::string &patchHome(std::string &path, const std::string &home) { const size_t location = path.find("~"); if(location != std::string::npos) { return path.replace(location, 1, home); } return path; }