• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

maxwroc / battery-state-card / 23910221447

02 Apr 2026 04:14PM UTC coverage: 92.846% (+0.1%) from 92.747%
23910221447

Pull #886

github

web-flow
Merge c798bef35 into 02005ba50
Pull Request #886: Release v4.2.0

930 of 1033 branches covered (90.03%)

Branch coverage included in aggregate %.

70 of 71 new or added lines in 3 files covered. (98.59%)

926 of 966 relevant lines covered (95.86%)

106.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.49
/src/rich-string-processor.ts
1
import { log } from "./utils";
24✔
2
import { EntityDataAccessor } from "./entity-data-accessor";
3

4
/**
5
 * Class for processing keyword strings
6
 */
7
 export class RichStringProcessor {
24✔
8

9
    constructor(private accessor: EntityDataAccessor | undefined) {
423✔
10
    }
11

12
    /**
13
     * Replaces keywords in given string with the data
14
     */
15
    process(text: string): string {
16
        if (!text) {
324✔
17
            return "";
9✔
18
        }
19

20
        return text.replace(/\{([^\}]+)\}/g, (matchWithBraces, keyword) => this.replaceKeyword(keyword, ""));
315✔
21
    }
22

23
    /**
24
     * Converts keyword in the final value
25
     */
26
    private replaceKeyword(keyword: string, defaultValue: string): string {
27
        const processingDetails = keyword.split("|");
279✔
28
        const dataSource = processingDetails.shift();
279✔
29

30
        const value = this.getValue(dataSource);
279✔
31

32
        if (value === undefined) {
279✔
33
            return defaultValue;
3✔
34
        }
35

36
        if (processingDetails.length === 0) {
276✔
37
            return value;
132✔
38
        }
39

40
        const result = applyKStringProcessors(value, processingDetails);
144✔
41

42
        return result === undefined ? defaultValue : result;
144!
43
    }
44

45
    private getValue(dataSource: string | undefined): string | undefined {
46

47
        if (dataSource === undefined) {
279!
48
            return dataSource;
×
49
        }
50

51
        if (this.accessor) {
279!
52
            let data = this.accessor.resolve(dataSource);
279✔
53
            if (typeof data == "object") {
279✔
54
                data = JSON.stringify(data);
3✔
55
            }
56
            return data === undefined ? undefined : data.toString();
279✔
57
        }
58

59
        return undefined;
×
60
    }
61
}
62

63
const commandPattern = /(?<func>[a-z]+)\((?<params>[^\)]*)\)/;
24✔
64

