@heroiclands/package-build 25.1.1 → 25.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/CHANGELOG.md +7 -0
- package/bin/content-commands.mjs +43 -6
- package/engine/content-alerts.mjs +2 -2
- package/engine/content-blocks.mjs +2 -2
- package/engine/content-index.mjs +23 -1
- package/engine/content-scripts.mjs +15 -6
- package/engine/content-scroll.mjs +2 -4
- package/engine/content-session.mjs +1 -0
- package/engine/content-timings.mjs +13 -1
- package/engine/content-verse.mjs +2 -2
- package/engine/dictionary-rows.mjs +14 -2
- package/engine/encoding-language.mjs +47 -20
- package/engine/encoding-lexicon.mjs +13 -1
- package/engine/heading-attributes.mjs +2 -5
- package/engine/language-writings.mjs +34 -1
- package/engine/markdown-blocks.mjs +58 -0
- package/package.json +1 -1
- package/types/engine/content-index.d.mts +28 -2
- package/types/engine/content-session.d.mts +1 -0
- package/types/engine/content-timings.d.mts +7 -0
- package/types/engine/encoding-lexicon.d.mts +6 -35
- package/types/engine/language-writings.d.mts +3 -1
- package/types/engine/markdown-blocks.d.mts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 25.1.2
|
|
4
|
+
|
|
5
|
+
**Build speed**
|
|
6
|
+
|
|
7
|
+
- Content builds and content lint run several times faster on large packages, above all when building the content index and checking text written through a language's encoding.
|
|
8
|
+
- The build log shows how long each stage of the content index takes.
|
|
9
|
+
|
|
3
10
|
## 25.1.1
|
|
4
11
|
|
|
5
12
|
**Language conversion**
|
package/bin/content-commands.mjs
CHANGED
|
@@ -1912,6 +1912,40 @@ function linksCommand() {
|
|
|
1912
1912
|
};
|
|
1913
1913
|
}
|
|
1914
1914
|
|
|
1915
|
+
/** The stages that build the content index, in the order they run. */
|
|
1916
|
+
const INDEX_STAGES = Object.freeze([
|
|
1917
|
+
"source capture",
|
|
1918
|
+
"dependency indexes",
|
|
1919
|
+
"index read",
|
|
1920
|
+
"index notes",
|
|
1921
|
+
"index citations",
|
|
1922
|
+
"index encoding runs",
|
|
1923
|
+
"index calendars and cultures",
|
|
1924
|
+
"index lexical sections",
|
|
1925
|
+
"index order",
|
|
1926
|
+
"index derivation",
|
|
1927
|
+
"sql:all",
|
|
1928
|
+
"sql:public",
|
|
1929
|
+
"publication",
|
|
1930
|
+
"index emission",
|
|
1931
|
+
]);
|
|
1932
|
+
|
|
1933
|
+
/**
|
|
1934
|
+
* The content index's stage timings as one line, each stage's own time
|
|
1935
|
+
* excluding the stages inside it.
|
|
1936
|
+
*
|
|
1937
|
+
* @param {Record<string, number>} stages - Exclusive milliseconds by stage.
|
|
1938
|
+
* @returns {string} The line.
|
|
1939
|
+
*/
|
|
1940
|
+
function indexTimings(stages) {
|
|
1941
|
+
const shown = INDEX_STAGES.filter((name) => name in stages);
|
|
1942
|
+
const total = shown.reduce((sum, name) => sum + stages[name], 0);
|
|
1943
|
+
return (
|
|
1944
|
+
`Index timings: ${shown.map((name) => `${name} ${stages[name].toFixed(1)} ms`).join("; ")}; ` +
|
|
1945
|
+
`total ${total.toFixed(1)} ms.`
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1915
1949
|
/**
|
|
1916
1950
|
* `package-build content-index` — emit this package's note index.
|
|
1917
1951
|
*
|
|
@@ -1961,12 +1995,14 @@ function contentIndexCommand() {
|
|
|
1961
1995
|
session.assertContext({ config, contentBase });
|
|
1962
1996
|
const publication = await session.publication();
|
|
1963
1997
|
const { file, notes, bytes, full, draft, stub, dictionary, terms } =
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1998
|
+
session.timings.sync("index emission", () =>
|
|
1999
|
+
emitContentIndex({
|
|
2000
|
+
config,
|
|
2001
|
+
publication,
|
|
2002
|
+
...(argv.root ? { contentBase: argv.root } : {}),
|
|
2003
|
+
...(argv.out ? { outDir: argv.out } : {}),
|
|
2004
|
+
}),
|
|
2005
|
+
);
|
|
1970
2006
|
log.info(
|
|
1971
2007
|
`${config.contentPackage} → ${path.relative(process.cwd(), file)} ` +
|
|
1972
2008
|
`(${notes} notes — ${full} full, ${draft} draft, ` +
|
|
@@ -1977,6 +2013,7 @@ function contentIndexCommand() {
|
|
|
1977
2013
|
`${config.contentPackage} → ${path.relative(process.cwd(), written.file)} ` +
|
|
1978
2014
|
`(${written.rows} ${relation} rows)`,
|
|
1979
2015
|
);
|
|
2016
|
+
log.info(indexTimings(session.timings.snapshot().stages));
|
|
1980
2017
|
return true;
|
|
1981
2018
|
} catch (err) {
|
|
1982
2019
|
reportContentFailure(err, argv);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import MarkdownIt from "markdown-it";
|
|
15
|
+
import { blockTokens } from "./markdown-blocks.mjs";
|
|
15
16
|
import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
|
|
16
17
|
import { slugify } from "./content-slug.mjs";
|
|
17
18
|
|
|
@@ -48,7 +49,6 @@ export const ALERT_TYPES = Object.freeze({
|
|
|
48
49
|
),
|
|
49
50
|
},
|
|
50
51
|
});
|
|
51
|
-
const parser = new MarkdownIt({ html: true });
|
|
52
52
|
const titleParser = new MarkdownIt({ html: false });
|
|
53
53
|
const marker = /^ {0,3}>[ \t]?\[!([^\]\n]+)\](.*)$/;
|
|
54
54
|
/** Read alerts outside code examples, retaining inclusive source line ranges.
|
|
@@ -60,7 +60,7 @@ export function scanAlerts(source) {
|
|
|
60
60
|
blocks = [],
|
|
61
61
|
errors = [];
|
|
62
62
|
let depth = 0;
|
|
63
|
-
for (const token of
|
|
63
|
+
for (const token of blockTokens(text)) {
|
|
64
64
|
if (token.type === "blockquote_close") {
|
|
65
65
|
depth--;
|
|
66
66
|
continue;
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
import crypto from "node:crypto";
|
|
53
53
|
import { slugify } from "./content-slug.mjs";
|
|
54
54
|
import MarkdownIt from "markdown-it";
|
|
55
|
+
import { blockTokens } from "./markdown-blocks.mjs";
|
|
55
56
|
import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
|
|
56
57
|
import { scanVerse, renderVerse } from "./content-verse.mjs";
|
|
57
58
|
import { renderScroll, scanScrolls } from "./content-scroll.mjs";
|
|
@@ -63,7 +64,6 @@ import { WITHHELD_CLASS, parseHeadingLine, withheldSections } from "./heading-at
|
|
|
63
64
|
* and a stray `<section>` would close the block early.
|
|
64
65
|
*/
|
|
65
66
|
const titleParser = new MarkdownIt({ html: false });
|
|
66
|
-
const structureParser = new MarkdownIt({ html: true });
|
|
67
67
|
|
|
68
68
|
/** The blocks an author may open, with the heading each takes by default. */
|
|
69
69
|
export const BLOCK_NAMES = Object.freeze({
|
|
@@ -108,7 +108,7 @@ export function scanBlocks(source) {
|
|
|
108
108
|
// A bare fence can belong to a quote through lazy continuation.
|
|
109
109
|
// Parser ranges decide whether it can delimit a container.
|
|
110
110
|
const literalLines = new Set();
|
|
111
|
-
for (const token of
|
|
111
|
+
for (const token of blockTokens(source)) {
|
|
112
112
|
if (
|
|
113
113
|
!token.map ||
|
|
114
114
|
!["blockquote_open", "fence", "code_block", "html_block"].includes(token.type)
|
package/engine/content-index.mjs
CHANGED
|
@@ -1087,6 +1087,8 @@ export function resolveCultures(
|
|
|
1087
1087
|
* @param {Map<string, {raw: string}>} [options.sources] - Selected captured source strings.
|
|
1088
1088
|
* @param {import("./content-session.mjs").ContentSyntax} [options.syntax] - Session scanner projections.
|
|
1089
1089
|
* @param {Iterable<string>} [options.nameTypes] - Additional bare name kinds from configured type declarations.
|
|
1090
|
+
* @param {ReturnType<typeof import("./content-timings.mjs").createContentTimings>} [options.timings] -
|
|
1091
|
+
* Invocation stage accounting, which records each phase of the derivation.
|
|
1090
1092
|
* @returns {Array<Record<string, any>>} The records, in a total order that does
|
|
1091
1093
|
* not depend on directory-read order.
|
|
1092
1094
|
*/
|
|
@@ -1109,8 +1111,16 @@ export function collectContentIndex(
|
|
|
1109
1111
|
sources,
|
|
1110
1112
|
syntax,
|
|
1111
1113
|
nameTypes,
|
|
1114
|
+
timings,
|
|
1112
1115
|
},
|
|
1113
1116
|
) {
|
|
1117
|
+
// Each phase runs until the next begins; the caller's stage closes the last.
|
|
1118
|
+
let phaseEnd;
|
|
1119
|
+
const phase = (name) => {
|
|
1120
|
+
phaseEnd?.();
|
|
1121
|
+
phaseEnd = timings?.start(name);
|
|
1122
|
+
};
|
|
1123
|
+
phase("index read");
|
|
1114
1124
|
const records = [];
|
|
1115
1125
|
// Passed through rather than defaulted away: an absent scope is the
|
|
1116
1126
|
// caller's omission, and `walkMarkdownTree` says so.
|
|
@@ -1175,6 +1185,7 @@ export function collectContentIndex(
|
|
|
1175
1185
|
);
|
|
1176
1186
|
|
|
1177
1187
|
// Each recorded note with its body, for the words its prose cites.
|
|
1188
|
+
phase("index notes");
|
|
1178
1189
|
const bodies = [];
|
|
1179
1190
|
for (const { frontmatter, body, bodyLine, bodyColumn, raw, absPath } of parsedNotes) {
|
|
1180
1191
|
// Before the index derives anything onto the frontmatter, so a later
|
|
@@ -1251,16 +1262,19 @@ export function collectContentIndex(
|
|
|
1251
1262
|
|
|
1252
1263
|
// The words each note's prose cites, read once every language's words
|
|
1253
1264
|
// are recorded; the index keeps the citing Addresses and no text.
|
|
1265
|
+
phase("index citations");
|
|
1254
1266
|
attachCitations(records, bodies);
|
|
1255
1267
|
|
|
1256
1268
|
// A run marked with a language alone takes its language's script, which
|
|
1257
1269
|
// is read once every language note is recorded.
|
|
1270
|
+
phase("index encoding runs");
|
|
1258
1271
|
fillEncodingRuns(records, { languages, contentPackage, rootDir });
|
|
1259
1272
|
|
|
1260
1273
|
// Every note record exists and every Address in it is a tuple, so a parent
|
|
1261
1274
|
// is found by its canonical address. Before the sort, which reads nothing
|
|
1262
1275
|
// the pass writes. Calendars first: they read the culture a note states,
|
|
1263
1276
|
// which the culture pass then replaces with the one it resolves.
|
|
1277
|
+
phase("index calendars and cultures");
|
|
1264
1278
|
resolveNoteCalendars(records, {
|
|
1265
1279
|
contentPackage,
|
|
1266
1280
|
defaultCalendar,
|
|
@@ -1298,6 +1312,7 @@ export function collectContentIndex(
|
|
|
1298
1312
|
// projection as the surfaces, without making the index emitter async.
|
|
1299
1313
|
// Public notes resolve providers in their audience, including lineage
|
|
1300
1314
|
// through hidden languages, just as their build does.
|
|
1315
|
+
phase("index lexical sections");
|
|
1301
1316
|
const visible = records.filter((record) => !isGmNote(record));
|
|
1302
1317
|
const publicSections = lexicalSectionsFor(visible, records);
|
|
1303
1318
|
const allSections = lexicalSectionsFor(records);
|
|
@@ -1319,6 +1334,7 @@ export function collectContentIndex(
|
|
|
1319
1334
|
// about the content, and the artifact is meant to be byte-identical across
|
|
1320
1335
|
// two runs over an unchanged tree. Collected above, ahead of the notes —
|
|
1321
1336
|
// see `resolveRole` — rather than walked a second time here.
|
|
1337
|
+
phase("index order");
|
|
1322
1338
|
for (const record of assetRecords) {
|
|
1323
1339
|
records.push(/** @type {Record<string, any>} */ (sortKeysDeep(record)));
|
|
1324
1340
|
}
|
|
@@ -1344,9 +1360,11 @@ export function collectContentIndex(
|
|
|
1344
1360
|
) ||
|
|
1345
1361
|
String(a.id ?? "").localeCompare(String(b.id ?? ""), "en"),
|
|
1346
1362
|
);
|
|
1347
|
-
|
|
1363
|
+
const decoded = records.map((record) =>
|
|
1348
1364
|
decodeIndexAddresses(record, addressContext ?? { package: contentPackage }),
|
|
1349
1365
|
);
|
|
1366
|
+
phaseEnd?.();
|
|
1367
|
+
return decoded;
|
|
1350
1368
|
}
|
|
1351
1369
|
|
|
1352
1370
|
/**
|
|
@@ -1465,6 +1483,8 @@ function dependencyAddresses(config) {
|
|
|
1465
1483
|
* @param {import("./content-session.mjs").ContentSyntax} [opts.syntax] - Session scanner projections.
|
|
1466
1484
|
* @param {Iterable<string>} [opts.nameTypes] - Bare name kinds, defaulting to configured item types.
|
|
1467
1485
|
* @param {Map<string, object>} [opts.foreignIndex] - Captured dependency address view.
|
|
1486
|
+
* @param {ReturnType<typeof import("./content-timings.mjs").createContentTimings>} [opts.timings] -
|
|
1487
|
+
* Invocation stage accounting.
|
|
1468
1488
|
* @returns {object[]} One record per note, plus one per documentation entry.
|
|
1469
1489
|
*/
|
|
1470
1490
|
export function indexRecordsFor({
|
|
@@ -1478,6 +1498,7 @@ export function indexRecordsFor({
|
|
|
1478
1498
|
syntax,
|
|
1479
1499
|
nameTypes,
|
|
1480
1500
|
foreignIndex,
|
|
1501
|
+
timings,
|
|
1481
1502
|
} = {}) {
|
|
1482
1503
|
const resolved = config ?? loadPackConfig();
|
|
1483
1504
|
const tree = contentBase ?? resolved.paths.content;
|
|
@@ -1512,6 +1533,7 @@ export function indexRecordsFor({
|
|
|
1512
1533
|
sources,
|
|
1513
1534
|
syntax,
|
|
1514
1535
|
nameTypes: nameTypes ?? resolved.itemTypes,
|
|
1536
|
+
timings,
|
|
1515
1537
|
});
|
|
1516
1538
|
}
|
|
1517
1539
|
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
43
|
import MarkdownIt from "markdown-it";
|
|
44
|
+
import { blockTokens } from "./markdown-blocks.mjs";
|
|
44
45
|
|
|
45
46
|
import { scanBlocks, BLOCK_CONTAINERS } from "./content-blocks.mjs";
|
|
46
47
|
import { scanVerse, hasColumns, verseLine, verseGroups, STANZA_BREAK } from "./content-verse.mjs";
|
|
@@ -737,9 +738,6 @@ function codeName(point) {
|
|
|
737
738
|
return `U+${point.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
738
739
|
}
|
|
739
740
|
|
|
740
|
-
/** A parser for the block structure of a div's body. */
|
|
741
|
-
const blockParser = new MarkdownIt({ html: true });
|
|
742
|
-
|
|
743
741
|
/** What a block token is called in a finding. */
|
|
744
742
|
const BLOCK_WHAT = Object.freeze({
|
|
745
743
|
heading_open: "a heading",
|
|
@@ -1485,7 +1483,7 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1485
1483
|
{ line: first + line + 1, column: /^ */.exec(body[line])[0].length + 1 },
|
|
1486
1484
|
`a div marked \`${marking}\` holds paragraphs and line breaks only, not ${what}`,
|
|
1487
1485
|
);
|
|
1488
|
-
for (const token of
|
|
1486
|
+
for (const token of blockTokens(body.join("\n"))) {
|
|
1489
1487
|
if (token.level !== 0 || !token.map) continue;
|
|
1490
1488
|
const [top, past] = token.map;
|
|
1491
1489
|
if (token.type === "paragraph_open") {
|
|
@@ -1535,7 +1533,14 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1535
1533
|
const boundsOf = (detail) =>
|
|
1536
1534
|
detail.reference ??
|
|
1537
1535
|
(detail.run.kind === "span" ? [detail.run.start + 1, detail.contentEnd] : detail.body);
|
|
1536
|
+
// Each run's parent and owner are read many times while ownership is
|
|
1537
|
+
// assigned below, so each is found once.
|
|
1538
|
+
const parents = new Map();
|
|
1538
1539
|
const parentOf = (detail) => {
|
|
1540
|
+
if (!parents.has(detail)) parents.set(detail, findParent(detail));
|
|
1541
|
+
return parents.get(detail);
|
|
1542
|
+
};
|
|
1543
|
+
const findParent = (detail) => {
|
|
1539
1544
|
if (detail.run.kind !== "span" || Object.keys(detail.keys).length) return undefined;
|
|
1540
1545
|
return details
|
|
1541
1546
|
.filter(
|
|
@@ -1547,9 +1552,13 @@ function analyseRuns(source, ctx, syntax) {
|
|
|
1547
1552
|
)
|
|
1548
1553
|
.sort((a, b) => boundsOf(a)[1] - boundsOf(a)[0] - (boundsOf(b)[1] - boundsOf(b)[0]))[0];
|
|
1549
1554
|
};
|
|
1555
|
+
const owners = new Map();
|
|
1550
1556
|
const ownerOf = (detail) => {
|
|
1551
|
-
|
|
1552
|
-
|
|
1557
|
+
if (!owners.has(detail)) {
|
|
1558
|
+
const parent = parentOf(detail);
|
|
1559
|
+
owners.set(detail, parent ? ownerOf(parent) : detail);
|
|
1560
|
+
}
|
|
1561
|
+
return owners.get(detail);
|
|
1553
1562
|
};
|
|
1554
1563
|
const wholeVerseReading = (detail) => {
|
|
1555
1564
|
if (detail.run.kind === "verse") return true;
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
* @module
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
|
-
import
|
|
45
|
+
import { blockTokens } from "./markdown-blocks.mjs";
|
|
46
46
|
|
|
47
47
|
import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
|
|
48
48
|
|
|
@@ -78,8 +78,6 @@ export const BOOK_SCROLL_ROWS = 40;
|
|
|
78
78
|
/** The class every scroll carries. */
|
|
79
79
|
export const SCROLL_CLASS = "scroll";
|
|
80
80
|
|
|
81
|
-
const structureParser = new MarkdownIt({ html: true });
|
|
82
|
-
|
|
83
81
|
/**
|
|
84
82
|
* One `scroll` fence.
|
|
85
83
|
*
|
|
@@ -129,7 +127,7 @@ export function scanScrolls(source) {
|
|
|
129
127
|
if (!/^ *(?:`{3,}|~{3,}) *scroll(?:\s|$)/m.test(text)) return { blocks, errors };
|
|
130
128
|
const lines = text.split("\n");
|
|
131
129
|
const literalLines = new Set();
|
|
132
|
-
for (const token of
|
|
130
|
+
for (const token of blockTokens(text)) {
|
|
133
131
|
if (!token.map || !["blockquote_open", "html_block"].includes(token.type)) continue;
|
|
134
132
|
for (let line = token.map[0]; line < token.map[1]; line++) literalLines.add(line);
|
|
135
133
|
}
|
|
@@ -24,12 +24,15 @@ export function createContentTimings() {
|
|
|
24
24
|
stages.set(name, (stages.get(name) ?? 0) + now - changed);
|
|
25
25
|
changed = now;
|
|
26
26
|
};
|
|
27
|
+
// Ending a stage also ends any stage opened inside it and left open.
|
|
27
28
|
const begin = (name) => {
|
|
28
29
|
account();
|
|
30
|
+
const depth = stack.length;
|
|
29
31
|
stack.push(name);
|
|
30
32
|
return () => {
|
|
33
|
+
if (stack.length <= depth) return;
|
|
31
34
|
account();
|
|
32
|
-
stack.
|
|
35
|
+
stack.length = depth;
|
|
33
36
|
};
|
|
34
37
|
};
|
|
35
38
|
return {
|
|
@@ -51,6 +54,15 @@ export function createContentTimings() {
|
|
|
51
54
|
stop();
|
|
52
55
|
}
|
|
53
56
|
},
|
|
57
|
+
/**
|
|
58
|
+
* Open a stage that runs until the returned function is called, or
|
|
59
|
+
* until the stage it was opened in ends.
|
|
60
|
+
* @param {string} name
|
|
61
|
+
* @returns {() => void}
|
|
62
|
+
*/
|
|
63
|
+
start(name) {
|
|
64
|
+
return begin(name);
|
|
65
|
+
},
|
|
54
66
|
/** @param {string} name */
|
|
55
67
|
hit(name) {
|
|
56
68
|
hits.set(name, (hits.get(name) ?? 0) + 1);
|
package/engine/content-verse.mjs
CHANGED
|
@@ -180,11 +180,11 @@ function columnsMarkdown(block, lines, step) {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
import MarkdownIt from "markdown-it";
|
|
183
|
+
import { blockTokens } from "./markdown-blocks.mjs";
|
|
183
184
|
import { slugify } from "./content-slug.mjs";
|
|
184
185
|
import { spanMarkdownPlugin } from "./content-spans.mjs";
|
|
185
186
|
import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
|
|
186
187
|
|
|
187
|
-
const structureParser = new MarkdownIt({ html: true });
|
|
188
188
|
const lineParser = new MarkdownIt({ html: true }).use(spanMarkdownPlugin);
|
|
189
189
|
|
|
190
190
|
/** The structural first span of a line, with its source ranges and keys. */
|
|
@@ -223,7 +223,7 @@ export function scanVerse(source) {
|
|
|
223
223
|
blocks = [],
|
|
224
224
|
errors = [];
|
|
225
225
|
const literalLines = new Set();
|
|
226
|
-
for (const token of
|
|
226
|
+
for (const token of blockTokens(source)) {
|
|
227
227
|
if (!token.map || !["blockquote_open", "html_block"].includes(token.type)) continue;
|
|
228
228
|
for (let line = token.map[0]; line < token.map[1]; line++) literalLines.add(line);
|
|
229
229
|
}
|
|
@@ -464,6 +464,16 @@ export function dictionaryAndTermRows(records, all = records) {
|
|
|
464
464
|
);
|
|
465
465
|
return hit ? hit.entry[key] : undefined;
|
|
466
466
|
};
|
|
467
|
+
// One piece lookup per language, shared by every word that language states.
|
|
468
|
+
const glossers = new Map();
|
|
469
|
+
const glossesFor = (language) => {
|
|
470
|
+
if (!glossers.has(language))
|
|
471
|
+
glossers.set(
|
|
472
|
+
language,
|
|
473
|
+
pieceGlosses(...lineage(language).map((key) => languages.get(key).data)),
|
|
474
|
+
);
|
|
475
|
+
return glossers.get(language);
|
|
476
|
+
};
|
|
467
477
|
const etymologyOf = ({ word, statedIn, depth }) => {
|
|
468
478
|
const blank = Object.fromEntries(ETYMOLOGY_KEYS.map((key) => [key, null]));
|
|
469
479
|
if (depth > 0)
|
|
@@ -476,13 +486,15 @@ export function dictionaryAndTermRows(records, all = records) {
|
|
|
476
486
|
};
|
|
477
487
|
const source = isMap(word.source) ? word.source : {};
|
|
478
488
|
const built = Array.isArray(word.built) ? word.built.map(String) : [];
|
|
479
|
-
const glossOf = pieceGlosses(...lineage(statedIn).map((key) => languages.get(key).data));
|
|
480
489
|
const out = {
|
|
481
490
|
...blank,
|
|
482
491
|
kind: typeof source.kind === "string" ? source.kind : null,
|
|
483
492
|
language: typeof source.language === "string" ? source.language : null,
|
|
484
493
|
form: typeof source.form === "string" ? source.form : null,
|
|
485
|
-
parts:
|
|
494
|
+
parts:
|
|
495
|
+
built.length ?
|
|
496
|
+
built.map((form) => ({ form, gloss: glossesFor(statedIn)(form) }))
|
|
497
|
+
: null,
|
|
486
498
|
root: typeof word.root === "string" ? word.root : null,
|
|
487
499
|
frame: typeof word.frame === "string" ? word.frame : null,
|
|
488
500
|
};
|
|
@@ -69,7 +69,14 @@ function capitalWriting(enc, word, native, font) {
|
|
|
69
69
|
upper.to + native.slice(lower.to.length)
|
|
70
70
|
: undefined;
|
|
71
71
|
}
|
|
72
|
+
/** Each lexicon's forms, found once; a lexicon is not modified once it is converted with. */
|
|
73
|
+
const formsOf = new WeakMap();
|
|
72
74
|
function entryForms(lexicon) {
|
|
75
|
+
if (!lexicon) return findEntryForms(lexicon);
|
|
76
|
+
if (!formsOf.has(lexicon)) formsOf.set(lexicon, findEntryForms(lexicon));
|
|
77
|
+
return formsOf.get(lexicon);
|
|
78
|
+
}
|
|
79
|
+
function findEntryForms(lexicon) {
|
|
73
80
|
const data = lexicon?.data ?? {};
|
|
74
81
|
const derived = new Map();
|
|
75
82
|
for (const entry of derivedForms(data)) {
|
|
@@ -122,6 +129,44 @@ function entryForms(lexicon) {
|
|
|
122
129
|
];
|
|
123
130
|
}
|
|
124
131
|
|
|
132
|
+
/** Each lexicon's phrases of more than one word, with the patterns that find them. */
|
|
133
|
+
const phrasesFound = new WeakMap();
|
|
134
|
+
function phrasesOf(lexicon) {
|
|
135
|
+
if (!phrasesFound.has(lexicon))
|
|
136
|
+
phrasesFound.set(
|
|
137
|
+
lexicon,
|
|
138
|
+
entryForms(lexicon)
|
|
139
|
+
.map((entry) => entry.form)
|
|
140
|
+
.filter((form) => [...form.matchAll(WORD)].length > 1)
|
|
141
|
+
.map((form) => ({
|
|
142
|
+
form,
|
|
143
|
+
spaces: form.match(/[\p{Zs}\u0009-\u000D]+/gu) ?? [],
|
|
144
|
+
pattern: new RegExp(
|
|
145
|
+
form
|
|
146
|
+
.split(/[\p{Zs}\u0009-\u000D]+/u)
|
|
147
|
+
.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
148
|
+
.join("[\\p{Zs}\\u0009-\\u000D]+"),
|
|
149
|
+
"iuy",
|
|
150
|
+
),
|
|
151
|
+
})),
|
|
152
|
+
);
|
|
153
|
+
return phrasesFound.get(lexicon);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Each lexicon's words' forms and variants, as written. */
|
|
157
|
+
const statedFound = new WeakMap();
|
|
158
|
+
function statedForms(lexicon) {
|
|
159
|
+
if (!statedFound.has(lexicon)) {
|
|
160
|
+
const forms = new Set();
|
|
161
|
+
for (const entry of list(lexicon.data.words)) {
|
|
162
|
+
forms.add(entry.form);
|
|
163
|
+
for (const variant of list(entry.variants)) forms.add(variant);
|
|
164
|
+
}
|
|
165
|
+
statedFound.set(lexicon, forms);
|
|
166
|
+
}
|
|
167
|
+
return statedFound.get(lexicon);
|
|
168
|
+
}
|
|
169
|
+
|
|
125
170
|
function literalEnd(text, at) {
|
|
126
171
|
let cursor = at + OPEN.length;
|
|
127
172
|
while (cursor < text.length) {
|
|
@@ -164,23 +209,7 @@ export function encodeWithLanguage(enc, text, options = {}) {
|
|
|
164
209
|
}
|
|
165
210
|
const writing =
|
|
166
211
|
options.lexicon ? writingsOf(options.lexicon.data, enc.name, options.font) : undefined;
|
|
167
|
-
const phrases =
|
|
168
|
-
options.lexicon ?
|
|
169
|
-
entryForms(options.lexicon)
|
|
170
|
-
.map((entry) => entry.form)
|
|
171
|
-
.filter((form) => [...form.matchAll(WORD)].length > 1)
|
|
172
|
-
.map((form) => ({
|
|
173
|
-
form,
|
|
174
|
-
spaces: form.match(/[\p{Zs}\u0009-\u000D]+/gu) ?? [],
|
|
175
|
-
pattern: new RegExp(
|
|
176
|
-
form
|
|
177
|
-
.split(/[\p{Zs}\u0009-\u000D]+/u)
|
|
178
|
-
.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
179
|
-
.join("[\\p{Zs}\\u0009-\\u000D]+"),
|
|
180
|
-
"iuy",
|
|
181
|
-
),
|
|
182
|
-
}))
|
|
183
|
-
: [];
|
|
212
|
+
const phrases = options.lexicon ? phrasesOf(options.lexicon) : [];
|
|
184
213
|
let at = 0;
|
|
185
214
|
const appendRows = (from, to) => {
|
|
186
215
|
let cursor = from;
|
|
@@ -267,9 +296,7 @@ export function encodeWithLanguage(enc, text, options = {}) {
|
|
|
267
296
|
} else if (own && "text" in own) {
|
|
268
297
|
appendRows(at, start);
|
|
269
298
|
let native = own.text;
|
|
270
|
-
const exact =
|
|
271
|
-
(entry) => entry.form === lookup || list(entry.variants).includes(lookup),
|
|
272
|
-
);
|
|
299
|
+
const exact = statedForms(options.lexicon).has(lookup);
|
|
273
300
|
if (!exact && match[0] !== match[0].toLowerCase()) {
|
|
274
301
|
const capital =
|
|
275
302
|
match[0] === initialCapital(match[0].toLowerCase()) ?
|
|
@@ -389,9 +389,21 @@ export function nativeRows(records) {
|
|
|
389
389
|
return rows;
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
-
/**
|
|
392
|
+
/** Each lexicon's projection, made once and shared by every conversion that reads it. */
|
|
393
|
+
const serialized = new WeakMap();
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Project only conversion-readable language data to a JSON-safe object. The
|
|
397
|
+
* projection of one lexicon is made once and shared, so it must not be modified.
|
|
398
|
+
*/
|
|
393
399
|
export function serializeLexicon(lexicon) {
|
|
394
400
|
if (!lexicon) return undefined;
|
|
401
|
+
if (!serialized.has(lexicon)) serialized.set(lexicon, projectLexicon(lexicon));
|
|
402
|
+
return serialized.get(lexicon);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** The conversion-readable data of a lexicon, copied. */
|
|
406
|
+
function projectLexicon(lexicon) {
|
|
395
407
|
const select = (value, keys) =>
|
|
396
408
|
Object.fromEntries(
|
|
397
409
|
keys
|
|
@@ -34,15 +34,12 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
|
|
37
|
-
import
|
|
38
|
-
|
|
39
|
-
/** Literal block ranges use the renderer's Markdown grammar. */
|
|
40
|
-
const structureParser = new MarkdownIt({ html: true });
|
|
37
|
+
import { blockTokens } from "./markdown-blocks.mjs";
|
|
41
38
|
|
|
42
39
|
/** Body lines occupied by code or raw HTML, including comment examples. */
|
|
43
40
|
function literalHeadingLines(source) {
|
|
44
41
|
const lines = new Set();
|
|
45
|
-
for (const token of
|
|
42
|
+
for (const token of blockTokens(source)) {
|
|
46
43
|
if (!token.map || !["fence", "code_block", "html_block"].includes(token.type)) continue;
|
|
47
44
|
for (let line = token.map[0]; line < token.map[1]; line++) lines.add(line);
|
|
48
45
|
}
|
|
@@ -80,8 +80,13 @@ function oneOf(form, candidates) {
|
|
|
80
80
|
return { ambiguous: { form, glosses: candidates.map(({ gloss }) => gloss) } };
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/** Each language data's writings, by encoding and face. */
|
|
84
|
+
const writingsFound = new WeakMap();
|
|
85
|
+
|
|
83
86
|
/**
|
|
84
|
-
* The writings of a language's words under one encoding in one face.
|
|
87
|
+
* The writings of a language's words under one encoding in one face. One
|
|
88
|
+
* `data` yields one set of writings per encoding and face, and each word's
|
|
89
|
+
* writing is found once, so `data` must not be modified once it is read here.
|
|
85
90
|
*
|
|
86
91
|
* @param {Record<string, any>|undefined} data - The language note's `data`.
|
|
87
92
|
* @param {string} enc - The encoding's name.
|
|
@@ -90,6 +95,34 @@ function oneOf(form, candidates) {
|
|
|
90
95
|
* The writing of a run's word, and of the `data.words` entry at an index.
|
|
91
96
|
*/
|
|
92
97
|
export function writingsOf(data, enc, font) {
|
|
98
|
+
if (!isMap(data)) return findWritings(data, enc, font);
|
|
99
|
+
if (!writingsFound.has(data)) writingsFound.set(data, new Map());
|
|
100
|
+
const byFace = writingsFound.get(data);
|
|
101
|
+
const key = JSON.stringify([enc, font ?? null]);
|
|
102
|
+
if (!byFace.has(key)) {
|
|
103
|
+
const found = findWritings(data, enc, font);
|
|
104
|
+
const words = new Map();
|
|
105
|
+
byFace.set(key, {
|
|
106
|
+
word: (written) => {
|
|
107
|
+
if (!words.has(written)) words.set(written, found.word(written));
|
|
108
|
+
return words.get(written);
|
|
109
|
+
},
|
|
110
|
+
entry: found.entry,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return byFace.get(key);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The writings of a language's words under one encoding in one face, found
|
|
118
|
+
* afresh; see {@link writingsOf}.
|
|
119
|
+
*
|
|
120
|
+
* @param {Record<string, any>|undefined} data - The language note's `data`.
|
|
121
|
+
* @param {string} enc - The encoding's name.
|
|
122
|
+
* @param {string|undefined} font - The face.
|
|
123
|
+
* @returns {{word: (word: string) => Writing, entry: (index: number) => Writing}}
|
|
124
|
+
*/
|
|
125
|
+
function findWritings(data, enc, font) {
|
|
93
126
|
const words = listOf(data?.words);
|
|
94
127
|
const affixes = listOf(data?.affixes).filter(isMap);
|
|
95
128
|
const grammar = isMap(data?.grammar) ? data.grammar : undefined;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
|
|
3
|
+
* Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
|
|
4
|
+
*
|
|
5
|
+
* This work is licensed under the GNU General Public License v3.0 (GPLv3).
|
|
6
|
+
* You may copy, modify, and distribute it under the terms of that license.
|
|
7
|
+
*
|
|
8
|
+
* For full terms, see the LICENSE.md file in the project root or visit:
|
|
9
|
+
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
10
|
+
*
|
|
11
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The block structure of a Markdown source, in the renderer's grammar.
|
|
16
|
+
*
|
|
17
|
+
* Several scanners ask only where a source's fences, code blocks, raw HTML and
|
|
18
|
+
* quotes lie, each reading block tokens' `type`, `map` and `level` and nothing
|
|
19
|
+
* else. Inline parsing fills only an `inline` token's `children`, so this
|
|
20
|
+
* parser skips it, and the block tokens it yields are the ones a full parse
|
|
21
|
+
* yields. The scanners commonly ask about the same source in turn, so the most
|
|
22
|
+
* recent sources' tokens are kept.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import MarkdownIt from "markdown-it";
|
|
28
|
+
|
|
29
|
+
/** The renderer's block grammar, with inline parsing and its passes switched off. */
|
|
30
|
+
const parser = new MarkdownIt({ html: true });
|
|
31
|
+
parser.core.ruler.disable(["inline", "linkify", "replacements", "smartquotes", "text_join"]);
|
|
32
|
+
|
|
33
|
+
/** How many recent sources keep their tokens. */
|
|
34
|
+
const RECENT = 16;
|
|
35
|
+
|
|
36
|
+
/** @type {Map<string, import("markdown-it").Token[]>} */
|
|
37
|
+
const recent = new Map();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The block tokens of a Markdown source. The array is shared between callers
|
|
41
|
+
* and must not be modified; an `inline` token carries no `children`.
|
|
42
|
+
*
|
|
43
|
+
* @param {unknown} source - Markdown.
|
|
44
|
+
* @returns {readonly import("markdown-it").Token[]} The block tokens, in order.
|
|
45
|
+
*/
|
|
46
|
+
export function blockTokens(source) {
|
|
47
|
+
const text = String(source ?? "");
|
|
48
|
+
const hit = recent.get(text);
|
|
49
|
+
if (hit) {
|
|
50
|
+
recent.delete(text);
|
|
51
|
+
recent.set(text, hit);
|
|
52
|
+
return hit;
|
|
53
|
+
}
|
|
54
|
+
const tokens = parser.parse(text, {});
|
|
55
|
+
recent.set(text, tokens);
|
|
56
|
+
if (recent.size > RECENT) recent.delete(recent.keys().next().value);
|
|
57
|
+
return tokens;
|
|
58
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "25.1.
|
|
3
|
+
"version": "25.1.2",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package \u2014 content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
|
@@ -225,10 +225,12 @@ export function resolveCultures(records: Array<Record<string, any>>, { contentPa
|
|
|
225
225
|
* @param {Map<string, {raw: string}>} [options.sources] - Selected captured source strings.
|
|
226
226
|
* @param {import("./content-session.mjs").ContentSyntax} [options.syntax] - Session scanner projections.
|
|
227
227
|
* @param {Iterable<string>} [options.nameTypes] - Additional bare name kinds from configured type declarations.
|
|
228
|
+
* @param {ReturnType<typeof import("./content-timings.mjs").createContentTimings>} [options.timings] -
|
|
229
|
+
* Invocation stage accounting, which records each phase of the derivation.
|
|
228
230
|
* @returns {Array<Record<string, any>>} The records, in a total order that does
|
|
229
231
|
* not depend on directory-read order.
|
|
230
232
|
*/
|
|
231
|
-
export function collectContentIndex(contentBase: string, { contentPackage, currentDate, defaultCalendar, configFile, rootDir, languages, skipDirectories, assetsBase, manifest, problems, addressContext, foreignRecord, parsed, sources, syntax, nameTypes, }: {
|
|
233
|
+
export function collectContentIndex(contentBase: string, { contentPackage, currentDate, defaultCalendar, configFile, rootDir, languages, skipDirectories, assetsBase, manifest, problems, addressContext, foreignRecord, parsed, sources, syntax, nameTypes, timings, }: {
|
|
232
234
|
contentPackage: string;
|
|
233
235
|
skipDirectories: readonly string[];
|
|
234
236
|
assetsBase?: string | undefined;
|
|
@@ -257,6 +259,17 @@ export function collectContentIndex(contentBase: string, { contentPackage, curre
|
|
|
257
259
|
}> | undefined;
|
|
258
260
|
syntax?: import("./content-session.mjs").ContentSyntax | undefined;
|
|
259
261
|
nameTypes?: Iterable<string> | undefined;
|
|
262
|
+
timings?: {
|
|
263
|
+
sync<T>(name: string, work: () => T): T;
|
|
264
|
+
measure<T>(name: string, work: () => Promise<T> | T): Promise<T>;
|
|
265
|
+
start(name: string): () => void;
|
|
266
|
+
hit(name: string): void;
|
|
267
|
+
snapshot(): {
|
|
268
|
+
totalMs: number;
|
|
269
|
+
stages: any;
|
|
270
|
+
hits: any;
|
|
271
|
+
};
|
|
272
|
+
} | undefined;
|
|
260
273
|
}): Array<Record<string, any>>;
|
|
261
274
|
/**
|
|
262
275
|
* Serialize records as JSON Lines.
|
|
@@ -301,9 +314,11 @@ export function serializeContentIndex(records: Array<Record<string, any>>): stri
|
|
|
301
314
|
* @param {import("./content-session.mjs").ContentSyntax} [opts.syntax] - Session scanner projections.
|
|
302
315
|
* @param {Iterable<string>} [opts.nameTypes] - Bare name kinds, defaulting to configured item types.
|
|
303
316
|
* @param {Map<string, object>} [opts.foreignIndex] - Captured dependency address view.
|
|
317
|
+
* @param {ReturnType<typeof import("./content-timings.mjs").createContentTimings>} [opts.timings] -
|
|
318
|
+
* Invocation stage accounting.
|
|
304
319
|
* @returns {object[]} One record per note, plus one per documentation entry.
|
|
305
320
|
*/
|
|
306
|
-
export function indexRecordsFor({ contentBase, assetsBase, config, skipDirectories, problems, parsed, sources, syntax, nameTypes, foreignIndex, }?: {
|
|
321
|
+
export function indexRecordsFor({ contentBase, assetsBase, config, skipDirectories, problems, parsed, sources, syntax, nameTypes, foreignIndex, timings, }?: {
|
|
307
322
|
contentBase?: string | undefined;
|
|
308
323
|
assetsBase?: string | undefined;
|
|
309
324
|
config?: object | undefined;
|
|
@@ -322,6 +337,17 @@ export function indexRecordsFor({ contentBase, assetsBase, config, skipDirectori
|
|
|
322
337
|
syntax?: import("./content-session.mjs").ContentSyntax | undefined;
|
|
323
338
|
nameTypes?: Iterable<string> | undefined;
|
|
324
339
|
foreignIndex?: Map<string, object> | undefined;
|
|
340
|
+
timings?: {
|
|
341
|
+
sync<T>(name: string, work: () => T): T;
|
|
342
|
+
measure<T>(name: string, work: () => Promise<T> | T): Promise<T>;
|
|
343
|
+
start(name: string): () => void;
|
|
344
|
+
hit(name: string): void;
|
|
345
|
+
snapshot(): {
|
|
346
|
+
totalMs: number;
|
|
347
|
+
stages: any;
|
|
348
|
+
hits: any;
|
|
349
|
+
};
|
|
350
|
+
} | undefined;
|
|
325
351
|
}): object[];
|
|
326
352
|
/**
|
|
327
353
|
* Prepare the complete bodies a content-index publication describes.
|
|
@@ -27,6 +27,7 @@ export function createContentSession({ config, contentBase, assetsBase, skipDire
|
|
|
27
27
|
timings?: {
|
|
28
28
|
sync<T>(name: string, work: () => T): T;
|
|
29
29
|
measure<T>(name: string, work: () => Promise<T> | T): Promise<T>;
|
|
30
|
+
start(name: string): () => void;
|
|
30
31
|
hit(name: string): void;
|
|
31
32
|
snapshot(): {
|
|
32
33
|
totalMs: number;
|
|
@@ -4,6 +4,13 @@ export function createContentTimings(): {
|
|
|
4
4
|
sync<T>(name: string, work: () => T): T;
|
|
5
5
|
/** @template T @param {string} name @param {() => Promise<T>|T} work @returns {Promise<T>} */
|
|
6
6
|
measure<T>(name: string, work: () => Promise<T> | T): Promise<T>;
|
|
7
|
+
/**
|
|
8
|
+
* Open a stage that runs until the returned function is called, or
|
|
9
|
+
* until the stage it was opened in ends.
|
|
10
|
+
* @param {string} name
|
|
11
|
+
* @returns {() => void}
|
|
12
|
+
*/
|
|
13
|
+
start(name: string): () => void;
|
|
7
14
|
/** @param {string} name */
|
|
8
15
|
hit(name: string): void;
|
|
9
16
|
snapshot(): {
|
|
@@ -59,24 +59,11 @@ export function languageNoteFindings(config: {
|
|
|
59
59
|
* word's 0-based place in `data.words`, `native` the form's in its `native`.
|
|
60
60
|
*/
|
|
61
61
|
export function nativeRows(records: Iterable<Record<string, any>>): Array<Record<string, string | number | null>>;
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
[k: string]: any;
|
|
68
|
-
} | undefined;
|
|
69
|
-
words: {
|
|
70
|
-
[k: string]: any;
|
|
71
|
-
}[];
|
|
72
|
-
affixes: {
|
|
73
|
-
[k: string]: any;
|
|
74
|
-
}[];
|
|
75
|
-
register: {
|
|
76
|
-
[k: string]: any;
|
|
77
|
-
}[];
|
|
78
|
-
};
|
|
79
|
-
} | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Project only conversion-readable language data to a JSON-safe object. The
|
|
64
|
+
* projection of one lexicon is made once and shared, so it must not be modified.
|
|
65
|
+
*/
|
|
66
|
+
export function serializeLexicon(lexicon: any): any;
|
|
80
67
|
/**
|
|
81
68
|
* Resolve encoding, face and optional language input without loading files.
|
|
82
69
|
*
|
|
@@ -129,23 +116,7 @@ export function encodingFor(ctx: import("./content-scripts.mjs").RunContext, { e
|
|
|
129
116
|
};
|
|
130
117
|
}>;
|
|
131
118
|
font: any;
|
|
132
|
-
lexicon:
|
|
133
|
-
language: any;
|
|
134
|
-
data: {
|
|
135
|
-
grammar?: {
|
|
136
|
-
[k: string]: any;
|
|
137
|
-
} | undefined;
|
|
138
|
-
words: {
|
|
139
|
-
[k: string]: any;
|
|
140
|
-
}[];
|
|
141
|
-
affixes: {
|
|
142
|
-
[k: string]: any;
|
|
143
|
-
}[];
|
|
144
|
-
register: {
|
|
145
|
-
[k: string]: any;
|
|
146
|
-
}[];
|
|
147
|
-
};
|
|
148
|
-
} | undefined;
|
|
119
|
+
lexicon: any;
|
|
149
120
|
} | undefined;
|
|
150
121
|
/** Why a face is incompatible with the selected encoding. */
|
|
151
122
|
export function fontProblem(encoding: any, font: any): string | undefined;
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export function nativeText(natives: unknown, enc: string, font: string | undefined): string | undefined;
|
|
18
18
|
/**
|
|
19
|
-
* The writings of a language's words under one encoding in one face.
|
|
19
|
+
* The writings of a language's words under one encoding in one face. One
|
|
20
|
+
* `data` yields one set of writings per encoding and face, and each word's
|
|
21
|
+
* writing is found once, so `data` must not be modified once it is read here.
|
|
20
22
|
*
|
|
21
23
|
* @param {Record<string, any>|undefined} data - The language note's `data`.
|
|
22
24
|
* @param {string} enc - The encoding's name.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The block tokens of a Markdown source. The array is shared between callers
|
|
3
|
+
* and must not be modified; an `inline` token carries no `children`.
|
|
4
|
+
*
|
|
5
|
+
* @param {unknown} source - Markdown.
|
|
6
|
+
* @returns {readonly import("markdown-it").Token[]} The block tokens, in order.
|
|
7
|
+
*/
|
|
8
|
+
export function blockTokens(source: unknown): readonly import("markdown-it").Token[];
|