UNPKG

homebridge-homeconnect

Version:

A Homebridge plugin that connects Home Connect appliances to Apple HomeKit

158 lines 5.89 kB
// Homebridge plugin for Home Connect home appliances // Copyright © 2023-2025 Alexander Thoukydides import assert from 'assert'; // Milliseconds in a second export const MS = 1000; // Type assertions export function assertIsDefined(value) { assert.notStrictEqual(value, undefined); assert.notStrictEqual(value, null); } export function assertIsUndefined(value) { assert.strictEqual(value, undefined); } export function assertIsString(value) { assert.strictEqual(typeof value, 'string'); } export function assertIsNumber(value) { assert.strictEqual(typeof value, 'number'); } export function assertIsBoolean(value) { assert.strictEqual(typeof value, 'boolean'); } export function assertIsInstanceOf(value, type) { assert(value instanceof type, `Not an instance of ${type.name}`); } // Format a milliseconds duration export function formatMilliseconds(ms, maxParts = 2) { if (ms < 1) return 'n/a'; // Split the duration into components const duration = [ ['day', Math.floor(ms / (24 * 60 * 60 * MS))], ['hour', Math.floor(ms / (60 * 60 * MS)) % 24], ['minute', Math.floor(ms / (60 * MS)) % 60], ['second', Math.floor(ms / MS) % 60], ['millisecond', Math.floor(ms) % MS] ]; // Remove any leading zero components while (duration[0]?.[1] === 0) duration.shift(); // Combine the required number of remaining components return duration.slice(0, maxParts) .filter(([_key, value]) => value !== 0) .map(([key, value]) => plural(value, key)) .join(' '); } // Format a seconds duration export function formatSeconds(seconds, maxParts = 2) { return formatMilliseconds(seconds * 1000, maxParts); } // Format a list (with Oxford comma) export function formatList(items) { switch (items.length) { case 0: return 'n/a'; case 1: return items[0] ?? ''; case 2: return `${items[0]} and ${items[1]}`; default: return [...items.slice(0, -1), `and ${items[items.length - 1]}`].join(', '); } } // Format a counted noun (handling most regular cases automatically) export function plural(count, noun, showCount = true) { const [singular, plural] = Array.isArray(noun) ? noun : [noun, '']; noun = count === 1 ? singular : plural; if (!noun) { // Apply regular rules const rules = [ ['on$', 'a', 2], // phenomenon/phenomena criterion/criteria ['us$', 'i', 1], // cactus/cacti focus/foci ['[^aeiou]y$', 'ies', 1], // cty/cites puppy/puppies ['(ch|is|o|s|sh|x|z)$', 'es', 0], // iris/irises truss/trusses ['', 's', 0] // cat/cats house/houses ]; const rule = rules.find(([ending]) => new RegExp(ending, 'i').test(singular)); assertIsDefined(rule); const matchCase = (s) => singular === singular.toUpperCase() ? s.toUpperCase() : s; noun = singular.substring(0, singular.length - rule[2]).concat(matchCase(rule[1])); } return showCount ? `${count} ${noun}` : noun; } // Format strings in columns export function columns(rows, separator = ' ') { // Determine the required column widths const width = []; rows.forEach(row => { row.forEach((value, index) => { width[index] = Math.max(width[index] ?? 0, value.length); }); }); width.splice(-1, 1, 0); // Format the rows return rows.map(row => row.map((value, index) => value.padEnd(width[index] ?? 0)).join(separator)); } // Recursive object assignment, skipping undefined values export function deepMerge(...objects) { const isObject = (value) => value !== undefined && typeof value === 'object' && !Array.isArray(value); return objects.reduce((acc, object) => { Object.entries(object).forEach(([key, value]) => { const accValue = acc[key]; if (value === undefined) return; if (isObject(accValue) && isObject(value)) acc[key] = deepMerge(accValue, value); else acc[key] = value; }); return acc; }, {}); } // Convert checker validation error into lines of text export function getValidationTree(errors) { const lines = []; errors.forEach((error, index) => { const prefix = (a, b) => index < errors.length - 1 ? a : b; lines.push(`${prefix('├─ ', '└─ ')}${error.path} ${error.message}`); if (error.nested) { const nested = getValidationTree(error.nested); lines.push(...nested.map(line => `${prefix('│ ', ' ')} ${line}`)); } }); return lines; } // Extract property keys or union literal from a ti-interface-checker type export function keyofChecker(typeSuite, type) { const checker = type; // TIface const props = []; if (checker.propSet instanceof Set) { props.push(...checker.propSet); } if (Array.isArray(checker.bases)) { for (const base of checker.bases) { const baseType = typeSuite[base]; assertIsDefined(baseType); props.push(...keyofChecker(typeSuite, baseType)); } } // TUnion or TIntersection if (Array.isArray(checker.ttypes)) { for (const ttype of checker.ttypes) props.push(...keyofChecker(typeSuite, ttype)); } // TEnum if (checker.validValues instanceof Set) { props.push(...checker.validValues); } if (typeof checker.value === 'string') { // TLiteral props.push(checker.value); } else if (typeof checker.name === 'string') { // TName const nameType = typeSuite[checker.name]; assertIsDefined(nameType); props.push(...keyofChecker(typeSuite, nameType)); } return props; } //# sourceMappingURL=utils.js.map