summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/config/funcs.ts59
-rw-r--r--src/config/types.ts71
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[] };