@aikotools/datafilter 1.0.4 → 1.1.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 +93 -7
- package/dist/aikotools-datafilter.cjs +937 -1
- package/dist/aikotools-datafilter.cjs.map +1 -0
- package/dist/aikotools-datafilter.mjs +677 -354
- package/dist/aikotools-datafilter.mjs.map +1 -0
- package/dist/src/core/types.d.ts +46 -0
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/matcher/Matcher.d.ts +10 -4
- package/dist/src/matcher/Matcher.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,83 +1,90 @@
|
|
|
1
|
-
import { DateTime
|
|
2
|
-
function
|
|
3
|
-
return "matchAny" in
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
function isWildcardRule(rule) {
|
|
3
|
+
return "matchAny" in rule;
|
|
4
4
|
}
|
|
5
|
-
function
|
|
6
|
-
return "match" in
|
|
5
|
+
function isSingleMatchRule(rule) {
|
|
6
|
+
return "match" in rule && "expected" in rule;
|
|
7
7
|
}
|
|
8
|
-
function
|
|
9
|
-
const
|
|
10
|
-
if (
|
|
8
|
+
function getValueFromPath(object, path) {
|
|
9
|
+
const validPath = [];
|
|
10
|
+
if (path.length === 0) {
|
|
11
11
|
return {
|
|
12
|
-
value:
|
|
13
|
-
found:
|
|
12
|
+
value: object,
|
|
13
|
+
found: true,
|
|
14
14
|
validPath: []
|
|
15
15
|
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
}
|
|
17
|
+
let current = object;
|
|
18
|
+
for (let i = 0; i < path.length; i++) {
|
|
19
|
+
const segment = path[i];
|
|
20
|
+
if (current === null || current === void 0) {
|
|
20
21
|
return {
|
|
21
22
|
value: void 0,
|
|
22
|
-
found:
|
|
23
|
-
error: `Cannot read property '${
|
|
24
|
-
validPath
|
|
23
|
+
found: false,
|
|
24
|
+
error: `Cannot read property '${segment}' of ${current}`,
|
|
25
|
+
validPath
|
|
25
26
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
}
|
|
28
|
+
if (Array.isArray(current)) {
|
|
29
|
+
const index = typeof segment === "number" ? segment : parseInt(String(segment), 10);
|
|
30
|
+
if (isNaN(index)) {
|
|
29
31
|
return {
|
|
30
32
|
value: void 0,
|
|
31
|
-
found:
|
|
32
|
-
error: `Array index must be a number, got '${
|
|
33
|
-
validPath
|
|
33
|
+
found: false,
|
|
34
|
+
error: `Array index must be a number, got '${segment}'`,
|
|
35
|
+
validPath
|
|
34
36
|
};
|
|
35
|
-
|
|
37
|
+
}
|
|
38
|
+
if (index < 0 || index >= current.length) {
|
|
36
39
|
return {
|
|
37
40
|
value: void 0,
|
|
38
|
-
found:
|
|
39
|
-
error: `Array index ${
|
|
40
|
-
validPath
|
|
41
|
+
found: false,
|
|
42
|
+
error: `Array index ${index} out of bounds (length: ${current.length})`,
|
|
43
|
+
validPath
|
|
41
44
|
};
|
|
42
|
-
|
|
45
|
+
}
|
|
46
|
+
validPath.push(index);
|
|
47
|
+
current = current[index];
|
|
43
48
|
continue;
|
|
44
49
|
}
|
|
45
|
-
if (typeof
|
|
46
|
-
const
|
|
47
|
-
if (!(
|
|
50
|
+
if (typeof current === "object") {
|
|
51
|
+
const key = String(segment);
|
|
52
|
+
if (!(key in current)) {
|
|
48
53
|
return {
|
|
49
54
|
value: void 0,
|
|
50
|
-
found:
|
|
51
|
-
error: `Property '${
|
|
52
|
-
validPath
|
|
55
|
+
found: false,
|
|
56
|
+
error: `Property '${key}' does not exist`,
|
|
57
|
+
validPath
|
|
53
58
|
};
|
|
54
|
-
|
|
59
|
+
}
|
|
60
|
+
validPath.push(key);
|
|
61
|
+
current = current[key];
|
|
55
62
|
continue;
|
|
56
63
|
}
|
|
57
64
|
return {
|
|
58
65
|
value: void 0,
|
|
59
|
-
found:
|
|
60
|
-
error: `Cannot access property '${
|
|
61
|
-
validPath
|
|
66
|
+
found: false,
|
|
67
|
+
error: `Cannot access property '${segment}' of primitive type ${typeof current}`,
|
|
68
|
+
validPath
|
|
62
69
|
};
|
|
63
70
|
}
|
|
64
71
|
return {
|
|
65
|
-
value:
|
|
66
|
-
found:
|
|
67
|
-
validPath
|
|
72
|
+
value: current,
|
|
73
|
+
found: true,
|
|
74
|
+
validPath
|
|
68
75
|
};
|
|
69
76
|
}
|
|
70
|
-
function
|
|
71
|
-
const
|
|
72
|
-
return
|
|
77
|
+
function pathExists(object, path) {
|
|
78
|
+
const result = getValueFromPath(object, path);
|
|
79
|
+
return result.found && result.value !== void 0;
|
|
73
80
|
}
|
|
74
|
-
function
|
|
75
|
-
const
|
|
76
|
-
return
|
|
81
|
+
function getValueOr(object, path, defaultValue) {
|
|
82
|
+
const result = getValueFromPath(object, path);
|
|
83
|
+
return result.found && result.value !== void 0 ? result.value : defaultValue;
|
|
77
84
|
}
|
|
78
|
-
class
|
|
79
|
-
constructor(
|
|
80
|
-
this.context =
|
|
85
|
+
class FilterEngine {
|
|
86
|
+
constructor(context) {
|
|
87
|
+
this.context = context;
|
|
81
88
|
}
|
|
82
89
|
/**
|
|
83
90
|
* Evaluates a single filter criterion against a data object.
|
|
@@ -87,12 +94,31 @@ class S {
|
|
|
87
94
|
* @returns FilterCheckResult indicating success or failure
|
|
88
95
|
*/
|
|
89
96
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
-
evaluateCriterion(
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
evaluateCriterion(data, criterion) {
|
|
98
|
+
const check = criterion.check;
|
|
99
|
+
if ("value" in check) {
|
|
100
|
+
return this.checkValue(data, criterion.path, check);
|
|
101
|
+
}
|
|
102
|
+
if ("exists" in check) {
|
|
103
|
+
return this.checkExists(data, criterion.path, check);
|
|
104
|
+
}
|
|
105
|
+
if ("itemExists" in check && "item" in check) {
|
|
106
|
+
return this.checkArrayElement(data, criterion.path, check);
|
|
107
|
+
}
|
|
108
|
+
if ("type" in check && "size" in check) {
|
|
109
|
+
return this.checkArraySize(data, criterion.path, check);
|
|
110
|
+
}
|
|
111
|
+
if ("min" in check && "max" in check) {
|
|
112
|
+
if (typeof check.min === "number" && typeof check.max === "number") {
|
|
113
|
+
return this.checkNumericRange(data, criterion.path, check);
|
|
114
|
+
} else {
|
|
115
|
+
return this.checkTimeRange(data, criterion.path, check);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
status: false,
|
|
94
120
|
checkType: "unknown",
|
|
95
|
-
reason: `Unknown check type: ${JSON.stringify(
|
|
121
|
+
reason: `Unknown check type: ${JSON.stringify(check)}`
|
|
96
122
|
};
|
|
97
123
|
}
|
|
98
124
|
/**
|
|
@@ -103,29 +129,34 @@ class S {
|
|
|
103
129
|
* @param check - The value check specification
|
|
104
130
|
* @returns FilterCheckResult
|
|
105
131
|
*/
|
|
106
|
-
checkValue(
|
|
107
|
-
const
|
|
108
|
-
if (!
|
|
132
|
+
checkValue(data, path, check) {
|
|
133
|
+
const accessResult = getValueFromPath(data, path);
|
|
134
|
+
if (!accessResult.found) {
|
|
109
135
|
return {
|
|
110
|
-
status:
|
|
136
|
+
status: false,
|
|
111
137
|
checkType: "checkValue",
|
|
112
138
|
reason: {
|
|
113
|
-
message:
|
|
114
|
-
path
|
|
139
|
+
message: accessResult.error || "Path not found",
|
|
140
|
+
path
|
|
115
141
|
}
|
|
116
142
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
143
|
+
}
|
|
144
|
+
const actual = accessResult.value;
|
|
145
|
+
const expected = check.value;
|
|
146
|
+
if (this.deepEqual(actual, expected)) {
|
|
147
|
+
return {
|
|
148
|
+
status: true,
|
|
149
|
+
checkType: "checkValue"
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
status: false,
|
|
123
154
|
checkType: "checkValue",
|
|
124
155
|
reason: {
|
|
125
156
|
message: "Value mismatch",
|
|
126
|
-
path
|
|
127
|
-
expected
|
|
128
|
-
actual
|
|
157
|
+
path,
|
|
158
|
+
expected,
|
|
159
|
+
actual
|
|
129
160
|
}
|
|
130
161
|
};
|
|
131
162
|
}
|
|
@@ -137,17 +168,21 @@ class S {
|
|
|
137
168
|
* @param check - The exists check specification
|
|
138
169
|
* @returns FilterCheckResult
|
|
139
170
|
*/
|
|
140
|
-
checkExists(
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
171
|
+
checkExists(data, path, check) {
|
|
172
|
+
const accessResult = getValueFromPath(data, path);
|
|
173
|
+
const exists = accessResult.found && accessResult.value !== void 0;
|
|
174
|
+
if (check.exists === exists) {
|
|
175
|
+
return {
|
|
176
|
+
status: true,
|
|
177
|
+
checkType: "checkExists"
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
status: false,
|
|
147
182
|
checkType: "checkExists",
|
|
148
183
|
reason: {
|
|
149
|
-
message:
|
|
150
|
-
path
|
|
184
|
+
message: check.exists ? `Path should exist but doesn't: ${accessResult.error}` : "Path should not exist but does",
|
|
185
|
+
path
|
|
151
186
|
}
|
|
152
187
|
};
|
|
153
188
|
}
|
|
@@ -159,39 +194,44 @@ class S {
|
|
|
159
194
|
* @param check - The array element check specification
|
|
160
195
|
* @returns FilterCheckResult
|
|
161
196
|
*/
|
|
162
|
-
checkArrayElement(
|
|
163
|
-
const
|
|
164
|
-
if (!
|
|
197
|
+
checkArrayElement(data, path, check) {
|
|
198
|
+
const accessResult = getValueFromPath(data, path);
|
|
199
|
+
if (!accessResult.found) {
|
|
165
200
|
return {
|
|
166
|
-
status:
|
|
201
|
+
status: false,
|
|
167
202
|
checkType: "checkArrayElement",
|
|
168
203
|
reason: {
|
|
169
|
-
message:
|
|
170
|
-
path
|
|
204
|
+
message: accessResult.error || "Path not found",
|
|
205
|
+
path
|
|
171
206
|
}
|
|
172
207
|
};
|
|
173
|
-
|
|
174
|
-
|
|
208
|
+
}
|
|
209
|
+
const array = accessResult.value;
|
|
210
|
+
if (!Array.isArray(array)) {
|
|
175
211
|
return {
|
|
176
|
-
status:
|
|
212
|
+
status: false,
|
|
177
213
|
checkType: "checkArrayElement",
|
|
178
214
|
reason: {
|
|
179
215
|
message: "Value is not an array",
|
|
180
|
-
path
|
|
181
|
-
actualType: typeof
|
|
216
|
+
path,
|
|
217
|
+
actualType: typeof array
|
|
182
218
|
}
|
|
183
219
|
};
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
220
|
+
}
|
|
221
|
+
const found = array.some((elem) => this.deepEqual(elem, check.item));
|
|
222
|
+
if (check.itemExists === found) {
|
|
223
|
+
return {
|
|
224
|
+
status: true,
|
|
225
|
+
checkType: "checkArrayElement"
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
status: false,
|
|
190
230
|
checkType: "checkArrayElement",
|
|
191
231
|
reason: {
|
|
192
|
-
message:
|
|
193
|
-
path
|
|
194
|
-
item:
|
|
232
|
+
message: check.itemExists ? "Item should exist in array but doesn't" : "Item should not exist in array but does",
|
|
233
|
+
path,
|
|
234
|
+
item: check.item
|
|
195
235
|
}
|
|
196
236
|
};
|
|
197
237
|
}
|
|
@@ -203,52 +243,62 @@ class S {
|
|
|
203
243
|
* @param check - The array size check specification
|
|
204
244
|
* @returns FilterCheckResult
|
|
205
245
|
*/
|
|
206
|
-
checkArraySize(
|
|
207
|
-
const
|
|
208
|
-
if (!
|
|
246
|
+
checkArraySize(data, path, check) {
|
|
247
|
+
const accessResult = getValueFromPath(data, path);
|
|
248
|
+
if (!accessResult.found) {
|
|
209
249
|
return {
|
|
210
|
-
status:
|
|
250
|
+
status: false,
|
|
211
251
|
checkType: "checkArraySize",
|
|
212
252
|
reason: {
|
|
213
|
-
message:
|
|
214
|
-
path
|
|
253
|
+
message: accessResult.error || "Path not found",
|
|
254
|
+
path
|
|
215
255
|
}
|
|
216
256
|
};
|
|
217
|
-
|
|
218
|
-
|
|
257
|
+
}
|
|
258
|
+
const array = accessResult.value;
|
|
259
|
+
if (!Array.isArray(array)) {
|
|
219
260
|
return {
|
|
220
|
-
status:
|
|
261
|
+
status: false,
|
|
221
262
|
checkType: "checkArraySize",
|
|
222
263
|
reason: {
|
|
223
264
|
message: "Value is not an array",
|
|
224
|
-
path
|
|
225
|
-
actualType: typeof
|
|
265
|
+
path,
|
|
266
|
+
actualType: typeof array
|
|
226
267
|
}
|
|
227
268
|
};
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
269
|
+
}
|
|
270
|
+
const actualSize = array.length;
|
|
271
|
+
const expectedSize = check.size;
|
|
272
|
+
let passes = false;
|
|
273
|
+
let message = "";
|
|
274
|
+
switch (check.type) {
|
|
231
275
|
case "equal":
|
|
232
|
-
|
|
276
|
+
passes = actualSize === expectedSize;
|
|
277
|
+
message = `Array length should be ${expectedSize} but is ${actualSize}`;
|
|
233
278
|
break;
|
|
234
279
|
case "lessThan":
|
|
235
|
-
|
|
280
|
+
passes = actualSize < expectedSize;
|
|
281
|
+
message = `Array length should be less than ${expectedSize} but is ${actualSize}`;
|
|
236
282
|
break;
|
|
237
283
|
case "greaterThan":
|
|
238
|
-
|
|
284
|
+
passes = actualSize > expectedSize;
|
|
285
|
+
message = `Array length should be greater than ${expectedSize} but is ${actualSize}`;
|
|
239
286
|
break;
|
|
240
287
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
288
|
+
if (passes) {
|
|
289
|
+
return {
|
|
290
|
+
status: true,
|
|
291
|
+
checkType: "checkArraySize"
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
status: false,
|
|
246
296
|
checkType: "checkArraySize",
|
|
247
297
|
reason: {
|
|
248
|
-
message
|
|
249
|
-
path
|
|
250
|
-
expected:
|
|
251
|
-
actual:
|
|
298
|
+
message,
|
|
299
|
+
path,
|
|
300
|
+
expected: expectedSize,
|
|
301
|
+
actual: actualSize
|
|
252
302
|
}
|
|
253
303
|
};
|
|
254
304
|
}
|
|
@@ -260,81 +310,100 @@ class S {
|
|
|
260
310
|
* @param check - The time range check specification
|
|
261
311
|
* @returns FilterCheckResult
|
|
262
312
|
*/
|
|
263
|
-
checkTimeRange(
|
|
264
|
-
const
|
|
265
|
-
if (!
|
|
313
|
+
checkTimeRange(data, path, check) {
|
|
314
|
+
const accessResult = getValueFromPath(data, path);
|
|
315
|
+
if (!accessResult.found) {
|
|
266
316
|
return {
|
|
267
|
-
status:
|
|
317
|
+
status: false,
|
|
268
318
|
checkType: "checkTimeRange",
|
|
269
319
|
reason: {
|
|
270
|
-
message:
|
|
271
|
-
path
|
|
320
|
+
message: accessResult.error || "Path not found",
|
|
321
|
+
path
|
|
272
322
|
}
|
|
273
323
|
};
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
324
|
+
}
|
|
325
|
+
const value = accessResult.value;
|
|
326
|
+
if (typeof value === "number") {
|
|
327
|
+
const min = parseInt(check.min);
|
|
328
|
+
const max = parseInt(check.max);
|
|
329
|
+
if (isNaN(min) || isNaN(max)) {
|
|
330
|
+
return {
|
|
331
|
+
status: false,
|
|
332
|
+
checkType: "checkTimeRange",
|
|
333
|
+
reason: {
|
|
334
|
+
message: "Min or max is not a valid number",
|
|
335
|
+
min: check.min,
|
|
336
|
+
max: check.max
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (value >= min && value <= max) {
|
|
341
|
+
return {
|
|
342
|
+
status: true,
|
|
343
|
+
checkType: "checkTimeRange"
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
status: false,
|
|
290
348
|
checkType: "checkTimeRange",
|
|
291
349
|
reason: {
|
|
292
|
-
message: `Timestamp ${
|
|
293
|
-
path
|
|
294
|
-
actual:
|
|
295
|
-
min
|
|
296
|
-
max
|
|
350
|
+
message: `Timestamp ${value} is outside range [${min}, ${max}]`,
|
|
351
|
+
path,
|
|
352
|
+
actual: value,
|
|
353
|
+
min,
|
|
354
|
+
max
|
|
297
355
|
}
|
|
298
356
|
};
|
|
299
357
|
}
|
|
300
|
-
if (typeof
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
358
|
+
if (typeof value === "string") {
|
|
359
|
+
const timestamp = DateTime.fromISO(value);
|
|
360
|
+
const minTime = DateTime.fromISO(check.min);
|
|
361
|
+
const maxTime = DateTime.fromISO(check.max);
|
|
362
|
+
if (!timestamp.isValid) {
|
|
363
|
+
return {
|
|
364
|
+
status: false,
|
|
365
|
+
checkType: "checkTimeRange",
|
|
366
|
+
reason: {
|
|
367
|
+
message: `Invalid timestamp: ${value}`,
|
|
368
|
+
path
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (!minTime.isValid || !maxTime.isValid) {
|
|
373
|
+
return {
|
|
374
|
+
status: false,
|
|
375
|
+
checkType: "checkTimeRange",
|
|
376
|
+
reason: {
|
|
377
|
+
message: "Invalid min or max time",
|
|
378
|
+
min: check.min,
|
|
379
|
+
max: check.max
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
if (timestamp >= minTime && timestamp <= maxTime) {
|
|
384
|
+
return {
|
|
385
|
+
status: true,
|
|
386
|
+
checkType: "checkTimeRange"
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
status: false,
|
|
325
391
|
checkType: "checkTimeRange",
|
|
326
392
|
reason: {
|
|
327
|
-
message: `
|
|
328
|
-
path
|
|
393
|
+
message: `Timestamp ${value} is outside range [${check.min}, ${check.max}]`,
|
|
394
|
+
path,
|
|
395
|
+
actual: value,
|
|
396
|
+
min: check.min,
|
|
397
|
+
max: check.max
|
|
329
398
|
}
|
|
330
399
|
};
|
|
331
400
|
}
|
|
332
401
|
return {
|
|
333
|
-
status:
|
|
402
|
+
status: false,
|
|
334
403
|
checkType: "checkTimeRange",
|
|
335
404
|
reason: {
|
|
336
|
-
message: `Timestamp must be a string or number, got ${typeof
|
|
337
|
-
path
|
|
405
|
+
message: `Timestamp must be a string or number, got ${typeof value}`,
|
|
406
|
+
path
|
|
338
407
|
}
|
|
339
408
|
};
|
|
340
409
|
}
|
|
@@ -346,38 +415,45 @@ class S {
|
|
|
346
415
|
* @param check - The numeric range check specification
|
|
347
416
|
* @returns FilterCheckResult
|
|
348
417
|
*/
|
|
349
|
-
checkNumericRange(
|
|
350
|
-
const
|
|
351
|
-
if (!
|
|
418
|
+
checkNumericRange(data, path, check) {
|
|
419
|
+
const accessResult = getValueFromPath(data, path);
|
|
420
|
+
if (!accessResult.found) {
|
|
352
421
|
return {
|
|
353
|
-
status:
|
|
422
|
+
status: false,
|
|
354
423
|
checkType: "checkNumericRange",
|
|
355
424
|
reason: {
|
|
356
|
-
message:
|
|
357
|
-
path
|
|
425
|
+
message: accessResult.error || "Path not found",
|
|
426
|
+
path
|
|
358
427
|
}
|
|
359
428
|
};
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
|
|
429
|
+
}
|
|
430
|
+
const value = accessResult.value;
|
|
431
|
+
if (typeof value !== "number") {
|
|
432
|
+
return {
|
|
433
|
+
status: false,
|
|
434
|
+
checkType: "checkNumericRange",
|
|
435
|
+
reason: {
|
|
436
|
+
message: `Value must be a number, got ${typeof value}`,
|
|
437
|
+
path,
|
|
438
|
+
actual: value
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
if (value >= check.min && value <= check.max) {
|
|
443
|
+
return {
|
|
444
|
+
status: true,
|
|
445
|
+
checkType: "checkNumericRange"
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
status: false,
|
|
374
450
|
checkType: "checkNumericRange",
|
|
375
451
|
reason: {
|
|
376
|
-
message: `Value ${
|
|
377
|
-
path
|
|
378
|
-
actual:
|
|
379
|
-
min:
|
|
380
|
-
max:
|
|
452
|
+
message: `Value ${value} is outside range [${check.min}, ${check.max}]`,
|
|
453
|
+
path,
|
|
454
|
+
actual: value,
|
|
455
|
+
min: check.min,
|
|
456
|
+
max: check.max
|
|
381
457
|
}
|
|
382
458
|
};
|
|
383
459
|
}
|
|
@@ -386,24 +462,30 @@ class S {
|
|
|
386
462
|
* Compares two values recursively for equality.
|
|
387
463
|
*/
|
|
388
464
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
389
|
-
deepEqual(
|
|
390
|
-
if (
|
|
391
|
-
if (
|
|
392
|
-
if (
|
|
393
|
-
if (
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
|
|
399
|
-
return
|
|
400
|
-
}
|
|
401
|
-
|
|
465
|
+
deepEqual(a, b) {
|
|
466
|
+
if (a === b) return true;
|
|
467
|
+
if (a === null || b === null) return a === b;
|
|
468
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
469
|
+
if (typeof a !== typeof b) return false;
|
|
470
|
+
if (a instanceof Date && b instanceof Date) {
|
|
471
|
+
return a.getTime() === b.getTime();
|
|
472
|
+
}
|
|
473
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
474
|
+
if (a.length !== b.length) return false;
|
|
475
|
+
return a.every((val, idx) => this.deepEqual(val, b[idx]));
|
|
476
|
+
}
|
|
477
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
478
|
+
const keysA = Object.keys(a);
|
|
479
|
+
const keysB = Object.keys(b);
|
|
480
|
+
if (keysA.length !== keysB.length) return false;
|
|
481
|
+
return keysA.every((key) => this.deepEqual(a[key], b[key]));
|
|
482
|
+
}
|
|
483
|
+
return a === b;
|
|
402
484
|
}
|
|
403
485
|
}
|
|
404
|
-
class
|
|
405
|
-
constructor(
|
|
406
|
-
this.engine = new
|
|
486
|
+
class Matcher {
|
|
487
|
+
constructor(context) {
|
|
488
|
+
this.engine = new FilterEngine(context);
|
|
407
489
|
}
|
|
408
490
|
/**
|
|
409
491
|
* Matches a single file against a rule.
|
|
@@ -412,12 +494,16 @@ class M {
|
|
|
412
494
|
* @param rule - The rule to match against
|
|
413
495
|
* @returns MatchResult indicating if all criteria matched
|
|
414
496
|
*/
|
|
415
|
-
matchFile(
|
|
416
|
-
const
|
|
497
|
+
matchFile(file, rule) {
|
|
498
|
+
const criteria = isWildcardRule(rule) ? rule.matchAny : rule.match;
|
|
499
|
+
const checks = criteria.map((criterion) => {
|
|
500
|
+
return this.engine.evaluateCriterion(file.data, criterion);
|
|
501
|
+
});
|
|
502
|
+
const matched = checks.every((check) => check.status);
|
|
417
503
|
return {
|
|
418
|
-
matched
|
|
419
|
-
checks
|
|
420
|
-
rule
|
|
504
|
+
matched,
|
|
505
|
+
checks,
|
|
506
|
+
rule
|
|
421
507
|
};
|
|
422
508
|
}
|
|
423
509
|
/**
|
|
@@ -427,16 +513,23 @@ class M {
|
|
|
427
513
|
* @param preFilter - Filter criteria that all files must match
|
|
428
514
|
* @returns Object with matched files and excluded files (with failed checks)
|
|
429
515
|
*/
|
|
430
|
-
applyPreFilter(
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
file
|
|
436
|
-
failedChecks: a.filter((u) => !u.status)
|
|
516
|
+
applyPreFilter(files, preFilter) {
|
|
517
|
+
const matched = [];
|
|
518
|
+
const excluded = [];
|
|
519
|
+
for (const file of files) {
|
|
520
|
+
const checks = preFilter.map((criterion) => {
|
|
521
|
+
return this.engine.evaluateCriterion(file.data, criterion);
|
|
437
522
|
});
|
|
523
|
+
if (checks.every((check) => check.status)) {
|
|
524
|
+
matched.push(file);
|
|
525
|
+
} else {
|
|
526
|
+
excluded.push({
|
|
527
|
+
file,
|
|
528
|
+
failedChecks: checks.filter((check) => !check.status)
|
|
529
|
+
});
|
|
530
|
+
}
|
|
438
531
|
}
|
|
439
|
-
return { matched
|
|
532
|
+
return { matched, excluded };
|
|
440
533
|
}
|
|
441
534
|
/**
|
|
442
535
|
* Main filtering function that processes files according to rules.
|
|
@@ -445,100 +538,292 @@ class M {
|
|
|
445
538
|
* @param rules - Matching rules (can include arrays for flexible ordering)
|
|
446
539
|
* @param sortFn - Optional sort function for file ordering
|
|
447
540
|
* @param preFilter - Optional pre-filter criteria (files not matching are excluded)
|
|
448
|
-
* @
|
|
541
|
+
* @param mode - Matching mode ('strict', 'optional', or 'strict-optional')
|
|
542
|
+
* @returns FilterResult with mapped, wildcardMatched, optionalFiles and unmapped files
|
|
449
543
|
*/
|
|
450
|
-
filterFiles(
|
|
451
|
-
let
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
544
|
+
filterFiles(files, rules, sortFn, preFilter, mode = "strict") {
|
|
545
|
+
let filteredFiles;
|
|
546
|
+
let preFiltered = [];
|
|
547
|
+
if (preFilter) {
|
|
548
|
+
const preFilterResult = this.applyPreFilter(files, preFilter);
|
|
549
|
+
filteredFiles = preFilterResult.matched;
|
|
550
|
+
preFiltered = preFilterResult.excluded;
|
|
551
|
+
} else {
|
|
552
|
+
filteredFiles = files;
|
|
553
|
+
}
|
|
554
|
+
const sortedFiles = sortFn ? [...filteredFiles].sort(sortFn) : [...filteredFiles];
|
|
555
|
+
const mapped = [];
|
|
556
|
+
const wildcardMatched = [];
|
|
557
|
+
const unmapped = [];
|
|
558
|
+
const optionalFiles = [];
|
|
559
|
+
const matchedFileIndices = /* @__PURE__ */ new Set();
|
|
560
|
+
if (mode === "optional" || mode === "strict-optional") {
|
|
561
|
+
return this.filterFilesOptionalMode(sortedFiles, rules, preFiltered, files.length, mode);
|
|
562
|
+
}
|
|
563
|
+
const usedFlexibleRules = /* @__PURE__ */ new Map();
|
|
564
|
+
let fileIndex = 0;
|
|
565
|
+
let ruleIndex = 0;
|
|
566
|
+
while (fileIndex < sortedFiles.length) {
|
|
567
|
+
const file = sortedFiles[fileIndex];
|
|
568
|
+
if (ruleIndex >= rules.length) {
|
|
569
|
+
if (!matchedFileIndices.has(fileIndex)) {
|
|
570
|
+
unmapped.push({
|
|
571
|
+
file,
|
|
572
|
+
attemptedRules: []
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
fileIndex++;
|
|
466
576
|
continue;
|
|
467
577
|
}
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
578
|
+
const ruleOrRules = rules[ruleIndex];
|
|
579
|
+
const attemptedMatches = [];
|
|
580
|
+
let fileMatched = false;
|
|
581
|
+
if (Array.isArray(ruleOrRules)) {
|
|
582
|
+
for (let subIndex = 0; subIndex < ruleOrRules.length; subIndex++) {
|
|
583
|
+
const used = usedFlexibleRules.get(ruleIndex)?.has(subIndex);
|
|
584
|
+
if (used) {
|
|
473
585
|
continue;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
586
|
+
}
|
|
587
|
+
const rule = ruleOrRules[subIndex];
|
|
588
|
+
const matchResult = this.matchFile(file, rule);
|
|
589
|
+
attemptedMatches.push(matchResult);
|
|
590
|
+
if (matchResult.matched && isSingleMatchRule(rule)) {
|
|
591
|
+
if (!usedFlexibleRules.has(ruleIndex)) {
|
|
592
|
+
usedFlexibleRules.set(ruleIndex, /* @__PURE__ */ new Set());
|
|
593
|
+
}
|
|
594
|
+
const usedSet = usedFlexibleRules.get(ruleIndex);
|
|
595
|
+
if (usedSet) {
|
|
596
|
+
usedSet.add(subIndex);
|
|
597
|
+
}
|
|
598
|
+
mapped.push({
|
|
599
|
+
expected: rule.expected,
|
|
600
|
+
file,
|
|
601
|
+
matchResult,
|
|
602
|
+
optional: rule.optional || false,
|
|
603
|
+
info: rule.info
|
|
604
|
+
});
|
|
605
|
+
matchedFileIndices.add(fileIndex);
|
|
606
|
+
fileMatched = true;
|
|
485
607
|
break;
|
|
486
608
|
}
|
|
487
609
|
}
|
|
488
|
-
|
|
610
|
+
const allUsed = usedFlexibleRules.get(ruleIndex)?.size === ruleOrRules.length;
|
|
611
|
+
if (allUsed) {
|
|
612
|
+
ruleIndex++;
|
|
613
|
+
}
|
|
614
|
+
if (fileMatched) {
|
|
615
|
+
fileIndex++;
|
|
616
|
+
} else {
|
|
617
|
+
fileIndex++;
|
|
618
|
+
}
|
|
489
619
|
} else {
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
620
|
+
const rule = ruleOrRules;
|
|
621
|
+
const matchResult = this.matchFile(file, rule);
|
|
622
|
+
attemptedMatches.push(matchResult);
|
|
623
|
+
if (matchResult.matched) {
|
|
624
|
+
if (isSingleMatchRule(rule)) {
|
|
625
|
+
mapped.push({
|
|
626
|
+
expected: rule.expected,
|
|
627
|
+
file,
|
|
628
|
+
matchResult,
|
|
629
|
+
optional: rule.optional || false,
|
|
630
|
+
info: rule.info
|
|
631
|
+
});
|
|
632
|
+
matchedFileIndices.add(fileIndex);
|
|
633
|
+
ruleIndex++;
|
|
634
|
+
fileIndex++;
|
|
635
|
+
} else if (isWildcardRule(rule)) {
|
|
636
|
+
wildcardMatched.push({
|
|
637
|
+
file,
|
|
638
|
+
matchResult,
|
|
639
|
+
info: rule.info
|
|
640
|
+
});
|
|
641
|
+
matchedFileIndices.add(fileIndex);
|
|
642
|
+
if (rule.greedy) {
|
|
643
|
+
fileIndex++;
|
|
644
|
+
} else {
|
|
645
|
+
ruleIndex++;
|
|
646
|
+
fileIndex++;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
if (rule.optional) {
|
|
651
|
+
ruleIndex++;
|
|
652
|
+
} else if (isWildcardRule(rule)) {
|
|
653
|
+
ruleIndex++;
|
|
654
|
+
} else {
|
|
655
|
+
unmapped.push({
|
|
656
|
+
file,
|
|
657
|
+
attemptedRules: attemptedMatches
|
|
658
|
+
});
|
|
659
|
+
fileIndex++;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
505
662
|
}
|
|
506
663
|
}
|
|
507
|
-
const
|
|
508
|
-
totalFiles:
|
|
509
|
-
mappedFiles:
|
|
510
|
-
wildcardMatchedFiles:
|
|
511
|
-
unmappedFiles:
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
664
|
+
const stats = {
|
|
665
|
+
totalFiles: files.length,
|
|
666
|
+
mappedFiles: mapped.length,
|
|
667
|
+
wildcardMatchedFiles: wildcardMatched.length,
|
|
668
|
+
unmappedFiles: unmapped.length,
|
|
669
|
+
optionalFiles: optionalFiles.length,
|
|
670
|
+
preFilteredFiles: preFiltered.length,
|
|
671
|
+
totalRules: this.countRules(rules),
|
|
672
|
+
mandatoryRules: this.countMandatoryRules(rules),
|
|
673
|
+
optionalRules: this.countOptionalRules(rules)
|
|
516
674
|
};
|
|
517
675
|
return {
|
|
518
|
-
mapped
|
|
519
|
-
wildcardMatched
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
676
|
+
mapped,
|
|
677
|
+
wildcardMatched,
|
|
678
|
+
optionalFiles,
|
|
679
|
+
unmapped,
|
|
680
|
+
preFiltered,
|
|
681
|
+
stats
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Filtering with optional mode - uses scan-forward algorithm
|
|
686
|
+
*/
|
|
687
|
+
filterFilesOptionalMode(sortedFiles, rules, preFiltered, totalFiles, mode) {
|
|
688
|
+
const mapped = [];
|
|
689
|
+
const wildcardMatched = [];
|
|
690
|
+
const optionalFiles = [];
|
|
691
|
+
let currentFileIndex = 0;
|
|
692
|
+
let lastMatchedIndex = -1;
|
|
693
|
+
let lastMatchedRule = null;
|
|
694
|
+
for (let ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) {
|
|
695
|
+
const ruleOrRules = rules[ruleIndex];
|
|
696
|
+
let found = false;
|
|
697
|
+
for (let fileIndex = currentFileIndex; fileIndex < sortedFiles.length; fileIndex++) {
|
|
698
|
+
const file = sortedFiles[fileIndex];
|
|
699
|
+
let matchResult = null;
|
|
700
|
+
let matchedRule = null;
|
|
701
|
+
if (Array.isArray(ruleOrRules)) {
|
|
702
|
+
for (const rule of ruleOrRules) {
|
|
703
|
+
const result = this.matchFile(file, rule);
|
|
704
|
+
if (result.matched) {
|
|
705
|
+
matchResult = result;
|
|
706
|
+
matchedRule = rule;
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
} else {
|
|
711
|
+
matchResult = this.matchFile(file, ruleOrRules);
|
|
712
|
+
if (matchResult.matched) {
|
|
713
|
+
matchedRule = ruleOrRules;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (matchResult && matchResult.matched && matchedRule) {
|
|
717
|
+
for (let i = lastMatchedIndex + 1; i < fileIndex; i++) {
|
|
718
|
+
const optionalFile = sortedFiles[i];
|
|
719
|
+
optionalFiles.push({
|
|
720
|
+
fileName: optionalFile.fileName,
|
|
721
|
+
position: i,
|
|
722
|
+
between: {
|
|
723
|
+
afterRule: lastMatchedRule || "(start)",
|
|
724
|
+
beforeRule: isSingleMatchRule(matchedRule) ? matchedRule.expected : "(wildcard)"
|
|
725
|
+
},
|
|
726
|
+
failedMatches: []
|
|
727
|
+
// TODO: collect actual failed matches
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
if (isSingleMatchRule(matchedRule)) {
|
|
731
|
+
mapped.push({
|
|
732
|
+
expected: matchedRule.expected,
|
|
733
|
+
file,
|
|
734
|
+
matchResult,
|
|
735
|
+
optional: matchedRule.optional || false,
|
|
736
|
+
info: matchedRule.info
|
|
737
|
+
});
|
|
738
|
+
lastMatchedRule = matchedRule.expected;
|
|
739
|
+
} else if (isWildcardRule(matchedRule)) {
|
|
740
|
+
wildcardMatched.push({
|
|
741
|
+
file,
|
|
742
|
+
matchResult,
|
|
743
|
+
info: matchedRule.info
|
|
744
|
+
});
|
|
745
|
+
lastMatchedRule = "(wildcard)";
|
|
746
|
+
}
|
|
747
|
+
lastMatchedIndex = fileIndex;
|
|
748
|
+
currentFileIndex = fileIndex + 1;
|
|
749
|
+
found = true;
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (!found) {
|
|
754
|
+
const isMandatory = Array.isArray(ruleOrRules) ? ruleOrRules.some((r) => !r.optional && !isWildcardRule(r)) : !ruleOrRules.optional && !isWildcardRule(ruleOrRules);
|
|
755
|
+
if (isMandatory && mode === "strict-optional") {
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
for (let i = lastMatchedIndex + 1; i < sortedFiles.length; i++) {
|
|
761
|
+
const optionalFile = sortedFiles[i];
|
|
762
|
+
optionalFiles.push({
|
|
763
|
+
fileName: optionalFile.fileName,
|
|
764
|
+
position: i,
|
|
765
|
+
between: {
|
|
766
|
+
afterRule: lastMatchedRule || "(start)",
|
|
767
|
+
beforeRule: "(end)"
|
|
768
|
+
},
|
|
769
|
+
failedMatches: []
|
|
770
|
+
// TODO: collect actual failed matches
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
const stats = {
|
|
774
|
+
totalFiles,
|
|
775
|
+
mappedFiles: mapped.length,
|
|
776
|
+
wildcardMatchedFiles: wildcardMatched.length,
|
|
777
|
+
unmappedFiles: 0,
|
|
778
|
+
// Always 0 in optional modes
|
|
779
|
+
optionalFiles: optionalFiles.length,
|
|
780
|
+
preFilteredFiles: preFiltered.length,
|
|
781
|
+
totalRules: this.countRules(rules),
|
|
782
|
+
mandatoryRules: this.countMandatoryRules(rules),
|
|
783
|
+
optionalRules: this.countOptionalRules(rules)
|
|
784
|
+
};
|
|
785
|
+
return {
|
|
786
|
+
mapped,
|
|
787
|
+
wildcardMatched,
|
|
788
|
+
optionalFiles,
|
|
789
|
+
unmapped: [],
|
|
790
|
+
// Always empty in optional modes
|
|
791
|
+
preFiltered,
|
|
792
|
+
stats
|
|
523
793
|
};
|
|
524
794
|
}
|
|
525
795
|
/**
|
|
526
796
|
* Counts total number of rules (flattening arrays)
|
|
527
797
|
*/
|
|
528
|
-
countRules(
|
|
529
|
-
return
|
|
798
|
+
countRules(rules) {
|
|
799
|
+
return rules.reduce((count, rule) => {
|
|
800
|
+
if (Array.isArray(rule)) {
|
|
801
|
+
return count + rule.length;
|
|
802
|
+
}
|
|
803
|
+
return count + 1;
|
|
804
|
+
}, 0);
|
|
530
805
|
}
|
|
531
806
|
/**
|
|
532
807
|
* Counts mandatory rules
|
|
533
808
|
*/
|
|
534
|
-
countMandatoryRules(
|
|
535
|
-
return
|
|
809
|
+
countMandatoryRules(rules) {
|
|
810
|
+
return rules.reduce((count, rule) => {
|
|
811
|
+
if (Array.isArray(rule)) {
|
|
812
|
+
return count + rule.filter((r) => !r.optional && !isWildcardRule(r)).length;
|
|
813
|
+
}
|
|
814
|
+
return count + (rule.optional || isWildcardRule(rule) ? 0 : 1);
|
|
815
|
+
}, 0);
|
|
536
816
|
}
|
|
537
817
|
/**
|
|
538
818
|
* Counts optional rules
|
|
539
819
|
*/
|
|
540
|
-
countOptionalRules(
|
|
541
|
-
return
|
|
820
|
+
countOptionalRules(rules) {
|
|
821
|
+
return rules.reduce((count, rule) => {
|
|
822
|
+
if (Array.isArray(rule)) {
|
|
823
|
+
return count + rule.filter((r) => r.optional || isWildcardRule(r)).length;
|
|
824
|
+
}
|
|
825
|
+
return count + (rule.optional || isWildcardRule(rule) ? 1 : 0);
|
|
826
|
+
}, 0);
|
|
542
827
|
}
|
|
543
828
|
/**
|
|
544
829
|
* Filtering function for grouped rules with common filter criteria.
|
|
@@ -549,66 +834,104 @@ class M {
|
|
|
549
834
|
* @param groups - Filter groups with common criteria and rules
|
|
550
835
|
* @param sortFn - Optional sort function for file ordering
|
|
551
836
|
* @param preFilter - Optional pre-filter criteria (files not matching are excluded)
|
|
552
|
-
* @
|
|
837
|
+
* @param mode - Matching mode ('strict', 'optional', or 'strict-optional')
|
|
838
|
+
* @returns FilterResult with mapped, wildcardMatched, optionalFiles and unmapped files
|
|
553
839
|
*/
|
|
554
|
-
filterFilesWithGroups(
|
|
555
|
-
let
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
840
|
+
filterFilesWithGroups(files, groups, sortFn, preFilter, mode = "strict") {
|
|
841
|
+
let filteredFiles;
|
|
842
|
+
let preFiltered = [];
|
|
843
|
+
if (preFilter) {
|
|
844
|
+
const preFilterResult = this.applyPreFilter(files, preFilter);
|
|
845
|
+
filteredFiles = preFilterResult.matched;
|
|
846
|
+
preFiltered = preFilterResult.excluded;
|
|
847
|
+
} else {
|
|
848
|
+
filteredFiles = files;
|
|
849
|
+
}
|
|
850
|
+
const sortedFiles = sortFn ? [...filteredFiles].sort(sortFn) : [...filteredFiles];
|
|
851
|
+
const mapped = [];
|
|
852
|
+
const wildcardMatched = [];
|
|
853
|
+
const unmapped = [];
|
|
854
|
+
const optionalFiles = [];
|
|
855
|
+
for (const group of groups) {
|
|
856
|
+
const groupFiles = sortedFiles.filter((file) => {
|
|
857
|
+
const checks = group.groupFilter.map((criterion) => {
|
|
858
|
+
return this.engine.evaluateCriterion(file.data, criterion);
|
|
859
|
+
});
|
|
860
|
+
return checks.every((check) => check.status);
|
|
861
|
+
});
|
|
862
|
+
if (groupFiles.length === 0) {
|
|
565
863
|
continue;
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
864
|
+
}
|
|
865
|
+
const rulesArray = group.rules.map((rule) => {
|
|
866
|
+
if (Array.isArray(rule)) {
|
|
867
|
+
return rule;
|
|
868
|
+
}
|
|
869
|
+
return rule;
|
|
870
|
+
});
|
|
871
|
+
const groupResult = this.filterFiles(groupFiles, rulesArray, void 0, void 0, mode);
|
|
872
|
+
mapped.push(...groupResult.mapped);
|
|
873
|
+
wildcardMatched.push(...groupResult.wildcardMatched);
|
|
874
|
+
unmapped.push(...groupResult.unmapped);
|
|
875
|
+
optionalFiles.push(...groupResult.optionalFiles);
|
|
876
|
+
}
|
|
877
|
+
const allRules = groups.flatMap((g) => g.rules);
|
|
878
|
+
const stats = {
|
|
879
|
+
totalFiles: files.length,
|
|
880
|
+
mappedFiles: mapped.length,
|
|
881
|
+
wildcardMatchedFiles: wildcardMatched.length,
|
|
882
|
+
unmappedFiles: unmapped.length,
|
|
883
|
+
optionalFiles: optionalFiles.length,
|
|
884
|
+
preFilteredFiles: preFiltered.length,
|
|
885
|
+
totalRules: this.countRules(allRules),
|
|
886
|
+
mandatoryRules: this.countMandatoryRules(allRules),
|
|
887
|
+
optionalRules: this.countOptionalRules(allRules)
|
|
578
888
|
};
|
|
579
889
|
return {
|
|
580
|
-
mapped
|
|
581
|
-
wildcardMatched
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
890
|
+
mapped,
|
|
891
|
+
wildcardMatched,
|
|
892
|
+
optionalFiles,
|
|
893
|
+
unmapped,
|
|
894
|
+
preFiltered,
|
|
895
|
+
stats
|
|
585
896
|
};
|
|
586
897
|
}
|
|
587
898
|
}
|
|
588
|
-
function
|
|
589
|
-
const
|
|
590
|
-
if (
|
|
899
|
+
function filterFiles(request) {
|
|
900
|
+
const matcher = new Matcher(request.context);
|
|
901
|
+
if (request.rules && request.groups) {
|
|
591
902
|
throw new Error('FilterRequest: Provide either "rules" or "groups", not both');
|
|
592
|
-
|
|
903
|
+
}
|
|
904
|
+
if (!request.rules && !request.groups) {
|
|
593
905
|
throw new Error('FilterRequest: Must provide either "rules" or "groups"');
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
906
|
+
}
|
|
907
|
+
if (request.groups) {
|
|
908
|
+
return matcher.filterFilesWithGroups(
|
|
909
|
+
request.files,
|
|
910
|
+
request.groups,
|
|
911
|
+
request.sortFn,
|
|
912
|
+
request.preFilter,
|
|
913
|
+
request.mode
|
|
600
914
|
);
|
|
601
|
-
|
|
915
|
+
}
|
|
916
|
+
if (!request.rules) {
|
|
602
917
|
throw new Error("FilterRequest: Rules are required");
|
|
603
|
-
|
|
918
|
+
}
|
|
919
|
+
return matcher.filterFiles(
|
|
920
|
+
request.files,
|
|
921
|
+
request.rules,
|
|
922
|
+
request.sortFn,
|
|
923
|
+
request.preFilter,
|
|
924
|
+
request.mode
|
|
925
|
+
);
|
|
604
926
|
}
|
|
605
927
|
export {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
928
|
+
FilterEngine,
|
|
929
|
+
Matcher,
|
|
930
|
+
filterFiles,
|
|
931
|
+
getValueFromPath,
|
|
932
|
+
getValueOr,
|
|
933
|
+
isSingleMatchRule,
|
|
934
|
+
isWildcardRule,
|
|
935
|
+
pathExists
|
|
614
936
|
};
|
|
937
|
+
//# sourceMappingURL=aikotools-datafilter.mjs.map
|