@wdprlib/parser 5.0.0 → 5.1.2
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/dist/index.cjs +250 -144
- package/dist/index.d.cts +8 -13
- package/dist/index.d.ts +8 -13
- package/dist/index.js +230 -124
- package/package.json +2 -2
- package/src/parser/parse/context.ts +7 -2
- package/src/parser/parse/index.ts +21 -3
- package/src/parser/parse/parser.ts +37 -28
- package/src/parser/rules/block/include/index.ts +20 -2
- package/src/parser/rules/block/module/include/resolve/index.ts +28 -0
- package/src/parser/rules/block/module/include/resolve/iterate.ts +28 -4
- package/src/parser/rules/block/module/include/scanner.ts +58 -1
- package/src/parser/rules/contracts/parse-context.ts +2 -0
- package/src/pipeline/process.ts +48 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wdprlib/parser",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.2",
|
|
4
4
|
"description": "Parser for Wikidot markup",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ast",
|
|
@@ -41,6 +41,6 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@braintree/sanitize-url": "^7.1.1",
|
|
44
|
-
"@wdprlib/ast": "3.0.
|
|
44
|
+
"@wdprlib/ast": "3.0.1"
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { DEFAULT_SETTINGS } from "@wdprlib/ast";
|
|
1
|
+
import { DEFAULT_SETTINGS, type PageRef } from "@wdprlib/ast";
|
|
2
2
|
import type { Token } from "../../lexer";
|
|
3
3
|
import { blockFallbackRule, blockRules, inlineRules, type ParseContext } from "../rules";
|
|
4
4
|
import type { ParserOptions } from "./options";
|
|
5
5
|
|
|
6
|
-
export function createParseContext(
|
|
6
|
+
export function createParseContext(
|
|
7
|
+
tokens: Token[],
|
|
8
|
+
options: ParserOptions = {},
|
|
9
|
+
deferInclude?: (location: PageRef) => boolean,
|
|
10
|
+
): ParseContext {
|
|
7
11
|
return {
|
|
8
12
|
tokens,
|
|
9
13
|
pos: 0,
|
|
@@ -11,6 +15,7 @@ export function createParseContext(tokens: Token[], options: ParserOptions = {})
|
|
|
11
15
|
trackPositions: options.trackPositions ?? true,
|
|
12
16
|
settings: options.settings ?? DEFAULT_SETTINGS,
|
|
13
17
|
appendImplicitFootnoteBlock: options.appendImplicitFootnoteBlock ?? true,
|
|
18
|
+
deferInclude,
|
|
14
19
|
footnotes: [],
|
|
15
20
|
tocEntries: [],
|
|
16
21
|
codeBlocks: [],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ParseResult } from "@wdprlib/ast";
|
|
1
|
+
import type { PageRef, ParseResult } from "@wdprlib/ast";
|
|
2
2
|
import { tokenize } from "../../lexer";
|
|
3
|
-
import { Parser } from "./parser";
|
|
3
|
+
import { Parser, parseTokensWithIncludeDeferral } from "./parser";
|
|
4
4
|
import { parseLargePlainTextDocument, parsePlainNonAsciiDocument } from "./plain-non-ascii";
|
|
5
5
|
import { prepareSourceForParse } from "./source";
|
|
6
6
|
import type { ParserOptions } from "./options";
|
|
@@ -23,6 +23,22 @@ export { Parser } from "./parser";
|
|
|
23
23
|
* @since 2.0.0
|
|
24
24
|
*/
|
|
25
25
|
export function parse(source: string, options?: ParserOptions): ParseResult {
|
|
26
|
+
return parseSource(source, options);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function parseWithIncludeDeferral(
|
|
30
|
+
source: string,
|
|
31
|
+
options: ParserOptions,
|
|
32
|
+
deferInclude: (location: PageRef) => boolean,
|
|
33
|
+
): ParseResult {
|
|
34
|
+
return parseSource(source, options, deferInclude);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseSource(
|
|
38
|
+
source: string,
|
|
39
|
+
options?: ParserOptions,
|
|
40
|
+
deferInclude?: (location: PageRef) => boolean,
|
|
41
|
+
): ParseResult {
|
|
26
42
|
const plainResult = parsePlainNonAsciiDocument(source);
|
|
27
43
|
if (plainResult) {
|
|
28
44
|
return plainResult;
|
|
@@ -38,5 +54,7 @@ export function parse(source: string, options?: ParserOptions): ParseResult {
|
|
|
38
54
|
trackPositions: options?.trackPositions,
|
|
39
55
|
compactTextRuns: preprocessed.length >= COMPACT_TEXT_RUN_SOURCE_LENGTH,
|
|
40
56
|
});
|
|
41
|
-
return
|
|
57
|
+
return deferInclude
|
|
58
|
+
? parseTokensWithIncludeDeferral(tokens, options ?? {}, deferInclude)
|
|
59
|
+
: new Parser(tokens, options).parse();
|
|
42
60
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Element, ParseResult } from "@wdprlib/ast";
|
|
1
|
+
import type { Element, PageRef, ParseResult } from "@wdprlib/ast";
|
|
2
2
|
import type { Token } from "../../lexer";
|
|
3
3
|
import type { ParseContext } from "../rules";
|
|
4
4
|
import type { ParserOptions } from "./options";
|
|
@@ -33,26 +33,35 @@ export class Parser {
|
|
|
33
33
|
* @since 2.0.0
|
|
34
34
|
*/
|
|
35
35
|
parse(): ParseResult {
|
|
36
|
-
|
|
36
|
+
return parseContext(this.ctx);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
export function parseTokensWithIncludeDeferral(
|
|
41
|
+
tokens: Token[],
|
|
42
|
+
options: ParserOptions,
|
|
43
|
+
deferInclude: (location: PageRef) => boolean,
|
|
44
|
+
): ParseResult {
|
|
45
|
+
return parseContext(createParseContext(tokens, options, deferInclude));
|
|
46
|
+
}
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
function parseContext(ctx: ParseContext): ParseResult {
|
|
49
|
+
const children: Element[] = [];
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
while (!isAtEnd(ctx)) {
|
|
52
|
+
children.push(...parseBlock(ctx));
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
55
|
+
return finalizeParseResult(ctx, children);
|
|
56
|
+
}
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
function isAtEnd(ctx: ParseContext): boolean {
|
|
59
|
+
return ctx.pos >= ctx.tokens.length || currentToken(ctx).type === "EOF";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function currentToken(ctx: ParseContext): Token {
|
|
63
|
+
return (
|
|
64
|
+
ctx.tokens[ctx.pos] ?? {
|
|
56
65
|
type: "EOF",
|
|
57
66
|
value: "",
|
|
58
67
|
position: {
|
|
@@ -60,20 +69,20 @@ export class Parser {
|
|
|
60
69
|
end: { line: 0, column: 0, offset: 0 },
|
|
61
70
|
},
|
|
62
71
|
lineStart: false,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private skipWhitespace(): void {
|
|
67
|
-
while (this.currentToken().type === "WHITESPACE") {
|
|
68
|
-
this.ctx.pos++;
|
|
69
72
|
}
|
|
70
|
-
|
|
73
|
+
);
|
|
74
|
+
}
|
|
71
75
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
() => this.skipWhitespace(),
|
|
76
|
-
() => this.isAtEnd(),
|
|
77
|
-
);
|
|
76
|
+
function skipWhitespace(ctx: ParseContext): void {
|
|
77
|
+
while (currentToken(ctx).type === "WHITESPACE") {
|
|
78
|
+
ctx.pos++;
|
|
78
79
|
}
|
|
79
80
|
}
|
|
81
|
+
|
|
82
|
+
function parseBlock(ctx: ParseContext): Element[] {
|
|
83
|
+
return parseNextBlock(
|
|
84
|
+
ctx,
|
|
85
|
+
() => skipWhitespace(ctx),
|
|
86
|
+
() => isAtEnd(ctx),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { lineBreak, paragraph, text, type Element } from "@wdprlib/ast";
|
|
2
2
|
import type { BlockRule, ParseContext, RuleResult } from "../../types";
|
|
3
3
|
import { currentToken } from "../../types";
|
|
4
4
|
import { parseBlockName } from "../utils";
|
|
@@ -46,6 +46,7 @@ export const includeRule: BlockRule = {
|
|
|
46
46
|
}
|
|
47
47
|
pos++;
|
|
48
48
|
consumed++;
|
|
49
|
+
const directiveEnd = pos;
|
|
49
50
|
|
|
50
51
|
if (ctx.tokens[pos]?.type === "NEWLINE") {
|
|
51
52
|
pos++;
|
|
@@ -56,6 +57,23 @@ export const includeRule: BlockRule = {
|
|
|
56
57
|
return { success: false };
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
const location = parsePageRef(args.target);
|
|
61
|
+
if (ctx.deferInclude?.(location)) {
|
|
62
|
+
// Keep Wikidot's parser extent here. Nested block markup in an include
|
|
63
|
+
// value must delay its closing brackets through variable expansion.
|
|
64
|
+
const source = ctx.tokens
|
|
65
|
+
.slice(ctx.pos, directiveEnd)
|
|
66
|
+
.map((token) => token.value)
|
|
67
|
+
.join("");
|
|
68
|
+
const elements: Element[] = [];
|
|
69
|
+
const lines = source.split("\n");
|
|
70
|
+
for (let index = 0; index < lines.length; index++) {
|
|
71
|
+
if (index > 0) elements.push(lineBreak());
|
|
72
|
+
if (lines[index] !== "") elements.push(text(lines[index]!));
|
|
73
|
+
}
|
|
74
|
+
return { success: true, elements: [paragraph(elements)], consumed };
|
|
75
|
+
}
|
|
76
|
+
|
|
59
77
|
return {
|
|
60
78
|
success: true,
|
|
61
79
|
elements: [
|
|
@@ -64,7 +82,7 @@ export const includeRule: BlockRule = {
|
|
|
64
82
|
data: {
|
|
65
83
|
"paragraph-safe": false,
|
|
66
84
|
variables: parseVariables(args.argumentTokens),
|
|
67
|
-
location
|
|
85
|
+
location,
|
|
68
86
|
elements: [],
|
|
69
87
|
},
|
|
70
88
|
},
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import type {
|
|
16
16
|
AsyncIncludeFetcher,
|
|
17
17
|
IncludeFetcher,
|
|
18
|
+
IncludeReference,
|
|
18
19
|
ResolveIncludesOptions,
|
|
19
20
|
ResolveIncludesTraceResult,
|
|
20
21
|
} from "./types";
|
|
@@ -113,6 +114,33 @@ export async function resolveIncludesAsyncWithTrace(
|
|
|
113
114
|
return expandIterativeAsyncWithTrace(source, cachedFetcher, maxIterations);
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Resolve includes while leaving selected directives untouched for the
|
|
119
|
+
* high-level parser pipeline to render literally.
|
|
120
|
+
*
|
|
121
|
+
* This helper is intentionally not exported from the package barrel so the
|
|
122
|
+
* public low-level resolver contract remains unchanged.
|
|
123
|
+
*/
|
|
124
|
+
export async function resolveIncludesAsyncWithTraceSelective(
|
|
125
|
+
source: string,
|
|
126
|
+
fetcher: AsyncIncludeFetcher,
|
|
127
|
+
shouldDefer: (reference: IncludeReference) => boolean,
|
|
128
|
+
options?: ResolveIncludesOptions,
|
|
129
|
+
): Promise<ResolveIncludesTraceResult> {
|
|
130
|
+
if (options?.settings && !options.settings.enablePageSyntax) {
|
|
131
|
+
return {
|
|
132
|
+
source,
|
|
133
|
+
dependencies: [],
|
|
134
|
+
iterations: [],
|
|
135
|
+
reachedMaxIterations: false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const maxIterations = options?.maxIterations ?? 10;
|
|
140
|
+
const cachedFetcher = createCachedAsyncIncludeFetcher(fetcher, normalizePageKey);
|
|
141
|
+
return expandIterativeAsyncWithTrace(source, cachedFetcher, maxIterations, shouldDefer);
|
|
142
|
+
}
|
|
143
|
+
|
|
116
144
|
/**
|
|
117
145
|
* Normalize a PageRef into a consistent string key for cache lookups.
|
|
118
146
|
*
|
|
@@ -107,6 +107,7 @@ export async function expandIterativeAsyncWithTrace(
|
|
|
107
107
|
source: string,
|
|
108
108
|
fetcher: AsyncIncludeFetcher,
|
|
109
109
|
maxIterations: number,
|
|
110
|
+
shouldDefer?: (reference: IncludeReference) => boolean,
|
|
110
111
|
): Promise<ResolveIncludesTraceResult> {
|
|
111
112
|
let current = source;
|
|
112
113
|
const replacementCache = new Map<string, Promise<string>>();
|
|
@@ -114,7 +115,13 @@ export async function expandIterativeAsyncWithTrace(
|
|
|
114
115
|
const iterations: ResolveIncludesTraceResult["iterations"] = [];
|
|
115
116
|
|
|
116
117
|
for (let i = 0; i < maxIterations; i++) {
|
|
117
|
-
const expanded = await expandOneIterationAsyncWithTrace(
|
|
118
|
+
const expanded = await expandOneIterationAsyncWithTrace(
|
|
119
|
+
current,
|
|
120
|
+
fetcher,
|
|
121
|
+
replacementCache,
|
|
122
|
+
i,
|
|
123
|
+
shouldDefer,
|
|
124
|
+
);
|
|
118
125
|
if (expanded === null) break;
|
|
119
126
|
|
|
120
127
|
dependencies.push(...expanded.dependencies);
|
|
@@ -133,7 +140,7 @@ export async function expandIterativeAsyncWithTrace(
|
|
|
133
140
|
dependencies,
|
|
134
141
|
iterations,
|
|
135
142
|
reachedMaxIterations:
|
|
136
|
-
iterations.length === maxIterations &&
|
|
143
|
+
iterations.length === maxIterations && hasResolvableDirectives(current, shouldDefer),
|
|
137
144
|
};
|
|
138
145
|
}
|
|
139
146
|
|
|
@@ -219,6 +226,7 @@ async function expandOneIterationAsyncWithTrace(
|
|
|
219
226
|
fetcher: AsyncIncludeFetcher,
|
|
220
227
|
replacementCache: Map<string, Promise<string>>,
|
|
221
228
|
iteration: number,
|
|
229
|
+
shouldDefer?: (reference: IncludeReference) => boolean,
|
|
222
230
|
): Promise<{
|
|
223
231
|
source: string;
|
|
224
232
|
references: IncludeReference[];
|
|
@@ -230,8 +238,13 @@ async function expandOneIterationAsyncWithTrace(
|
|
|
230
238
|
if (directives.length === 0) return null;
|
|
231
239
|
|
|
232
240
|
const references = directives.map(createIncludeReference);
|
|
241
|
+
const deferred = references.map((reference) => shouldDefer?.(reference) ?? false);
|
|
233
242
|
const replacements = await Promise.all(
|
|
234
|
-
directives.map((
|
|
243
|
+
directives.map((directive, index) =>
|
|
244
|
+
deferred[index]
|
|
245
|
+
? Promise.resolve(source.slice(directive.start, directive.end))
|
|
246
|
+
: replaceCachedAsync(directive.inner, fetcher, replacementCache),
|
|
247
|
+
),
|
|
235
248
|
);
|
|
236
249
|
const parts: string[] = [];
|
|
237
250
|
let lastPos = 0;
|
|
@@ -246,10 +259,21 @@ async function expandOneIterationAsyncWithTrace(
|
|
|
246
259
|
return {
|
|
247
260
|
source: parts.join(""),
|
|
248
261
|
references,
|
|
249
|
-
dependencies: references
|
|
262
|
+
dependencies: references
|
|
263
|
+
.filter((_, index) => !deferred[index])
|
|
264
|
+
.map((reference) => ({ ...reference, iteration })),
|
|
250
265
|
};
|
|
251
266
|
}
|
|
252
267
|
|
|
268
|
+
function hasResolvableDirectives(
|
|
269
|
+
source: string,
|
|
270
|
+
shouldDefer: ((reference: IncludeReference) => boolean) | undefined,
|
|
271
|
+
): boolean {
|
|
272
|
+
const directives = scanIncludeDirectives(source);
|
|
273
|
+
if (!shouldDefer) return directives.length > 0;
|
|
274
|
+
return directives.some((directive) => !shouldDefer(createIncludeReference(directive)));
|
|
275
|
+
}
|
|
276
|
+
|
|
253
277
|
function replaceCached(inner: string, fetcher: IncludeFetcher, cache: Map<string, string>): string {
|
|
254
278
|
const cached = cache.get(inner);
|
|
255
279
|
if (cached !== undefined) return cached;
|
|
@@ -40,6 +40,8 @@ export function scanIncludeDirectives(source: string): IncludeDirectiveMatch[] {
|
|
|
40
40
|
|
|
41
41
|
let depth = 0;
|
|
42
42
|
let linkDepth = 0;
|
|
43
|
+
const nestedQuotes: Array<number | null> = [];
|
|
44
|
+
const nestedQuoteAllowed: boolean[] = [];
|
|
43
45
|
let i = start;
|
|
44
46
|
let closeEnd = -1;
|
|
45
47
|
|
|
@@ -48,7 +50,37 @@ export function scanIncludeDirectives(source: string): IncludeDirectiveMatch[] {
|
|
|
48
50
|
const next = source.charCodeAt(i + 1);
|
|
49
51
|
const nextNext = source.charCodeAt(i + 2);
|
|
50
52
|
|
|
51
|
-
if (ch ===
|
|
53
|
+
if (ch === 10 || ch === 13) {
|
|
54
|
+
linkDepth = 0;
|
|
55
|
+
if (depth > 1) {
|
|
56
|
+
depth = 1;
|
|
57
|
+
nestedQuotes.length = 0;
|
|
58
|
+
nestedQuoteAllowed.length = 0;
|
|
59
|
+
}
|
|
60
|
+
i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const nestedQuote = nestedQuotes.at(-1);
|
|
65
|
+
if (nestedQuote !== undefined && nestedQuote !== null) {
|
|
66
|
+
if (ch === 92) {
|
|
67
|
+
i += 2;
|
|
68
|
+
} else {
|
|
69
|
+
if (ch === nestedQuote) {
|
|
70
|
+
nestedQuotes[nestedQuotes.length - 1] = null;
|
|
71
|
+
nestedQuoteAllowed[nestedQuoteAllowed.length - 1] = false;
|
|
72
|
+
}
|
|
73
|
+
i++;
|
|
74
|
+
}
|
|
75
|
+
} else if (
|
|
76
|
+
nestedQuote === null &&
|
|
77
|
+
nestedQuoteAllowed.at(-1) === true &&
|
|
78
|
+
(ch === 34 || ch === 39)
|
|
79
|
+
) {
|
|
80
|
+
nestedQuotes[nestedQuotes.length - 1] = ch;
|
|
81
|
+
nestedQuoteAllowed[nestedQuoteAllowed.length - 1] = false;
|
|
82
|
+
i++;
|
|
83
|
+
} else if (ch === OPEN_BRACKET && next === OPEN_BRACKET && nextNext === OPEN_BRACKET) {
|
|
52
84
|
linkDepth++;
|
|
53
85
|
i += 3;
|
|
54
86
|
} else if (
|
|
@@ -62,10 +94,28 @@ export function scanIncludeDirectives(source: string): IncludeDirectiveMatch[] {
|
|
|
62
94
|
} else if (linkDepth > 0) {
|
|
63
95
|
i++;
|
|
64
96
|
} else if (ch === OPEN_BRACKET && next === OPEN_BRACKET) {
|
|
97
|
+
if (nestedQuoteAllowed.length > 0) {
|
|
98
|
+
nestedQuoteAllowed[nestedQuoteAllowed.length - 1] = false;
|
|
99
|
+
}
|
|
100
|
+
if (depth > 0) {
|
|
101
|
+
nestedQuotes.push(null);
|
|
102
|
+
nestedQuoteAllowed.push(false);
|
|
103
|
+
}
|
|
65
104
|
depth++;
|
|
66
105
|
i += 2;
|
|
106
|
+
} else if (ch === CLOSE_BRACKET && next !== CLOSE_BRACKET && depth > 1) {
|
|
107
|
+
// A lone `]` is the first close of an intentionally incomplete nested
|
|
108
|
+
// token (for example `[[module ...]` passed through an include value).
|
|
109
|
+
depth--;
|
|
110
|
+
nestedQuotes.pop();
|
|
111
|
+
nestedQuoteAllowed.pop();
|
|
112
|
+
i++;
|
|
67
113
|
} else if (ch === CLOSE_BRACKET && next === CLOSE_BRACKET) {
|
|
68
114
|
const closeStart = i;
|
|
115
|
+
if (depth > 1) {
|
|
116
|
+
nestedQuotes.pop();
|
|
117
|
+
nestedQuoteAllowed.pop();
|
|
118
|
+
}
|
|
69
119
|
depth--;
|
|
70
120
|
i += 2;
|
|
71
121
|
|
|
@@ -83,6 +133,13 @@ export function scanIncludeDirectives(source: string): IncludeDirectiveMatch[] {
|
|
|
83
133
|
}
|
|
84
134
|
}
|
|
85
135
|
} else {
|
|
136
|
+
if (nestedQuoteAllowed.length > 0) {
|
|
137
|
+
if (ch === 61) {
|
|
138
|
+
nestedQuoteAllowed[nestedQuoteAllowed.length - 1] = true;
|
|
139
|
+
} else if (ch !== 32 && ch !== 9) {
|
|
140
|
+
nestedQuoteAllowed[nestedQuoteAllowed.length - 1] = false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
86
143
|
i++;
|
|
87
144
|
}
|
|
88
145
|
}
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
CodeBlockData,
|
|
4
4
|
Diagnostic,
|
|
5
5
|
Element,
|
|
6
|
+
PageRef,
|
|
6
7
|
TocEntry,
|
|
7
8
|
Version,
|
|
8
9
|
WikitextSettings,
|
|
@@ -26,6 +27,7 @@ export interface ParseContext {
|
|
|
26
27
|
trackPositions: boolean;
|
|
27
28
|
settings: WikitextSettings;
|
|
28
29
|
appendImplicitFootnoteBlock: boolean;
|
|
30
|
+
deferInclude?: (location: PageRef) => boolean;
|
|
29
31
|
footnotes: Element[][];
|
|
30
32
|
tocEntries: TocEntry[];
|
|
31
33
|
codeBlocks: CodeBlockData[];
|
package/src/pipeline/process.ts
CHANGED
|
@@ -4,13 +4,10 @@ import {
|
|
|
4
4
|
type PageRef,
|
|
5
5
|
type WikitextPageContext,
|
|
6
6
|
} from "@wdprlib/ast";
|
|
7
|
-
import {
|
|
7
|
+
import { parseWithIncludeDeferral } from "../parser/parse";
|
|
8
8
|
import { extractDataRequirements } from "../parser/rules/block/module/listpages/extract";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
type AsyncIncludeFetcher,
|
|
12
|
-
type IncludeDependency,
|
|
13
|
-
} from "../parser/rules/block/module/include";
|
|
9
|
+
import type { AsyncIncludeFetcher, IncludeDependency } from "../parser/rules/block/module/include";
|
|
10
|
+
import { resolveIncludesAsyncWithTraceSelective } from "../parser/rules/block/module/include/resolve";
|
|
14
11
|
import type { DataProvider } from "../parser/rules/block/module/types-common";
|
|
15
12
|
import { resolveModulesWithAsyncParse } from "../parser/rules/block/module/resolution/resolve-async";
|
|
16
13
|
import type {
|
|
@@ -42,37 +39,48 @@ export async function processWikitext<TPage extends WikitextPageContext>(
|
|
|
42
39
|
: undefined,
|
|
43
40
|
);
|
|
44
41
|
const resolveSource = async (input: string): Promise<string> => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
`Include expansion stopped after ${options.includeMaxIterations ?? 10} iterations.`,
|
|
56
|
-
),
|
|
42
|
+
let expanded = input;
|
|
43
|
+
if (fetchInclude) {
|
|
44
|
+
const resolution = await resolveIncludesAsyncWithTraceSelective(
|
|
45
|
+
input,
|
|
46
|
+
fetchInclude,
|
|
47
|
+
(reference) => isCurrentPageInclude(reference.location, options.page),
|
|
48
|
+
{
|
|
49
|
+
maxIterations: options.includeMaxIterations,
|
|
50
|
+
settings,
|
|
51
|
+
},
|
|
57
52
|
);
|
|
53
|
+
dependencies.push(...resolution.dependencies);
|
|
54
|
+
if (resolution.reachedMaxIterations) {
|
|
55
|
+
diagnostics.push(
|
|
56
|
+
createLimitDiagnostic(
|
|
57
|
+
"include-resolution-limit",
|
|
58
|
+
`Include expansion stopped after ${options.includeMaxIterations ?? 10} iterations.`,
|
|
59
|
+
),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
expanded = resolution.source;
|
|
58
63
|
}
|
|
59
|
-
return
|
|
64
|
+
return expanded;
|
|
60
65
|
};
|
|
61
66
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
const parseSource = async (input: string) => {
|
|
68
|
+
const expanded = await resolveSource(input);
|
|
69
|
+
return parseWithIncludeDeferral(
|
|
70
|
+
expanded,
|
|
71
|
+
{
|
|
72
|
+
settings,
|
|
73
|
+
pageTags: options.page.tags,
|
|
74
|
+
appendImplicitFootnoteBlock: false,
|
|
75
|
+
},
|
|
76
|
+
(location) => isCurrentPageInclude(location, options.page),
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const initial = await parseSource(source);
|
|
68
81
|
diagnostics.push(...initial.diagnostics);
|
|
69
82
|
const dataProvider = createModuleDataProvider(options, callbackContext);
|
|
70
|
-
const parseFragment =
|
|
71
|
-
parse(await resolveSource(fragmentSource), {
|
|
72
|
-
settings,
|
|
73
|
-
pageTags: options.page.tags,
|
|
74
|
-
appendImplicitFootnoteBlock: false,
|
|
75
|
-
});
|
|
83
|
+
const parseFragment = parseSource;
|
|
76
84
|
let ast = initial.ast;
|
|
77
85
|
|
|
78
86
|
for (let pass = 0; pass < DEFAULT_MODULE_MAX_PASSES; pass++) {
|
|
@@ -111,6 +119,15 @@ export async function processWikitext<TPage extends WikitextPageContext>(
|
|
|
111
119
|
};
|
|
112
120
|
}
|
|
113
121
|
|
|
122
|
+
function isCurrentPageInclude(location: PageRef, page: WikitextPageContext): boolean {
|
|
123
|
+
if (location.site !== null) {
|
|
124
|
+
if (!page.site || location.site.toLowerCase() !== page.site.toLowerCase()) return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const target = location.page.toLowerCase();
|
|
128
|
+
return target === page.fullName.toLowerCase() || target === page.unixName?.toLowerCase();
|
|
129
|
+
}
|
|
130
|
+
|
|
114
131
|
function hasResolvableRequirements(
|
|
115
132
|
requirements: ReturnType<typeof extractDataRequirements>["requirements"],
|
|
116
133
|
provider:
|