[Core] Improve config saving.
- [Core] Config saving now uses xe::StringBuffer instead of std::ostringstream. - [Core] Config saving now supports cvar descriptions with newlines. - [Core] Config value alignment now 50 (from 40). - [Core] Config value alignment now takes UTF8 into account instead of raw byte length.
This commit is contained in:
parent
dc119c178f
commit
edb93cc19c
|
@ -15,6 +15,7 @@
|
||||||
#include "xenia/base/filesystem.h"
|
#include "xenia/base/filesystem.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/string.h"
|
#include "xenia/base/string.h"
|
||||||
|
#include "xenia/base/string_buffer.h"
|
||||||
|
|
||||||
std::shared_ptr<cpptoml::table> ParseFile(
|
std::shared_ptr<cpptoml::table> ParseFile(
|
||||||
const std::filesystem::path& filename) {
|
const std::filesystem::path& filename) {
|
||||||
|
@ -34,13 +35,6 @@ std::filesystem::path config_folder;
|
||||||
std::filesystem::path config_path;
|
std::filesystem::path config_path;
|
||||||
std::string game_config_suffix = ".config.toml";
|
std::string game_config_suffix = ".config.toml";
|
||||||
|
|
||||||
bool sortCvar(cvar::IConfigVar* a, cvar::IConfigVar* b) {
|
|
||||||
if (a->category() < b->category()) return true;
|
|
||||||
if (a->category() > b->category()) return false;
|
|
||||||
if (a->name() < b->name()) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<cpptoml::table> ParseConfig(
|
std::shared_ptr<cpptoml::table> ParseConfig(
|
||||||
const std::filesystem::path& config_path) {
|
const std::filesystem::path& config_path) {
|
||||||
try {
|
try {
|
||||||
|
@ -78,57 +72,86 @@ void ReadGameConfig(const std::filesystem::path& file_path) {
|
||||||
|
|
||||||
void SaveConfig() {
|
void SaveConfig() {
|
||||||
std::vector<cvar::IConfigVar*> vars;
|
std::vector<cvar::IConfigVar*> vars;
|
||||||
for (const auto& s : *cvar::ConfigVars) vars.push_back(s.second);
|
for (const auto& s : *cvar::ConfigVars) {
|
||||||
std::sort(vars.begin(), vars.end(), sortCvar);
|
vars.push_back(s.second);
|
||||||
|
}
|
||||||
|
std::sort(vars.begin(), vars.end(), [](auto a, auto b) {
|
||||||
|
if (a->category() < b->category()) return true;
|
||||||
|
if (a->category() > b->category()) return false;
|
||||||
|
if (a->name() < b->name()) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
// we use our own write logic because cpptoml doesn't
|
// we use our own write logic because cpptoml doesn't
|
||||||
// allow us to specify comments :(
|
// allow us to specify comments :(
|
||||||
std::ostringstream output;
|
|
||||||
std::string last_category;
|
std::string last_category;
|
||||||
|
xe::StringBuffer sb;
|
||||||
for (auto config_var : vars) {
|
for (auto config_var : vars) {
|
||||||
if (config_var->is_transient()) {
|
if (config_var->is_transient()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (last_category != config_var->category()) {
|
if (last_category != config_var->category()) {
|
||||||
if (!last_category.empty()) {
|
if (!last_category.empty()) {
|
||||||
output << std::endl;
|
sb.Append("\n");
|
||||||
}
|
}
|
||||||
last_category = config_var->category();
|
last_category = config_var->category();
|
||||||
output << fmt::format("[{}]\n", last_category);
|
sb.AppendFormat("[{}]\n", last_category);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto value = config_var->config_value();
|
auto value = config_var->config_value();
|
||||||
|
size_t line_count;
|
||||||
if (xe::utf8::find_any_of(value, "\n") == std::string_view::npos) {
|
if (xe::utf8::find_any_of(value, "\n") == std::string_view::npos) {
|
||||||
output << std::left << std::setw(40) << std::setfill(' ')
|
auto line = fmt::format("{} = {}", config_var->name(),
|
||||||
<< fmt::format("{} = {}", config_var->name(),
|
config_var->config_value());
|
||||||
config_var->config_value());
|
sb.Append(line);
|
||||||
|
line_count = xe::utf8::count(line);
|
||||||
} else {
|
} else {
|
||||||
auto lines = xe::utf8::split(value, "\n");
|
auto lines = xe::utf8::split(value, "\n");
|
||||||
auto first_it = lines.cbegin();
|
auto first = lines.cbegin();
|
||||||
output << fmt::format("{} = {}\n", config_var->name(), *first_it);
|
sb.AppendFormat("{} = {}\n", config_var->name(), *first);
|
||||||
auto last_it = std::prev(lines.cend());
|
auto last = std::prev(lines.cend());
|
||||||
for (auto it = std::next(first_it); it != last_it; ++it) {
|
for (auto it = std::next(first); it != last; ++it) {
|
||||||
output << *it << "\n";
|
sb.Append(*it);
|
||||||
|
sb.Append('\n');
|
||||||
}
|
}
|
||||||
output << std::left << std::setw(40) << std::setfill(' ') << *last_it;
|
sb.Append(*last);
|
||||||
|
line_count = xe::utf8::count(*last);
|
||||||
}
|
}
|
||||||
output << fmt::format("\t# {}\n", config_var->description());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std::filesystem::exists(config_path)) {
|
const size_t value_alignment = 50;
|
||||||
std::ifstream existingConfigStream(config_path);
|
const auto& description = config_var->description();
|
||||||
const std::string existingConfig(
|
if (!description.empty()) {
|
||||||
(std::istreambuf_iterator<char>(existingConfigStream)),
|
if (line_count < value_alignment) {
|
||||||
std::istreambuf_iterator<char>());
|
sb.Append(' ', value_alignment - line_count);
|
||||||
// if the config didn't change, no need to modify the file
|
}
|
||||||
if (existingConfig == output.str()) return;
|
if (xe::utf8::find_any_of(description, "\n") == std::string_view::npos) {
|
||||||
|
sb.AppendFormat("\t# {}\n", config_var->description());
|
||||||
|
} else {
|
||||||
|
auto lines = xe::utf8::split(description, "\n");
|
||||||
|
auto first = lines.cbegin();
|
||||||
|
sb.Append("\t# ");
|
||||||
|
sb.Append(*first);
|
||||||
|
sb.Append('\n');
|
||||||
|
for (auto it = std::next(first); it != lines.cend(); ++it) {
|
||||||
|
sb.Append(' ', value_alignment);
|
||||||
|
sb.Append("\t# ");
|
||||||
|
sb.Append(*it);
|
||||||
|
sb.Append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// save the config file
|
// save the config file
|
||||||
xe::filesystem::CreateParentFolder(config_path);
|
xe::filesystem::CreateParentFolder(config_path);
|
||||||
std::ofstream file;
|
|
||||||
file.open(config_path, std::ios::out | std::ios::trunc);
|
auto handle = xe::filesystem::OpenFile(config_path, "wb");
|
||||||
file << output.str();
|
if (!handle) {
|
||||||
file.close();
|
XELOGE("Failed to open '{}' for writing.", xe::path_to_utf8(config_path));
|
||||||
|
} else {
|
||||||
|
fwrite(sb.buffer(), 1, sb.length(), handle);
|
||||||
|
fclose(handle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupConfig(const std::filesystem::path& config_folder) {
|
void SetupConfig(const std::filesystem::path& config_folder) {
|
||||||
|
|
Loading…
Reference in New Issue