config: validate values
Validate values for keys with literal types, also better validation for array types
This commit is contained in:
parent
1ffd7e1d54
commit
9f6e227d7b
2 changed files with 124 additions and 6 deletions
|
|
@ -2,23 +2,70 @@ import { GLib, monitorFile, readFileAsync, Variable } from "astal";
|
|||
import config from ".";
|
||||
import { loadStyleAsync } from "../../app";
|
||||
import defaults from "./defaults";
|
||||
import types from "./types";
|
||||
|
||||
type Settings<T> = { [P in keyof T]: T[P] extends object & { length?: never } ? Settings<T[P]> : Variable<T[P]> };
|
||||
|
||||
const CONFIG = `${GLib.get_user_config_dir()}/caelestia/shell.json`;
|
||||
|
||||
const isObject = (o: any) => typeof o === "object" && o !== null && !Array.isArray(o);
|
||||
const isObject = (o: any): o is object => typeof o === "object" && o !== null && !Array.isArray(o);
|
||||
|
||||
const isCorrectType = (v: any, type: string | string[] | number[], path: string) => {
|
||||
if (Array.isArray(type)) {
|
||||
// type is array of valid values
|
||||
if (!type.includes(v as never)) {
|
||||
console.warn(`Invalid value for ${path}: ${v} != ${type.map(v => `"${v}"`).join(" | ")}`);
|
||||
return false;
|
||||
}
|
||||
} else if (type.startsWith("array of ")) {
|
||||
// Array of ...
|
||||
if (Array.isArray(v)) {
|
||||
// Remove invalid items but always return true
|
||||
const arrType = type.slice(9);
|
||||
try {
|
||||
// Recursively check type
|
||||
const type = JSON.parse(arrType);
|
||||
const valid = v.filter((item, i) =>
|
||||
Object.entries(type).some(([k, t]) => {
|
||||
if (!item[k]) {
|
||||
console.warn(`Invalid shape for ${path}[${i}]: ${JSON.stringify(item)} != ${arrType}`);
|
||||
return false;
|
||||
}
|
||||
return !isCorrectType(item[k], t as any, `${path}[${i}].${k}`);
|
||||
})
|
||||
);
|
||||
v.splice(0, v.length, ...valid); // In-place filter
|
||||
} catch {
|
||||
const valid = v.filter((item, i) => {
|
||||
if (typeof item !== arrType) {
|
||||
console.warn(`Invalid type for ${path}[${i}]: ${typeof item} != ${arrType}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
v.splice(0, v.length, ...valid); // In-place filter
|
||||
}
|
||||
} else {
|
||||
// Type is array but value is not
|
||||
console.warn(`Invalid type for ${path}: ${typeof v} != ${type}`);
|
||||
return false;
|
||||
}
|
||||
} else if (typeof v !== type) {
|
||||
// Value is not correct type
|
||||
console.warn(`Invalid type for ${path}: ${typeof v} != ${type}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const deepMerge = <T extends object, U extends object>(a: T, b: U, path = ""): T & U => {
|
||||
const merged: { [k: string]: any } = { ...b };
|
||||
for (const [k, v] of Object.entries(a)) {
|
||||
if (b.hasOwnProperty(k)) {
|
||||
const bv = b[k as keyof U];
|
||||
if (isObject(v) && isObject(bv)) merged[k] = deepMerge(v, bv as object, `${path}${k}.`);
|
||||
else if (typeof v !== typeof bv) {
|
||||
console.warn(`Invalid type for ${path}${k}: ${typeof v} != ${typeof bv}`);
|
||||
merged[k] = v;
|
||||
}
|
||||
if (isObject(v) && isObject(bv)) merged[k] = deepMerge(v, bv, `${path}${k}.`);
|
||||
else if (!isCorrectType(bv, types[path + k], path + k)) merged[k] = v;
|
||||
} else merged[k] = v;
|
||||
}
|
||||
return merged as any;
|
||||
|
|
|
|||
71
src/config/types.ts
Normal file
71
src/config/types.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
const BOOL = "boolean";
|
||||
const STR = "string";
|
||||
const NUM = "number";
|
||||
const ARR = (type: string) => `array of ${type}`;
|
||||
const OBJ_ARR = (shape: object) => ARR(JSON.stringify(shape));
|
||||
|
||||
export default {
|
||||
"style.transparency": ["off", "normal", "high"],
|
||||
"style.vibrant": BOOL,
|
||||
// Bar
|
||||
"bar.vertical": BOOL,
|
||||
"bar.modules.osIcon.enabled": BOOL,
|
||||
"bar.modules.activeWindow.enabled": BOOL,
|
||||
"bar.modules.mediaPlaying.enabled": BOOL,
|
||||
"bar.modules.workspaces.enabled": BOOL,
|
||||
"bar.modules.workspaces.shown": NUM,
|
||||
"bar.modules.tray.enabled": BOOL,
|
||||
"bar.modules.statusIcons.enabled": BOOL,
|
||||
"bar.modules.pkgUpdates.enabled": BOOL,
|
||||
"bar.modules.notifCount.enabled": BOOL,
|
||||
"bar.modules.battery.enabled": BOOL,
|
||||
"bar.modules.dateTime.enabled": BOOL,
|
||||
"bar.modules.dateTime.format": STR,
|
||||
"bar.modules.dateTime.detailedFormat": STR,
|
||||
"bar.modules.power.enabled": BOOL,
|
||||
// Launcher
|
||||
"launcher.actionPrefix": STR,
|
||||
"launcher.apps.maxResults": NUM,
|
||||
"launcher.files.maxResults": NUM,
|
||||
"launcher.files.fdOpts": ARR(STR),
|
||||
"launcher.files.shortenThreshold": NUM,
|
||||
"launcher.math.maxResults": NUM,
|
||||
"launcher.todo.notify": BOOL,
|
||||
"launcher.wallpaper.style": ["compact", "medium", "large"],
|
||||
"launcher.disabledActions": ARR(STR),
|
||||
// Notif popups
|
||||
"notifpopups.maxPopups": NUM,
|
||||
"notifpopups.expire": BOOL,
|
||||
"notifpopups.agoTime": BOOL,
|
||||
// OSDs
|
||||
"osds.volume.position": [2, 4, 8, 16],
|
||||
"osds.volume.margin": NUM,
|
||||
"osds.volume.hideDelay": NUM,
|
||||
"osds.volume.showValue": BOOL,
|
||||
"osds.brightness.position": [2, 4, 8, 16],
|
||||
"osds.brightness.margin": NUM,
|
||||
"osds.brightness.hideDelay": NUM,
|
||||
"osds.brightness.showValue": BOOL,
|
||||
"osds.lock.spacing": NUM,
|
||||
"osds.lock.caps.hideDelay": NUM,
|
||||
"osds.lock.num.hideDelay": NUM,
|
||||
// Sideleft
|
||||
"sideleft.directories.left.top": STR,
|
||||
"sideleft.directories.left.middle": STR,
|
||||
"sideleft.directories.left.bottom": STR,
|
||||
"sideleft.directories.right.top": STR,
|
||||
"sideleft.directories.right.middle": STR,
|
||||
"sideleft.directories.right.bottom": STR,
|
||||
// Services
|
||||
"math.maxHistory": NUM,
|
||||
"updates.interval": NUM,
|
||||
"weather.interval": NUM,
|
||||
"weather.key": STR,
|
||||
"weather.location": STR,
|
||||
"weather.imperial": BOOL,
|
||||
"cpu.interval": NUM,
|
||||
"gpu.interval": NUM,
|
||||
"memory.interval": NUM,
|
||||
"storage.interval": NUM,
|
||||
"wallpapers.paths": OBJ_ARR({ recursive: BOOL, path: STR }),
|
||||
} as { [k: string]: string | string[] | number[] };
|
||||
Loading…
Add table
Reference in a new issue