C++ / fmt (pull_request) Failing after 4s
C++ / build (pull_request) Failing after 12s
JS/TS / fmt (pull_request) Successful in 15s
JS/TS / lint (pull_request) Successful in 15s
C++ / clang-tidy (pull_request) Failing after 33s
Python / static (pull_request) Failing after 36s
Rust / fmt (pull_request) Successful in 1m14s
Rust / build (pull_request) Successful in 1m49s
Rust / clippy (pull_request) Successful in 1m33s
Python / verify (pull_request) Successful in 2m10s
124 lines
3.4 KiB
C++
124 lines
3.4 KiB
C++
#include "migration.hpp"
|
|
|
|
#include <QDebug>
|
|
|
|
namespace ZShell::config {
|
|
|
|
namespace {
|
|
|
|
// A stored value is "missing" if the path does not resolve to an entry.
|
|
// Note: default-constructed QJsonValue is Null, not Undefined.
|
|
bool isMissing(const QJsonValue& v) {
|
|
return v.isUndefined() || v.isNull();
|
|
}
|
|
|
|
QJsonValue valueAt(const QJsonObject& obj, const QStringList& path) {
|
|
QJsonObject cur = obj;
|
|
|
|
for (int i = 0; i < path.size() - 1; ++i) {
|
|
if (!cur.value(path.at(i)).isObject()) return QJsonValue::Undefined;
|
|
cur = cur.value(path.at(i)).toObject();
|
|
}
|
|
|
|
const QString key = path.last();
|
|
if (!cur.contains(key)) return QJsonValue::Undefined;
|
|
return cur.value(key);
|
|
}
|
|
|
|
QJsonObject placeAt(QJsonObject obj, QStringList path, const QJsonValue& value) {
|
|
const QString head = path.takeFirst();
|
|
|
|
if (path.isEmpty()) {
|
|
obj[head] = value;
|
|
return obj;
|
|
}
|
|
|
|
QJsonObject child = obj.value(head).isObject() ? obj.value(head).toObject()
|
|
: QJsonObject();
|
|
obj[head] = placeAt(child, path, value);
|
|
return obj;
|
|
}
|
|
|
|
// Removes `path` from `obj`. Returns true if a value was removed.
|
|
// Parent objects left empty are removed as well.
|
|
bool removeAt(QJsonObject& obj, const QStringList& path) {
|
|
const QString head = path.first();
|
|
|
|
if (path.size() == 1) {
|
|
if (!obj.contains(head)) return false;
|
|
obj.remove(head);
|
|
return true;
|
|
}
|
|
|
|
if (!obj.value(head).isObject()) return false;
|
|
|
|
QJsonObject child = obj.value(head).toObject();
|
|
const bool removed = removeAt(child, path.mid(1));
|
|
|
|
if (child.isEmpty())
|
|
obj.remove(head);
|
|
else
|
|
obj[head] = child;
|
|
|
|
return removed;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const QList<ConfigMigrationRule>& ConfigMigrations::rules() {
|
|
static const QList<ConfigMigrationRule> s_rules = {
|
|
migrate(
|
|
"general.color.scheduleHyprsunset", "display.nightlight.schedule"),
|
|
migrate(
|
|
"general.color.scheduleHyprsunsetStart",
|
|
"display.nightlight.scheduleStart"),
|
|
migrate(
|
|
"general.color.scheduleHyprsunsetEnd",
|
|
"display.nightlight.scheduleEnd"),
|
|
migrate("general.color.scheduleDark", "colors.scheduleDark"),
|
|
migrate("general.color.scheduleDarkStart", "colors.scheduleDarkStart"),
|
|
migrate("general.color.scheduleDarkEnd", "colors.scheduleDarkEnd"),
|
|
migrate("general.color.schemeGeneration", "colors.schemeGen"),
|
|
migrate(
|
|
"general.color.useNativeNightlight",
|
|
"display.nightlight.useNative"),
|
|
migrate(
|
|
"general.color.nativeFadeDuration",
|
|
"display.nightlight.fadeDuration"),
|
|
migrate("general.color.hyprsunsetTemp", "display.nightlight.temp"),
|
|
migrate("general.color.neovimColors", "colors.neovimColors"),
|
|
migrate("general.color.mode", "colors.mode"),
|
|
migrate("general.color.smart", "colors.smart"),
|
|
};
|
|
return s_rules;
|
|
}
|
|
|
|
QJsonObject ConfigMigrations::apply(const QJsonObject& json) {
|
|
QJsonObject obj = json;
|
|
|
|
for (const auto& rule : rules()) {
|
|
const QStringList from =
|
|
rule.from.split(QLatin1Char('.'), Qt::SkipEmptyParts);
|
|
const QStringList to =
|
|
rule.to.split(QLatin1Char('.'), Qt::SkipEmptyParts);
|
|
|
|
if (from.isEmpty() || to.isEmpty()) continue;
|
|
|
|
const QJsonValue oldValue = valueAt(obj, from);
|
|
if (isMissing(oldValue)) continue;
|
|
|
|
const bool targetExists = !isMissing(valueAt(obj, to));
|
|
if (!targetExists) obj = placeAt(obj, to, oldValue);
|
|
|
|
if (removeAt(obj, from)) {
|
|
qInfo() << "Config migration:" << rule.from << "->" << rule.to
|
|
<< (targetExists ? "(target already set, old value dropped)"
|
|
: "(value moved)");
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
} // namespace ZShell::config
|