Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/horst/settings.ts

Raw
// horst — settings.
//
// Parses the `horst` block from Pi settings.

export interface HorstSettings {
	os: Record<string, string[]>;
	hosts: Record<string, string[]>;
	preferredShell?: string;
}

export const EMPTY_HORST_SETTINGS: HorstSettings = { os: {}, hosts: {} };

function horstOsKey(platform: string): string | undefined {
	if (platform === "linux") return "lnx";
	if (platform === "darwin") return "mac";
	if (platform === "win32") return "win32";
	return undefined;
}

function isObject(value: unknown): value is Record<string, unknown> {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}

function pickMap(value: unknown, at: string): Record<string, string[]> {
	if (value === undefined) return {};
	if (!isObject(value)) throw new Error(`${at} must be an object`);
	const map: Record<string, string[]> = {};
	for (const [key, entry] of Object.entries(value)) {
		const tools = isObject(entry) ? entry["tools"] : undefined;
		if (!Array.isArray(tools))
			throw new Error(
				`${at}.${key}.tools must be an array of +name/-name modifiers; use ["+name", "-name"] instead of { add, remove }`,
			);
		if (
			!tools.every(
				(tool) =>
					typeof tool === "string" &&
					(tool.startsWith("+") || tool.startsWith("-")) &&
					tool.length > 1 &&
					tool === tool.trim() &&
					tool.slice(1) === tool.slice(1).trim() &&
					!tool.includes("*"),
			)
		)
			throw new Error(
				`${at}.${key}.tools must contain only +name/-name modifiers with non-empty exact tool names, not patterns`,
			);
		map[key.toLowerCase()] = tools;
	}
	return map;
}

/** Validate the `horst` block; throws on malformed values. */
export function parseHorst(raw: unknown): HorstSettings {
	if (!isObject(raw)) throw new Error("expected an object");
	const { preferredShell } = raw;
	if (
		preferredShell !== undefined &&
		(typeof preferredShell !== "string" || !preferredShell.trim())
	)
		throw new Error("preferredShell must be a non-empty string");
	return {
		os: pickMap(raw["os"], "os"),
		hosts: pickMap(raw["hosts"], "hosts"),
		...(preferredShell ? { preferredShell: preferredShell.trim() } : {}),
	};
}

/** Apply OS modifiers before matching host modifiers, in list order. */
export function resolveHorstTools(
	settings: HorstSettings,
	platform: string,
	hostname: string,
): string[] | undefined {
	const osTools = settings.os[horstOsKey(platform) ?? ""];
	const hostTools = settings.hosts[hostname.toLowerCase()];
	if (!osTools && !hostTools) return undefined;
	return [...(osTools ?? []), ...(hostTools ?? [])];
}