mirror of
https://github.com/DigiLive/mushroom-strategy.git
synced 2025-08-05 04:24:27 +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 { Registry } from '../Registry';
|
||||||
import { DeviceRegistryEntry } from '../types/homeassistant/data/device_registry';
|
import { DeviceRegistryEntry } from '../types/homeassistant/data/device_registry';
|
||||||
import { EntityCategory, EntityRegistryEntry } from '../types/homeassistant/data/entity_registry';
|
import { EntityCategory, EntityRegistryEntry } from '../types/homeassistant/data/entity_registry';
|
||||||
import { RegistryEntry, StrategyConfig } from '../types/strategy/strategy-generics';
|
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.
|
* 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[]) {
|
constructor(entries: T[]) {
|
||||||
this.entries = entries;
|
this.entries = entries;
|
||||||
this.entryIdentifier = (
|
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;
|
) 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`.
|
* Filters entries **strictly** by their `area_id`.
|
||||||
*
|
*
|
||||||
* - Entries with a matching `area_id` are kept.
|
* - 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 `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 {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.
|
* @param {boolean} [expandToDevice=true] - Whether to use the device's `area_id` if the entry has none.
|
||||||
*
|
|
||||||
* @remarks
|
|
||||||
* For area id `undisclosed`, the `area_id` of the entry's device may be `undisclosed` or `undefined`.
|
|
||||||
*/
|
*/
|
||||||
whereAreaId(areaId?: string, expandToDevice: boolean = true): this {
|
whereAreaId(areaId?: string, expandToDevice: boolean = true): this {
|
||||||
const predicate = (entry: T) => {
|
const predicate = (entry: T) => {
|
||||||
let deviceAreaId: string | null | undefined = undefined;
|
let deviceAreaId: string | null | undefined = undefined;
|
||||||
const entryObject = entry as EntityRegistryEntry;
|
const entryObject = entry as EntityRegistryEntry;
|
||||||
|
|
||||||
// Retrieve the device area ID only if expandToDevice is true
|
|
||||||
if (expandToDevice && entryObject.device_id) {
|
if (expandToDevice && entryObject.device_id) {
|
||||||
deviceAreaId = Registry.devices.find((device) => device.id === entryObject.device_id)?.area_id;
|
deviceAreaId = Registry.devices.find((device) => device.id === entryObject.device_id)?.area_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logic for 'undisclosed' areaId
|
if (areaId === 'undisclosed' || areaId === undefined) {
|
||||||
if (areaId === 'undisclosed') {
|
return entry.area_id === areaId && (!expandToDevice || deviceAreaId === areaId);
|
||||||
return entry.area_id === areaId && (deviceAreaId === areaId || deviceAreaId === undefined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
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)));
|
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.
|
* 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];
|
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;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,6 @@ export enum DebugLevel {
|
|||||||
Fatal = 5,
|
Fatal = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
/**
|
/**
|
||||||
* Individually exported log level constants.
|
* Individually exported log level constants.
|
||||||
*
|
*
|
||||||
@@ -41,49 +40,7 @@ export const {
|
|||||||
*
|
*
|
||||||
* @default DebugLevel.Off
|
* @default DebugLevel.Off
|
||||||
*/
|
*/
|
||||||
let currentLevel: DebugLevel = DebugLevel.Fatal;
|
let currentLevel: DebugLevel = DebugLevel.Off;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the global log level.
|
* Sets the global log level.
|
||||||
@@ -104,11 +61,7 @@ export function setDebugLevel(level: DebugLevel) {
|
|||||||
* @param {string} message - The message to log.
|
* @param {string} message - The message to log.
|
||||||
* @param {unknown[]} [details] - Optional extra details (e.g., error object).
|
* @param {unknown[]} [details] - Optional extra details (e.g., error object).
|
||||||
*
|
*
|
||||||
* @throws {Error} After logging, if the level is `lvlError` or `lvlFatal`.
|
* @throws {Error} After logging, if the level is `Fatal` or `Error`.
|
||||||
*
|
|
||||||
* @remarks
|
|
||||||
* It might be required to throw an additional Error after logging with `lvlError ` or `lvlFatal` to satify the
|
|
||||||
* TypeScript compiler.
|
|
||||||
*/
|
*/
|
||||||
export function logMessage(level: DebugLevel, message: string, ...details: unknown[]): void {
|
export function logMessage(level: DebugLevel, message: string, ...details: unknown[]): void {
|
||||||
if (currentLevel === DebugLevel.Off || level > currentLevel) {
|
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 frontEndMessage: string = 'Mushroom Strategy - An error occurred. Check the console (F12) for details.';
|
||||||
const prefix = `[${DebugLevel[level].toUpperCase()}]`;
|
const prefix = `[${DebugLevel[level].toUpperCase()}]`;
|
||||||
const safeDetails = details.map(deepClone);
|
const safeDetails = details.map(deepClone);
|
||||||
const caller = `[at ${getCallerName(new Error().stack)}]`;
|
|
||||||
|
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case DebugLevel.Debug:
|
case DebugLevel.Debug:
|
||||||
console.debug(`${prefix}${caller} ${message}`, ...safeDetails);
|
console.debug(`${prefix} ${message}`, ...safeDetails);
|
||||||
break;
|
break;
|
||||||
case DebugLevel.Info:
|
case DebugLevel.Info:
|
||||||
console.info(`${prefix}${caller} ${message}`, ...safeDetails);
|
console.info(`${prefix} ${message}`, ...safeDetails);
|
||||||
break;
|
break;
|
||||||
case DebugLevel.Warn:
|
case DebugLevel.Warn:
|
||||||
console.warn(`${prefix}${caller} ${message}`, ...safeDetails);
|
console.warn(`${prefix} ${message}`, ...safeDetails);
|
||||||
break;
|
break;
|
||||||
case DebugLevel.Error:
|
case DebugLevel.Error:
|
||||||
console.error(`${prefix}${caller} ${message}`, ...safeDetails);
|
console.error(`${prefix} ${message}`, ...safeDetails);
|
||||||
throw frontEndMessage;
|
throw frontEndMessage;
|
||||||
case DebugLevel.Fatal:
|
case DebugLevel.Fatal:
|
||||||
console.error(`${prefix}${caller} ${message}`, ...safeDetails);
|
console.error(`${prefix} ${message}`, ...safeDetails);
|
||||||
alert?.(`${prefix} ${message}`);
|
alert?.(`${prefix} ${message}`);
|
||||||
throw frontEndMessage;
|
throw frontEndMessage;
|
||||||
}
|
}
|
||||||
|
@@ -62,6 +62,7 @@ export default function setupCustomLocalize(hass?: HomeAssistant): void {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate a key using the globally configured localize function.
|
* Translate a key using the globally configured localize function.
|
||||||
|
* Throws if not initialized.
|
||||||
*/
|
*/
|
||||||
export function localize(key: string): string {
|
export function localize(key: string): string {
|
||||||
if (!_localize) {
|
if (!_localize) {
|
||||||
|
Reference in New Issue
Block a user