#include "client/settings.hh" namespace { const std::string home_dir = getenv("HOME"); const char* const config_dir = "/.config/blockgame_linux/"; const char* const config_name = "blockgame_linux.conf"; } // namespace namespace client { namespace settings { static std::string get_settings_file() noexcept { std::filesystem::create_directories(home_dir + ::config_dir); std::ifstream in{home_dir + ::config_dir + config_name, std::fstream::in | std::fstream::app}; // this must end with a newline, IT IS NOT UP FOR NEGOTIATION const std::string file{std::istreambuf_iterator(in), std::istreambuf_iterator()}; return file.ends_with('\n') ? file : file + '\n'; } static void set_settings_file(const std::string_view contents) noexcept { std::filesystem::create_directories(home_dir + ::config_dir); std::ofstream out{home_dir + ::config_dir + ::config_name, std::fstream::out | std::fstream::trunc}; out << contents; } // Settings maps subkeys to std::string values (ie, "fov" -> "100.0"f). // Heading maps setting to headings (ie, "camera -> {map with fov, 100.0f}). using value_map = std::unordered_map; using setting_map = std::unordered_map; static setting_map& get_settings() noexcept { static setting_map ret = []() -> setting_map { setting_map settings{}; std::stringstream ss{get_settings_file()}; std::string heading = "default"; for (std::string line; std::getline(ss, line);) { if (line.empty()) { continue; } if (line.starts_with('[') && line.ends_with(']')) { heading = std::string{std::begin(line) + 1, std::end(line) - 1}; continue; } const size_t split_pos = line.find_first_of(':'); if (split_pos == std::string::npos) { shared::print::warn("client: failed to parse line in settings " "file, consider manual intervention\n"); continue; } const std::string key{std::begin(line), std::begin(line) + static_cast(split_pos)}; const std::string value{std::begin(line) + static_cast(split_pos) + 1, std::end(line)}; value_map& values = settings[heading]; values.emplace(key, value); } return settings; }(); return ret; } // TODO fix this bit of code duplication std::string get_setting_str(const std::pair loc, const std::string default_value) noexcept { const auto& [heading, name] = loc; setting_map& settings = get_settings(); value_map& values = settings[heading]; const auto find_it = values.find(name); if (find_it == std::end(values)) { values.emplace(name, default_value); return default_value; } return find_it->second; } void set_setting_str( const std::pair loc, const std::string& value) noexcept { const auto& [heading, name] = loc; setting_map& settings = get_settings(); value_map& values = settings[heading]; const auto find_it = values.find(name); if (find_it == std::end(values)) { values.emplace(name, value); return; } find_it->second = value; } void save() noexcept { std::string contents; const setting_map& settings = get_settings(); // We alphabetically order the headings, and the key:values between them. std::vector headings; std::ranges::transform(std::begin(settings), std::end(settings), std::back_inserter(headings), [](const auto& map) { return map.first; }); std::ranges::sort(headings); for (const auto& heading : headings) { contents.append('[' + heading + "]\n"); const value_map& values = settings.find(heading)->second; std::vector keys; std::ranges::transform( std::begin(values), std::end(values), std::back_inserter(keys), [](const auto& key_val) { return key_val.first; }); std::ranges::sort(keys); for (const auto& key : keys) { contents.append(key + ':' + values.find(key)->second + '\n'); } } set_settings_file(contents); } } // namespace settings } // namespace client