diff options
| -rw-r--r-- | src/config/funcs.ts | 59 | ||||
| -rw-r--r-- | src/config/types.ts | 71 |
2 files changed, 124 insertions, 6 deletions
diff --git a/src/config/funcs.ts b/src/config/funcs.ts index bc0a7d1..473c502 100644 --- a/src/config/funcs.ts +++ b/src/config/funcs.ts @@ -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; diff --git a/src/config/types.ts b/src/config/types.ts new file mode 100644 index 0000000..aa0d921 --- /dev/null +++ b/src/config/types.ts @@ -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[] }; |