mirror of
https://github.com/DigiLive/mushroom-strategy.git
synced 2025-08-04 20:14:28 +02:00
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.
This commit is contained in:
@@ -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<T extends RegistryEntry, K extends keyof T = keyof T> {
|
||||
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<T extends RegistryEntry, K extends keyof T = keyof T> {
|
||||
* 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<T extends RegistryEntry, K extends keyof T = keyof T> {
|
||||
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<T[keyof T]>} 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<T[keyof T]> {
|
||||
const entries = this.toList(); // Call toList to get the full entries
|
||||
return entries.map((entry) => entry[propertyName]).filter((value) => value !== undefined) as Array<T[keyof T]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all the accumulated filters to the entries and returns the first remaining entry.
|
||||
*
|
||||
@@ -466,7 +441,7 @@ class RegistryFilter<T extends RegistryEntry, K extends keyof T = keyof T> {
|
||||
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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
Reference in New Issue
Block a user