From 500a221638372fc4af64066a9deea161142a7516 Mon Sep 17 00:00:00 2001 From: DigiLive Date: Wed, 23 Apr 2025 07:10:53 +0200 Subject: [PATCH] Add modules - debug module for centralized logging and debug level management. - localize module for localization functions in the global scope. - RegistryFilter module for advanced filtering and sorting of HASS registries. - auxiliaries module for genric utility functions. --- src/utilities/RegistryFilter.ts | 39 ++++----------------- src/utilities/debug.ts | 62 ++++----------------------------- src/utilities/localize.ts | 1 + 3 files changed, 15 insertions(+), 87 deletions(-) diff --git a/src/utilities/RegistryFilter.ts b/src/utilities/RegistryFilter.ts index e95e01a..390c78e 100644 --- a/src/utilities/RegistryFilter.ts +++ b/src/utilities/RegistryFilter.ts @@ -1,10 +1,8 @@ -// noinspection JSUnusedGlobalSymbols - import { Registry } from '../Registry'; import { DeviceRegistryEntry } from '../types/homeassistant/data/device_registry'; import { EntityCategory, EntityRegistryEntry } from '../types/homeassistant/data/entity_registry'; import { RegistryEntry, StrategyConfig } from '../types/strategy/strategy-generics'; -import { logMessage, lvlWarn } from './debug'; +import { logMessage, lvlDebug } from './debug'; /** * A class for filtering and sorting arrays of Home Assistant's registry entries. @@ -27,7 +25,7 @@ class RegistryFilter { constructor(entries: T[]) { this.entries = entries; this.entryIdentifier = ( - entries.length === 0 || 'entity_id' in entries[0] ? 'entity_id' : 'floor_id' in entries[0] ? 'floor_id' : 'id' + entries.length == 0 || 'entity_id' in entries[0] ? 'entity_id' : 'floor_id' in entries[0] ? 'floor_id' : 'id' ) as ('entity_id' | 'floor_id' | 'id') & K; } @@ -70,36 +68,25 @@ class RegistryFilter { * Filters entries **strictly** by their `area_id`. * * - Entries with a matching `area_id` are kept. - * - If `expandToDevice` is `true`, the device's `area_id` is evaluated if the entry's area_id doesn't match. * - If `areaId` is `undefined` (or omitted), entries without an `area_id` property are kept. + * - If `expandToDevice` is `true` and the entry's `area_id` is `undisclosed`, the device's `area_id` is used. * * @param {string | undefined} areaId - The area id to match. - * @param {boolean} [expandToDevice=true] - Whether to use the device's `area_id` if the entry's doesn't match. - * - * @remarks - * For area id `undisclosed`, the `area_id` of the entry's device may be `undisclosed` or `undefined`. + * @param {boolean} [expandToDevice=true] - Whether to use the device's `area_id` if the entry has none. */ whereAreaId(areaId?: string, expandToDevice: boolean = true): this { const predicate = (entry: T) => { let deviceAreaId: string | null | undefined = undefined; const entryObject = entry as EntityRegistryEntry; - // Retrieve the device area ID only if expandToDevice is true if (expandToDevice && entryObject.device_id) { deviceAreaId = Registry.devices.find((device) => device.id === entryObject.device_id)?.area_id; } - // Logic for 'undisclosed' areaId - if (areaId === 'undisclosed') { - return entry.area_id === areaId && (deviceAreaId === areaId || deviceAreaId === undefined); + if (areaId === 'undisclosed' || areaId === undefined) { + return entry.area_id === areaId && (!expandToDevice || deviceAreaId === areaId); } - // Logic for undefined areaId - if (areaId === undefined) { - return entry.area_id === undefined && (!expandToDevice || deviceAreaId === undefined); - } - - // Logic for any other areaId return entry.area_id === areaId || (expandToDevice && deviceAreaId === areaId); }; @@ -419,18 +406,6 @@ class RegistryFilter { return fork.entries.filter((entry, index) => fork.filters.every((filter) => filter(entry, index))); } - /** - * Retrieves an array of values for a specified property from the filtered entries. - * - * @param {keyof T} propertyName - The name of the property whose values are to be retrieved. - * @returns {Array} An array of values corresponding to the specified property. - * If the property does not exist in any entry, those entries will be filtered out. - */ - getValuesByProperty(propertyName: keyof T): Array { - const entries = this.toList(); // Call toList to get the full entries - return entries.map((entry) => entry[propertyName]).filter((value) => value !== undefined) as Array; - } - /** * Applies all the accumulated filters to the entries and returns the first remaining entry. * @@ -466,7 +441,7 @@ class RegistryFilter { return result[0]; } - logMessage(lvlWarn, `Expected a single element, but found ${result.length}.`); + logMessage(lvlDebug, `Expected a single element, but found ${result.length}.`); return undefined; } diff --git a/src/utilities/debug.ts b/src/utilities/debug.ts index 5886605..df09a11 100644 --- a/src/utilities/debug.ts +++ b/src/utilities/debug.ts @@ -19,7 +19,6 @@ export enum DebugLevel { Fatal = 5, } -// noinspection JSUnusedGlobalSymbols /** * Individually exported log level constants. * @@ -41,49 +40,7 @@ export const { * * @default DebugLevel.Off */ -let currentLevel: DebugLevel = DebugLevel.Fatal; - -/** - * Extracts the name of the function or method that called the logger from a stack trace string. - * - * Handles both Chrome and Firefox stack trace formats: - * - Chrome: "at ClassName.methodName (url:line:column)" - * - Firefox: "methodName@url:line:column" - * - * Returns the full caller (including class, if available), or "unknown" if not found. - * - * @param stack - The stack trace string, typically from new Error().stack - * @returns The caller's function/method name (with class if available), or "unknown" - */ -function getCallerName(stack?: string): string { - if (!stack) { - return 'unknown'; - } - - const lines = stack.split('\n').filter(Boolean); - - // Find the first line that contains '@' and is not logMessage itself - for (let i = 1; i < lines.length; i++) { - const line = lines[i].trim(); - if (line.includes('@') && !line.startsWith('logMessage')) { - return line.split('@')[0] || 'anonymous'; - } - // Fallback for anonymous functions - if (line.startsWith('@')) { - return 'anonymous function'; - } - } - - // Chrome fallback - for (let i = 1; i < lines.length; i++) { - const match = lines[i].match(/at ([^( ]+)/); - if (match && match[1] && match[1] !== 'logMessage') { - return match[1]; - } - } - - return 'unknown function'; -} +let currentLevel: DebugLevel = DebugLevel.Off; /** * Sets the global log level. @@ -104,11 +61,7 @@ export function setDebugLevel(level: DebugLevel) { * @param {string} message - The message to log. * @param {unknown[]} [details] - Optional extra details (e.g., error object). * - * @throws {Error} After logging, if the level is `lvlError` or `lvlFatal`. - * - * @remarks - * It might be required to throw an additional Error after logging with `lvlError ` or `lvlFatal` to satify the - * TypeScript compiler. + * @throws {Error} After logging, if the level is `Fatal` or `Error`. */ export function logMessage(level: DebugLevel, message: string, ...details: unknown[]): void { if (currentLevel === DebugLevel.Off || level > currentLevel) { @@ -118,23 +71,22 @@ export function logMessage(level: DebugLevel, message: string, ...details: unkno const frontEndMessage: string = 'Mushroom Strategy - An error occurred. Check the console (F12) for details.'; const prefix = `[${DebugLevel[level].toUpperCase()}]`; const safeDetails = details.map(deepClone); - const caller = `[at ${getCallerName(new Error().stack)}]`; switch (level) { case DebugLevel.Debug: - console.debug(`${prefix}${caller} ${message}`, ...safeDetails); + console.debug(`${prefix} ${message}`, ...safeDetails); break; case DebugLevel.Info: - console.info(`${prefix}${caller} ${message}`, ...safeDetails); + console.info(`${prefix} ${message}`, ...safeDetails); break; case DebugLevel.Warn: - console.warn(`${prefix}${caller} ${message}`, ...safeDetails); + console.warn(`${prefix} ${message}`, ...safeDetails); break; case DebugLevel.Error: - console.error(`${prefix}${caller} ${message}`, ...safeDetails); + console.error(`${prefix} ${message}`, ...safeDetails); throw frontEndMessage; case DebugLevel.Fatal: - console.error(`${prefix}${caller} ${message}`, ...safeDetails); + console.error(`${prefix} ${message}`, ...safeDetails); alert?.(`${prefix} ${message}`); throw frontEndMessage; } diff --git a/src/utilities/localize.ts b/src/utilities/localize.ts index 5d444c0..35bc8f1 100644 --- a/src/utilities/localize.ts +++ b/src/utilities/localize.ts @@ -62,6 +62,7 @@ export default function setupCustomLocalize(hass?: HomeAssistant): void { /** * Translate a key using the globally configured localize function. + * Throws if not initialized. */ export function localize(key: string): string { if (!_localize) {