65
const availableProcessors: IMap<IProcessorCtor> = {
24✔
66
    "replace": (params) => {
67
        const separatorIndex = params.indexOf(",");
9✔
68
        if (separatorIndex == -1) {
9!
69
            log("'replace' function requires two params");
×
70
            return undefined;
×
71
        }
72
        const replaceDataChunks = [params.substring(0, separatorIndex), params.substring(separatorIndex + 1)];
9✔
73

74
        return val => {
9✔
75
            return val.replace(replaceDataChunks[0], replaceDataChunks[1])
9✔
76
        };
77
    },
78
    "round": (params) => {
79
        let decimalPlaces = parseInt(params);
21✔
80
        if (isNaN(decimalPlaces)) {
21✔
81
            decimalPlaces = 0;
6✔
82
        }
83

84
        return val => parseFloat(val).toFixed(decimalPlaces);
21✔
85
    },
86
    "multiply": (params) => {
87
        if (params === "") {
12✔
88
            log("[KString]multiply function is missing parameter");
3✔
89
            return val => val;
3✔
90
        }
91

92
        const multiplier = Number(params);
9✔
93

94
        return val => isNaN(multiplier) ? val : (Number(val) * multiplier).toString();
9!
95
    },
96
    "greaterthan": (params) => {
97
        const chunks = params.split(",");
75✔
98
        if (chunks.length != 2) {
75✔
99
            log("[KString]greaterthan function requires two parameters");
3✔
100
            return val => val;
3✔
101
        }
102

103
        const compareTo = Number(chunks[0]);
72✔
104
        return val =>  Number(val) > compareTo ? chunks[1] : val;
72✔
105
    },
106
    "lessthan": (params) => {
107
        const chunks = params.split(",");
27✔
108
        if (chunks.length != 2) {
27✔
109
            log("[KString]lessthan function requires two parameters");
3✔
110
            return val => val;
3✔
111
        }
112

113
        const compareTo = Number(chunks[0]);
24✔
114
        return val =>  Number(val) < compareTo ? chunks[1] : val;
24✔
115
    },
116
    "between": (params) => {
117
        const chunks = params.split(",");
42✔
118
        if (chunks.length != 3) {
42✔
119
            log("[KString]between function requires three parameters");
3✔
120
            return val => val;
3✔
121
        }
122

123
        const compareLower = Number(chunks[0]);
39✔
124
        const compareGreater = Number(chunks[1]);
39✔
125
        return val => {
39✔
126
            const numericVal = Number(val);
39✔
127
            return compareLower <= numericVal && compareGreater >= numericVal ? chunks[2] : val;
39✔
128
        }
129
    },
130
    "thresholds": (params) => {
131
        const thresholds = params.split(",").map(v => Number(v));
102✔
132

133
        return val => {
27✔
134
            const numericVal = Number(val);
27✔
135
            const result = thresholds.findIndex(v => numericVal < v);
78✔
136

137
            if (result == -1) {
27✔
138
                // looks like the value is higher than the last threshold
139
                return "100";
6✔
140
            }
141

142
            return Math.round(100 / thresholds.length * result).toString();
21✔
143
        }
144
    },
145
    "abs": () =>
146
        val => Math.abs(Number(val)).toString(),
18✔
147
    "equals": (params) => {
148
        const chunks = params.split(",");
9✔
149
        if (chunks.length != 2) {
9✔
150
            log("[KString]equals function requires two parameters");
3✔
151
            return val => val;
3✔
152
        }
153

154
        return val =>  val == chunks[0] ? chunks[1] : val;
6✔
155
    },
156
    "add": (params) => {
157
        if (params === "") {
12✔
158
            log("[KString]add function is missing parameter");
3✔
159
            return val => val;
3✔
160
        }
161

162
        const addend = Number(params);
9✔
163

164
        return val => isNaN(addend) ? val : (Number(val) + addend).toString();
9✔
165
    },
166
    "reltime": () => {
167
        return val => {
9✔
168
            const unixTime = Date.parse(val);
9✔
169
            if (isNaN(unixTime)) {
9✔
170
                log("[KString]value isn't a valid date: " + val);
3✔
171
                return val;
3✔
172
            }
173

174
            // The RT tags will be converted to proper HA tags at the views layer
175
            return `<rt>${val}</rt>`
6✔
176
        };
177
    }
178
}
179

180
/**
181
 * Applies pipe processors to a value string.
182
 * Reuses the same processors available in KString expressions.
183
 */
184
export const applyKStringProcessors = (value: string, pipes: string[]): string => {
24✔
185
    const processors = pipes.map(command => {
150✔
186
        const match = commandPattern.exec(command);
261✔
187
        if (!match || !match.groups || !availableProcessors[match.groups.func]) {
261✔
NEW
188
            return undefined;
×
189
        }
190
        return availableProcessors[match.groups.func](match.groups.params);
261✔
191
    });
192

193
    const result = processors.filter(p => p !== undefined).reduce((res, proc) => proc!(res), value);
261✔
194
    return result === undefined ? "" : result;
150!
195
}
196

197
interface IProcessor {
198
    (val: string): string;
199
}
200

201
interface IProcessorCtor {
202
    (params: string): IProcessor | undefined
203
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc