@halleyassist/rule-templater 0.0.10 → 0.0.12
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 +50 -2
- package/dist/rule-templater.browser.js +168 -56
- package/index.d.ts +25 -4
- package/index.js +8 -1
- package/package.json +1 -1
- package/src/GeneralTemplate.js +125 -0
- package/src/RuleTemplater.js +40 -23
- package/src/RuleTemplater.production.js +40 -23
- package/src/TemplateFilters.js +127 -33
package/README.md
CHANGED
|
@@ -121,6 +121,8 @@ const prepared = parsed.prepare({
|
|
|
121
121
|
- **round**: Round number to nearest integer
|
|
122
122
|
- **floor**: Round number down
|
|
123
123
|
- **ceil**: Round number up
|
|
124
|
+
- **time_start**: Extract `from` from `time period` / `time period ago` and convert to `time value`
|
|
125
|
+
- **time_end**: Extract `to` from `time period` / `time period ago` and convert to `time value`
|
|
124
126
|
|
|
125
127
|
#### Filter Examples
|
|
126
128
|
|
|
@@ -137,6 +139,32 @@ const prepared = parsed.prepare({
|
|
|
137
139
|
|
|
138
140
|
// Chaining filters
|
|
139
141
|
'${text|trim|upper}' with text=' hello ' → HELLO
|
|
142
|
+
|
|
143
|
+
// Time period conversion
|
|
144
|
+
'${window|time_start}' with window={from:'08:00',to:'12:00'} → 08:00
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### General String Templating
|
|
148
|
+
|
|
149
|
+
For non-rule text templates, use `GeneralTemplate`:
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
const RuleTemplate = require('@halleyassist/rule-templater');
|
|
153
|
+
const { GeneralTemplate } = RuleTemplate;
|
|
154
|
+
|
|
155
|
+
const template = 'If a door is opened between ${ALERT_PERIOD | time_start} AND ${ALERT_PERIOD | time_end}';
|
|
156
|
+
|
|
157
|
+
const variables = GeneralTemplate.getVariables(template);
|
|
158
|
+
// [{ name: 'ALERT_PERIOD', filters: ['time_start', 'time_end'], positions: [...] }]
|
|
159
|
+
|
|
160
|
+
const parsed = GeneralTemplate.parse(template);
|
|
161
|
+
const prepared = parsed.prepare({
|
|
162
|
+
ALERT_PERIOD: {
|
|
163
|
+
value: { from: '08:00', to: '12:00' },
|
|
164
|
+
type: 'time period'
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
// If a door is opened between 08:00 AND 12:00
|
|
140
168
|
```
|
|
141
169
|
|
|
142
170
|
## API
|
|
@@ -208,6 +236,22 @@ Prepares the template by replacing variables with their values and applying any
|
|
|
208
236
|
|
|
209
237
|
**Returns:** The prepared rule string with variables replaced and filters applied
|
|
210
238
|
|
|
239
|
+
### `GeneralTemplate.parse(templateText)`
|
|
240
|
+
|
|
241
|
+
Parses a general string template and returns a `GeneralTemplate` instance.
|
|
242
|
+
|
|
243
|
+
### `GeneralTemplate.getVariables(templateText)` (Static)
|
|
244
|
+
|
|
245
|
+
Extracts variables from a general string template without creating an instance manually.
|
|
246
|
+
|
|
247
|
+
### `generalTemplate.getVariables()`
|
|
248
|
+
|
|
249
|
+
Extracts variables from a general string template.
|
|
250
|
+
|
|
251
|
+
### `generalTemplate.prepare(variables)`
|
|
252
|
+
|
|
253
|
+
Prepares a general string template by replacing `${...}` placeholders with values and applying filters.
|
|
254
|
+
|
|
211
255
|
### `RuleTemplate.validateVariableNode(astNode, variableType)` (Static)
|
|
212
256
|
|
|
213
257
|
Helper method to validate that an AST node matches the expected variable type.
|
|
@@ -222,13 +266,16 @@ Helper method to validate that an AST node matches the expected variable type.
|
|
|
222
266
|
|
|
223
267
|
Access to the filter functions used by the template engine. Can be extended with custom filters.
|
|
224
268
|
|
|
269
|
+
Custom filters receive a cloned `varData` object (`{ value, type }`) used for template rendering. They can mutate both fields without mutating the original input variables.
|
|
270
|
+
|
|
225
271
|
**Example:**
|
|
226
272
|
```javascript
|
|
227
273
|
const RuleTemplate = require('@halleyassist/rule-templater');
|
|
228
274
|
|
|
229
275
|
// Add a custom filter
|
|
230
|
-
RuleTemplate.TemplateFilters.reverse = (
|
|
231
|
-
|
|
276
|
+
RuleTemplate.TemplateFilters.reverse = (varData) => {
|
|
277
|
+
varData.value = String(varData.value).split('').reverse().join('');
|
|
278
|
+
return varData;
|
|
232
279
|
};
|
|
233
280
|
|
|
234
281
|
// Use the custom filter
|
|
@@ -247,6 +294,7 @@ The following variable types are supported:
|
|
|
247
294
|
- `boolean`
|
|
248
295
|
- `object`
|
|
249
296
|
- `time period`
|
|
297
|
+
- `time period ago`
|
|
250
298
|
- `time value`
|
|
251
299
|
- `string array`
|
|
252
300
|
- `number array`
|
|
@@ -3663,6 +3663,7 @@ const VariableTypes = [
|
|
|
3663
3663
|
'boolean',
|
|
3664
3664
|
'object',
|
|
3665
3665
|
'time period',
|
|
3666
|
+
'time period ago',
|
|
3666
3667
|
'time value',
|
|
3667
3668
|
'string array',
|
|
3668
3669
|
'number array',
|
|
@@ -3675,6 +3676,7 @@ const AllowedTypeMapping = {
|
|
|
3675
3676
|
'number': ['number_atom', 'math_expr'],
|
|
3676
3677
|
'boolean': ['boolean_atom', 'boolean_expr'],
|
|
3677
3678
|
'time period': ['time_period_atom'],
|
|
3679
|
+
'time period ago': ['time_period_atom'],
|
|
3678
3680
|
'time value': ['time_value_atom', 'tod_atom'],
|
|
3679
3681
|
'string array': ['string_array'],
|
|
3680
3682
|
'number array': ['number_array'],
|
|
@@ -3985,12 +3987,12 @@ class RuleTemplate {
|
|
|
3985
3987
|
throw new Error(`Variable '${varName}' not provided in variables object`);
|
|
3986
3988
|
}
|
|
3987
3989
|
|
|
3988
|
-
|
|
3990
|
+
let varData = variables[varName];
|
|
3989
3991
|
if (typeof varData !== 'object' || !varData.hasOwnProperty('value')) {
|
|
3990
3992
|
throw new Error(`Variable '${varName}' must be an object with 'value' property`);
|
|
3991
3993
|
}
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
+
|
|
3995
|
+
varData = Object.assign({}, varData);
|
|
3994
3996
|
|
|
3995
3997
|
// Require type property for all variables
|
|
3996
3998
|
if (!varData.hasOwnProperty('type')) {
|
|
@@ -3998,8 +4000,8 @@ class RuleTemplate {
|
|
|
3998
4000
|
}
|
|
3999
4001
|
|
|
4000
4002
|
// Validate type
|
|
4001
|
-
if (!VariableTypes.includes(type)) {
|
|
4002
|
-
throw new Error(`Invalid variable type '${type}' for variable '${varName}'`);
|
|
4003
|
+
if (!VariableTypes.includes(varData.type)) {
|
|
4004
|
+
throw new Error(`Invalid variable type '${varData.type}' for variable '${varName}'`);
|
|
4003
4005
|
}
|
|
4004
4006
|
|
|
4005
4007
|
// Apply filters if present
|
|
@@ -4008,27 +4010,42 @@ class RuleTemplate {
|
|
|
4008
4010
|
if (!TemplateFilters[filterName]) {
|
|
4009
4011
|
throw new Error(`Unknown filter '${filterName}'`);
|
|
4010
4012
|
}
|
|
4011
|
-
|
|
4013
|
+
|
|
4014
|
+
TemplateFilters[filterName](varData);
|
|
4012
4015
|
}
|
|
4013
|
-
// After applying filters, the result is already a string representation
|
|
4014
|
-
return String(value);
|
|
4015
4016
|
}
|
|
4016
|
-
|
|
4017
|
-
|
|
4017
|
+
|
|
4018
|
+
return this._serializeVarData(varData, varName);
|
|
4019
|
+
}
|
|
4020
|
+
|
|
4021
|
+
_serializeVarData(varData, varName) {
|
|
4022
|
+
const { value, type } = varData;
|
|
4023
|
+
|
|
4024
|
+
if (!VariableTypes.includes(type)) {
|
|
4025
|
+
throw new Error(`Invalid variable type '${type}' for variable '${varName}'`);
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4018
4028
|
if (type === 'string') {
|
|
4019
|
-
// Escape backslashes first, then quotes in string values.
|
|
4020
|
-
// Order is critical: escaping backslashes first prevents double-escaping.
|
|
4021
|
-
// E.g., "test\" becomes "test\\" then "test\\\"" (correct)
|
|
4022
|
-
// If reversed, "test\" would become "test\\"" then "test\\\\"" (incorrect)
|
|
4023
4029
|
return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
4024
|
-
}
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
if (type === 'number') {
|
|
4025
4033
|
return String(value);
|
|
4026
|
-
}
|
|
4034
|
+
}
|
|
4035
|
+
|
|
4036
|
+
if (type === 'boolean') {
|
|
4027
4037
|
return value ? 'true' : 'false';
|
|
4028
|
-
} else {
|
|
4029
|
-
// Default behavior - just insert the value as-is
|
|
4030
|
-
return String(value);
|
|
4031
4038
|
}
|
|
4039
|
+
|
|
4040
|
+
if (type === 'time period' || type === 'time period ago') {
|
|
4041
|
+
let ret = `${value.from} TO ${value.to}`;
|
|
4042
|
+
if(value.ago) {
|
|
4043
|
+
ret += ` AGO ${value.ago[0]} ${value.ago[1]}`;
|
|
4044
|
+
}
|
|
4045
|
+
return ret;
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
return String(value);
|
|
4032
4049
|
}
|
|
4033
4050
|
|
|
4034
4051
|
/**
|
|
@@ -4051,11 +4068,12 @@ class RuleTemplate {
|
|
|
4051
4068
|
}
|
|
4052
4069
|
}
|
|
4053
4070
|
|
|
4054
|
-
|
|
4071
|
+
RuleTemplate.ParserRules = ParserRules;
|
|
4072
|
+
RuleTemplate.VariableTypes = VariableTypes;
|
|
4073
|
+
RuleTemplate.TemplateFilters = TemplateFilters;
|
|
4074
|
+
|
|
4055
4075
|
module.exports = RuleTemplate;
|
|
4056
|
-
|
|
4057
|
-
module.exports.VariableTypes = VariableTypes;
|
|
4058
|
-
module.exports.TemplateFilters = TemplateFilters;
|
|
4076
|
+
|
|
4059
4077
|
},{"./RuleTemplate.production.ebnf.js":14,"./TemplateFilters":17,"@halleyassist/rule-parser":2,"ebnf":13}],17:[function(require,module,exports){
|
|
4060
4078
|
/*
|
|
4061
4079
|
Template filters are functions that transform variable values.
|
|
@@ -4063,59 +4081,153 @@ They are applied in the template syntax as ${variable|filter} or ${variable|filt
|
|
|
4063
4081
|
*/
|
|
4064
4082
|
const TemplateFilters = {
|
|
4065
4083
|
// Convert value to JSON string representation
|
|
4066
|
-
string:
|
|
4067
|
-
|
|
4084
|
+
string: varData => {
|
|
4085
|
+
varData.value = String(varData.value);
|
|
4086
|
+
varData.type = 'string';
|
|
4087
|
+
|
|
4088
|
+
},
|
|
4089
|
+
|
|
4068
4090
|
// Convert to uppercase
|
|
4069
|
-
upper:
|
|
4070
|
-
|
|
4091
|
+
upper: varData => {
|
|
4092
|
+
varData.value = String(varData.value).toUpperCase();
|
|
4093
|
+
varData.type = 'string';
|
|
4094
|
+
|
|
4095
|
+
},
|
|
4096
|
+
|
|
4071
4097
|
// Convert to lowercase
|
|
4072
|
-
lower:
|
|
4073
|
-
|
|
4098
|
+
lower: varData => {
|
|
4099
|
+
varData.value = String(varData.value).toLowerCase();
|
|
4100
|
+
varData.type = 'string';
|
|
4101
|
+
|
|
4102
|
+
},
|
|
4103
|
+
|
|
4074
4104
|
// Capitalize first letter
|
|
4075
|
-
capitalize:
|
|
4076
|
-
const str = String(value);
|
|
4077
|
-
|
|
4105
|
+
capitalize: varData => {
|
|
4106
|
+
const str = String(varData.value);
|
|
4107
|
+
varData.value = str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
4108
|
+
varData.type = 'string';
|
|
4109
|
+
|
|
4078
4110
|
},
|
|
4079
|
-
|
|
4111
|
+
|
|
4080
4112
|
// Convert to title case
|
|
4081
|
-
title:
|
|
4082
|
-
|
|
4113
|
+
title: varData => {
|
|
4114
|
+
varData.value = String(varData.value).split(' ')
|
|
4083
4115
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
4084
4116
|
.join(' ');
|
|
4117
|
+
varData.type = 'string';
|
|
4118
|
+
|
|
4085
4119
|
},
|
|
4086
|
-
|
|
4120
|
+
|
|
4087
4121
|
// Trim whitespace
|
|
4088
|
-
trim:
|
|
4089
|
-
|
|
4122
|
+
trim: varData => {
|
|
4123
|
+
varData.value = String(varData.value).trim();
|
|
4124
|
+
varData.type = 'string';
|
|
4125
|
+
|
|
4126
|
+
},
|
|
4127
|
+
|
|
4090
4128
|
// Convert to number
|
|
4091
|
-
number:
|
|
4092
|
-
|
|
4129
|
+
number: varData => {
|
|
4130
|
+
varData.value = Number(varData.value);
|
|
4131
|
+
varData.type = 'number';
|
|
4132
|
+
|
|
4133
|
+
},
|
|
4134
|
+
|
|
4093
4135
|
// Convert to boolean
|
|
4094
|
-
boolean:
|
|
4095
|
-
|
|
4136
|
+
boolean: varData => {
|
|
4137
|
+
const value = varData.value;
|
|
4138
|
+
if (typeof value === 'boolean') {
|
|
4139
|
+
varData.type = 'boolean';
|
|
4140
|
+
|
|
4141
|
+
}
|
|
4142
|
+
|
|
4096
4143
|
if (typeof value === 'string') {
|
|
4097
4144
|
const lower = value.toLowerCase();
|
|
4098
|
-
if (lower === 'true' || lower === '1' || lower === 'yes')
|
|
4099
|
-
|
|
4145
|
+
if (lower === 'true' || lower === '1' || lower === 'yes') {
|
|
4146
|
+
varData.value = true;
|
|
4147
|
+
varData.type = 'boolean';
|
|
4148
|
+
|
|
4149
|
+
}
|
|
4150
|
+
if (lower === 'false' || lower === '0' || lower === 'no') {
|
|
4151
|
+
varData.value = false;
|
|
4152
|
+
varData.type = 'boolean';
|
|
4153
|
+
|
|
4154
|
+
}
|
|
4100
4155
|
}
|
|
4101
|
-
|
|
4156
|
+
|
|
4157
|
+
varData.value = Boolean(value);
|
|
4158
|
+
varData.type = 'boolean';
|
|
4159
|
+
|
|
4102
4160
|
},
|
|
4103
|
-
|
|
4161
|
+
|
|
4104
4162
|
// Convert to absolute value (for numbers)
|
|
4105
|
-
abs:
|
|
4106
|
-
|
|
4163
|
+
abs: varData => {
|
|
4164
|
+
varData.value = Math.abs(Number(varData.value));
|
|
4165
|
+
varData.type = 'number';
|
|
4166
|
+
|
|
4167
|
+
},
|
|
4168
|
+
|
|
4107
4169
|
// Round number
|
|
4108
|
-
round:
|
|
4109
|
-
|
|
4170
|
+
round: varData => {
|
|
4171
|
+
varData.value = Math.round(Number(varData.value));
|
|
4172
|
+
varData.type = 'number';
|
|
4173
|
+
|
|
4174
|
+
},
|
|
4175
|
+
|
|
4110
4176
|
// Floor number
|
|
4111
|
-
floor:
|
|
4112
|
-
|
|
4177
|
+
floor: varData => {
|
|
4178
|
+
varData.value = Math.floor(Number(varData.value));
|
|
4179
|
+
varData.type = 'number';
|
|
4180
|
+
|
|
4181
|
+
},
|
|
4182
|
+
|
|
4113
4183
|
// Ceil number
|
|
4114
|
-
ceil:
|
|
4115
|
-
|
|
4184
|
+
ceil: varData => {
|
|
4185
|
+
varData.value = Math.ceil(Number(varData.value));
|
|
4186
|
+
varData.type = 'number';
|
|
4187
|
+
|
|
4188
|
+
},
|
|
4189
|
+
|
|
4116
4190
|
// Default value if empty/null/undefined
|
|
4117
|
-
default: (
|
|
4118
|
-
|
|
4191
|
+
default: (varData, defaultValue = '') => {
|
|
4192
|
+
varData.value = (varData.value === null || varData.value === undefined || varData.value === '') ? defaultValue : varData.value;
|
|
4193
|
+
if (typeof varData.value === 'string') {
|
|
4194
|
+
varData.type = 'string';
|
|
4195
|
+
} else if (typeof varData.value === 'number') {
|
|
4196
|
+
varData.type = 'number';
|
|
4197
|
+
} else if (typeof varData.value === 'boolean') {
|
|
4198
|
+
varData.type = 'boolean';
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4201
|
+
},
|
|
4202
|
+
|
|
4203
|
+
// Extract start time from time period/time period ago as time value
|
|
4204
|
+
time_start: varData => {
|
|
4205
|
+
if (varData.type === 'time period' || varData.type === 'time period ago') {
|
|
4206
|
+
if (!varData.value || typeof varData.value !== 'object' || !Object.prototype.hasOwnProperty.call(varData.value, 'from')) {
|
|
4207
|
+
throw new Error('time_start filter requires value.from for time period types');
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4210
|
+
varData.value = varData.value.from;
|
|
4211
|
+
varData.type = 'time value';
|
|
4212
|
+
return;
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4215
|
+
throw new Error('time_start filter requires variable type to be \"time period\" or \"time period ago\"');
|
|
4216
|
+
},
|
|
4217
|
+
|
|
4218
|
+
// Extract end time from time period/time period ago as time value
|
|
4219
|
+
time_end: varData => {
|
|
4220
|
+
if (varData.type === 'time period' || varData.type === 'time period ago') {
|
|
4221
|
+
if (!varData.value || typeof varData.value !== 'object' || !Object.prototype.hasOwnProperty.call(varData.value, 'to')) {
|
|
4222
|
+
throw new Error('time_end filter requires value.from for time period types');
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
varData.value = varData.value.to;
|
|
4226
|
+
varData.type = 'time value';
|
|
4227
|
+
return;
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4230
|
+
throw new Error('time_end filter requires variable type to be \"time period\" or \"time period ago\"');
|
|
4119
4231
|
}
|
|
4120
4232
|
}
|
|
4121
4233
|
|
package/index.d.ts
CHANGED
|
@@ -10,8 +10,12 @@ export interface VariableInfo {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export interface VariableValue {
|
|
13
|
-
value: string | number | boolean
|
|
14
|
-
|
|
13
|
+
value: string | number | boolean | {
|
|
14
|
+
from: string;
|
|
15
|
+
to: string;
|
|
16
|
+
ago?: [number, string];
|
|
17
|
+
};
|
|
18
|
+
type?: 'string' | 'number' | 'boolean' | 'object' | 'time period' | 'time period ago' | 'time value' | 'string array' | 'number array' | 'boolean array' | 'object array';
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
export interface Variables {
|
|
@@ -30,7 +34,7 @@ export interface ASTNode {
|
|
|
30
34
|
[key: string]: any;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
export type FilterFunction = (
|
|
37
|
+
export type FilterFunction = (varData: VariableValue, ...args: any[]) => VariableValue | any;
|
|
34
38
|
|
|
35
39
|
export interface TemplateFiltersType {
|
|
36
40
|
string: FilterFunction;
|
|
@@ -46,10 +50,11 @@ export interface TemplateFiltersType {
|
|
|
46
50
|
floor: FilterFunction;
|
|
47
51
|
ceil: FilterFunction;
|
|
48
52
|
default: FilterFunction;
|
|
53
|
+
time_start: FilterFunction;
|
|
49
54
|
[key: string]: FilterFunction;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
export
|
|
57
|
+
export class RuleTemplate {
|
|
53
58
|
ruleTemplateText: string;
|
|
54
59
|
ast: ASTNode;
|
|
55
60
|
|
|
@@ -98,6 +103,22 @@ export default class RuleTemplate {
|
|
|
98
103
|
static validateVariableNode(astNode: ASTNode | null | undefined, variableType: string): boolean;
|
|
99
104
|
}
|
|
100
105
|
|
|
106
|
+
export class GeneralTemplate {
|
|
107
|
+
templateText: string;
|
|
108
|
+
|
|
109
|
+
constructor(templateText: string);
|
|
110
|
+
|
|
111
|
+
static parse(templateText: string): GeneralTemplate;
|
|
112
|
+
|
|
113
|
+
static getVariables(templateText: string): VariableInfo[];
|
|
114
|
+
|
|
115
|
+
getVariables(): VariableInfo[];
|
|
116
|
+
|
|
117
|
+
extractVariables(): VariableInfo[];
|
|
118
|
+
|
|
119
|
+
prepare(variables: Variables): string;
|
|
120
|
+
}
|
|
121
|
+
|
|
101
122
|
export const ParserRules: any[];
|
|
102
123
|
export const VariableTypes: string[];
|
|
103
124
|
export const TemplateFilters: TemplateFiltersType;
|
package/index.js
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
const RuleTemplate = require('./src/RuleTemplater');
|
|
2
|
+
const GeneralTemplate = require('./src/GeneralTemplate');
|
|
3
|
+
|
|
4
|
+
module.exports.RuleTemplate = RuleTemplate;
|
|
5
|
+
module.exports.ParserRules = RuleTemplate.ParserRules;
|
|
6
|
+
module.exports.VariableTypes = RuleTemplate.VariableTypes;
|
|
7
|
+
module.exports.TemplateFilters = RuleTemplate.TemplateFilters;
|
|
8
|
+
module.exports.GeneralTemplate = GeneralTemplate;
|
package/package.json
CHANGED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const TemplateFilters = require('./TemplateFilters');
|
|
2
|
+
|
|
3
|
+
class GeneralTemplate {
|
|
4
|
+
constructor(templateText) {
|
|
5
|
+
this.templateText = templateText;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static parse(templateText) {
|
|
9
|
+
return new GeneralTemplate(templateText);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static getVariables(templateText) {
|
|
13
|
+
return GeneralTemplate.parse(templateText).getVariables();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getVariables() {
|
|
17
|
+
const variables = [];
|
|
18
|
+
const variableMap = new Map();
|
|
19
|
+
const pattern = /\$\{([^}]*)\}/g;
|
|
20
|
+
|
|
21
|
+
for (const match of this.templateText.matchAll(pattern)) {
|
|
22
|
+
const parsedExpression = this._parseTemplateExpression(match[1]);
|
|
23
|
+
if (parsedExpression) {
|
|
24
|
+
if (variableMap.has(parsedExpression.name)) {
|
|
25
|
+
const existing = variableMap.get(parsedExpression.name);
|
|
26
|
+
existing.positions.push({
|
|
27
|
+
start: match.index,
|
|
28
|
+
end: match.index + match[0].length
|
|
29
|
+
});
|
|
30
|
+
existing.filters = Array.from(new Set(existing.filters.concat(parsedExpression.filters)));
|
|
31
|
+
} else {
|
|
32
|
+
variableMap.set(parsedExpression.name, {
|
|
33
|
+
name: parsedExpression.name,
|
|
34
|
+
filters: parsedExpression.filters,
|
|
35
|
+
positions: [{
|
|
36
|
+
start: match.index,
|
|
37
|
+
end: match.index + match[0].length
|
|
38
|
+
}]
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const variable of variableMap.values()) {
|
|
45
|
+
variables.push(variable);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return variables;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
extractVariables() {
|
|
52
|
+
return this.getVariables();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
prepare(variables) {
|
|
56
|
+
if (!variables || typeof variables !== 'object') {
|
|
57
|
+
throw new Error('Variables must be provided as an object');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return this.templateText.replace(/\$\{([^}]*)\}/g, (matchText, expression) => {
|
|
61
|
+
const parsedExpression = this._parseTemplateExpression(expression);
|
|
62
|
+
if (!parsedExpression) {
|
|
63
|
+
return matchText;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const varName = parsedExpression.name;
|
|
67
|
+
if (!Object.prototype.hasOwnProperty.call(variables, varName)) {
|
|
68
|
+
throw new Error(`Variable '${varName}' not provided in variables object`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let varData = variables[varName];
|
|
72
|
+
if (typeof varData !== 'object' || !Object.prototype.hasOwnProperty.call(varData, 'value')) {
|
|
73
|
+
throw new Error(`Variable '${varName}' must be an object with 'value' property`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
varData = Object.assign({}, varData);
|
|
77
|
+
|
|
78
|
+
if (parsedExpression.filters && parsedExpression.filters.length > 0) {
|
|
79
|
+
for (const filterName of parsedExpression.filters) {
|
|
80
|
+
if (!TemplateFilters[filterName]) {
|
|
81
|
+
throw new Error(`Unknown filter '${filterName}'`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
TemplateFilters[filterName](varData);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return this._serializeVariable(varData);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_parseTemplateExpression(expression) {
|
|
93
|
+
if (!expression) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const segments = expression.split('|').map(s => s.trim()).filter(Boolean);
|
|
98
|
+
if (segments.length === 0) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
name: segments[0],
|
|
104
|
+
filters: segments.slice(1)
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_serializeVariable(varData) {
|
|
109
|
+
if (varData.value === null || varData.value === undefined) {
|
|
110
|
+
return '';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (varData.type === 'time period' || varData.type === 'time period ago') {
|
|
114
|
+
let ret = `${varData.value.from} TO ${varData.value.to}`;
|
|
115
|
+
if (varData.value.ago) {
|
|
116
|
+
ret += ` AGO ${varData.value.ago[0]} ${varData.value.ago[1]}`;
|
|
117
|
+
}
|
|
118
|
+
return ret;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return String(varData.value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = GeneralTemplate;
|
package/src/RuleTemplater.js
CHANGED
|
@@ -13,6 +13,7 @@ const VariableTypes = [
|
|
|
13
13
|
'boolean',
|
|
14
14
|
'object',
|
|
15
15
|
'time period',
|
|
16
|
+
'time period ago',
|
|
16
17
|
'time value',
|
|
17
18
|
'string array',
|
|
18
19
|
'number array',
|
|
@@ -25,6 +26,7 @@ const AllowedTypeMapping = {
|
|
|
25
26
|
'number': ['number_atom', 'math_expr'],
|
|
26
27
|
'boolean': ['boolean_atom', 'boolean_expr'],
|
|
27
28
|
'time period': ['time_period_atom'],
|
|
29
|
+
'time period ago': ['time_period_atom'],
|
|
28
30
|
'time value': ['time_value_atom', 'tod_atom'],
|
|
29
31
|
'string array': ['string_array'],
|
|
30
32
|
'number array': ['number_array'],
|
|
@@ -335,12 +337,12 @@ class RuleTemplate {
|
|
|
335
337
|
throw new Error(`Variable '${varName}' not provided in variables object`);
|
|
336
338
|
}
|
|
337
339
|
|
|
338
|
-
|
|
340
|
+
let varData = variables[varName];
|
|
339
341
|
if (typeof varData !== 'object' || !varData.hasOwnProperty('value')) {
|
|
340
342
|
throw new Error(`Variable '${varName}' must be an object with 'value' property`);
|
|
341
343
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
+
|
|
345
|
+
varData = Object.assign({}, varData);
|
|
344
346
|
|
|
345
347
|
// Require type property for all variables
|
|
346
348
|
if (!varData.hasOwnProperty('type')) {
|
|
@@ -348,8 +350,8 @@ class RuleTemplate {
|
|
|
348
350
|
}
|
|
349
351
|
|
|
350
352
|
// Validate type
|
|
351
|
-
if (!VariableTypes.includes(type)) {
|
|
352
|
-
throw new Error(`Invalid variable type '${type}' for variable '${varName}'`);
|
|
353
|
+
if (!VariableTypes.includes(varData.type)) {
|
|
354
|
+
throw new Error(`Invalid variable type '${varData.type}' for variable '${varName}'`);
|
|
353
355
|
}
|
|
354
356
|
|
|
355
357
|
// Apply filters if present
|
|
@@ -358,27 +360,42 @@ class RuleTemplate {
|
|
|
358
360
|
if (!TemplateFilters[filterName]) {
|
|
359
361
|
throw new Error(`Unknown filter '${filterName}'`);
|
|
360
362
|
}
|
|
361
|
-
|
|
363
|
+
|
|
364
|
+
TemplateFilters[filterName](varData);
|
|
362
365
|
}
|
|
363
|
-
// After applying filters, the result is already a string representation
|
|
364
|
-
return String(value);
|
|
365
366
|
}
|
|
366
|
-
|
|
367
|
-
|
|
367
|
+
|
|
368
|
+
return this._serializeVarData(varData, varName);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
_serializeVarData(varData, varName) {
|
|
372
|
+
const { value, type } = varData;
|
|
373
|
+
|
|
374
|
+
if (!VariableTypes.includes(type)) {
|
|
375
|
+
throw new Error(`Invalid variable type '${type}' for variable '${varName}'`);
|
|
376
|
+
}
|
|
377
|
+
|
|
368
378
|
if (type === 'string') {
|
|
369
|
-
// Escape backslashes first, then quotes in string values.
|
|
370
|
-
// Order is critical: escaping backslashes first prevents double-escaping.
|
|
371
|
-
// E.g., "test\" becomes "test\\" then "test\\\"" (correct)
|
|
372
|
-
// If reversed, "test\" would become "test\\"" then "test\\\\"" (incorrect)
|
|
373
379
|
return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
374
|
-
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (type === 'number') {
|
|
375
383
|
return String(value);
|
|
376
|
-
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (type === 'boolean') {
|
|
377
387
|
return value ? 'true' : 'false';
|
|
378
|
-
} else {
|
|
379
|
-
// Default behavior - just insert the value as-is
|
|
380
|
-
return String(value);
|
|
381
388
|
}
|
|
389
|
+
|
|
390
|
+
if (type === 'time period' || type === 'time period ago') {
|
|
391
|
+
let ret = `${value.from} TO ${value.to}`;
|
|
392
|
+
if(value.ago) {
|
|
393
|
+
ret += ` AGO ${value.ago[0]} ${value.ago[1]}`;
|
|
394
|
+
}
|
|
395
|
+
return ret;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return String(value);
|
|
382
399
|
}
|
|
383
400
|
|
|
384
401
|
/**
|
|
@@ -401,8 +418,8 @@ class RuleTemplate {
|
|
|
401
418
|
}
|
|
402
419
|
}
|
|
403
420
|
|
|
404
|
-
|
|
421
|
+
RuleTemplate.ParserRules = ParserRules;
|
|
422
|
+
RuleTemplate.VariableTypes = VariableTypes;
|
|
423
|
+
RuleTemplate.TemplateFilters = TemplateFilters;
|
|
424
|
+
|
|
405
425
|
module.exports = RuleTemplate;
|
|
406
|
-
module.exports.ParserRules = ParserRules;
|
|
407
|
-
module.exports.VariableTypes = VariableTypes;
|
|
408
|
-
module.exports.TemplateFilters = TemplateFilters;
|
|
@@ -13,6 +13,7 @@ const VariableTypes = [
|
|
|
13
13
|
'boolean',
|
|
14
14
|
'object',
|
|
15
15
|
'time period',
|
|
16
|
+
'time period ago',
|
|
16
17
|
'time value',
|
|
17
18
|
'string array',
|
|
18
19
|
'number array',
|
|
@@ -25,6 +26,7 @@ const AllowedTypeMapping = {
|
|
|
25
26
|
'number': ['number_atom', 'math_expr'],
|
|
26
27
|
'boolean': ['boolean_atom', 'boolean_expr'],
|
|
27
28
|
'time period': ['time_period_atom'],
|
|
29
|
+
'time period ago': ['time_period_atom'],
|
|
28
30
|
'time value': ['time_value_atom', 'tod_atom'],
|
|
29
31
|
'string array': ['string_array'],
|
|
30
32
|
'number array': ['number_array'],
|
|
@@ -335,12 +337,12 @@ class RuleTemplate {
|
|
|
335
337
|
throw new Error(`Variable '${varName}' not provided in variables object`);
|
|
336
338
|
}
|
|
337
339
|
|
|
338
|
-
|
|
340
|
+
let varData = variables[varName];
|
|
339
341
|
if (typeof varData !== 'object' || !varData.hasOwnProperty('value')) {
|
|
340
342
|
throw new Error(`Variable '${varName}' must be an object with 'value' property`);
|
|
341
343
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
+
|
|
345
|
+
varData = Object.assign({}, varData);
|
|
344
346
|
|
|
345
347
|
// Require type property for all variables
|
|
346
348
|
if (!varData.hasOwnProperty('type')) {
|
|
@@ -348,8 +350,8 @@ class RuleTemplate {
|
|
|
348
350
|
}
|
|
349
351
|
|
|
350
352
|
// Validate type
|
|
351
|
-
if (!VariableTypes.includes(type)) {
|
|
352
|
-
throw new Error(`Invalid variable type '${type}' for variable '${varName}'`);
|
|
353
|
+
if (!VariableTypes.includes(varData.type)) {
|
|
354
|
+
throw new Error(`Invalid variable type '${varData.type}' for variable '${varName}'`);
|
|
353
355
|
}
|
|
354
356
|
|
|
355
357
|
// Apply filters if present
|
|
@@ -358,27 +360,42 @@ class RuleTemplate {
|
|
|
358
360
|
if (!TemplateFilters[filterName]) {
|
|
359
361
|
throw new Error(`Unknown filter '${filterName}'`);
|
|
360
362
|
}
|
|
361
|
-
|
|
363
|
+
|
|
364
|
+
TemplateFilters[filterName](varData);
|
|
362
365
|
}
|
|
363
|
-
// After applying filters, the result is already a string representation
|
|
364
|
-
return String(value);
|
|
365
366
|
}
|
|
366
|
-
|
|
367
|
-
|
|
367
|
+
|
|
368
|
+
return this._serializeVarData(varData, varName);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
_serializeVarData(varData, varName) {
|
|
372
|
+
const { value, type } = varData;
|
|
373
|
+
|
|
374
|
+
if (!VariableTypes.includes(type)) {
|
|
375
|
+
throw new Error(`Invalid variable type '${type}' for variable '${varName}'`);
|
|
376
|
+
}
|
|
377
|
+
|
|
368
378
|
if (type === 'string') {
|
|
369
|
-
// Escape backslashes first, then quotes in string values.
|
|
370
|
-
// Order is critical: escaping backslashes first prevents double-escaping.
|
|
371
|
-
// E.g., "test\" becomes "test\\" then "test\\\"" (correct)
|
|
372
|
-
// If reversed, "test\" would become "test\\"" then "test\\\\"" (incorrect)
|
|
373
379
|
return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
374
|
-
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (type === 'number') {
|
|
375
383
|
return String(value);
|
|
376
|
-
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (type === 'boolean') {
|
|
377
387
|
return value ? 'true' : 'false';
|
|
378
|
-
} else {
|
|
379
|
-
// Default behavior - just insert the value as-is
|
|
380
|
-
return String(value);
|
|
381
388
|
}
|
|
389
|
+
|
|
390
|
+
if (type === 'time period' || type === 'time period ago') {
|
|
391
|
+
let ret = `${value.from} TO ${value.to}`;
|
|
392
|
+
if(value.ago) {
|
|
393
|
+
ret += ` AGO ${value.ago[0]} ${value.ago[1]}`;
|
|
394
|
+
}
|
|
395
|
+
return ret;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return String(value);
|
|
382
399
|
}
|
|
383
400
|
|
|
384
401
|
/**
|
|
@@ -401,8 +418,8 @@ class RuleTemplate {
|
|
|
401
418
|
}
|
|
402
419
|
}
|
|
403
420
|
|
|
404
|
-
|
|
421
|
+
RuleTemplate.ParserRules = ParserRules;
|
|
422
|
+
RuleTemplate.VariableTypes = VariableTypes;
|
|
423
|
+
RuleTemplate.TemplateFilters = TemplateFilters;
|
|
424
|
+
|
|
405
425
|
module.exports = RuleTemplate;
|
|
406
|
-
module.exports.ParserRules = ParserRules;
|
|
407
|
-
module.exports.VariableTypes = VariableTypes;
|
|
408
|
-
module.exports.TemplateFilters = TemplateFilters;
|
package/src/TemplateFilters.js
CHANGED
|
@@ -4,59 +4,153 @@ They are applied in the template syntax as ${variable|filter} or ${variable|filt
|
|
|
4
4
|
*/
|
|
5
5
|
const TemplateFilters = {
|
|
6
6
|
// Convert value to JSON string representation
|
|
7
|
-
string:
|
|
8
|
-
|
|
7
|
+
string: varData => {
|
|
8
|
+
varData.value = String(varData.value);
|
|
9
|
+
varData.type = 'string';
|
|
10
|
+
|
|
11
|
+
},
|
|
12
|
+
|
|
9
13
|
// Convert to uppercase
|
|
10
|
-
upper:
|
|
11
|
-
|
|
14
|
+
upper: varData => {
|
|
15
|
+
varData.value = String(varData.value).toUpperCase();
|
|
16
|
+
varData.type = 'string';
|
|
17
|
+
|
|
18
|
+
},
|
|
19
|
+
|
|
12
20
|
// Convert to lowercase
|
|
13
|
-
lower:
|
|
14
|
-
|
|
21
|
+
lower: varData => {
|
|
22
|
+
varData.value = String(varData.value).toLowerCase();
|
|
23
|
+
varData.type = 'string';
|
|
24
|
+
|
|
25
|
+
},
|
|
26
|
+
|
|
15
27
|
// Capitalize first letter
|
|
16
|
-
capitalize:
|
|
17
|
-
const str = String(value);
|
|
18
|
-
|
|
28
|
+
capitalize: varData => {
|
|
29
|
+
const str = String(varData.value);
|
|
30
|
+
varData.value = str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
31
|
+
varData.type = 'string';
|
|
32
|
+
|
|
19
33
|
},
|
|
20
|
-
|
|
34
|
+
|
|
21
35
|
// Convert to title case
|
|
22
|
-
title:
|
|
23
|
-
|
|
36
|
+
title: varData => {
|
|
37
|
+
varData.value = String(varData.value).split(' ')
|
|
24
38
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
25
39
|
.join(' ');
|
|
40
|
+
varData.type = 'string';
|
|
41
|
+
|
|
26
42
|
},
|
|
27
|
-
|
|
43
|
+
|
|
28
44
|
// Trim whitespace
|
|
29
|
-
trim:
|
|
30
|
-
|
|
45
|
+
trim: varData => {
|
|
46
|
+
varData.value = String(varData.value).trim();
|
|
47
|
+
varData.type = 'string';
|
|
48
|
+
|
|
49
|
+
},
|
|
50
|
+
|
|
31
51
|
// Convert to number
|
|
32
|
-
number:
|
|
33
|
-
|
|
52
|
+
number: varData => {
|
|
53
|
+
varData.value = Number(varData.value);
|
|
54
|
+
varData.type = 'number';
|
|
55
|
+
|
|
56
|
+
},
|
|
57
|
+
|
|
34
58
|
// Convert to boolean
|
|
35
|
-
boolean:
|
|
36
|
-
|
|
59
|
+
boolean: varData => {
|
|
60
|
+
const value = varData.value;
|
|
61
|
+
if (typeof value === 'boolean') {
|
|
62
|
+
varData.type = 'boolean';
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
37
66
|
if (typeof value === 'string') {
|
|
38
67
|
const lower = value.toLowerCase();
|
|
39
|
-
if (lower === 'true' || lower === '1' || lower === 'yes')
|
|
40
|
-
|
|
68
|
+
if (lower === 'true' || lower === '1' || lower === 'yes') {
|
|
69
|
+
varData.value = true;
|
|
70
|
+
varData.type = 'boolean';
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
if (lower === 'false' || lower === '0' || lower === 'no') {
|
|
74
|
+
varData.value = false;
|
|
75
|
+
varData.type = 'boolean';
|
|
76
|
+
|
|
77
|
+
}
|
|
41
78
|
}
|
|
42
|
-
|
|
79
|
+
|
|
80
|
+
varData.value = Boolean(value);
|
|
81
|
+
varData.type = 'boolean';
|
|
82
|
+
|
|
43
83
|
},
|
|
44
|
-
|
|
84
|
+
|
|
45
85
|
// Convert to absolute value (for numbers)
|
|
46
|
-
abs:
|
|
47
|
-
|
|
86
|
+
abs: varData => {
|
|
87
|
+
varData.value = Math.abs(Number(varData.value));
|
|
88
|
+
varData.type = 'number';
|
|
89
|
+
|
|
90
|
+
},
|
|
91
|
+
|
|
48
92
|
// Round number
|
|
49
|
-
round:
|
|
50
|
-
|
|
93
|
+
round: varData => {
|
|
94
|
+
varData.value = Math.round(Number(varData.value));
|
|
95
|
+
varData.type = 'number';
|
|
96
|
+
|
|
97
|
+
},
|
|
98
|
+
|
|
51
99
|
// Floor number
|
|
52
|
-
floor:
|
|
53
|
-
|
|
100
|
+
floor: varData => {
|
|
101
|
+
varData.value = Math.floor(Number(varData.value));
|
|
102
|
+
varData.type = 'number';
|
|
103
|
+
|
|
104
|
+
},
|
|
105
|
+
|
|
54
106
|
// Ceil number
|
|
55
|
-
ceil:
|
|
56
|
-
|
|
107
|
+
ceil: varData => {
|
|
108
|
+
varData.value = Math.ceil(Number(varData.value));
|
|
109
|
+
varData.type = 'number';
|
|
110
|
+
|
|
111
|
+
},
|
|
112
|
+
|
|
57
113
|
// Default value if empty/null/undefined
|
|
58
|
-
default: (
|
|
59
|
-
|
|
114
|
+
default: (varData, defaultValue = '') => {
|
|
115
|
+
varData.value = (varData.value === null || varData.value === undefined || varData.value === '') ? defaultValue : varData.value;
|
|
116
|
+
if (typeof varData.value === 'string') {
|
|
117
|
+
varData.type = 'string';
|
|
118
|
+
} else if (typeof varData.value === 'number') {
|
|
119
|
+
varData.type = 'number';
|
|
120
|
+
} else if (typeof varData.value === 'boolean') {
|
|
121
|
+
varData.type = 'boolean';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// Extract start time from time period/time period ago as time value
|
|
127
|
+
time_start: varData => {
|
|
128
|
+
if (varData.type === 'time period' || varData.type === 'time period ago') {
|
|
129
|
+
if (!varData.value || typeof varData.value !== 'object' || !Object.prototype.hasOwnProperty.call(varData.value, 'from')) {
|
|
130
|
+
throw new Error('time_start filter requires value.from for time period types');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
varData.value = varData.value.from;
|
|
134
|
+
varData.type = 'time value';
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
throw new Error('time_start filter requires variable type to be \"time period\" or \"time period ago\"');
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
// Extract end time from time period/time period ago as time value
|
|
142
|
+
time_end: varData => {
|
|
143
|
+
if (varData.type === 'time period' || varData.type === 'time period ago') {
|
|
144
|
+
if (!varData.value || typeof varData.value !== 'object' || !Object.prototype.hasOwnProperty.call(varData.value, 'to')) {
|
|
145
|
+
throw new Error('time_end filter requires value.from for time period types');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
varData.value = varData.value.to;
|
|
149
|
+
varData.type = 'time value';
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
throw new Error('time_end filter requires variable type to be \"time period\" or \"time period ago\"');
|
|
60
154
|
}
|
|
61
155
|
}
|
|
62
156
|
|