Improved templates

This commit is contained in:
Thomas Lovén 2019-02-27 16:22:57 +01:00
parent 01e9afef1b
commit a89860815f
2 changed files with 67 additions and 32 deletions

View File

@ -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`
---
<a href="https://www.buymeacoffee.com/uqD6KHCdJ" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/white_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>

View File

@ -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;
}