#include "dusk/config.hpp" #include "fmt/format.h" #include "nlohmann/json.hpp" #include "absl/container/flat_hash_map.h" #include "aurora/lib/logging.hpp" #include "dusk/io.hpp" #include #include #include "dusk/dusk.h" using namespace dusk::config; constexpr auto ConfigFileName = "config.json"; using json = nlohmann::json; aurora::Module DuskConfigLog("dusk::config"); static absl::flat_hash_map RegisteredConfigVars; static bool RegistrationDone = false; static std::string GetConfigJsonPath() { return fmt::format("{}{}", configPath, ConfigFileName); } ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) { } const char* ConfigVarBase::getName() const noexcept { return name; } const ConfigImplBase* ConfigVarBase::getImpl() const noexcept { return impl; } template void ConfigImpl::loadFromJson(ConfigVar& cVar, const json& jsonValue) { cVar.setValue(jsonValue.get(), false); } template nlohmann::json ConfigImpl::dumpToJson(const ConfigVar& cVar) { return cVar.getValue(); } template requires std::is_integral_v && std::is_signed_v static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { const std::string str(stringValue); const auto result = std::stoll(str); if (result >= std::numeric_limits::min() && result <= std::numeric_limits::max()) { cVar.setOverrideValue(result); } else { throw std::out_of_range("Value is too large"); } } template requires std::is_integral_v && std::is_unsigned_v static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { const std::string str(stringValue); const auto result = std::stoull(str); if (result <= std::numeric_limits::max()) { cVar.setOverrideValue(result); } else { throw std::out_of_range("Value is too large"); } } static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { const std::string str(stringValue); const auto result = std::stof(str); cVar.setOverrideValue(result); } static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { const std::string str(stringValue); const auto result = std::stod(str); cVar.setOverrideValue(result); } static void loadFromArgImpl(ConfigVar& cVar, const std::string_view stringValue) { cVar.setOverrideValue(std::string(stringValue)); } template void ConfigImpl::loadFromArg(ConfigVar& cVar, const std::string_view stringValue) { loadFromArgImpl(cVar, stringValue); } template<> void ConfigImpl::loadFromArg(ConfigVar& cVar, const std::string_view stringValue) { if (stringValue == "1" || stringValue == "TRUE" || stringValue == "true" || stringValue == "True") { cVar.setOverrideValue(true); } else if (stringValue == "0" || stringValue == "FALSE" || stringValue == "false" || stringValue == "False") { cVar.setOverrideValue(false); } else { throw InvalidConfigError("Value cannot be parsed as boolean"); } } // My IDE is convinced this namespace is necessary. It shouldn't be AFAICT? namespace dusk::config { template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; } void dusk::config::Register(ConfigVarBase& configVar) { const auto& name = configVar.getName(); if (RegistrationDone) { DuskConfigLog.fatal("Tried to register CVar {} after registrations closed!", name); } if (RegisteredConfigVars.contains(name)) { DuskConfigLog.fatal("Tried to register CVar {} twice!", name); } RegisteredConfigVars[name] = &configVar; configVar.markRegistered(); } void ConfigVarBase::markRegistered() { if (registered) abort(); registered = true; } void dusk::config::FinishRegistration() { RegistrationDone = true; } void dusk::config::LoadFromUserPreferences() { const auto configJsonPath = GetConfigJsonPath(); if (configJsonPath.empty()) { return; } LoadFromFileName(configJsonPath.c_str()); } static void LoadFromPath(const char* path) { auto data = dusk::io::FileStream::ReadAllBytes(path); json j = json::parse(data); if (!j.is_object()) { DuskConfigLog.error("Config JSON is not an object!"); return; } for (const auto& el : j.items()) { const auto& key = el.key(); auto configVar = RegisteredConfigVars.find(key); if (configVar == RegisteredConfigVars.end()) { DuskConfigLog.error("Unknown key '{}' found in config!", key); continue; } try { configVar->second->getImpl()->loadFromJson(*configVar->second, el.value()); } catch (std::exception& e) { DuskConfigLog.error("Failed to load key '{}' from config: {}", key, e.what()); } } } void dusk::config::LoadFromFileName(const char* path) { if (!RegistrationDone) { DuskConfigLog.fatal("Registration not finished yet!"); } DuskConfigLog.info("Loading config from '{}'", path); try { LoadFromPath(path); } catch (const std::system_error& e) { if (e.code() == std::errc::no_such_file_or_directory) { DuskConfigLog.info("Config file did not exist, staying with defaults"); } else { DuskConfigLog.error("Failed to load from config! {}", e.what()); } } } void dusk::config::Save() { const auto configJsonPath = GetConfigJsonPath(); if (configJsonPath.empty()) { return; } DuskConfigLog.info("Saving config to '{}'", configJsonPath); json j; for (const auto& pair : RegisteredConfigVars) { if (pair.second->getLayer() == ConfigVarLayer::Value) { j[pair.first] = pair.second->getImpl()->dumpToJson(*pair.second); } } io::FileStream::WriteAllText(configJsonPath.c_str(), j.dump(4)); } ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) { const auto configVar = RegisteredConfigVars.find(name); if (configVar != RegisteredConfigVars.end()) { return configVar->second; } return nullptr; }