ha-nunjucks 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -54,6 +54,34 @@ const context = {
54
54
  const renderedString = renderTemplate(this.hass, templateString, context);
55
55
  ```
56
56
 
57
+ `renderTemplate` will try to validate that inputs contain valid templates by default using the exported function `hasTemplate`. You can disable this by setting the fourth argument of `renderTemplate` to `false`. This way you can perform this check yourself before any additional templating setup you perform in your code.
58
+
59
+ ```typescript
60
+ import { hasTemplate, renderTemplate } from 'ha-nunjucks';
61
+
62
+ if (!hasTemplate(templateString)) {
63
+ return templateString;
64
+ }
65
+
66
+ const context = {
67
+ foo: 'bar',
68
+ doThing(thing: string) {
69
+ return `doing ${thing}!`;
70
+ },
71
+ config: {
72
+ entity: 'foo.bar',
73
+ attribute: 'baz_bah',
74
+ },
75
+ };
76
+
77
+ const renderedString = renderTemplate(
78
+ this.hass,
79
+ templateString,
80
+ context,
81
+ false,
82
+ );
83
+ ```
84
+
57
85
  ### Return Types
58
86
 
59
87
  `renderTemplate` will return a string unless the result is `true` or `false` (_not_ case sensitive), in which case it will return a boolean.
@@ -115,6 +143,10 @@ Functions used to determine an entity's state or an attribute.
115
143
  | state_translated | function, filter | entity_id, state (optional) | Returns the formatted and translated state of an entity or provided state using a language that is currently configured in the general settings. |
116
144
  | attr_name_translated | function, filter | entity_id, attr_name | Returns the formatted and translated attribute name of an entity using a language that is currently configured in the general settings. |
117
145
  | attr_value_translated | function, filter | entity_id, attr_name, attr_value (optional) | Returns the formatted and translated attribute value of an entity or provided attribute value using a language that is currently configured in the general settings. |
146
+ | number_translated | function, filter | value | Returns the formatted and translated input number using a language that is currently configured in the general settings. |
147
+ | date_translated | function, filter | value | Returns the formatted and translated input date or datetime as a date using a language that is currently configured in the general settings. |
148
+ | time_translated | function, filter | value | Returns the formatted and translated input time or datetime as a time using a language that is currently configured in the general settings. |
149
+ | datetime_translated | function, filter | value | Returns the formatted and translated input datetime using a language that is currently configured in the general settings. |
118
150
 
119
151
  ### [Groups](https://www.home-assistant.io/docs/configuration/templating/#working-with-groups)
120
152
 
@@ -164,7 +196,7 @@ Functions used to determine an entity's state or an attribute.
164
196
 
165
197
  ### [Labels](https://www.home-assistant.io/docs/configuration/templating/#labels)
166
198
 
167
- **NOTE**: Labels are not available in the `hass` object and must be retrieved asynchronously from the Home Assistant backend the first time `renderTemplate` is called. Since this package is otherwise synchronous, this can cause a race condition where no labels are found the first time `renderTemplate` is run. This generally resolves itself once the template re-renders.
199
+ **NOTE**: Labels are not available in the `hass` object and must be retrieved asynchronously from the Home Assistant backend the first time `ha-nunjucks` is imported. Since this package is otherwise synchronous, this can cause a race condition where no labels are found the first time `renderTemplate` is run. This generally resolves itself once the template re-renders.
168
200
 
169
201
  | Name | Type | Arguments | Description |
170
202
  | -------------- | ---------------- | ----------------------- | ------------------------------------------------------------------------------------------ |
package/dist/filters.js CHANGED
@@ -10,7 +10,7 @@ import { label_areas, label_devices, label_entities, label_id, label_name, label
10
10
  import { str } from './utils/miscellaneous';
11
11
  import { acos, add, asin, atan, atan2, average, bitwise_and, bitwise_not, bitwise_or, bitwise_xor, bool, cos, is_number, log, max, median, min, multiply, ord, sin, sqrt, statistical_mode, tan, } from './utils/numeric';
12
12
  import { regex_findall, regex_findall_index, regex_replace, } from './utils/regexp';
13
- import { attr_name_translated, attr_value_translated, state_translated, } from './utils/state_translated';
13
+ import { attr_name_translated, attr_value_translated, date_translated, datetime_translated, number_translated, state_translated, time_translated, } from './utils/state_translated';
14
14
  import { has_value, state_attr, states } from './utils/states';
15
15
  import { as_datetime, as_local, as_timestamp, time_since, time_until, timestamp_custom, timestamp_local, timestamp_utc, today_at, } from './utils/time';
16
16
  export function addFilters(env) {
@@ -74,6 +74,9 @@ const FILTERS = {
74
74
  timestamp_local,
75
75
  timestamp_utc,
76
76
  timestamp_custom,
77
+ date_translated,
78
+ time_translated,
79
+ datetime_translated,
77
80
  // To/From JSON
78
81
  to_json,
79
82
  from_json,
@@ -107,6 +110,7 @@ const FILTERS = {
107
110
  ord,
108
111
  multiply,
109
112
  add,
113
+ number_translated,
110
114
  // Regular Expressions
111
115
  regex_replace,
112
116
  regex_findall,
package/dist/globals.js CHANGED
@@ -9,7 +9,7 @@ import { integration_entities } from './utils/integrations';
9
9
  import { label_areas, label_devices, label_entities, label_id, label_name, labels, } from './utils/labels';
10
10
  import { match_media, str } from './utils/miscellaneous';
11
11
  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';
12
- import { attr_name_translated, attr_value_translated, state_translated, } from './utils/state_translated';
12
+ import { attr_name_translated, attr_value_translated, date_translated, datetime_translated, number_translated, state_translated, time_translated, } from './utils/state_translated';
13
13
  import { has_value, is_state, is_state_attr, state_attr, states, } from './utils/states';
14
14
  import { as_datetime, as_local, as_timedelta, as_timestamp, now, strptime, time_since, time_until, today_at, utcnow, } from './utils/time';
15
15
  import { list, set } from './utils/type_conversions';
@@ -95,6 +95,9 @@ const GLOBALS = {
95
95
  time_since,
96
96
  time_until,
97
97
  as_timedelta,
98
+ date_translated,
99
+ time_translated,
100
+ datetime_translated,
98
101
  // Numeric,
99
102
  float,
100
103
  is_number,
@@ -114,6 +117,7 @@ const GLOBALS = {
114
117
  average,
115
118
  median,
116
119
  statistical_mode,
120
+ number_translated,
117
121
  // Type Conversions
118
122
  set,
119
123
  list,
package/dist/index.d.ts CHANGED
@@ -4,6 +4,13 @@ import { HomeAssistant } from './models/interfaces/hass';
4
4
  * @param {HomeAssistant} hass The Home Assistant object
5
5
  * @param {string} str The template string to render
6
6
  * @param {object} [context] Additional context to expose to nunjucks
7
+ * @param {boolean} [validate=true] Validate that the input contains a template.
7
8
  * @returns {string | boolean} The rendered template string if a string was provided, otherwise the unaltered input
8
9
  */
9
- export declare function renderTemplate(hass: HomeAssistant, str: string, context?: object): string | boolean;
10
+ export declare function renderTemplate(hass: HomeAssistant, str: string, context?: object, validate?: boolean): string | boolean;
11
+ /**
12
+ * Test if the input contains a valid template
13
+ * @param {any} str the variable to check
14
+ * @returns if the input is a string that contains a template
15
+ */
16
+ export declare function hasTemplate(str: any): boolean;
package/dist/index.js CHANGED
@@ -5,49 +5,72 @@ import { addTests } from './tests';
5
5
  import { fetchLabelRegistry } from './utils/labels';
6
6
  import { buildStatesObject } from './utils/states';
7
7
  if (!window.haNunjucks) {
8
- window.haNunjucks = {};
8
+ window.haNunjucks = {
9
+ states: {},
10
+ labelRegistry: {},
11
+ };
12
+ // Async setup label registry and states object on import
13
+ const registrySetup = async () => {
14
+ const ha = document.querySelector('home-assistant');
15
+ if (!ha ||
16
+ !ha.hass ||
17
+ !ha.hass.connected ||
18
+ !ha.hass.connection ||
19
+ !ha.hass.connection.connected) {
20
+ setTimeout(registrySetup, 10);
21
+ return;
22
+ }
23
+ // Number and datetime translators
24
+ window.haNunjucks.numberFormat = new Intl.NumberFormat(ha.hass.language);
25
+ window.haNunjucks.dateFormat = new Intl.DateTimeFormat(ha.hass.language, { dateStyle: 'full' });
26
+ window.haNunjucks.timeFormat = new Intl.DateTimeFormat(ha.hass.language, { timeStyle: 'long' });
27
+ window.haNunjucks.datetimeFormat = new Intl.DateTimeFormat(ha.hass.language, { dateStyle: 'full', timeStyle: 'long' });
28
+ // Label registry and states object
29
+ window.haNunjucks.hass = ha.hass;
30
+ fetchLabelRegistry();
31
+ buildStatesObject();
32
+ };
33
+ registrySetup();
34
+ // Initialize global ha-nunjucks environment
9
35
  nunjucks.installJinjaCompat();
10
36
  window.haNunjucks.env = addTests(addFilters(addGlobals(new nunjucks.Environment())));
11
- window.haNunjucks.states = {};
12
- window.hassConnection?.then((hassConnection) => {
13
- const entities = hassConnection?.conn?._entityRegistryDisplay?.state
14
- ?.entities;
15
- for (const entity of entities) {
16
- const [domain, _id] = entity.ei.split('.');
17
- window.haNunjucks.states[domain] ??= {};
18
- }
19
- });
20
37
  }
21
38
  /**
22
39
  * Render a Home Assistant template string using nunjucks
23
40
  * @param {HomeAssistant} hass The Home Assistant object
24
41
  * @param {string} str The template string to render
25
42
  * @param {object} [context] Additional context to expose to nunjucks
43
+ * @param {boolean} [validate=true] Validate that the input contains a template.
26
44
  * @returns {string | boolean} The rendered template string if a string was provided, otherwise the unaltered input
27
45
  */
28
- export function renderTemplate(hass, str, context) {
29
- if (!window.haNunjucks?.labelRegistry) {
30
- fetchLabelRegistry(hass);
46
+ export function renderTemplate(hass, str, context, validate = true) {
47
+ if (validate && !hasTemplate(str)) {
48
+ return str;
31
49
  }
32
50
  window.haNunjucks.hass = hass;
33
- if (typeof str == 'string' &&
34
- ((str.includes('{{') && str.includes('}}')) ||
35
- (str.includes('{%') && str.includes('%}')))) {
36
- str = window.haNunjucks.env
37
- .renderString(structuredClone(str), {
38
- hass,
39
- _states: buildStatesObject(),
40
- ...context,
41
- })
42
- .trim();
43
- if ([undefined, null, 'undefined', 'null', 'None'].includes(str)) {
44
- return '';
45
- }
46
- const lowerStr = str.toLowerCase();
47
- if (['true', 'false'].includes(lowerStr)) {
48
- return lowerStr == 'true';
49
- }
50
- return str;
51
+ buildStatesObject();
52
+ str = window.haNunjucks.env
53
+ .renderString(structuredClone(str), {
54
+ hass,
55
+ _states: window.haNunjucks.states,
56
+ ...context,
57
+ })
58
+ .trim();
59
+ if ([undefined, null, 'undefined', 'null', 'None'].includes(str)) {
60
+ return '';
61
+ }
62
+ const lowerStr = str.toLowerCase();
63
+ if (['true', 'false'].includes(lowerStr)) {
64
+ return lowerStr == 'true';
51
65
  }
52
66
  return str;
53
67
  }
68
+ const hasTemplateRegex = /{{.*?}}|{%.*?%}/;
69
+ /**
70
+ * Test if the input contains a valid template
71
+ * @param {any} str the variable to check
72
+ * @returns if the input is a string that contains a template
73
+ */
74
+ export function hasTemplate(str) {
75
+ return hasTemplateRegex.test(str);
76
+ }
@@ -37,3 +37,6 @@ export interface HomeAssistant {
37
37
  formatEntityAttributeValue(stateObj: HassEntity, attribute: string, value?: any): string;
38
38
  formatEntityAttributeName(stateObj: HassEntity, attribute: string): string;
39
39
  }
40
+ export interface HassElement extends HTMLElement {
41
+ hass: HomeAssistant;
42
+ }
@@ -1,5 +1,5 @@
1
1
  import { HomeAssistant } from '../models/interfaces/hass';
2
- export declare function fetchLabelRegistry(hass: HomeAssistant): Promise<void>;
2
+ export declare function fetchLabelRegistry(): Promise<void>;
3
3
  export declare function labels(hass: HomeAssistant, lookup_value?: string): string[];
4
4
  export declare function label_id(lookup_value: string): string | undefined;
5
5
  export declare function label_name(lookup_value: string): string;
@@ -1,13 +1,10 @@
1
- export async function fetchLabelRegistry(hass) {
2
- if (hass.connection) {
3
- window.haNunjucks.labelRegistry = {};
4
- const labels = await hass.connection.sendMessagePromise({
5
- type: 'config/label_registry/list',
6
- });
7
- labels.sort((ent1, ent2) => ent1.name.localeCompare(ent2.name));
8
- for (const label of labels) {
9
- window.haNunjucks.labelRegistry[label.label_id] = label;
10
- }
1
+ export async function fetchLabelRegistry() {
2
+ const labels = await window.haNunjucks.hass.connection.sendMessagePromise({
3
+ type: 'config/label_registry/list',
4
+ });
5
+ labels.sort((ent1, ent2) => ent1.name.localeCompare(ent2.name));
6
+ for (const label of labels) {
7
+ window.haNunjucks.labelRegistry[label.label_id] = label;
11
8
  }
12
9
  }
13
10
  export function labels(hass, lookup_value) {
@@ -1,4 +1,9 @@
1
+ import { date, datetime, time } from 'ts-py-datetime';
1
2
  import { HomeAssistant } from '../models/interfaces/hass';
2
3
  export declare function state_translated(hass: HomeAssistant, entity_id: string, state?: string): string;
3
4
  export declare function attr_name_translated(hass: HomeAssistant, entity_id: string, attr_name: string): string;
4
5
  export declare function attr_value_translated(hass: HomeAssistant, entity_id: string, attr_name: string, attr_value?: string): any;
6
+ export declare function number_translated(value: number): string | number;
7
+ export declare function date_translated(value: date | datetime): string | date | datetime;
8
+ export declare function time_translated(value: time | datetime): string | datetime | time;
9
+ export declare function datetime_translated(value: datetime): string | datetime;
@@ -26,3 +26,33 @@ export function attr_value_translated(hass, entity_id, attr_name, attr_value) {
26
26
  undefined);
27
27
  }
28
28
  }
29
+ export function number_translated(value) {
30
+ if (isNaN(value)) {
31
+ return value;
32
+ }
33
+ return window.haNunjucks.numberFormat.format(value);
34
+ }
35
+ export function date_translated(value) {
36
+ try {
37
+ return window.haNunjucks.dateFormat.format(value.jsDate);
38
+ }
39
+ catch {
40
+ return value;
41
+ }
42
+ }
43
+ export function time_translated(value) {
44
+ try {
45
+ return window.haNunjucks.timeFormat.format(value.jsDate);
46
+ }
47
+ catch {
48
+ return value;
49
+ }
50
+ }
51
+ export function datetime_translated(value) {
52
+ try {
53
+ return window.haNunjucks.datetimeFormat.format(value.jsDate);
54
+ }
55
+ catch {
56
+ return value;
57
+ }
58
+ }
@@ -1,7 +1,7 @@
1
1
  import { HomeAssistant } from '../models/interfaces/hass';
2
+ export declare function buildStatesObject(): void;
2
3
  export declare function states(hass: HomeAssistant, entity_id: string, rounded?: boolean | Record<string, boolean>, with_unit?: boolean): string | undefined;
3
4
  export declare function is_state(hass: HomeAssistant, entity_id: string, value: string | string[]): boolean;
4
5
  export declare function state_attr(hass: HomeAssistant, entity_id: string, attribute: string): any;
5
6
  export declare function is_state_attr(hass: HomeAssistant, entity_id: string, attribute: string, value: string): boolean;
6
7
  export declare function has_value(hass: HomeAssistant, entity_id: string): boolean;
7
- export declare function buildStatesObject(): Record<string, Record<string, import("home-assistant-js-websocket").HassEntity>>;
@@ -1,3 +1,11 @@
1
+ export function buildStatesObject() {
2
+ for (const entityId in window.haNunjucks.hass.states) {
3
+ const [domain, id] = entityId.split('.');
4
+ window.haNunjucks.states[domain] ??= {};
5
+ window.haNunjucks.states[domain][id] =
6
+ window.haNunjucks.hass.states[entityId];
7
+ }
8
+ }
1
9
  export function states(hass, entity_id, rounded, with_unit) {
2
10
  if (typeof rounded == 'object' && !Array.isArray(rounded)) {
3
11
  with_unit = rounded.with_unit ?? with_unit;
@@ -64,12 +72,3 @@ export function has_value(hass, entity_id) {
64
72
  return false;
65
73
  }
66
74
  }
67
- export function buildStatesObject() {
68
- for (const entityId in window.haNunjucks.hass.states) {
69
- const [domain, id] = entityId.split('.');
70
- window.haNunjucks.states[domain] ??= {};
71
- window.haNunjucks.states[domain][id] =
72
- window.haNunjucks.hass.states[entityId];
73
- }
74
- return window.haNunjucks.states;
75
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ha-nunjucks",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Wrapper for nunjucks for use with Home Assistant frontend custom components to render templates",
5
5
  "main": "./dist/index.js",
6
6
  "files": [
@@ -8,7 +8,7 @@
8
8
  ],
9
9
  "type": "module",
10
10
  "scripts": {
11
- "test": "ts-mocha tests/**/*.test.ts",
11
+ "test": "ts-mocha tests/**/*.test.ts --require fixtures.cjs",
12
12
  "build": "tsc",
13
13
  "prelint": "tsc --noemit",
14
14
  "lint": "eslint --config ./.eslintrc.config.cjs"