@podlite/schema 0.0.41 → 0.0.43
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/CHANGELOG.podlite +18 -2
- package/esm/blocks-helpers.d.ts +5 -0
- package/esm/blocks-helpers.js +3 -0
- package/esm/blocks-helpers.js.map +1 -1
- package/esm/exportAny.d.ts +1 -0
- package/esm/exportAny.js +1 -1
- package/esm/exportAny.js.map +1 -1
- package/esm/exportHtml.js +47 -5
- package/esm/exportHtml.js.map +1 -1
- package/esm/exportMarkdown.js +37 -10
- package/esm/exportMarkdown.js.map +1 -1
- package/esm/grammar.js +232 -160
- package/esm/grammar.js.map +1 -1
- package/esm/grammarfc.js +418 -406
- package/esm/grammarfc.js.map +1 -1
- package/esm/helpers/handlers.d.ts +11 -0
- package/esm/helpers/handlers.js +25 -0
- package/esm/helpers/handlers.js.map +1 -1
- package/esm/helpers/makeInterator.js +2 -4
- package/esm/helpers/makeInterator.js.map +1 -1
- package/esm/index.js +2 -0
- package/esm/index.js.map +1 -1
- package/esm/plugin-data-table.d.ts +2 -0
- package/esm/plugin-data-table.js +152 -0
- package/esm/plugin-data-table.js.map +1 -0
- package/esm/plugin-tables.d.ts +10 -0
- package/esm/plugin-tables.js +7 -7
- package/esm/plugin-tables.js.map +1 -1
- package/esm/selectors.d.ts +17 -1
- package/esm/selectors.js +309 -19
- package/esm/selectors.js.map +1 -1
- package/lib/blocks-helpers.d.ts +5 -0
- package/lib/blocks-helpers.js +5 -1
- package/lib/exportAny.d.ts +1 -0
- package/lib/exportAny.js +1 -1
- package/lib/exportHtml.js +46 -4
- package/lib/exportMarkdown.js +36 -9
- package/lib/grammar.js +232 -160
- package/lib/grammarfc.js +418 -406
- package/lib/helpers/handlers.d.ts +11 -0
- package/lib/helpers/handlers.js +28 -1
- package/lib/helpers/makeInterator.js +2 -4
- package/lib/index.js +2 -0
- package/lib/plugin-data-table.d.ts +2 -0
- package/lib/plugin-data-table.js +157 -0
- package/lib/plugin-tables.d.ts +10 -0
- package/lib/plugin-tables.js +8 -0
- package/lib/selectors.d.ts +17 -1
- package/lib/selectors.js +309 -19
- package/package.json +1 -1
package/lib/selectors.js
CHANGED
|
@@ -2,36 +2,327 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runSelector = exports.parseSelector = void 0;
|
|
4
4
|
const index_1 = require("./index");
|
|
5
|
+
// --- predicate parser ---------------------------------------------------
|
|
6
|
+
const isIdentStart = (c) => /[a-zA-Z_]/.test(c);
|
|
7
|
+
const isIdentCont = (c) => /[a-zA-Z0-9_-]/.test(c);
|
|
8
|
+
// Read an angle-bracketed value starting at `<`. Returns the inner content
|
|
9
|
+
// (without delimiters) and the index right after the closing `>`.
|
|
10
|
+
// Nested `<>` pairs are matched recursively.
|
|
11
|
+
const readAngleValue = (s, start) => {
|
|
12
|
+
if (s[start] !== '<')
|
|
13
|
+
return undefined;
|
|
14
|
+
let depth = 1;
|
|
15
|
+
let i = start + 1;
|
|
16
|
+
while (i < s.length && depth > 0) {
|
|
17
|
+
if (s[i] === '<')
|
|
18
|
+
depth++;
|
|
19
|
+
else if (s[i] === '>') {
|
|
20
|
+
depth--;
|
|
21
|
+
if (depth === 0)
|
|
22
|
+
return { value: s.slice(start + 1, i), end: i + 1 };
|
|
23
|
+
}
|
|
24
|
+
i++;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
};
|
|
28
|
+
const parseCondition = (raw) => {
|
|
29
|
+
const s = raw.trim();
|
|
30
|
+
if (!s.startsWith(':'))
|
|
31
|
+
return undefined;
|
|
32
|
+
let i = 1;
|
|
33
|
+
let modifier;
|
|
34
|
+
if (s.startsWith('!?', i)) {
|
|
35
|
+
modifier = '!?';
|
|
36
|
+
i += 2;
|
|
37
|
+
}
|
|
38
|
+
else if (s[i] === '!') {
|
|
39
|
+
modifier = '!';
|
|
40
|
+
i += 1;
|
|
41
|
+
}
|
|
42
|
+
else if (s[i] === '?') {
|
|
43
|
+
modifier = '?';
|
|
44
|
+
i += 1;
|
|
45
|
+
}
|
|
46
|
+
if (i >= s.length || !isIdentStart(s[i]))
|
|
47
|
+
return undefined;
|
|
48
|
+
const nameStart = i;
|
|
49
|
+
while (i < s.length && isIdentCont(s[i]))
|
|
50
|
+
i++;
|
|
51
|
+
const attrName = s.slice(nameStart, i);
|
|
52
|
+
if (i >= s.length)
|
|
53
|
+
return { modifier, attrName };
|
|
54
|
+
if (s[i] === '~' && s[i + 1] === '<') {
|
|
55
|
+
const read = readAngleValue(s, i + 1);
|
|
56
|
+
if (!read || read.end !== s.length)
|
|
57
|
+
return undefined;
|
|
58
|
+
return { modifier, attrName, valueSpec: { kind: 'contains', value: read.value } };
|
|
59
|
+
}
|
|
60
|
+
if (s[i] === '<') {
|
|
61
|
+
const read = readAngleValue(s, i);
|
|
62
|
+
if (!read || read.end !== s.length)
|
|
63
|
+
return undefined;
|
|
64
|
+
return { modifier, attrName, valueSpec: { kind: 'angle', value: read.value } };
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
};
|
|
68
|
+
// Split predicate body by whitespace at top level, respecting <...> nesting.
|
|
69
|
+
const splitConditions = (body) => {
|
|
70
|
+
const chunks = [];
|
|
71
|
+
let depth = 0;
|
|
72
|
+
let buf = '';
|
|
73
|
+
for (let i = 0; i < body.length; i++) {
|
|
74
|
+
const c = body[i];
|
|
75
|
+
if (c === '<')
|
|
76
|
+
depth++;
|
|
77
|
+
else if (c === '>') {
|
|
78
|
+
if (depth === 0)
|
|
79
|
+
return undefined;
|
|
80
|
+
depth--;
|
|
81
|
+
}
|
|
82
|
+
if (depth === 0 && /\s/.test(c)) {
|
|
83
|
+
if (buf) {
|
|
84
|
+
chunks.push(buf);
|
|
85
|
+
buf = '';
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
buf += c;
|
|
90
|
+
}
|
|
91
|
+
if (depth !== 0)
|
|
92
|
+
return undefined;
|
|
93
|
+
if (buf)
|
|
94
|
+
chunks.push(buf);
|
|
95
|
+
return chunks;
|
|
96
|
+
};
|
|
97
|
+
const parsePredicate = (body) => {
|
|
98
|
+
const chunks = splitConditions(body);
|
|
99
|
+
if (!chunks)
|
|
100
|
+
return undefined;
|
|
101
|
+
const conditions = [];
|
|
102
|
+
for (const chunk of chunks) {
|
|
103
|
+
const cond = parseCondition(chunk);
|
|
104
|
+
if (!cond)
|
|
105
|
+
return undefined;
|
|
106
|
+
conditions.push(cond);
|
|
107
|
+
}
|
|
108
|
+
return conditions;
|
|
109
|
+
};
|
|
110
|
+
const parsePattern = (raw) => {
|
|
111
|
+
const s = raw.trim();
|
|
112
|
+
if (!s)
|
|
113
|
+
return undefined;
|
|
114
|
+
let blockType;
|
|
115
|
+
let i = 0;
|
|
116
|
+
if (s[0] === '*') {
|
|
117
|
+
blockType = '*';
|
|
118
|
+
i = 1;
|
|
119
|
+
}
|
|
120
|
+
else if (isIdentStart(s[0])) {
|
|
121
|
+
let j = 1;
|
|
122
|
+
while (j < s.length && isIdentCont(s[j]))
|
|
123
|
+
j++;
|
|
124
|
+
blockType = s.slice(0, j);
|
|
125
|
+
i = j;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
// skip optional whitespace between block-type and predicate
|
|
131
|
+
while (i < s.length && /\s/.test(s[i]))
|
|
132
|
+
i++;
|
|
133
|
+
if (i >= s.length)
|
|
134
|
+
return { blockType };
|
|
135
|
+
if (s[i] !== '[' || s[s.length - 1] !== ']')
|
|
136
|
+
return undefined;
|
|
137
|
+
const body = s.slice(i + 1, s.length - 1).trim();
|
|
138
|
+
if (!body)
|
|
139
|
+
return undefined;
|
|
140
|
+
const predicate = parsePredicate(body);
|
|
141
|
+
if (!predicate)
|
|
142
|
+
return undefined;
|
|
143
|
+
return { blockType, predicate };
|
|
144
|
+
};
|
|
145
|
+
// Split pattern-list by comma at top level, respecting [...] and <...> nesting.
|
|
146
|
+
const splitPatterns = (s) => {
|
|
147
|
+
const chunks = [];
|
|
148
|
+
let bracketDepth = 0;
|
|
149
|
+
let angleDepth = 0;
|
|
150
|
+
let buf = '';
|
|
151
|
+
for (let i = 0; i < s.length; i++) {
|
|
152
|
+
const c = s[i];
|
|
153
|
+
if (c === '[')
|
|
154
|
+
bracketDepth++;
|
|
155
|
+
else if (c === ']') {
|
|
156
|
+
if (bracketDepth === 0)
|
|
157
|
+
return undefined;
|
|
158
|
+
bracketDepth--;
|
|
159
|
+
}
|
|
160
|
+
else if (c === '<')
|
|
161
|
+
angleDepth++;
|
|
162
|
+
else if (c === '>') {
|
|
163
|
+
if (angleDepth === 0)
|
|
164
|
+
return undefined;
|
|
165
|
+
angleDepth--;
|
|
166
|
+
}
|
|
167
|
+
if (c === ',' && bracketDepth === 0 && angleDepth === 0) {
|
|
168
|
+
chunks.push(buf);
|
|
169
|
+
buf = '';
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
buf += c;
|
|
173
|
+
}
|
|
174
|
+
if (bracketDepth !== 0 || angleDepth !== 0)
|
|
175
|
+
return undefined;
|
|
176
|
+
chunks.push(buf);
|
|
177
|
+
return chunks;
|
|
178
|
+
};
|
|
179
|
+
const parsePatternList = (filterPart) => {
|
|
180
|
+
if (!filterPart)
|
|
181
|
+
return [];
|
|
182
|
+
const chunks = splitPatterns(filterPart);
|
|
183
|
+
if (!chunks)
|
|
184
|
+
return undefined;
|
|
185
|
+
const patterns = [];
|
|
186
|
+
for (const chunk of chunks) {
|
|
187
|
+
const trimmed = chunk.trim();
|
|
188
|
+
if (!trimmed)
|
|
189
|
+
continue;
|
|
190
|
+
const p = parsePattern(trimmed);
|
|
191
|
+
if (!p)
|
|
192
|
+
return undefined;
|
|
193
|
+
patterns.push(p);
|
|
194
|
+
}
|
|
195
|
+
return patterns;
|
|
196
|
+
};
|
|
5
197
|
const parseSelector = (selector) => {
|
|
6
198
|
const trimmed = selector.trim();
|
|
7
199
|
if (!trimmed)
|
|
8
200
|
return undefined;
|
|
9
|
-
// Split on the first '|' — left is source, right is
|
|
201
|
+
// Split on the first '|' — left is source, right is pattern-list
|
|
10
202
|
const pipeIdx = trimmed.indexOf('|');
|
|
11
203
|
const sourcePart = (pipeIdx === -1 ? trimmed : trimmed.slice(0, pipeIdx)).trim();
|
|
12
204
|
const filterPart = pipeIdx === -1 ? '' : trimmed.slice(pipeIdx + 1).trim();
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// Source: scheme:path or scheme:path#anchor
|
|
20
|
-
const sourceMatch = sourcePart.match(/^([^:]+):([^#]+)(?:#(.+))?$/);
|
|
205
|
+
const patterns = parsePatternList(filterPart);
|
|
206
|
+
if (patterns === undefined)
|
|
207
|
+
return undefined;
|
|
208
|
+
// Source: scheme:path or scheme:path#anchor. Scheme is identifier-shaped
|
|
209
|
+
// so that bare predicates like `*[:attr<v>]` don't get consumed as scheme.
|
|
210
|
+
const sourceMatch = sourcePart.match(/^([a-zA-Z][a-zA-Z0-9-]*):([^#]+)(?:#(.+))?$/);
|
|
21
211
|
if (sourceMatch) {
|
|
22
212
|
return {
|
|
23
213
|
scheme: sourceMatch[1],
|
|
24
214
|
document: sourceMatch[2].trim(),
|
|
25
215
|
anchor: sourceMatch[3],
|
|
26
|
-
|
|
216
|
+
patterns,
|
|
27
217
|
};
|
|
28
218
|
}
|
|
29
|
-
if (
|
|
30
|
-
return {
|
|
219
|
+
if (patterns.length > 0) {
|
|
220
|
+
return { patterns };
|
|
221
|
+
}
|
|
222
|
+
// No pipe — treat the whole input as a pattern-list (CLI-friendly shorthand
|
|
223
|
+
// for filter-only selectors, e.g. `head1, code[:lang<python>]`).
|
|
224
|
+
if (pipeIdx === -1) {
|
|
225
|
+
const fallback = parsePatternList(sourcePart);
|
|
226
|
+
if (fallback && fallback.length > 0)
|
|
227
|
+
return { patterns: fallback };
|
|
31
228
|
}
|
|
32
229
|
return undefined;
|
|
33
230
|
};
|
|
34
231
|
exports.parseSelector = parseSelector;
|
|
232
|
+
const matchCondition = (node, cond, ctx) => {
|
|
233
|
+
const attrs = (0, index_1.makeAttrs)(node, ctx);
|
|
234
|
+
const exists = attrs.exists(cond.attrName);
|
|
235
|
+
if (!cond.valueSpec) {
|
|
236
|
+
switch (cond.modifier) {
|
|
237
|
+
case undefined:
|
|
238
|
+
return exists && Boolean(attrs.getFirstValue(cond.attrName));
|
|
239
|
+
case '!':
|
|
240
|
+
return exists && attrs.getFirstValue(cond.attrName) === false;
|
|
241
|
+
case '?':
|
|
242
|
+
return exists;
|
|
243
|
+
case '!?':
|
|
244
|
+
return !exists;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (cond.valueSpec.kind === 'angle') {
|
|
248
|
+
if (!exists)
|
|
249
|
+
return false;
|
|
250
|
+
const equal = String(attrs.getFirstValue(cond.attrName)) === cond.valueSpec.value;
|
|
251
|
+
return cond.modifier === '!' ? !equal : equal;
|
|
252
|
+
}
|
|
253
|
+
if (cond.valueSpec.kind === 'contains') {
|
|
254
|
+
if (!exists)
|
|
255
|
+
return false;
|
|
256
|
+
// Author-facing semantics: `:tags<a b c>` is a list of three elements.
|
|
257
|
+
// Grammar parses unquoted identifier sequences as a single string, so
|
|
258
|
+
// split string values on whitespace/comma to recover list shape.
|
|
259
|
+
const tokens = [];
|
|
260
|
+
for (const v of attrs.getAllValues(cond.attrName)) {
|
|
261
|
+
if (typeof v === 'string') {
|
|
262
|
+
for (const t of v.split(/[\s,]+/))
|
|
263
|
+
if (t)
|
|
264
|
+
tokens.push(t);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
tokens.push(String(v));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const present = tokens.includes(cond.valueSpec.value);
|
|
271
|
+
return cond.modifier === '!' ? !present : present;
|
|
272
|
+
}
|
|
273
|
+
return false;
|
|
274
|
+
};
|
|
275
|
+
// Replicate name/level handling from getFromTree for backward compat with
|
|
276
|
+
// 'head1' / 'item' style block-types.
|
|
277
|
+
const blockTypeMatches = (node, blockType) => {
|
|
278
|
+
if (blockType === '*')
|
|
279
|
+
return true;
|
|
280
|
+
const anyNode = node;
|
|
281
|
+
if (anyNode.name === blockType)
|
|
282
|
+
return true;
|
|
283
|
+
const m = blockType.match(/^(head|item)(\d+)?$/);
|
|
284
|
+
if (m) {
|
|
285
|
+
const [, baseName, levelStr] = m;
|
|
286
|
+
if (anyNode.name !== baseName)
|
|
287
|
+
return false;
|
|
288
|
+
const expectedLevel = levelStr ? parseInt(levelStr, 10) : baseName === 'item' ? 1 : undefined;
|
|
289
|
+
if (expectedLevel === undefined)
|
|
290
|
+
return true;
|
|
291
|
+
// Heading plugin stores level as the regex capture string; coerce.
|
|
292
|
+
return Number(anyNode.level) === expectedLevel;
|
|
293
|
+
}
|
|
294
|
+
return false;
|
|
295
|
+
};
|
|
296
|
+
const matchesPattern = (node, pattern, ctx) => {
|
|
297
|
+
if (!blockTypeMatches(node, pattern.blockType))
|
|
298
|
+
return false;
|
|
299
|
+
if (!pattern.predicate)
|
|
300
|
+
return true;
|
|
301
|
+
return pattern.predicate.every(c => matchCondition(node, c, ctx));
|
|
302
|
+
};
|
|
303
|
+
// Walk one document in source order, accumulating =config defaults forward
|
|
304
|
+
// (last-wins, shared across the subtree) so a block matches against the same
|
|
305
|
+
// effective attributes the renderer would apply.
|
|
306
|
+
const collectMatches = (node, patterns, config, seen, out) => {
|
|
307
|
+
if (Array.isArray(node)) {
|
|
308
|
+
for (const child of node)
|
|
309
|
+
collectMatches(child, patterns, config, seen, out);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (!node || typeof node !== 'object')
|
|
313
|
+
return;
|
|
314
|
+
const anyNode = node;
|
|
315
|
+
if (anyNode.type === 'config' && typeof anyNode.name === 'string' && anyNode.config) {
|
|
316
|
+
config[anyNode.name] = anyNode.config;
|
|
317
|
+
}
|
|
318
|
+
else if (anyNode.type === 'block' && !seen.has(node) && patterns.some(p => matchesPattern(node, p, { config }))) {
|
|
319
|
+
out.push(node);
|
|
320
|
+
seen.add(node);
|
|
321
|
+
}
|
|
322
|
+
if (anyNode.content !== undefined) {
|
|
323
|
+
collectMatches(anyNode.content, patterns, config, seen, out);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
35
326
|
// Normalize a path for loose suffix comparison:
|
|
36
327
|
// 'src/foo.podlite' ~= 'foo.podlite'
|
|
37
328
|
// './includes/x.podlite' ~= 'includes/x.podlite'
|
|
@@ -124,7 +415,7 @@ const runSelector = (selector, docs) => {
|
|
|
124
415
|
const parsed = (0, exports.parseSelector)(selector);
|
|
125
416
|
if (!parsed)
|
|
126
417
|
return [];
|
|
127
|
-
const { scheme, document, anchor,
|
|
418
|
+
const { scheme, document, anchor, patterns } = parsed;
|
|
128
419
|
let matchedDocs = docs;
|
|
129
420
|
if (scheme === 'doc' && document) {
|
|
130
421
|
matchedDocs = docs.filter(doc => getDocIDs(doc).includes(document));
|
|
@@ -146,17 +437,16 @@ const runSelector = (selector, docs) => {
|
|
|
146
437
|
}
|
|
147
438
|
return collectedBlocks;
|
|
148
439
|
}
|
|
149
|
-
//
|
|
150
|
-
if (
|
|
440
|
+
// Patterns — source-order traversal, apply each pattern, dedupe across patterns
|
|
441
|
+
if (patterns.length > 0) {
|
|
151
442
|
const collectedBlocks = [];
|
|
443
|
+
const seen = new Set();
|
|
152
444
|
for (const d of matchedDocs) {
|
|
153
|
-
|
|
154
|
-
collectedBlocks.push(...(0, index_1.getFromTree)(d.node, name));
|
|
155
|
-
}
|
|
445
|
+
collectMatches(d.node, patterns, {}, seen, collectedBlocks);
|
|
156
446
|
}
|
|
157
447
|
return collectedBlocks;
|
|
158
448
|
}
|
|
159
|
-
// No anchor, no
|
|
449
|
+
// No anchor, no patterns — return whole docs
|
|
160
450
|
return matchedDocs.map(d => d.node);
|
|
161
451
|
};
|
|
162
452
|
exports.runSelector = runSelector;
|
package/package.json
CHANGED