@wdprlib/parser 4.4.0 → 5.0.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/dist/index.cjs +163 -44
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +166 -45
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/parser/preprocess/expr/index.ts +26 -0
- package/src/parser/preprocess/utils/raw-regions.ts +16 -7
- package/src/parser/rules/block/module/index.ts +2 -0
- package/src/parser/rules/block/module/listpages/index.ts +2 -0
- package/src/parser/rules/block/module/listpages/selectors.ts +64 -0
- package/src/parser/rules/block/module/listpages/types/external-data.ts +22 -0
- package/src/pipeline/process.ts +84 -17
package/dist/index.cjs
CHANGED
|
@@ -50,6 +50,7 @@ __export(exports_src, {
|
|
|
50
50
|
parse: () => parse,
|
|
51
51
|
paragraph: () => import_ast8.paragraph,
|
|
52
52
|
normalizeQuery: () => normalizeQuery,
|
|
53
|
+
matchesListPagesSelectors: () => matchesListPagesSelectors,
|
|
53
54
|
listItemSubList: () => import_ast8.listItemSubList,
|
|
54
55
|
listItemElements: () => import_ast8.listItemElements,
|
|
55
56
|
list: () => import_ast8.list,
|
|
@@ -63,6 +64,7 @@ __export(exports_src, {
|
|
|
63
64
|
extractListUsersVariables: () => extractListUsersVariables,
|
|
64
65
|
extractIncludeReferences: () => extractIncludeReferences,
|
|
65
66
|
extractDataRequirements: () => extractDataRequirements,
|
|
67
|
+
definePageData: () => definePageData,
|
|
66
68
|
createToken: () => createToken,
|
|
67
69
|
createSettings: () => import_ast9.createSettings,
|
|
68
70
|
createPosition: () => import_ast8.createPosition,
|
|
@@ -5919,6 +5921,60 @@ function resolveListPages(module2, data, compiledTemplate, parse) {
|
|
|
5919
5921
|
const items = renderListPagesItems(module2, data, compiledTemplate, parse);
|
|
5920
5922
|
return wrapListPagesResult(module2, items, parse);
|
|
5921
5923
|
}
|
|
5924
|
+
// packages/parser/src/parser/rules/block/module/listpages/types/external-data.ts
|
|
5925
|
+
function definePageData(input) {
|
|
5926
|
+
const separator = input.fullname.indexOf(":");
|
|
5927
|
+
const category = separator === -1 ? "_default" : input.fullname.slice(0, separator);
|
|
5928
|
+
const name = separator === -1 ? input.fullname : input.fullname.slice(separator + 1);
|
|
5929
|
+
return {
|
|
5930
|
+
...input,
|
|
5931
|
+
name: input.name ?? name,
|
|
5932
|
+
category: input.category ?? category,
|
|
5933
|
+
hiddenTags: input.hiddenTags ?? [],
|
|
5934
|
+
children: input.children ?? 0,
|
|
5935
|
+
comments: input.comments ?? 0,
|
|
5936
|
+
size: input.size ?? 0,
|
|
5937
|
+
rating: input.rating ?? 0,
|
|
5938
|
+
ratingVotes: input.ratingVotes ?? 0,
|
|
5939
|
+
revisions: input.revisions ?? 0
|
|
5940
|
+
};
|
|
5941
|
+
}
|
|
5942
|
+
// packages/parser/src/parser/rules/block/module/listpages/selectors.ts
|
|
5943
|
+
function matchesListPagesSelectors(page, query, currentPage) {
|
|
5944
|
+
return matchesCategory(page.category, query.category, currentPage.category) && matchesTags(page, query.tags, currentPage);
|
|
5945
|
+
}
|
|
5946
|
+
function matchesCategory(category, selector, currentCategory) {
|
|
5947
|
+
if (!selector)
|
|
5948
|
+
return category === currentCategory;
|
|
5949
|
+
if (selector.exclude.includes(category))
|
|
5950
|
+
return false;
|
|
5951
|
+
const hasPositiveSelector = selector.all || selector.current || selector.include.length > 0;
|
|
5952
|
+
return !hasPositiveSelector || selector.all || selector.current && category === currentCategory || selector.include.includes(category);
|
|
5953
|
+
}
|
|
5954
|
+
function matchesTags(page, selector, currentPage) {
|
|
5955
|
+
if (!selector)
|
|
5956
|
+
return true;
|
|
5957
|
+
const allTags = new Set([...page.tags, ...page.hiddenTags]);
|
|
5958
|
+
if (selector.all.some((tag) => !allTags.has(tag)))
|
|
5959
|
+
return false;
|
|
5960
|
+
if (selector.any.length > 0 && !selector.any.some((tag) => allTags.has(tag)))
|
|
5961
|
+
return false;
|
|
5962
|
+
if (selector.none.some((tag) => allTags.has(tag)))
|
|
5963
|
+
return false;
|
|
5964
|
+
if (selector.special === "none")
|
|
5965
|
+
return allTags.size === 0;
|
|
5966
|
+
const currentTags = new Set(currentPage.tags.filter((tag) => !tag.startsWith("_")));
|
|
5967
|
+
if (selector.special === "same-visible") {
|
|
5968
|
+
return page.tags.some((tag) => currentTags.has(tag));
|
|
5969
|
+
}
|
|
5970
|
+
if (selector.special === "same-all") {
|
|
5971
|
+
return setsEqual(new Set(page.tags), currentTags);
|
|
5972
|
+
}
|
|
5973
|
+
return true;
|
|
5974
|
+
}
|
|
5975
|
+
function setsEqual(left, right) {
|
|
5976
|
+
return left.size === right.size && [...left].every((value) => right.has(value));
|
|
5977
|
+
}
|
|
5922
5978
|
// packages/parser/src/parser/rules/block/module/listpages/url-resolution/fields.ts
|
|
5923
5979
|
var URL_RESOLVABLE_FIELDS = [
|
|
5924
5980
|
{ attr: "offset", queryKey: "offset", type: "number" },
|
|
@@ -6348,13 +6404,20 @@ var BASE_PLACEHOLDER_OPEN = "";
|
|
|
6348
6404
|
var BASE_PLACEHOLDER_CLOSE = "";
|
|
6349
6405
|
var RAW_BLOCK_OPEN_PATTERN = /\[\[\s*(code|html)\b[^\]]*\]\]/iy;
|
|
6350
6406
|
function makeUniqueSentinels(source) {
|
|
6351
|
-
let
|
|
6352
|
-
let
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6407
|
+
let openRun = 0;
|
|
6408
|
+
let closeRun = 0;
|
|
6409
|
+
let longestOpenRun = 0;
|
|
6410
|
+
let longestCloseRun = 0;
|
|
6411
|
+
for (const char of source) {
|
|
6412
|
+
openRun = char === BASE_PLACEHOLDER_OPEN ? openRun + 1 : 0;
|
|
6413
|
+
closeRun = char === BASE_PLACEHOLDER_CLOSE ? closeRun + 1 : 0;
|
|
6414
|
+
longestOpenRun = Math.max(longestOpenRun, openRun);
|
|
6415
|
+
longestCloseRun = Math.max(longestCloseRun, closeRun);
|
|
6356
6416
|
}
|
|
6357
|
-
return {
|
|
6417
|
+
return {
|
|
6418
|
+
open: BASE_PLACEHOLDER_OPEN.repeat(longestOpenRun + 1),
|
|
6419
|
+
close: BASE_PLACEHOLDER_CLOSE.repeat(longestCloseRun + 1)
|
|
6420
|
+
};
|
|
6358
6421
|
}
|
|
6359
6422
|
function maskRawRegions(source, sentinels) {
|
|
6360
6423
|
const placeholders = [];
|
|
@@ -13754,30 +13817,6 @@ function preprocess(text) {
|
|
|
13754
13817
|
return result;
|
|
13755
13818
|
}
|
|
13756
13819
|
|
|
13757
|
-
// packages/parser/src/parser/preprocess/expr/evaluate.ts
|
|
13758
|
-
var import_ast6 = require("@wdprlib/ast");
|
|
13759
|
-
function evaluateDirective(kind, match) {
|
|
13760
|
-
if (kind === "expr") {
|
|
13761
|
-
const result2 = import_ast6.evaluateExpression(match.head);
|
|
13762
|
-
if (result2.success)
|
|
13763
|
-
return import_ast6.formatExprValue(result2.value);
|
|
13764
|
-
if (result2.error === "empty expression")
|
|
13765
|
-
return "";
|
|
13766
|
-
return "ERROR";
|
|
13767
|
-
}
|
|
13768
|
-
if (kind === "if") {
|
|
13769
|
-
if (!match.hasPipe)
|
|
13770
|
-
return "";
|
|
13771
|
-
return import_ast6.isTruthy(match.head) ? match.thenText : match.elseText;
|
|
13772
|
-
}
|
|
13773
|
-
if (!match.hasPipe)
|
|
13774
|
-
return "";
|
|
13775
|
-
const result = import_ast6.evaluateExpression(match.head);
|
|
13776
|
-
if (!result.success)
|
|
13777
|
-
return "ERROR";
|
|
13778
|
-
return result.value !== 0 && !Number.isNaN(result.value) ? match.thenText : match.elseText;
|
|
13779
|
-
}
|
|
13780
|
-
|
|
13781
13820
|
// packages/parser/src/parser/preprocess/expr/chars.ts
|
|
13782
13821
|
function isWhitespace(ch) {
|
|
13783
13822
|
return ch === " " || ch === "\t" || ch === `
|
|
@@ -13806,6 +13845,30 @@ function matchDirectiveKind(source, i) {
|
|
|
13806
13845
|
return null;
|
|
13807
13846
|
}
|
|
13808
13847
|
|
|
13848
|
+
// packages/parser/src/parser/preprocess/expr/evaluate.ts
|
|
13849
|
+
var import_ast6 = require("@wdprlib/ast");
|
|
13850
|
+
function evaluateDirective(kind, match) {
|
|
13851
|
+
if (kind === "expr") {
|
|
13852
|
+
const result2 = import_ast6.evaluateExpression(match.head);
|
|
13853
|
+
if (result2.success)
|
|
13854
|
+
return import_ast6.formatExprValue(result2.value);
|
|
13855
|
+
if (result2.error === "empty expression")
|
|
13856
|
+
return "";
|
|
13857
|
+
return "ERROR";
|
|
13858
|
+
}
|
|
13859
|
+
if (kind === "if") {
|
|
13860
|
+
if (!match.hasPipe)
|
|
13861
|
+
return "";
|
|
13862
|
+
return import_ast6.isTruthy(match.head) ? match.thenText : match.elseText;
|
|
13863
|
+
}
|
|
13864
|
+
if (!match.hasPipe)
|
|
13865
|
+
return "";
|
|
13866
|
+
const result = import_ast6.evaluateExpression(match.head);
|
|
13867
|
+
if (!result.success)
|
|
13868
|
+
return "ERROR";
|
|
13869
|
+
return result.value !== 0 && !Number.isNaN(result.value) ? match.thenText : match.elseText;
|
|
13870
|
+
}
|
|
13871
|
+
|
|
13809
13872
|
// packages/parser/src/parser/preprocess/expr/parse.ts
|
|
13810
13873
|
function tryParseInnermostDirective(source, start, kind) {
|
|
13811
13874
|
const keywordLen = kind === "ifexpr" ? 6 : kind === "expr" ? 4 : 2;
|
|
@@ -13907,11 +13970,14 @@ function expandInnermost(source) {
|
|
|
13907
13970
|
}
|
|
13908
13971
|
|
|
13909
13972
|
// packages/parser/src/parser/preprocess/expr/index.ts
|
|
13973
|
+
var MAX_EXPR_NESTING = 64;
|
|
13910
13974
|
function preprocessExpr(source) {
|
|
13911
13975
|
if (!source.includes("[[#"))
|
|
13912
13976
|
return source;
|
|
13913
13977
|
const sentinels = makeUniqueSentinels(source);
|
|
13914
13978
|
const { masked, placeholders } = maskRawRegions(source, sentinels);
|
|
13979
|
+
if (exceedsExprNestingLimit(masked))
|
|
13980
|
+
return source;
|
|
13915
13981
|
const reduced = reduceExpr(masked);
|
|
13916
13982
|
return restorePlaceholders(reduced, placeholders, sentinels);
|
|
13917
13983
|
}
|
|
@@ -13926,6 +13992,26 @@ function reduceExpr(source) {
|
|
|
13926
13992
|
}
|
|
13927
13993
|
return current2;
|
|
13928
13994
|
}
|
|
13995
|
+
function exceedsExprNestingLimit(source) {
|
|
13996
|
+
const expressionStack = [];
|
|
13997
|
+
let expressionDepth = 0;
|
|
13998
|
+
for (let i = 0;i < source.length; i++) {
|
|
13999
|
+
if (source.startsWith("[[", i)) {
|
|
14000
|
+
const isExpression = matchDirectiveKind(source, i) !== null;
|
|
14001
|
+
expressionStack.push(isExpression);
|
|
14002
|
+
if (isExpression && ++expressionDepth > MAX_EXPR_NESTING)
|
|
14003
|
+
return true;
|
|
14004
|
+
i++;
|
|
14005
|
+
continue;
|
|
14006
|
+
}
|
|
14007
|
+
if (source.startsWith("]]", i)) {
|
|
14008
|
+
if (expressionStack.pop())
|
|
14009
|
+
expressionDepth--;
|
|
14010
|
+
i++;
|
|
14011
|
+
}
|
|
14012
|
+
}
|
|
14013
|
+
return false;
|
|
14014
|
+
}
|
|
13929
14015
|
|
|
13930
14016
|
// packages/parser/src/parser/parse/source.ts
|
|
13931
14017
|
function prepareSourceForParse(source, options) {
|
|
@@ -14129,6 +14215,11 @@ async function resolveElementChildren(element, context, state) {
|
|
|
14129
14215
|
}
|
|
14130
14216
|
|
|
14131
14217
|
// packages/parser/src/pipeline/process.ts
|
|
14218
|
+
var DEFAULT_MODULE_MAX_PASSES = 5;
|
|
14219
|
+
var PIPELINE_DIAGNOSTIC_POSITION = {
|
|
14220
|
+
start: { line: 1, column: 1, offset: 0 },
|
|
14221
|
+
end: { line: 1, column: 1, offset: 0 }
|
|
14222
|
+
};
|
|
14132
14223
|
async function processWikitext(source, options) {
|
|
14133
14224
|
const settings = options.settings ?? import_ast7.DEFAULT_SETTINGS;
|
|
14134
14225
|
const callbackContext = {
|
|
@@ -14136,6 +14227,7 @@ async function processWikitext(source, options) {
|
|
|
14136
14227
|
settings
|
|
14137
14228
|
};
|
|
14138
14229
|
const dependencies = [];
|
|
14230
|
+
const diagnostics = [];
|
|
14139
14231
|
const fetchInclude = createRequestIncludeFetcher(options.dataProvider?.fetchInclude ? (pageRef) => options.dataProvider.fetchInclude(pageRef, callbackContext) : undefined);
|
|
14140
14232
|
const resolveSource = async (input) => {
|
|
14141
14233
|
if (!fetchInclude)
|
|
@@ -14145,6 +14237,9 @@ async function processWikitext(source, options) {
|
|
|
14145
14237
|
settings
|
|
14146
14238
|
});
|
|
14147
14239
|
dependencies.push(...resolution.dependencies);
|
|
14240
|
+
if (resolution.reachedMaxIterations) {
|
|
14241
|
+
diagnostics.push(createLimitDiagnostic("include-resolution-limit", `Include expansion stopped after ${options.includeMaxIterations ?? 10} iterations.`));
|
|
14242
|
+
}
|
|
14148
14243
|
return resolution.source;
|
|
14149
14244
|
};
|
|
14150
14245
|
const expandedSource = await resolveSource(source);
|
|
@@ -14153,28 +14248,52 @@ async function processWikitext(source, options) {
|
|
|
14153
14248
|
pageTags: options.page.tags,
|
|
14154
14249
|
appendImplicitFootnoteBlock: false
|
|
14155
14250
|
});
|
|
14156
|
-
|
|
14251
|
+
diagnostics.push(...initial.diagnostics);
|
|
14157
14252
|
const dataProvider = createModuleDataProvider(options, callbackContext);
|
|
14158
|
-
const
|
|
14159
|
-
|
|
14160
|
-
|
|
14161
|
-
|
|
14162
|
-
appendImplicitFootnoteBlock: false
|
|
14163
|
-
}),
|
|
14164
|
-
compiledListPagesTemplates: extraction.compiledListPagesTemplates,
|
|
14165
|
-
compiledListUsersTemplates: extraction.compiledListUsersTemplates,
|
|
14166
|
-
requirements: extraction.requirements,
|
|
14167
|
-
urlPath: options.page.urlPath,
|
|
14168
|
-
pageTags: options.page.tags
|
|
14253
|
+
const parseFragment = async (fragmentSource) => parse(await resolveSource(fragmentSource), {
|
|
14254
|
+
settings,
|
|
14255
|
+
pageTags: options.page.tags,
|
|
14256
|
+
appendImplicitFootnoteBlock: false
|
|
14169
14257
|
});
|
|
14258
|
+
let ast = initial.ast;
|
|
14259
|
+
for (let pass = 0;pass < DEFAULT_MODULE_MAX_PASSES; pass++) {
|
|
14260
|
+
const extraction = extractDataRequirements(ast);
|
|
14261
|
+
if (pass > 0 && !hasResolvableRequirements(extraction.requirements, options.dataProvider)) {
|
|
14262
|
+
break;
|
|
14263
|
+
}
|
|
14264
|
+
const resolved = await resolveModulesWithAsyncParse(ast, dataProvider, {
|
|
14265
|
+
parse: parseFragment,
|
|
14266
|
+
compiledListPagesTemplates: extraction.compiledListPagesTemplates,
|
|
14267
|
+
compiledListUsersTemplates: extraction.compiledListUsersTemplates,
|
|
14268
|
+
requirements: extraction.requirements,
|
|
14269
|
+
urlPath: options.page.urlPath,
|
|
14270
|
+
pageTags: options.page.tags
|
|
14271
|
+
});
|
|
14272
|
+
ast = resolved.ast;
|
|
14273
|
+
diagnostics.push(...resolved.diagnostics);
|
|
14274
|
+
}
|
|
14275
|
+
if (hasResolvableRequirements(extractDataRequirements(ast).requirements, options.dataProvider)) {
|
|
14276
|
+
diagnostics.push(createLimitDiagnostic("module-resolution-limit", `Module resolution stopped after ${DEFAULT_MODULE_MAX_PASSES} passes.`));
|
|
14277
|
+
}
|
|
14170
14278
|
return {
|
|
14171
|
-
ast
|
|
14279
|
+
ast,
|
|
14172
14280
|
page: options.page,
|
|
14173
14281
|
settings,
|
|
14174
|
-
diagnostics
|
|
14282
|
+
diagnostics,
|
|
14175
14283
|
dependencies
|
|
14176
14284
|
};
|
|
14177
14285
|
}
|
|
14286
|
+
function hasResolvableRequirements(requirements, provider) {
|
|
14287
|
+
return Boolean(provider?.fetchListPages && requirements.listPages.length > 0 || provider?.fetchListUsers && requirements.listUsers.length > 0 || provider?.fetchTagCloud && requirements.tagCloud.length > 0);
|
|
14288
|
+
}
|
|
14289
|
+
function createLimitDiagnostic(code, message) {
|
|
14290
|
+
return {
|
|
14291
|
+
severity: "warning",
|
|
14292
|
+
code,
|
|
14293
|
+
message,
|
|
14294
|
+
position: PIPELINE_DIAGNOSTIC_POSITION
|
|
14295
|
+
};
|
|
14296
|
+
}
|
|
14178
14297
|
function createModuleDataProvider(options, context) {
|
|
14179
14298
|
const provider = options.dataProvider;
|
|
14180
14299
|
return {
|
package/dist/index.d.cts
CHANGED
|
@@ -674,6 +674,8 @@ interface PageData {
|
|
|
674
674
|
ratingPercent?: number;
|
|
675
675
|
revisions: number;
|
|
676
676
|
}
|
|
677
|
+
type PageDataInput = Pick<PageData, "fullname" | "title" | "createdAt" | "updatedAt" | "tags"> & Partial<Omit<PageData, "fullname" | "title" | "createdAt" | "updatedAt" | "tags">>;
|
|
678
|
+
declare function definePageData(input: PageDataInput): PageData;
|
|
677
679
|
/**
|
|
678
680
|
* Site context information.
|
|
679
681
|
*/
|
|
@@ -967,6 +969,12 @@ interface ExtractionResult {
|
|
|
967
969
|
* @returns Extraction result containing requirements and compiled templates
|
|
968
970
|
*/
|
|
969
971
|
declare function extractDataRequirements(ast: SyntaxTree3): ExtractionResult;
|
|
972
|
+
type SelectorPage = Pick<PageData, "category" | "tags" | "hiddenTags">;
|
|
973
|
+
type CurrentPage = {
|
|
974
|
+
category: string;
|
|
975
|
+
tags: readonly string[];
|
|
976
|
+
};
|
|
977
|
+
declare function matchesListPagesSelectors(page: SelectorPage, query: Pick<NormalizedListPagesQuery, "category" | "tags">, currentPage: CurrentPage): boolean;
|
|
970
978
|
/**
|
|
971
979
|
* Compile a ListPages template string into an executable function.
|
|
972
980
|
*
|
|
@@ -1192,4 +1200,4 @@ interface ResolveOptions {
|
|
|
1192
1200
|
*/
|
|
1193
1201
|
declare function resolveModules(ast: SyntaxTree4, dataProvider: DataProvider, options: ResolveOptions): Promise<SyntaxTree4>;
|
|
1194
1202
|
import { STYLE_SLOT_PREFIX } from "@wdprlib/ast";
|
|
1195
|
-
export { tokenize, text, resolveTagCloud, resolveModules, resolveListUsers, resolveIncludesWithTrace, resolveIncludesAsyncWithTrace, resolveIncludesAsync, resolveIncludes, processWikitext, preprocessIftags, parseTags, parseParent, parseOrder, parseNumericSelector, parseDateSelector, parseCategory, parse, paragraph, normalizeQuery, listItemSubList, listItemElements, list, link, lineBreak, italics, isTagCloudModule, isListUsersModule, horizontalRule, heading, extractListUsersVariables, extractIncludeReferences, extractDataRequirements, createToken, createSettings, createPosition, createPoint, container, compileTemplate, compileListUsersTemplate, bold, WikitextSettings5 as WikitextSettings, WikitextMode, Version2 as Version, VariableMap, VariableContext, UserInfo, TokenType, Token, TocEntry2 as TocEntry, TagCloudTagData, TagCloudModuleData, TagCloudExternalData, TagCloudDataRequirement, TagCloudDataFetcher, TableRow, TableData, TableCell, TabData, SyntaxTree5 as SyntaxTree, SiteContext, STYLE_SLOT_PREFIX, ResolveOptions, ResolveIncludesTraceResult, ResolveIncludesOptions, ProcessedWikitextDocument, ProcessWikitextOptions, ProcessWikitextDataProvider, ProcessWikitextCallbackContext, Position2 as Position, Point, ParserOptions, Parser, ParseResult4 as ParseResult, ParseFunction, PageRef4 as PageRef, PageData, NormalizedTags, NormalizedParent, NormalizedOrder, NormalizedNumericSelector, NormalizedListPagesQuery, NormalizedDateSelector, NormalizedCategory, ModuleSourceTransform, ModuleParseResult, Module5 as Module, ListUsersVariableContext, ListUsersVariable, ListUsersUserData, ListUsersExternalData, ListUsersDataRequirement, ListUsersDataFetcher, ListUsersCompiledTemplate, ListType, ListPagesVariable, ListPagesQuery, ListPagesExternalData, ListPagesDataRequirement, ListPagesDataFetcher, ListItem, ListData, LinkType, LinkLocation, LinkLabel, LexerOptions, Lexer, IncludeReference, IncludeIterationTrace, IncludeFetcher, IncludeDependency, ImageSource, HeadingLevel, Heading, HeaderType, GallerySize, GalleryOrder, GalleryItem, GalleryData, GalleryContent, FloatAlignment, ExtractionResult, Embed, Element8 as Element, DiagnosticSeverity, Diagnostic4 as Diagnostic, DefinitionListItem, DateItem, DataRequirements, DataProvider, DEFAULT_SETTINGS, ContainerType, ContainerData, CompiledTemplate, CollapsibleData, CodeBlockData2 as CodeBlockData, ClearFloat, AttributeMap, AsyncIncludeFetcher, AnchorTarget, Alignment, AlignType };
|
|
1203
|
+
export { tokenize, text, resolveTagCloud, resolveModules, resolveListUsers, resolveIncludesWithTrace, resolveIncludesAsyncWithTrace, resolveIncludesAsync, resolveIncludes, processWikitext, preprocessIftags, parseTags, parseParent, parseOrder, parseNumericSelector, parseDateSelector, parseCategory, parse, paragraph, normalizeQuery, matchesListPagesSelectors, listItemSubList, listItemElements, list, link, lineBreak, italics, isTagCloudModule, isListUsersModule, horizontalRule, heading, extractListUsersVariables, extractIncludeReferences, extractDataRequirements, definePageData, createToken, createSettings, createPosition, createPoint, container, compileTemplate, compileListUsersTemplate, bold, WikitextSettings5 as WikitextSettings, WikitextMode, Version2 as Version, VariableMap, VariableContext, UserInfo, TokenType, Token, TocEntry2 as TocEntry, TagCloudTagData, TagCloudModuleData, TagCloudExternalData, TagCloudDataRequirement, TagCloudDataFetcher, TableRow, TableData, TableCell, TabData, SyntaxTree5 as SyntaxTree, SiteContext, STYLE_SLOT_PREFIX, ResolveOptions, ResolveIncludesTraceResult, ResolveIncludesOptions, ProcessedWikitextDocument, ProcessWikitextOptions, ProcessWikitextDataProvider, ProcessWikitextCallbackContext, Position2 as Position, Point, ParserOptions, Parser, ParseResult4 as ParseResult, ParseFunction, PageRef4 as PageRef, PageData, NormalizedTags, NormalizedParent, NormalizedOrder, NormalizedNumericSelector, NormalizedListPagesQuery, NormalizedDateSelector, NormalizedCategory, ModuleSourceTransform, ModuleParseResult, Module5 as Module, ListUsersVariableContext, ListUsersVariable, ListUsersUserData, ListUsersExternalData, ListUsersDataRequirement, ListUsersDataFetcher, ListUsersCompiledTemplate, ListType, ListPagesVariable, ListPagesQuery, ListPagesExternalData, ListPagesDataRequirement, ListPagesDataFetcher, ListItem, ListData, LinkType, LinkLocation, LinkLabel, LexerOptions, Lexer, IncludeReference, IncludeIterationTrace, IncludeFetcher, IncludeDependency, ImageSource, HeadingLevel, Heading, HeaderType, GallerySize, GalleryOrder, GalleryItem, GalleryData, GalleryContent, FloatAlignment, ExtractionResult, Embed, Element8 as Element, DiagnosticSeverity, Diagnostic4 as Diagnostic, DefinitionListItem, DateItem, DataRequirements, DataProvider, DEFAULT_SETTINGS, ContainerType, ContainerData, CompiledTemplate, CollapsibleData, CodeBlockData2 as CodeBlockData, ClearFloat, AttributeMap, AsyncIncludeFetcher, AnchorTarget, Alignment, AlignType };
|
package/dist/index.d.ts
CHANGED
|
@@ -674,6 +674,8 @@ interface PageData {
|
|
|
674
674
|
ratingPercent?: number;
|
|
675
675
|
revisions: number;
|
|
676
676
|
}
|
|
677
|
+
type PageDataInput = Pick<PageData, "fullname" | "title" | "createdAt" | "updatedAt" | "tags"> & Partial<Omit<PageData, "fullname" | "title" | "createdAt" | "updatedAt" | "tags">>;
|
|
678
|
+
declare function definePageData(input: PageDataInput): PageData;
|
|
677
679
|
/**
|
|
678
680
|
* Site context information.
|
|
679
681
|
*/
|
|
@@ -967,6 +969,12 @@ interface ExtractionResult {
|
|
|
967
969
|
* @returns Extraction result containing requirements and compiled templates
|
|
968
970
|
*/
|
|
969
971
|
declare function extractDataRequirements(ast: SyntaxTree3): ExtractionResult;
|
|
972
|
+
type SelectorPage = Pick<PageData, "category" | "tags" | "hiddenTags">;
|
|
973
|
+
type CurrentPage = {
|
|
974
|
+
category: string;
|
|
975
|
+
tags: readonly string[];
|
|
976
|
+
};
|
|
977
|
+
declare function matchesListPagesSelectors(page: SelectorPage, query: Pick<NormalizedListPagesQuery, "category" | "tags">, currentPage: CurrentPage): boolean;
|
|
970
978
|
/**
|
|
971
979
|
* Compile a ListPages template string into an executable function.
|
|
972
980
|
*
|
|
@@ -1192,4 +1200,4 @@ interface ResolveOptions {
|
|
|
1192
1200
|
*/
|
|
1193
1201
|
declare function resolveModules(ast: SyntaxTree4, dataProvider: DataProvider, options: ResolveOptions): Promise<SyntaxTree4>;
|
|
1194
1202
|
import { STYLE_SLOT_PREFIX } from "@wdprlib/ast";
|
|
1195
|
-
export { tokenize, text, resolveTagCloud, resolveModules, resolveListUsers, resolveIncludesWithTrace, resolveIncludesAsyncWithTrace, resolveIncludesAsync, resolveIncludes, processWikitext, preprocessIftags, parseTags, parseParent, parseOrder, parseNumericSelector, parseDateSelector, parseCategory, parse, paragraph, normalizeQuery, listItemSubList, listItemElements, list, link, lineBreak, italics, isTagCloudModule, isListUsersModule, horizontalRule, heading, extractListUsersVariables, extractIncludeReferences, extractDataRequirements, createToken, createSettings, createPosition, createPoint, container, compileTemplate, compileListUsersTemplate, bold, WikitextSettings5 as WikitextSettings, WikitextMode, Version2 as Version, VariableMap, VariableContext, UserInfo, TokenType, Token, TocEntry2 as TocEntry, TagCloudTagData, TagCloudModuleData, TagCloudExternalData, TagCloudDataRequirement, TagCloudDataFetcher, TableRow, TableData, TableCell, TabData, SyntaxTree5 as SyntaxTree, SiteContext, STYLE_SLOT_PREFIX, ResolveOptions, ResolveIncludesTraceResult, ResolveIncludesOptions, ProcessedWikitextDocument, ProcessWikitextOptions, ProcessWikitextDataProvider, ProcessWikitextCallbackContext, Position2 as Position, Point, ParserOptions, Parser, ParseResult4 as ParseResult, ParseFunction, PageRef4 as PageRef, PageData, NormalizedTags, NormalizedParent, NormalizedOrder, NormalizedNumericSelector, NormalizedListPagesQuery, NormalizedDateSelector, NormalizedCategory, ModuleSourceTransform, ModuleParseResult, Module5 as Module, ListUsersVariableContext, ListUsersVariable, ListUsersUserData, ListUsersExternalData, ListUsersDataRequirement, ListUsersDataFetcher, ListUsersCompiledTemplate, ListType, ListPagesVariable, ListPagesQuery, ListPagesExternalData, ListPagesDataRequirement, ListPagesDataFetcher, ListItem, ListData, LinkType, LinkLocation, LinkLabel, LexerOptions, Lexer, IncludeReference, IncludeIterationTrace, IncludeFetcher, IncludeDependency, ImageSource, HeadingLevel, Heading, HeaderType, GallerySize, GalleryOrder, GalleryItem, GalleryData, GalleryContent, FloatAlignment, ExtractionResult, Embed, Element8 as Element, DiagnosticSeverity, Diagnostic4 as Diagnostic, DefinitionListItem, DateItem, DataRequirements, DataProvider, DEFAULT_SETTINGS, ContainerType, ContainerData, CompiledTemplate, CollapsibleData, CodeBlockData2 as CodeBlockData, ClearFloat, AttributeMap, AsyncIncludeFetcher, AnchorTarget, Alignment, AlignType };
|
|
1203
|
+
export { tokenize, text, resolveTagCloud, resolveModules, resolveListUsers, resolveIncludesWithTrace, resolveIncludesAsyncWithTrace, resolveIncludesAsync, resolveIncludes, processWikitext, preprocessIftags, parseTags, parseParent, parseOrder, parseNumericSelector, parseDateSelector, parseCategory, parse, paragraph, normalizeQuery, matchesListPagesSelectors, listItemSubList, listItemElements, list, link, lineBreak, italics, isTagCloudModule, isListUsersModule, horizontalRule, heading, extractListUsersVariables, extractIncludeReferences, extractDataRequirements, definePageData, createToken, createSettings, createPosition, createPoint, container, compileTemplate, compileListUsersTemplate, bold, WikitextSettings5 as WikitextSettings, WikitextMode, Version2 as Version, VariableMap, VariableContext, UserInfo, TokenType, Token, TocEntry2 as TocEntry, TagCloudTagData, TagCloudModuleData, TagCloudExternalData, TagCloudDataRequirement, TagCloudDataFetcher, TableRow, TableData, TableCell, TabData, SyntaxTree5 as SyntaxTree, SiteContext, STYLE_SLOT_PREFIX, ResolveOptions, ResolveIncludesTraceResult, ResolveIncludesOptions, ProcessedWikitextDocument, ProcessWikitextOptions, ProcessWikitextDataProvider, ProcessWikitextCallbackContext, Position2 as Position, Point, ParserOptions, Parser, ParseResult4 as ParseResult, ParseFunction, PageRef4 as PageRef, PageData, NormalizedTags, NormalizedParent, NormalizedOrder, NormalizedNumericSelector, NormalizedListPagesQuery, NormalizedDateSelector, NormalizedCategory, ModuleSourceTransform, ModuleParseResult, Module5 as Module, ListUsersVariableContext, ListUsersVariable, ListUsersUserData, ListUsersExternalData, ListUsersDataRequirement, ListUsersDataFetcher, ListUsersCompiledTemplate, ListType, ListPagesVariable, ListPagesQuery, ListPagesExternalData, ListPagesDataRequirement, ListPagesDataFetcher, ListItem, ListData, LinkType, LinkLocation, LinkLabel, LexerOptions, Lexer, IncludeReference, IncludeIterationTrace, IncludeFetcher, IncludeDependency, ImageSource, HeadingLevel, Heading, HeaderType, GallerySize, GalleryOrder, GalleryItem, GalleryData, GalleryContent, FloatAlignment, ExtractionResult, Embed, Element8 as Element, DiagnosticSeverity, Diagnostic4 as Diagnostic, DefinitionListItem, DateItem, DataRequirements, DataProvider, DEFAULT_SETTINGS, ContainerType, ContainerData, CompiledTemplate, CollapsibleData, CodeBlockData2 as CodeBlockData, ClearFloat, AttributeMap, AsyncIncludeFetcher, AnchorTarget, Alignment, AlignType };
|
package/dist/index.js
CHANGED
|
@@ -5856,6 +5856,60 @@ function resolveListPages(module, data, compiledTemplate, parse) {
|
|
|
5856
5856
|
const items = renderListPagesItems(module, data, compiledTemplate, parse);
|
|
5857
5857
|
return wrapListPagesResult(module, items, parse);
|
|
5858
5858
|
}
|
|
5859
|
+
// packages/parser/src/parser/rules/block/module/listpages/types/external-data.ts
|
|
5860
|
+
function definePageData(input) {
|
|
5861
|
+
const separator = input.fullname.indexOf(":");
|
|
5862
|
+
const category = separator === -1 ? "_default" : input.fullname.slice(0, separator);
|
|
5863
|
+
const name = separator === -1 ? input.fullname : input.fullname.slice(separator + 1);
|
|
5864
|
+
return {
|
|
5865
|
+
...input,
|
|
5866
|
+
name: input.name ?? name,
|
|
5867
|
+
category: input.category ?? category,
|
|
5868
|
+
hiddenTags: input.hiddenTags ?? [],
|
|
5869
|
+
children: input.children ?? 0,
|
|
5870
|
+
comments: input.comments ?? 0,
|
|
5871
|
+
size: input.size ?? 0,
|
|
5872
|
+
rating: input.rating ?? 0,
|
|
5873
|
+
ratingVotes: input.ratingVotes ?? 0,
|
|
5874
|
+
revisions: input.revisions ?? 0
|
|
5875
|
+
};
|
|
5876
|
+
}
|
|
5877
|
+
// packages/parser/src/parser/rules/block/module/listpages/selectors.ts
|
|
5878
|
+
function matchesListPagesSelectors(page, query, currentPage) {
|
|
5879
|
+
return matchesCategory(page.category, query.category, currentPage.category) && matchesTags(page, query.tags, currentPage);
|
|
5880
|
+
}
|
|
5881
|
+
function matchesCategory(category, selector, currentCategory) {
|
|
5882
|
+
if (!selector)
|
|
5883
|
+
return category === currentCategory;
|
|
5884
|
+
if (selector.exclude.includes(category))
|
|
5885
|
+
return false;
|
|
5886
|
+
const hasPositiveSelector = selector.all || selector.current || selector.include.length > 0;
|
|
5887
|
+
return !hasPositiveSelector || selector.all || selector.current && category === currentCategory || selector.include.includes(category);
|
|
5888
|
+
}
|
|
5889
|
+
function matchesTags(page, selector, currentPage) {
|
|
5890
|
+
if (!selector)
|
|
5891
|
+
return true;
|
|
5892
|
+
const allTags = new Set([...page.tags, ...page.hiddenTags]);
|
|
5893
|
+
if (selector.all.some((tag) => !allTags.has(tag)))
|
|
5894
|
+
return false;
|
|
5895
|
+
if (selector.any.length > 0 && !selector.any.some((tag) => allTags.has(tag)))
|
|
5896
|
+
return false;
|
|
5897
|
+
if (selector.none.some((tag) => allTags.has(tag)))
|
|
5898
|
+
return false;
|
|
5899
|
+
if (selector.special === "none")
|
|
5900
|
+
return allTags.size === 0;
|
|
5901
|
+
const currentTags = new Set(currentPage.tags.filter((tag) => !tag.startsWith("_")));
|
|
5902
|
+
if (selector.special === "same-visible") {
|
|
5903
|
+
return page.tags.some((tag) => currentTags.has(tag));
|
|
5904
|
+
}
|
|
5905
|
+
if (selector.special === "same-all") {
|
|
5906
|
+
return setsEqual(new Set(page.tags), currentTags);
|
|
5907
|
+
}
|
|
5908
|
+
return true;
|
|
5909
|
+
}
|
|
5910
|
+
function setsEqual(left, right) {
|
|
5911
|
+
return left.size === right.size && [...left].every((value) => right.has(value));
|
|
5912
|
+
}
|
|
5859
5913
|
// packages/parser/src/parser/rules/block/module/listpages/url-resolution/fields.ts
|
|
5860
5914
|
var URL_RESOLVABLE_FIELDS = [
|
|
5861
5915
|
{ attr: "offset", queryKey: "offset", type: "number" },
|
|
@@ -6285,13 +6339,20 @@ var BASE_PLACEHOLDER_OPEN = "";
|
|
|
6285
6339
|
var BASE_PLACEHOLDER_CLOSE = "";
|
|
6286
6340
|
var RAW_BLOCK_OPEN_PATTERN = /\[\[\s*(code|html)\b[^\]]*\]\]/iy;
|
|
6287
6341
|
function makeUniqueSentinels(source) {
|
|
6288
|
-
let
|
|
6289
|
-
let
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6342
|
+
let openRun = 0;
|
|
6343
|
+
let closeRun = 0;
|
|
6344
|
+
let longestOpenRun = 0;
|
|
6345
|
+
let longestCloseRun = 0;
|
|
6346
|
+
for (const char of source) {
|
|
6347
|
+
openRun = char === BASE_PLACEHOLDER_OPEN ? openRun + 1 : 0;
|
|
6348
|
+
closeRun = char === BASE_PLACEHOLDER_CLOSE ? closeRun + 1 : 0;
|
|
6349
|
+
longestOpenRun = Math.max(longestOpenRun, openRun);
|
|
6350
|
+
longestCloseRun = Math.max(longestCloseRun, closeRun);
|
|
6293
6351
|
}
|
|
6294
|
-
return {
|
|
6352
|
+
return {
|
|
6353
|
+
open: BASE_PLACEHOLDER_OPEN.repeat(longestOpenRun + 1),
|
|
6354
|
+
close: BASE_PLACEHOLDER_CLOSE.repeat(longestCloseRun + 1)
|
|
6355
|
+
};
|
|
6295
6356
|
}
|
|
6296
6357
|
function maskRawRegions(source, sentinels) {
|
|
6297
6358
|
const placeholders = [];
|
|
@@ -13694,30 +13755,6 @@ function preprocess(text) {
|
|
|
13694
13755
|
return result;
|
|
13695
13756
|
}
|
|
13696
13757
|
|
|
13697
|
-
// packages/parser/src/parser/preprocess/expr/evaluate.ts
|
|
13698
|
-
import { evaluateExpression as evaluateExpression2, formatExprValue, isTruthy as isTruthy2 } from "@wdprlib/ast";
|
|
13699
|
-
function evaluateDirective(kind, match) {
|
|
13700
|
-
if (kind === "expr") {
|
|
13701
|
-
const result2 = evaluateExpression2(match.head);
|
|
13702
|
-
if (result2.success)
|
|
13703
|
-
return formatExprValue(result2.value);
|
|
13704
|
-
if (result2.error === "empty expression")
|
|
13705
|
-
return "";
|
|
13706
|
-
return "ERROR";
|
|
13707
|
-
}
|
|
13708
|
-
if (kind === "if") {
|
|
13709
|
-
if (!match.hasPipe)
|
|
13710
|
-
return "";
|
|
13711
|
-
return isTruthy2(match.head) ? match.thenText : match.elseText;
|
|
13712
|
-
}
|
|
13713
|
-
if (!match.hasPipe)
|
|
13714
|
-
return "";
|
|
13715
|
-
const result = evaluateExpression2(match.head);
|
|
13716
|
-
if (!result.success)
|
|
13717
|
-
return "ERROR";
|
|
13718
|
-
return result.value !== 0 && !Number.isNaN(result.value) ? match.thenText : match.elseText;
|
|
13719
|
-
}
|
|
13720
|
-
|
|
13721
13758
|
// packages/parser/src/parser/preprocess/expr/chars.ts
|
|
13722
13759
|
function isWhitespace(ch) {
|
|
13723
13760
|
return ch === " " || ch === "\t" || ch === `
|
|
@@ -13746,6 +13783,30 @@ function matchDirectiveKind(source, i) {
|
|
|
13746
13783
|
return null;
|
|
13747
13784
|
}
|
|
13748
13785
|
|
|
13786
|
+
// packages/parser/src/parser/preprocess/expr/evaluate.ts
|
|
13787
|
+
import { evaluateExpression as evaluateExpression2, formatExprValue, isTruthy as isTruthy2 } from "@wdprlib/ast";
|
|
13788
|
+
function evaluateDirective(kind, match) {
|
|
13789
|
+
if (kind === "expr") {
|
|
13790
|
+
const result2 = evaluateExpression2(match.head);
|
|
13791
|
+
if (result2.success)
|
|
13792
|
+
return formatExprValue(result2.value);
|
|
13793
|
+
if (result2.error === "empty expression")
|
|
13794
|
+
return "";
|
|
13795
|
+
return "ERROR";
|
|
13796
|
+
}
|
|
13797
|
+
if (kind === "if") {
|
|
13798
|
+
if (!match.hasPipe)
|
|
13799
|
+
return "";
|
|
13800
|
+
return isTruthy2(match.head) ? match.thenText : match.elseText;
|
|
13801
|
+
}
|
|
13802
|
+
if (!match.hasPipe)
|
|
13803
|
+
return "";
|
|
13804
|
+
const result = evaluateExpression2(match.head);
|
|
13805
|
+
if (!result.success)
|
|
13806
|
+
return "ERROR";
|
|
13807
|
+
return result.value !== 0 && !Number.isNaN(result.value) ? match.thenText : match.elseText;
|
|
13808
|
+
}
|
|
13809
|
+
|
|
13749
13810
|
// packages/parser/src/parser/preprocess/expr/parse.ts
|
|
13750
13811
|
function tryParseInnermostDirective(source, start, kind) {
|
|
13751
13812
|
const keywordLen = kind === "ifexpr" ? 6 : kind === "expr" ? 4 : 2;
|
|
@@ -13847,11 +13908,14 @@ function expandInnermost(source) {
|
|
|
13847
13908
|
}
|
|
13848
13909
|
|
|
13849
13910
|
// packages/parser/src/parser/preprocess/expr/index.ts
|
|
13911
|
+
var MAX_EXPR_NESTING = 64;
|
|
13850
13912
|
function preprocessExpr(source) {
|
|
13851
13913
|
if (!source.includes("[[#"))
|
|
13852
13914
|
return source;
|
|
13853
13915
|
const sentinels = makeUniqueSentinels(source);
|
|
13854
13916
|
const { masked, placeholders } = maskRawRegions(source, sentinels);
|
|
13917
|
+
if (exceedsExprNestingLimit(masked))
|
|
13918
|
+
return source;
|
|
13855
13919
|
const reduced = reduceExpr(masked);
|
|
13856
13920
|
return restorePlaceholders(reduced, placeholders, sentinels);
|
|
13857
13921
|
}
|
|
@@ -13866,6 +13930,26 @@ function reduceExpr(source) {
|
|
|
13866
13930
|
}
|
|
13867
13931
|
return current2;
|
|
13868
13932
|
}
|
|
13933
|
+
function exceedsExprNestingLimit(source) {
|
|
13934
|
+
const expressionStack = [];
|
|
13935
|
+
let expressionDepth = 0;
|
|
13936
|
+
for (let i = 0;i < source.length; i++) {
|
|
13937
|
+
if (source.startsWith("[[", i)) {
|
|
13938
|
+
const isExpression = matchDirectiveKind(source, i) !== null;
|
|
13939
|
+
expressionStack.push(isExpression);
|
|
13940
|
+
if (isExpression && ++expressionDepth > MAX_EXPR_NESTING)
|
|
13941
|
+
return true;
|
|
13942
|
+
i++;
|
|
13943
|
+
continue;
|
|
13944
|
+
}
|
|
13945
|
+
if (source.startsWith("]]", i)) {
|
|
13946
|
+
if (expressionStack.pop())
|
|
13947
|
+
expressionDepth--;
|
|
13948
|
+
i++;
|
|
13949
|
+
}
|
|
13950
|
+
}
|
|
13951
|
+
return false;
|
|
13952
|
+
}
|
|
13869
13953
|
|
|
13870
13954
|
// packages/parser/src/parser/parse/source.ts
|
|
13871
13955
|
function prepareSourceForParse(source, options) {
|
|
@@ -13893,7 +13977,9 @@ function parse(source, options) {
|
|
|
13893
13977
|
return new Parser(tokens, options).parse();
|
|
13894
13978
|
}
|
|
13895
13979
|
// packages/parser/src/pipeline/process.ts
|
|
13896
|
-
import {
|
|
13980
|
+
import {
|
|
13981
|
+
DEFAULT_SETTINGS as DEFAULT_SETTINGS2
|
|
13982
|
+
} from "@wdprlib/ast";
|
|
13897
13983
|
|
|
13898
13984
|
// packages/parser/src/parser/rules/block/module/resolution/resolve-async.ts
|
|
13899
13985
|
async function resolveModulesWithAsyncParse(ast, dataProvider, options) {
|
|
@@ -14069,6 +14155,11 @@ async function resolveElementChildren(element, context, state) {
|
|
|
14069
14155
|
}
|
|
14070
14156
|
|
|
14071
14157
|
// packages/parser/src/pipeline/process.ts
|
|
14158
|
+
var DEFAULT_MODULE_MAX_PASSES = 5;
|
|
14159
|
+
var PIPELINE_DIAGNOSTIC_POSITION = {
|
|
14160
|
+
start: { line: 1, column: 1, offset: 0 },
|
|
14161
|
+
end: { line: 1, column: 1, offset: 0 }
|
|
14162
|
+
};
|
|
14072
14163
|
async function processWikitext(source, options) {
|
|
14073
14164
|
const settings = options.settings ?? DEFAULT_SETTINGS2;
|
|
14074
14165
|
const callbackContext = {
|
|
@@ -14076,6 +14167,7 @@ async function processWikitext(source, options) {
|
|
|
14076
14167
|
settings
|
|
14077
14168
|
};
|
|
14078
14169
|
const dependencies = [];
|
|
14170
|
+
const diagnostics = [];
|
|
14079
14171
|
const fetchInclude = createRequestIncludeFetcher(options.dataProvider?.fetchInclude ? (pageRef) => options.dataProvider.fetchInclude(pageRef, callbackContext) : undefined);
|
|
14080
14172
|
const resolveSource = async (input) => {
|
|
14081
14173
|
if (!fetchInclude)
|
|
@@ -14085,6 +14177,9 @@ async function processWikitext(source, options) {
|
|
|
14085
14177
|
settings
|
|
14086
14178
|
});
|
|
14087
14179
|
dependencies.push(...resolution.dependencies);
|
|
14180
|
+
if (resolution.reachedMaxIterations) {
|
|
14181
|
+
diagnostics.push(createLimitDiagnostic("include-resolution-limit", `Include expansion stopped after ${options.includeMaxIterations ?? 10} iterations.`));
|
|
14182
|
+
}
|
|
14088
14183
|
return resolution.source;
|
|
14089
14184
|
};
|
|
14090
14185
|
const expandedSource = await resolveSource(source);
|
|
@@ -14093,28 +14188,52 @@ async function processWikitext(source, options) {
|
|
|
14093
14188
|
pageTags: options.page.tags,
|
|
14094
14189
|
appendImplicitFootnoteBlock: false
|
|
14095
14190
|
});
|
|
14096
|
-
|
|
14191
|
+
diagnostics.push(...initial.diagnostics);
|
|
14097
14192
|
const dataProvider = createModuleDataProvider(options, callbackContext);
|
|
14098
|
-
const
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
|
|
14102
|
-
appendImplicitFootnoteBlock: false
|
|
14103
|
-
}),
|
|
14104
|
-
compiledListPagesTemplates: extraction.compiledListPagesTemplates,
|
|
14105
|
-
compiledListUsersTemplates: extraction.compiledListUsersTemplates,
|
|
14106
|
-
requirements: extraction.requirements,
|
|
14107
|
-
urlPath: options.page.urlPath,
|
|
14108
|
-
pageTags: options.page.tags
|
|
14193
|
+
const parseFragment = async (fragmentSource) => parse(await resolveSource(fragmentSource), {
|
|
14194
|
+
settings,
|
|
14195
|
+
pageTags: options.page.tags,
|
|
14196
|
+
appendImplicitFootnoteBlock: false
|
|
14109
14197
|
});
|
|
14198
|
+
let ast = initial.ast;
|
|
14199
|
+
for (let pass = 0;pass < DEFAULT_MODULE_MAX_PASSES; pass++) {
|
|
14200
|
+
const extraction = extractDataRequirements(ast);
|
|
14201
|
+
if (pass > 0 && !hasResolvableRequirements(extraction.requirements, options.dataProvider)) {
|
|
14202
|
+
break;
|
|
14203
|
+
}
|
|
14204
|
+
const resolved = await resolveModulesWithAsyncParse(ast, dataProvider, {
|
|
14205
|
+
parse: parseFragment,
|
|
14206
|
+
compiledListPagesTemplates: extraction.compiledListPagesTemplates,
|
|
14207
|
+
compiledListUsersTemplates: extraction.compiledListUsersTemplates,
|
|
14208
|
+
requirements: extraction.requirements,
|
|
14209
|
+
urlPath: options.page.urlPath,
|
|
14210
|
+
pageTags: options.page.tags
|
|
14211
|
+
});
|
|
14212
|
+
ast = resolved.ast;
|
|
14213
|
+
diagnostics.push(...resolved.diagnostics);
|
|
14214
|
+
}
|
|
14215
|
+
if (hasResolvableRequirements(extractDataRequirements(ast).requirements, options.dataProvider)) {
|
|
14216
|
+
diagnostics.push(createLimitDiagnostic("module-resolution-limit", `Module resolution stopped after ${DEFAULT_MODULE_MAX_PASSES} passes.`));
|
|
14217
|
+
}
|
|
14110
14218
|
return {
|
|
14111
|
-
ast
|
|
14219
|
+
ast,
|
|
14112
14220
|
page: options.page,
|
|
14113
14221
|
settings,
|
|
14114
|
-
diagnostics
|
|
14222
|
+
diagnostics,
|
|
14115
14223
|
dependencies
|
|
14116
14224
|
};
|
|
14117
14225
|
}
|
|
14226
|
+
function hasResolvableRequirements(requirements, provider) {
|
|
14227
|
+
return Boolean(provider?.fetchListPages && requirements.listPages.length > 0 || provider?.fetchListUsers && requirements.listUsers.length > 0 || provider?.fetchTagCloud && requirements.tagCloud.length > 0);
|
|
14228
|
+
}
|
|
14229
|
+
function createLimitDiagnostic(code, message) {
|
|
14230
|
+
return {
|
|
14231
|
+
severity: "warning",
|
|
14232
|
+
code,
|
|
14233
|
+
message,
|
|
14234
|
+
position: PIPELINE_DIAGNOSTIC_POSITION
|
|
14235
|
+
};
|
|
14236
|
+
}
|
|
14118
14237
|
function createModuleDataProvider(options, context) {
|
|
14119
14238
|
const provider = options.dataProvider;
|
|
14120
14239
|
return {
|
|
@@ -14159,6 +14278,7 @@ export {
|
|
|
14159
14278
|
parse,
|
|
14160
14279
|
paragraph,
|
|
14161
14280
|
normalizeQuery,
|
|
14281
|
+
matchesListPagesSelectors,
|
|
14162
14282
|
listItemSubList,
|
|
14163
14283
|
listItemElements,
|
|
14164
14284
|
list,
|
|
@@ -14172,6 +14292,7 @@ export {
|
|
|
14172
14292
|
extractListUsersVariables,
|
|
14173
14293
|
extractIncludeReferences,
|
|
14174
14294
|
extractDataRequirements,
|
|
14295
|
+
definePageData,
|
|
14175
14296
|
createToken,
|
|
14176
14297
|
createSettings,
|
|
14177
14298
|
createPosition,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wdprlib/parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
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": "
|
|
44
|
+
"@wdprlib/ast": "3.0.0"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { makeUniqueSentinels, maskRawRegions, restorePlaceholders } from "../utils";
|
|
10
|
+
import { matchDirectiveKind } from "./kind";
|
|
10
11
|
import { expandInnermost } from "./scan";
|
|
11
12
|
|
|
13
|
+
const MAX_EXPR_NESTING = 64;
|
|
14
|
+
|
|
12
15
|
/**
|
|
13
16
|
* Resolve every `[[#if]]` / `[[#ifexpr]]` / `[[#expr]]` that sits inside
|
|
14
17
|
* another block's opener (depth > 0). Top-level directives are left for
|
|
@@ -21,6 +24,7 @@ export function preprocessExpr(source: string): string {
|
|
|
21
24
|
|
|
22
25
|
const sentinels = makeUniqueSentinels(source);
|
|
23
26
|
const { masked, placeholders } = maskRawRegions(source, sentinels);
|
|
27
|
+
if (exceedsExprNestingLimit(masked)) return source;
|
|
24
28
|
const reduced = reduceExpr(masked);
|
|
25
29
|
return restorePlaceholders(reduced, placeholders, sentinels);
|
|
26
30
|
}
|
|
@@ -43,3 +47,25 @@ function reduceExpr(source: string): string {
|
|
|
43
47
|
}
|
|
44
48
|
return current;
|
|
45
49
|
}
|
|
50
|
+
|
|
51
|
+
function exceedsExprNestingLimit(source: string): boolean {
|
|
52
|
+
const expressionStack: boolean[] = [];
|
|
53
|
+
let expressionDepth = 0;
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < source.length; i++) {
|
|
56
|
+
if (source.startsWith("[[", i)) {
|
|
57
|
+
const isExpression = matchDirectiveKind(source, i) !== null;
|
|
58
|
+
expressionStack.push(isExpression);
|
|
59
|
+
if (isExpression && ++expressionDepth > MAX_EXPR_NESTING) return true;
|
|
60
|
+
i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (source.startsWith("]]", i)) {
|
|
65
|
+
if (expressionStack.pop()) expressionDepth--;
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
@@ -13,16 +13,25 @@ export interface Sentinels {
|
|
|
13
13
|
* Choose sentinel strings that are guaranteed not to appear in `source`.
|
|
14
14
|
* The placeholders we splice into the masked source have the form
|
|
15
15
|
* `<open><digits><close>`, so the restore pass must not confuse them
|
|
16
|
-
* with content.
|
|
16
|
+
* with content.
|
|
17
17
|
*/
|
|
18
18
|
export function makeUniqueSentinels(source: string): Sentinels {
|
|
19
|
-
let
|
|
20
|
-
let
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
let openRun = 0;
|
|
20
|
+
let closeRun = 0;
|
|
21
|
+
let longestOpenRun = 0;
|
|
22
|
+
let longestCloseRun = 0;
|
|
23
|
+
|
|
24
|
+
for (const char of source) {
|
|
25
|
+
openRun = char === BASE_PLACEHOLDER_OPEN ? openRun + 1 : 0;
|
|
26
|
+
closeRun = char === BASE_PLACEHOLDER_CLOSE ? closeRun + 1 : 0;
|
|
27
|
+
longestOpenRun = Math.max(longestOpenRun, openRun);
|
|
28
|
+
longestCloseRun = Math.max(longestCloseRun, closeRun);
|
|
24
29
|
}
|
|
25
|
-
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
open: BASE_PLACEHOLDER_OPEN.repeat(longestOpenRun + 1),
|
|
33
|
+
close: BASE_PLACEHOLDER_CLOSE.repeat(longestCloseRun + 1),
|
|
34
|
+
};
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
/**
|
|
@@ -59,6 +59,8 @@ export { extractDataRequirements } from "./extract";
|
|
|
59
59
|
// Resolution
|
|
60
60
|
export type { ParseFunction, ListPagesModuleData } from "./resolve";
|
|
61
61
|
export { isListPagesModule, resolveListPages } from "./resolve";
|
|
62
|
+
export { definePageData } from "./types/external-data";
|
|
63
|
+
export { matchesListPagesSelectors } from "./selectors";
|
|
62
64
|
|
|
63
65
|
// Compiler
|
|
64
66
|
export { compileTemplate } from "./compiler";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { PageData } from "./types/external-data";
|
|
2
|
+
import type {
|
|
3
|
+
NormalizedCategory,
|
|
4
|
+
NormalizedListPagesQuery,
|
|
5
|
+
NormalizedTags,
|
|
6
|
+
} from "./types/normalized-query";
|
|
7
|
+
|
|
8
|
+
type SelectorPage = Pick<PageData, "category" | "tags" | "hiddenTags">;
|
|
9
|
+
type CurrentPage = { category: string; tags: readonly string[] };
|
|
10
|
+
|
|
11
|
+
export function matchesListPagesSelectors(
|
|
12
|
+
page: SelectorPage,
|
|
13
|
+
query: Pick<NormalizedListPagesQuery, "category" | "tags">,
|
|
14
|
+
currentPage: CurrentPage,
|
|
15
|
+
): boolean {
|
|
16
|
+
return (
|
|
17
|
+
matchesCategory(page.category, query.category, currentPage.category) &&
|
|
18
|
+
matchesTags(page, query.tags, currentPage)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function matchesCategory(
|
|
23
|
+
category: string,
|
|
24
|
+
selector: NormalizedCategory | undefined,
|
|
25
|
+
currentCategory: string,
|
|
26
|
+
): boolean {
|
|
27
|
+
if (!selector) return category === currentCategory;
|
|
28
|
+
if (selector.exclude.includes(category)) return false;
|
|
29
|
+
|
|
30
|
+
const hasPositiveSelector = selector.all || selector.current || selector.include.length > 0;
|
|
31
|
+
return (
|
|
32
|
+
!hasPositiveSelector ||
|
|
33
|
+
selector.all ||
|
|
34
|
+
(selector.current && category === currentCategory) ||
|
|
35
|
+
selector.include.includes(category)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function matchesTags(
|
|
40
|
+
page: Pick<SelectorPage, "tags" | "hiddenTags">,
|
|
41
|
+
selector: NormalizedTags | undefined,
|
|
42
|
+
currentPage: Pick<CurrentPage, "tags">,
|
|
43
|
+
): boolean {
|
|
44
|
+
if (!selector) return true;
|
|
45
|
+
|
|
46
|
+
const allTags = new Set([...page.tags, ...page.hiddenTags]);
|
|
47
|
+
if (selector.all.some((tag) => !allTags.has(tag))) return false;
|
|
48
|
+
if (selector.any.length > 0 && !selector.any.some((tag) => allTags.has(tag))) return false;
|
|
49
|
+
if (selector.none.some((tag) => allTags.has(tag))) return false;
|
|
50
|
+
|
|
51
|
+
if (selector.special === "none") return allTags.size === 0;
|
|
52
|
+
const currentTags = new Set(currentPage.tags.filter((tag) => !tag.startsWith("_")));
|
|
53
|
+
if (selector.special === "same-visible") {
|
|
54
|
+
return page.tags.some((tag) => currentTags.has(tag));
|
|
55
|
+
}
|
|
56
|
+
if (selector.special === "same-all") {
|
|
57
|
+
return setsEqual(new Set(page.tags), currentTags);
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function setsEqual(left: ReadonlySet<string>, right: ReadonlySet<string>): boolean {
|
|
63
|
+
return left.size === right.size && [...left].every((value) => right.has(value));
|
|
64
|
+
}
|
|
@@ -58,6 +58,28 @@ export interface PageData {
|
|
|
58
58
|
revisions: number;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
type PageDataInput = Pick<PageData, "fullname" | "title" | "createdAt" | "updatedAt" | "tags"> &
|
|
62
|
+
Partial<Omit<PageData, "fullname" | "title" | "createdAt" | "updatedAt" | "tags">>;
|
|
63
|
+
|
|
64
|
+
export function definePageData(input: PageDataInput): PageData {
|
|
65
|
+
const separator = input.fullname.indexOf(":");
|
|
66
|
+
const category = separator === -1 ? "_default" : input.fullname.slice(0, separator);
|
|
67
|
+
const name = separator === -1 ? input.fullname : input.fullname.slice(separator + 1);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
...input,
|
|
71
|
+
name: input.name ?? name,
|
|
72
|
+
category: input.category ?? category,
|
|
73
|
+
hiddenTags: input.hiddenTags ?? [],
|
|
74
|
+
children: input.children ?? 0,
|
|
75
|
+
comments: input.comments ?? 0,
|
|
76
|
+
size: input.size ?? 0,
|
|
77
|
+
rating: input.rating ?? 0,
|
|
78
|
+
ratingVotes: input.ratingVotes ?? 0,
|
|
79
|
+
revisions: input.revisions ?? 0,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
61
83
|
/**
|
|
62
84
|
* Site context information.
|
|
63
85
|
*/
|
package/src/pipeline/process.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_SETTINGS,
|
|
3
|
+
type Diagnostic,
|
|
4
|
+
type PageRef,
|
|
5
|
+
type WikitextPageContext,
|
|
6
|
+
} from "@wdprlib/ast";
|
|
2
7
|
import { parse } from "../parser";
|
|
3
8
|
import { extractDataRequirements } from "../parser/rules/block/module/listpages/extract";
|
|
4
9
|
import {
|
|
@@ -14,6 +19,12 @@ import type {
|
|
|
14
19
|
ProcessWikitextOptions,
|
|
15
20
|
} from "./types";
|
|
16
21
|
|
|
22
|
+
const DEFAULT_MODULE_MAX_PASSES = 5;
|
|
23
|
+
const PIPELINE_DIAGNOSTIC_POSITION = {
|
|
24
|
+
start: { line: 1, column: 1, offset: 0 },
|
|
25
|
+
end: { line: 1, column: 1, offset: 0 },
|
|
26
|
+
};
|
|
27
|
+
|
|
17
28
|
export async function processWikitext<TPage extends WikitextPageContext>(
|
|
18
29
|
source: string,
|
|
19
30
|
options: ProcessWikitextOptions<TPage>,
|
|
@@ -24,6 +35,7 @@ export async function processWikitext<TPage extends WikitextPageContext>(
|
|
|
24
35
|
settings,
|
|
25
36
|
};
|
|
26
37
|
const dependencies: IncludeDependency[] = [];
|
|
38
|
+
const diagnostics: Diagnostic[] = [];
|
|
27
39
|
const fetchInclude = createRequestIncludeFetcher(
|
|
28
40
|
options.dataProvider?.fetchInclude
|
|
29
41
|
? (pageRef) => options.dataProvider!.fetchInclude!(pageRef, callbackContext)
|
|
@@ -36,6 +48,14 @@ export async function processWikitext<TPage extends WikitextPageContext>(
|
|
|
36
48
|
settings,
|
|
37
49
|
});
|
|
38
50
|
dependencies.push(...resolution.dependencies);
|
|
51
|
+
if (resolution.reachedMaxIterations) {
|
|
52
|
+
diagnostics.push(
|
|
53
|
+
createLimitDiagnostic(
|
|
54
|
+
"include-resolution-limit",
|
|
55
|
+
`Include expansion stopped after ${options.includeMaxIterations ?? 10} iterations.`,
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
39
59
|
return resolution.source;
|
|
40
60
|
};
|
|
41
61
|
|
|
@@ -45,31 +65,78 @@ export async function processWikitext<TPage extends WikitextPageContext>(
|
|
|
45
65
|
pageTags: options.page.tags,
|
|
46
66
|
appendImplicitFootnoteBlock: false,
|
|
47
67
|
});
|
|
48
|
-
|
|
68
|
+
diagnostics.push(...initial.diagnostics);
|
|
49
69
|
const dataProvider = createModuleDataProvider(options, callbackContext);
|
|
50
|
-
const
|
|
51
|
-
parse
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
const parseFragment = async (fragmentSource: string) =>
|
|
71
|
+
parse(await resolveSource(fragmentSource), {
|
|
72
|
+
settings,
|
|
73
|
+
pageTags: options.page.tags,
|
|
74
|
+
appendImplicitFootnoteBlock: false,
|
|
75
|
+
});
|
|
76
|
+
let ast = initial.ast;
|
|
77
|
+
|
|
78
|
+
for (let pass = 0; pass < DEFAULT_MODULE_MAX_PASSES; pass++) {
|
|
79
|
+
const extraction = extractDataRequirements(ast);
|
|
80
|
+
if (pass > 0 && !hasResolvableRequirements(extraction.requirements, options.dataProvider)) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const resolved = await resolveModulesWithAsyncParse(ast, dataProvider, {
|
|
85
|
+
parse: parseFragment,
|
|
86
|
+
compiledListPagesTemplates: extraction.compiledListPagesTemplates,
|
|
87
|
+
compiledListUsersTemplates: extraction.compiledListUsersTemplates,
|
|
88
|
+
requirements: extraction.requirements,
|
|
89
|
+
urlPath: options.page.urlPath,
|
|
90
|
+
pageTags: options.page.tags,
|
|
91
|
+
});
|
|
92
|
+
ast = resolved.ast;
|
|
93
|
+
diagnostics.push(...resolved.diagnostics);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (hasResolvableRequirements(extractDataRequirements(ast).requirements, options.dataProvider)) {
|
|
97
|
+
diagnostics.push(
|
|
98
|
+
createLimitDiagnostic(
|
|
99
|
+
"module-resolution-limit",
|
|
100
|
+
`Module resolution stopped after ${DEFAULT_MODULE_MAX_PASSES} passes.`,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
63
104
|
|
|
64
105
|
return {
|
|
65
|
-
ast
|
|
106
|
+
ast,
|
|
66
107
|
page: options.page,
|
|
67
108
|
settings,
|
|
68
|
-
diagnostics
|
|
109
|
+
diagnostics,
|
|
69
110
|
dependencies,
|
|
70
111
|
};
|
|
71
112
|
}
|
|
72
113
|
|
|
114
|
+
function hasResolvableRequirements(
|
|
115
|
+
requirements: ReturnType<typeof extractDataRequirements>["requirements"],
|
|
116
|
+
provider:
|
|
117
|
+
| {
|
|
118
|
+
fetchListPages?: unknown;
|
|
119
|
+
fetchListUsers?: unknown;
|
|
120
|
+
fetchTagCloud?: unknown;
|
|
121
|
+
}
|
|
122
|
+
| undefined,
|
|
123
|
+
): boolean {
|
|
124
|
+
return Boolean(
|
|
125
|
+
(provider?.fetchListPages && requirements.listPages.length > 0) ||
|
|
126
|
+
(provider?.fetchListUsers && requirements.listUsers.length > 0) ||
|
|
127
|
+
(provider?.fetchTagCloud && requirements.tagCloud.length > 0),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function createLimitDiagnostic(code: string, message: string): Diagnostic {
|
|
132
|
+
return {
|
|
133
|
+
severity: "warning",
|
|
134
|
+
code,
|
|
135
|
+
message,
|
|
136
|
+
position: PIPELINE_DIAGNOSTIC_POSITION,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
73
140
|
function createModuleDataProvider<TPage extends WikitextPageContext>(
|
|
74
141
|
options: ProcessWikitextOptions<TPage>,
|
|
75
142
|
context: ProcessWikitextCallbackContext<TPage>,
|