@spaethtech/svelte-ui 0.19.0 → 0.19.1-dev.98.32fa323
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.
|
@@ -242,8 +242,8 @@
|
|
|
242
242
|
<tr><td>{"> >= < <="}</td><td>greater / less than (numbers & dates)</td></tr>
|
|
243
243
|
<tr><td>~= !~</td><td>contains / not — or regex if value is <code>/…/</code></td></tr>
|
|
244
244
|
<tr><td>^= $=</td><td>starts / ends with</td></tr>
|
|
245
|
-
<tr><td>|=</td><td>any of a set <code>[a, b]</code> or range <code>[1..9)</code></td></tr>
|
|
246
|
-
<tr><td>&=</td><td>all of a set (multi-value columns)</td></tr>
|
|
245
|
+
<tr><td>|=</td><td>any of a set <code>[a, b]</code> (a member may be a regex <code>/…/</code>) or range <code>[1..9)</code></td></tr>
|
|
246
|
+
<tr><td>&=</td><td>all of a set (members may be regex <code>/…/</code>; multi-value columns)</td></tr>
|
|
247
247
|
</tbody>
|
|
248
248
|
</table>
|
|
249
249
|
</div>
|
|
@@ -297,6 +297,7 @@
|
|
|
297
297
|
<tr><td>$age |= [21..40)</td><td>range — 21 ≤ age < 40</td></tr>
|
|
298
298
|
<tr><td>$role |= [admin, ops]</td><td>any of a set</td></tr>
|
|
299
299
|
<tr><td>$tags &= [urgent, vip]</td><td>all of (multi-value column)</td></tr>
|
|
300
|
+
<tr><td>$services |= [/^Managed/, "Static IP"]</td><td>set member as a regex (per element)</td></tr>
|
|
300
301
|
<tr><td>$created >= now()-7d</td><td>dynamic date (last 7 days)</td></tr>
|
|
301
302
|
<tr><td>$ip|inet >= 10.0.0.0|inet</td><td>typed IP compare</td></tr>
|
|
302
303
|
<tr><td>!($role == admin)</td><td>negate a group</td></tr>
|
package/dist/data/query/ast.d.ts
CHANGED
|
@@ -27,6 +27,14 @@ export declare function resolveNow(v: NowValue, at?: Date): Date;
|
|
|
27
27
|
export type Cast = "number" | "string" | "bool" | "date" | "inet";
|
|
28
28
|
/** Membership mode: `|=` matches ANY of the operand, `&=` matches ALL (e.g. array contains-all). */
|
|
29
29
|
export type MemberMode = "any" | "all";
|
|
30
|
+
/** A regex member inside a `|=`/`&=` set (`$col |= [/re/i]`). Reuses the `match`-node shape
|
|
31
|
+
* (`source`/`flags`). Evaluated per array ELEMENT: a member matches if the regex tests true against
|
|
32
|
+
* any element's string form (or the cell itself for a scalar column). A cast + a regex member is a
|
|
33
|
+
* parse error, same as a bare `~=` regex. */
|
|
34
|
+
export interface RegexMember {
|
|
35
|
+
source: string;
|
|
36
|
+
flags: string;
|
|
37
|
+
}
|
|
30
38
|
/** One end of an interval. `inclusive` reflects the bracket: `[`/`]` inclusive, `(`/`)` exclusive. */
|
|
31
39
|
export type Bound = {
|
|
32
40
|
value: Value;
|
|
@@ -63,6 +71,7 @@ export type QueryNode = {
|
|
|
63
71
|
col: string;
|
|
64
72
|
mode: MemberMode;
|
|
65
73
|
values: Value[];
|
|
74
|
+
patterns?: RegexMember[];
|
|
66
75
|
cast?: Cast;
|
|
67
76
|
} | {
|
|
68
77
|
kind: "range";
|
package/dist/data/query/eval.js
CHANGED
|
@@ -126,12 +126,19 @@ export function matchesRow(row, ast, opts = {}) {
|
|
|
126
126
|
}
|
|
127
127
|
case "in": {
|
|
128
128
|
const cell = get(row, node.col);
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
// One predicate per member. Scalars → exact-equality (honours cast + ci); regexes → test the
|
|
130
|
+
// string form. A member is satisfied if SOME array element matches it (per-element — the point
|
|
131
|
+
// of a regex member), or the cell itself for a scalar column.
|
|
132
|
+
const scalarTests = node.values.map((v) => {
|
|
133
|
+
const m = resolveVal(v, now);
|
|
134
|
+
return (x) => eq(x, m, node.cast, ci);
|
|
135
|
+
});
|
|
136
|
+
const regexTests = (node.patterns ?? []).map((p) => {
|
|
137
|
+
const re = safeRegExp(p.source, p.flags);
|
|
138
|
+
return (x) => re != null && re.test(toStr(x));
|
|
139
|
+
});
|
|
140
|
+
const memberHit = (test) => Array.isArray(cell) ? cell.some(test) : test(cell);
|
|
141
|
+
const hits = [...scalarTests, ...regexTests].map(memberHit);
|
|
135
142
|
return node.mode === "all" ? hits.every(Boolean) : hits.some(Boolean);
|
|
136
143
|
}
|
|
137
144
|
case "range": {
|
|
@@ -125,7 +125,7 @@ class Parser {
|
|
|
125
125
|
const node = this.parseClauseBody(col);
|
|
126
126
|
const cast = this.foldCast();
|
|
127
127
|
if (cast) {
|
|
128
|
-
if (node.kind === "match")
|
|
128
|
+
if (node.kind === "match" || (node.kind === "in" && node.patterns?.length))
|
|
129
129
|
throw new QueryError("A cast cannot be used with a regex", colTok.start);
|
|
130
130
|
node.cast = cast;
|
|
131
131
|
}
|
|
@@ -224,6 +224,17 @@ class Parser {
|
|
|
224
224
|
}
|
|
225
225
|
return value;
|
|
226
226
|
}
|
|
227
|
+
/** One member of a `[...]` set: a regex literal (→ per-element pattern) or a scalar (exact-equality).
|
|
228
|
+
* A regex is only valid in a SET, never as an interval bound (a pattern can't order). */
|
|
229
|
+
parseSetMember() {
|
|
230
|
+
if (this.is("regex")) {
|
|
231
|
+
const r = this.next();
|
|
232
|
+
if (r.type !== "regex")
|
|
233
|
+
throw new QueryError("Expected a regex", r.start);
|
|
234
|
+
return { pattern: { source: r.source, flags: r.flags } };
|
|
235
|
+
}
|
|
236
|
+
return { value: this.parseScalar() };
|
|
237
|
+
}
|
|
227
238
|
parseMemberOperand(col, mode) {
|
|
228
239
|
const open = this.next();
|
|
229
240
|
if (open.type !== "lbracket" && open.type !== "lparen") {
|
|
@@ -232,21 +243,28 @@ class Parser {
|
|
|
232
243
|
const lowerInclusive = open.type === "lbracket";
|
|
233
244
|
let lower = null;
|
|
234
245
|
if (!this.is("dotdot")) {
|
|
235
|
-
const first = this.
|
|
236
|
-
|
|
237
|
-
|
|
246
|
+
const first = this.parseSetMember();
|
|
247
|
+
// Interval only when the first member is a SCALAR immediately followed by `..`; a regex first
|
|
248
|
+
// member (or a scalar not followed by `..`) is a set.
|
|
249
|
+
if (!first.pattern && this.is("dotdot")) {
|
|
250
|
+
lower = { value: first.value, inclusive: lowerInclusive };
|
|
238
251
|
}
|
|
239
252
|
else {
|
|
240
|
-
// SET (must use square brackets).
|
|
253
|
+
// SET (must use square brackets). Scalars → exact-equality members; regexes → per-element.
|
|
241
254
|
if (open.type !== "lbracket")
|
|
242
255
|
throw new QueryError("A set must use '[' ']'", open.start);
|
|
243
|
-
const values = [
|
|
256
|
+
const values = [];
|
|
257
|
+
const patterns = [];
|
|
258
|
+
const add = (m) => m.pattern ? patterns.push(m.pattern) : values.push(m.value);
|
|
259
|
+
add(first);
|
|
244
260
|
while (this.is("comma")) {
|
|
245
261
|
this.next();
|
|
246
|
-
|
|
262
|
+
add(this.parseSetMember());
|
|
247
263
|
}
|
|
248
264
|
this.expect("rbracket");
|
|
249
|
-
return
|
|
265
|
+
return patterns.length
|
|
266
|
+
? { kind: "in", col, mode, values, patterns }
|
|
267
|
+
: { kind: "in", col, mode, values };
|
|
250
268
|
}
|
|
251
269
|
}
|
|
252
270
|
// interval
|
package/docs/components.md
CHANGED
|
@@ -400,7 +400,9 @@ Config-driven, responsive 12-column data table over a `DataGrid`.
|
|
|
400
400
|
|
|
401
401
|
Filter bar over a `DataSet` — monospace input with `$column` + enum-value autocomplete and a help
|
|
402
402
|
dialog (operators, casts, dynamic `now()` dates, and a worked examples table). The DSL supports
|
|
403
|
-
`now()` / `now()±<n><unit>` dynamic dates
|
|
403
|
+
`now()` / `now()±<n><unit>` dynamic dates, and **regex members inside `|=`/`&=` sets** —
|
|
404
|
+
`$services |= [/^Managed/, "Static IP"]` matches a set member as a regex, per array element (scalars
|
|
405
|
+
still exact-match; mix freely). See the help dialog + the data spec.
|
|
404
406
|
|
|
405
407
|
- **Location**: `src/lib/components/Query.svelte`
|
|
406
408
|
- **Props**: `dataset` (`DataSet`), `placeholder`
|