Skip to content

Commit

Permalink
update README with all extensions implemented and their types (and TO…
Browse files Browse the repository at this point in the history
…DO implementations), add HA backend like states object named _states, distance and groups tests to work with _states
  • Loading branch information
Nerwyn committed Sep 21, 2024
1 parent db39341 commit 65ae2e7
Show file tree
Hide file tree
Showing 25 changed files with 575 additions and 166 deletions.
266 changes: 150 additions & 116 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { integration_entities } from './utils/integrations';
import { label_areas, label_devices, label_entities, labels, } from './utils/labels';
import { match_media, str } from './utils/miscellaneous';
import { acos, asin, atan, atan2, average, bool, cos, e, float, inf, int, is_number, log, max, median, min, pi, sin, sqrt, statistical_mode, tan, tau, } from './utils/numeric';
import { list, set } from './utils/set';
import { attr_name_translated, attr_value_translated, state_translated, } from './utils/state_translated';
import { has_value, is_state, is_state_attr, state_attr, states, } from './utils/states';
import { as_datetime, as_local, as_timedelta, as_timestamp, now, strptime, time_since, time_until, timedelta, today_at, utcnow, } from './utils/time';
import { list, set } from './utils/type';
import { zip } from './utils/zip';
import { Template } from 'nunjucks';
export function addGlobals(env) {
Expand Down Expand Up @@ -114,7 +114,7 @@ const GLOBALS = {
list,
// Iterating Multiple Objects
zip,
// Other
// Miscellaneous
match_media,
str,
};
Expand Down
2 changes: 2 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import nunjucks from 'nunjucks';
import { addFilters } from './filters';
import { addGlobals } from './globals';
import { addTests } from './tests';
import { buildStatesObject } from './utils/states';
nunjucks.installJinjaCompat();
const env = addTests(addFilters(addGlobals(new nunjucks.Environment())));
/**
Expand All @@ -18,6 +19,7 @@ export function renderTemplate(hass, str, context) {
str = env
.renderString(structuredClone(str), {
hass,
_states: buildStatesObject(hass),
...context,
})
.trim();
Expand Down
22 changes: 20 additions & 2 deletions dist/tests.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Template } from 'nunjucks';
import { contains } from './utils/contains';
import { is_device_attr } from './utils/devices';
import { is_hidden_entity } from './utils/entities';
import { match, search } from './utils/regexp';
import { has_value, is_state, is_state_attr } from './utils/states';
export function addTests(env) {
for (const t in TESTS) {
env.addTest(t, function (...args) {
Expand All @@ -15,6 +20,19 @@ export function addTests(env) {
}
return env;
}
// TODO add tests
const HASS_TESTS = {};
const HASS_TESTS = {
// States
is_state,
is_state_attr,
has_value,
// Entities
is_hidden_entity,
// Devices
is_device_attr,
// Contains
contains,
// Regular Expressions
match,
search,
};
const TESTS = {};
2 changes: 1 addition & 1 deletion dist/utils/distance.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HomeAssistant } from 'custom-card-helpers';
import { HassEntities, HassEntity } from 'home-assistant-js-websocket';
export declare function distance(hass: HomeAssistant, ...args: (string | number)[]): number | null | undefined;
export declare function distance(hass: HomeAssistant, ...args: (string | HassEntity | number)[]): number | null | undefined;
export declare function closest(hass: HomeAssistant, ...args: (string | string[] | HassEntity | HassEntities)[]): HassEntity | null;
23 changes: 20 additions & 3 deletions dist/utils/distance.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ export function distance(hass, ...args) {
lon1 = hass.states[args[0]].attributes.longitude;
i = 1;
}
else if (typeof args[0] == 'object' && !Array.isArray(args[0])) {
lat1 = args[0].attributes.latitude;
lon1 = args[0].attributes.longitude;
i = 1;
}
else if (typeof args[0] == 'number') {
if (!(typeof args[1] == 'number')) {
throw Error('Latitude provided but not longitude 1');
Expand All @@ -106,6 +111,10 @@ export function distance(hass, ...args) {
lat2 = hass.states[args[i]].attributes.latitude;
lon2 = hass.states[args[i]].attributes.longitude;
}
else if (typeof args[0] == 'object' && !Array.isArray(args[0])) {
lat2 = args[i].attributes.latitude;
lon2 = args[i].attributes.longitude;
}
else if (typeof args[i] == 'number') {
if (!(typeof args[i + 1] == 'number')) {
throw Error('Latitude provided but not longitude 2');
Expand Down Expand Up @@ -158,10 +167,10 @@ export function closest(hass, ...args) {
start = 2;
}
else if (typeof args[0] == 'object') {
if (Array.isArray(args[0])) {
if (Array.isArray(args[0]) || !args[0].attributes) {
return null;
}
// Assume is stateobj
// Is state object
home = [
args[0].attributes.latitude,
args[0].attributes.longitude,
Expand Down Expand Up @@ -194,7 +203,15 @@ export function closest(hass, ...args) {
}
}
else {
entityIds0 = Object.keys(args[i]);
const entities = Object.keys(args[i]);
if (args[i][entities[0]].entity_id) {
entityIds0 = entities.map((key) => args[i][key].entity_id);
}
else {
for (const domain of entities) {
entityIds0.push(...Object.keys(args[i][domain]).map((key) => args[i][domain][key].entity_id));
}
}
}
for (const entity of entityIds0) {
entityIds.push(...getEntityIdsByString(entity));
Expand Down
4 changes: 2 additions & 2 deletions dist/utils/groups.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { HomeAssistant } from 'custom-card-helpers';
import { HassEntity } from 'home-assistant-js-websocket';
export declare function expand(hass: HomeAssistant, ...args: (string | HassEntity)[]): HassEntity[];
import { HassEntities, HassEntity } from 'home-assistant-js-websocket';
export declare function expand(hass: HomeAssistant, ...args: (string | HassEntity | HassEntities)[]): HassEntity[];
32 changes: 28 additions & 4 deletions dist/utils/groups.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,37 @@ export function expand(hass, ...args) {
if (Array.isArray(entity.attributes?.entity_id)) {
res.push(...expand(hass, ...entity.attributes?.entity_id));
}
else if (entity.attributes?.persons) {
res.push(...expand(hass, ...entity.attributes?.persons));
else if (entity?.attributes?.persons) {
res.push(...expand(hass, ...entity.attributes.persons));
}
else {
res.push(entity);
if (entity.entity_id) {
res.push(entity);
}
else {
const entities = Object.values(entity);
if (entities[0]?.entity_id) {
res.push(...entities);
}
else {
for (const domain of entities) {
const stateObjects = Object.values(domain);
for (const stateObj of stateObjects) {
if (stateObj?.attributes?.persons) {
res.push(...expand(hass, ...stateObj.attributes.persons));
}
else {
res.push(stateObj);
}
}
}
}
}
}
}
}
return Array.from(new Set(res)).sort((a, b) => a.entity_id.localeCompare(b.entity_id));
return res
.filter((value, index, self) => index ==
self.findIndex((obj) => obj.entity_id == value.entity_id))
.sort((a, b) => a.entity_id.localeCompare(b.entity_id));
}
4 changes: 2 additions & 2 deletions dist/utils/json.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export function to_json(obj, ensure_ascii = true, pretty_print = false, sort_keys = false) {
export function to_json(obj, ensure_ascii = false, pretty_print = false, sort_keys = false) {
if (typeof ensure_ascii == 'object' && !Array.isArray(ensure_ascii)) {
sort_keys = ensure_ascii.sort_keys ?? sort_keys;
pretty_print = ensure_ascii.pretty_print ?? pretty_print;
ensure_ascii = ensure_ascii.ensure_ascii ?? ensure_ascii;
ensure_ascii = ensure_ascii.ensure_ascii ?? false;
}
if (sort_keys) {
obj = Object.keys(obj)
Expand Down
2 changes: 2 additions & 0 deletions dist/utils/states.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { HomeAssistant } from 'custom-card-helpers';
import { HassEntity } from 'home-assistant-js-websocket';
export declare function states(hass: HomeAssistant, entity_id: string, rounded?: boolean | Record<string, boolean>, with_unit?: boolean): string | undefined;
export declare function is_state(hass: HomeAssistant, entity_id: string, value: string | string[]): boolean;
export declare function state_attr(hass: HomeAssistant, entity_id: string, attribute: string): any;
export declare function is_state_attr(hass: HomeAssistant, entity_id: string, attribute: string, value: string | string[]): boolean;
export declare function has_value(hass: HomeAssistant, entity_id: string): boolean;
export declare function buildStatesObject(hass: HomeAssistant): Record<string, Record<string, HassEntity>>;
10 changes: 9 additions & 1 deletion dist/utils/states.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export function states(hass, entity_id, rounded, with_unit) {
try {
const stateObj = hass.states[entity_id];
let state = stateObj?.state;
// https://www.home-assistant.io/docs/configuration/templating/#formatting-sensor-states
if (with_unit && rounded == undefined) {
rounded = true;
}
Expand Down Expand Up @@ -74,3 +73,12 @@ export function has_value(hass, entity_id) {
return false;
}
}
export function buildStatesObject(hass) {
const states = {};
for (const entityId in hass.states) {
const [domain, entity] = entityId.split('.');
states[domain] = states[domain] ?? {};
states[domain][entity] = hass.states[entityId];
}
return states;
}
2 changes: 2 additions & 0 deletions dist/utils/type.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export declare function set(...args: string[]): Set<string>;
export declare function list(...args: string[][]): FlatArray<string[], 0 | 1 | 2 | 16 | 4 | 3 | -1 | 6 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20>[];
7 changes: 7 additions & 0 deletions dist/utils/type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// TODO - validate and possibly add https://www.home-assistant.io/docs/configuration/templating/#complex-type-checking
export function set(...args) {
return new Set(args.flat(Infinity));
}
export function list(...args) {
return args.map((arg) => Array.from(arg)).flat(Infinity);
}
5 changes: 3 additions & 2 deletions src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import {
tan,
tau,
} from './utils/numeric';
import { list, set } from './utils/set';
import {
attr_name_translated,
attr_value_translated,
Expand All @@ -74,6 +73,7 @@ import {
today_at,
utcnow,
} from './utils/time';
import { list, set } from './utils/type';
import { zip } from './utils/zip';

import { Environment, Template } from 'nunjucks';
Expand Down Expand Up @@ -149,6 +149,7 @@ const HASS_GLOBALS: Record<string, CallableFunction> = {
label_areas,
label_devices,
label_entities,

// Immediate If
iif,

Expand Down Expand Up @@ -198,7 +199,7 @@ const GLOBALS: Record<string, CallableFunction> = {
// Iterating Multiple Objects
zip,

// Other
// Miscellaneous
match_media,
str,
};
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import nunjucks from 'nunjucks';
import { addFilters } from './filters';
import { addGlobals } from './globals';
import { addTests } from './tests';
import { buildStatesObject } from './utils/states';

nunjucks.installJinjaCompat();
const env = addTests(addFilters(addGlobals(new nunjucks.Environment())));
Expand All @@ -28,6 +29,7 @@ export function renderTemplate(
str = env
.renderString(structuredClone(str), {
hass,
_states: buildStatesObject(hass),
...context,
})
.trim();
Expand Down
25 changes: 23 additions & 2 deletions src/tests.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Environment, Template } from 'nunjucks';
import { contains } from './utils/contains';
import { is_device_attr } from './utils/devices';
import { is_hidden_entity } from './utils/entities';
import { match, search } from './utils/regexp';
import { has_value, is_state, is_state_attr } from './utils/states';

export function addTests(env: Environment) {
for (const t in TESTS) {
Expand Down Expand Up @@ -28,8 +33,24 @@ export function addTests(env: Environment) {
return env;
}

// TODO add tests
const HASS_TESTS: Record<string, CallableFunction> = {
// States
is_state,
is_state_attr,
has_value,

const HASS_TESTS: Record<string, CallableFunction> = {};
// Entities
is_hidden_entity,

// Devices
is_device_attr,

// Contains
contains,

// Regular Expressions
match,
search,
};

const TESTS: Record<string, CallableFunction> = {};
44 changes: 38 additions & 6 deletions src/utils/distance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,21 @@ function vincenty(
return s;
}

export function distance(hass: HomeAssistant, ...args: (string | number)[]) {
export function distance(
hass: HomeAssistant,
...args: (string | HassEntity | number)[]
) {
try {
let lat1, lat2, lon1, lon2: number;
let i: number = 0;
if (typeof args[0] == 'string') {
lat1 = hass.states[args[0]].attributes.latitude;
lon1 = hass.states[args[0]].attributes.longitude;
i = 1;
} else if (typeof args[0] == 'object' && !Array.isArray(args[0])) {
lat1 = (args[0] as HassEntity).attributes.latitude;
lon1 = (args[0] as HassEntity).attributes.longitude;
i = 1;
} else if (typeof args[0] == 'number') {
if (!(typeof args[1] == 'number')) {
throw Error('Latitude provided but not longitude 1');
Expand All @@ -124,8 +131,11 @@ export function distance(hass: HomeAssistant, ...args: (string | number)[]) {
return null;
}
if (typeof args[i] == 'string') {
lat2 = hass.states[args[i]].attributes.latitude;
lon2 = hass.states[args[i]].attributes.longitude;
lat2 = hass.states[args[i] as string].attributes.latitude;
lon2 = hass.states[args[i] as string].attributes.longitude;
} else if (typeof args[0] == 'object' && !Array.isArray(args[0])) {
lat2 = (args[i] as HassEntity).attributes.latitude;
lon2 = (args[i] as HassEntity).attributes.longitude;
} else if (typeof args[i] == 'number') {
if (!(typeof args[i + 1] == 'number')) {
throw Error('Latitude provided but not longitude 2');
Expand Down Expand Up @@ -188,10 +198,10 @@ export function closest(
home = [args[0], args[1]];
start = 2;
} else if (typeof args[0] == 'object') {
if (Array.isArray(args[0])) {
if (Array.isArray(args[0]) || !(args[0] as HassEntity).attributes) {
return null;
}
// Assume is stateobj
// Is state object
home = [
(args[0] as HassEntity).attributes.latitude,
(args[0] as HassEntity).attributes.longitude,
Expand Down Expand Up @@ -222,7 +232,29 @@ export function closest(
entityIds.push(args[i] as string);
}
} else {
entityIds0 = Object.keys(args[i]);
const entities = Object.keys(args[i]);
if ((args[i] as HassEntities)[entities[0]].entity_id) {
entityIds0 = entities.map(
(key) => (args[i] as HassEntities)[key].entity_id,
);
} else {
for (const domain of entities) {
entityIds0.push(
...Object.keys(
(args[i] as HassEntities)[
domain
] as unknown as HassEntities,
).map(
(key) =>
(
(args[i] as HassEntities)[
domain
] as unknown as HassEntities
)[key].entity_id,
),
);
}
}
}
for (const entity of entityIds0) {
entityIds.push(...getEntityIdsByString(entity));
Expand Down
Loading

0 comments on commit 65ae2e7

Please sign in to comment.