From a89860815f0d14c0c2ebc199e900e69fbc5cea35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Wed, 27 Feb 2019 16:22:57 +0100 Subject: [PATCH] Improved templates --- README.md | 5 +-- card-tools.js | 94 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 67 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index d7001fb..a3b67de 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ The following functions are defined: | `cardTools.moreInfo(entity)` | 0.1 | Brings up the `more-info` dialog for the specified `entity` id | | `cardTools.longPress(element)` | 0.1 | Bind `element` to the long-press handler of lovelace | | `cardTools.hasTemplate(text)` | 0.2 | Check if `text` contains a simple state template | -| `cardTools.parseTemplate(text, [error])` | 0.2 | Parse a simple state template and return results | +| `cardTools.parseTemplate(text, [data])` | 0.2 | Parse a simple state template and return results | | `cardTools.args(script)` | 0.3 | Returns URL parameters of the script from `resources:` | | `cardTools.localize(key)` | 0.3 | Returns translations of certains strings to the users language | | `cardTools.lovelace`| 0.4 | A reference to a structure containing some information about the users lovelace configuration | @@ -220,7 +220,7 @@ Next is one of: The function replaces any template found in the supplied string with the requested value, or an error message on failure. -The optional argument `error` is a string that replaces the default error message. +The optional argument `data` can be an object containing extra data for templates. In a template `{key}` will be evaluated to `data[key]`. `cardTools.hasTemplate` just checks if a string contains a simple state template. @@ -286,3 +286,4 @@ The `script` parameter is required if `cardTools.logger` is called from within a - Added `script` parameter to `cardTools.args` --- +Buy Me A Coffee diff --git a/card-tools.js b/card-tools.js index d5b6c57..1cfcb9e 100644 --- a/card-tools.js +++ b/card-tools.js @@ -211,23 +211,60 @@ class { return /\[\[\s+.*\s+\]\]/.test(text); } - static parseTemplateString(str) { + static parseTemplateString(str, specialData = {}) { if(typeof(str) !== "string") return text; - var RE_entity = /^[a-zA-Z0-9_.]+\.[a-zA-Z0-9_]+$/; - var RE_if = /^if\(([^,]*),([^,]*),(.*)\)$/; - var RE_expr = /([^=<>!]+)\s*(==|<|>|<=|>=|!=)\s*([^=<>!]+)/ + const FUNCTION = /^[a-zA-Z0-9_]+\(.*\)$/; + const EXPR = /([^=<>!]+)\s*(==|!=|<|>|<=|>=)\s*([^=<>!]+)/; + const SPECIAL = /^\{.+\}$/; + const STRING = /^"[^"]*"|'[^']*'$/; + + if(typeof(specialData) === "string") specialData = {}; + specialData = Object.assign({ + user: this.hass.user.name, + browser: this.deviceID, + hash: location.hash.substr(1) || ' ', + }, specialData); + + const _parse_function = (str) => { + let args = [str.substr(0, str.indexOf('(')).trim()] + str = str.substr(str.indexOf('(')+1); + while(str) { + let index = 0; + let stack = []; + while(str[index]) { + if(",)".includes(str[index]) && !stack.length) break; + if(str[index] == '(') stack.push(')'); + if(stack[stack.length - 1] === str[index]) stack.pop(); + else if(`"'`.includes(str[index])) stack.push(str[index]); + index = index + 1; + } + args.push(str.substr(0, index).trim()); + str = str.substr(index+1); + } + return args; + }; + + const _parse_special = (str) => { + str = str.substr(1, str.length - 2); + return specialData[str] || `{${str}}`; + }; const _parse_entity = (str) => { - str = str.trim(); - const parts = str.split("."); - let v = this.hass().states[`${parts.shift()}.${parts.shift()}`]; - if(!parts.length) return v['state']; - parts.forEach(item => v=v[item]); + str = str.split("."); + let v; + if(str[0].match(SPECIAL)) { + v = _parse_special(str.shift()); + v = this.hass().states[v] || v; + } else { + v = this.hass().states[`${str.shift()}.${str.shift()}`]; + if(!str.length) return v['state']; + } + str.forEach(item => v=v[item]); return v; } - const _parse_expr = (str) => { - str = RE_expr.exec(str); + const _eval_expr = (str) => { + str = EXPR.exec(str); if(str === null) return false; const lhs = this.parseTemplateString(str[1]); const rhs = this.parseTemplateString(str[3]); @@ -239,38 +276,35 @@ class { return eval(expr); } - const _parse_if = (str) => { - str = RE_if.exec(str); - if(_parse_expr(str[1])) - return this.parseTemplateString(str[2]); - return this.parseTemplateString(str[3]); + const _eval_function = (args) => { + if(args[0] === "if") { + if(_eval_expr(args[1])) + return this.parseTemplateString(args[2]); + return this.parseTemplateString(args[3]); + } } try { str = str.trim(); - if(str.match(RE_if)) - return _parse_if(str); - if(str.match(RE_entity)) + if(str.match(STRING)) + return str.substr(1, str.length - 2); + if(str.match(SPECIAL)) + return _parse_special(str); + if(str.match(FUNCTION)) + return _eval_function(_parse_function(str)); + if(str.includes(".")) return _parse_entity(str); - if(str.match(/^".*"$/) || str.match(/^'.*'$/)) - return str.substr(1, str.length-2); - if(str.match(/{user}/)) - return this.hass().user.name; - if(str.match(/{browser}/)) - return this.deviceID(); - if(str.match(/{hash}/)) - return location.hash.substr(1); return str; } catch (err) { - return `[[ Template matching failed ${str} ]]`; + return `[[ Template matching failed: ${str} ]]`; } } - static parseTemplate(text, error) { + static parseTemplate(text, data = {}) { if(typeof(text) !== "string") return text; // Note: .*? is javascript regex syntax for NON-greedy matching var RE_template = /\[\[\s(.*?)\s\]\]/g; - text = text.replace(RE_template, (str, p1, offset, s) => this.parseTemplateString(p1)); + text = text.replace(RE_template, (str, p1, offset, s) => this.parseTemplateString(p1, data)); return text; }