@heroiclands/package-build 23.12.7 → 23.13.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +1 -1
  3. package/assets/preview/gitea.ini +2 -2
  4. package/assets/styles/content-base.css +143 -23
  5. package/bin/content-commands.mjs +15 -4
  6. package/bin/package-build-preview.mjs +2 -2
  7. package/docs/api.md +18 -18
  8. package/docs/authoring/assets.md +1 -1
  9. package/docs/authoring/links-and-markup.md +156 -22
  10. package/docs/commands.md +32 -10
  11. package/docs/configuration.md +8 -1
  12. package/docs/content-format.md +3 -3
  13. package/docs/diagnostics.md +42 -0
  14. package/docs/reference/format-details.md +105 -22
  15. package/docs/reference/note-types.md +8 -1
  16. package/docs/reference/theme-pages.md +51 -0
  17. package/docs/source-preview.md +1 -1
  18. package/e2e.mjs +1 -1
  19. package/engine/anchors.mjs +2 -2
  20. package/engine/asset-shipping.mjs +22 -8
  21. package/engine/code-fences.mjs +3 -3
  22. package/engine/content-blocks.mjs +3 -3
  23. package/engine/content-figures.mjs +5 -5
  24. package/engine/content-index.mjs +1 -1
  25. package/engine/content-scripts.mjs +55 -21
  26. package/engine/content-spans.mjs +2 -2
  27. package/engine/content-verse.mjs +750 -0
  28. package/engine/dictionary-rows.mjs +15 -4
  29. package/engine/field-reference.mjs +4 -4
  30. package/engine/first-use.mjs +17 -27
  31. package/engine/key-case-collisions.mjs +136 -0
  32. package/engine/language-data.mjs +203 -6
  33. package/engine/language-terms.mjs +74 -5
  34. package/engine/map-lexicon.mjs +42 -23
  35. package/engine/names.mjs +36 -9
  36. package/engine/note-matter.mjs +66 -0
  37. package/engine/pdf-build.mjs +1 -0
  38. package/engine/pdf-render.mjs +107 -18
  39. package/engine/read-stdin.mjs +29 -0
  40. package/engine/run-rows.mjs +2 -2
  41. package/engine/run-words.mjs +4 -111
  42. package/engine/site-build.mjs +4 -4
  43. package/engine/site-preview.mjs +14 -14
  44. package/engine/sql-tables.mjs +20 -3
  45. package/engine/web-wikilinks.mjs +1 -1
  46. package/engine/word-forms.mjs +349 -0
  47. package/engine/word-terms.mjs +9 -0
  48. package/hugo-theme/static/css/style.css +92 -14
  49. package/package.json +3 -3
  50. package/types/e2e.d.mts +2 -2
  51. package/types/engine/address-fields.d.mts +1 -1
  52. package/types/engine/address-values.d.mts +1 -1
  53. package/types/engine/asset-shipping.d.mts +5 -0
  54. package/types/engine/code-fences.d.mts +3 -3
  55. package/types/engine/content-format-check.d.mts +1 -1
  56. package/types/engine/content-scripts.d.mts +8 -4
  57. package/types/engine/content-verse.d.mts +240 -0
  58. package/types/engine/dictionary-rows.d.mts +10 -0
  59. package/types/engine/field-reference.d.mts +1 -1
  60. package/types/engine/key-case-collisions.d.mts +28 -0
  61. package/types/engine/language-data.d.mts +0 -2
  62. package/types/engine/map-lexicon.d.mts +19 -6
  63. package/types/engine/names.d.mts +34 -3
  64. package/types/engine/note-matter.d.mts +48 -0
  65. package/types/engine/note-vocabulary.d.mts +1 -1
  66. package/types/engine/pdf-coverage.d.mts +1 -1
  67. package/types/engine/read-stdin.d.mts +11 -0
  68. package/types/engine/run-rows.d.mts +2 -2
  69. package/types/engine/run-words.d.mts +0 -15
  70. package/types/engine/sql-tables.d.mts +4 -1
  71. package/types/engine/text-maps.d.mts +1 -1
  72. package/types/engine/web-wikilinks.d.mts +1 -1
  73. package/types/engine/word-forms.d.mts +82 -0
  74. package/types/release.d.mts +1 -1
  75. package/engine/content-poetry.mjs +0 -240
  76. package/types/engine/content-poetry.d.mts +0 -58
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "23.12.7",
3
+ "version": "23.13.0",
4
4
  "description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "type": "module",
@@ -151,16 +151,16 @@
151
151
  "fflate": "^0.8.3",
152
152
  "flesch": "^2.0.1",
153
153
  "glob": "^13.0.6",
154
- "gray-matter": "^4.0.3",
155
154
  "handlebars": "^4.7.10",
156
155
  "ignore": "^7.0.12",
156
+ "js-yaml": "^5.5.0",
157
157
  "jsonc-parser": "^3.3.1",
158
158
  "loglevel": "^1.9.2",
159
159
  "loglevel-plugin-prefix": "^0.8.4",
160
160
  "markdown-it": "^15.0.0",
161
161
  "markdown-it-deflist": "^4.0.0",
162
162
  "markdown-it-footnote": "^4.0.0",
163
- "markdownlint": "^0.41.1",
163
+ "markdownlint": "^0.42.0",
164
164
  "minimatch": "^10.2.6",
165
165
  "pagefind": "^1.5.2",
166
166
  "prettier": "^3.9.6",
package/types/e2e.d.mts CHANGED
@@ -419,7 +419,7 @@ export function suiteCommand(config: object, mode: "run" | "open"): string[];
419
419
  export function e2eRun({ config, packageJson, mode, suiteArgs, env, log, }: {
420
420
  config: object;
421
421
  packageJson: object;
422
- mode?: "open" | "run" | undefined;
422
+ mode?: "run" | "open" | undefined;
423
423
  suiteArgs?: string[] | undefined;
424
424
  env?: NodeJS.ProcessEnv | undefined;
425
425
  log?: ((message: string) => void) | undefined;
@@ -494,7 +494,7 @@ export const E2E_SCENE_ID: "heroiclandsScene";
494
494
  /** How the suite may be run. */
495
495
  export const E2E_MODES: readonly string[];
496
496
  /** The Chrome for Testing build shared by local and CI Cypress runs. */
497
- export const CHROME_BUILD_ID: "155.0.8059.84";
497
+ export const CHROME_BUILD_ID: "158.0.8110.0";
498
498
  /**
499
499
  * The disposable world's identity and credentials.
500
500
  */
@@ -69,7 +69,7 @@ export type AddressPosition = {
69
69
  * - Where the Addresses are.
70
70
  * Defaults to `value`.
71
71
  */
72
- holds?: "keys" | "value" | "items" | undefined;
72
+ holds?: "keys" | "items" | "value" | undefined;
73
73
  /**
74
74
  * - Resolve each exact target to its published Item UUID.
75
75
  */
@@ -75,7 +75,7 @@ export class AddressLink {
75
75
  constructor(target: object, anchor: string, kind?: "prose" | "event" | null);
76
76
  target: object;
77
77
  anchor: string;
78
- kind: "event" | "prose" | null;
78
+ kind: "prose" | "event" | null;
79
79
  }
80
80
  /** The keys of an Address as the published index writes it, and no others. */
81
81
  export const PUBLISHED_ADDRESS_KEYS: readonly string[];
@@ -110,6 +110,11 @@ export function assetsManifest(staged: readonly object[], stageDir: string): Ass
110
110
  * stage before a file is copied: a package whose provenance cannot be read is
111
111
  * not one whose shipping can be decided.
112
112
  *
113
+ * The pictures are written beside whatever a `packageBuild.assets` row already
114
+ * put under `icons/` and `images/`; nothing there is removed. A picture whose
115
+ * deployed path is already a file in the stage is a finding, and no picture is
116
+ * written. Clearing an earlier run's pictures is the stage reset's work.
117
+ *
113
118
  * @param {object} opts - Options.
114
119
  * @param {string} opts.assetsBase - The package's asset directory, the roots'
115
120
  * parent.
@@ -17,13 +17,13 @@ export function parseHeaderArgs(info: string): {
17
17
  * @param {object} [options]
18
18
  * @param {boolean} [options.spans=true] - Include inline code spans. Set false
19
19
  * to consider only block-level code.
20
- * @param {boolean} [options.poetryAsProse=true] - Treat verse bodies as inline prose.
20
+ * @param {boolean} [options.verseAsProse=true] - Treat verse bodies as inline prose.
21
21
  * @returns {Array<{start: number, end: number}>} Non-overlapping regions, in
22
22
  * source order. `end` is exclusive.
23
23
  */
24
- export function codeRegions(markdown: string | null | undefined, { spans, poetryAsProse }?: {
24
+ export function codeRegions(markdown: string | null | undefined, { spans, verseAsProse }?: {
25
25
  spans?: boolean | undefined;
26
- poetryAsProse?: boolean | undefined;
26
+ verseAsProse?: boolean | undefined;
27
27
  }): Array<{
28
28
  start: number;
29
29
  end: number;
@@ -117,7 +117,7 @@ export function checkDeclaredFields({ format, itemFields, system, severity }: {
117
117
  format: import("./content-format.mjs").ContentFormat;
118
118
  itemFields: Record<string, readonly object[]>;
119
119
  system: string;
120
- severity?: "error" | "warning" | undefined;
120
+ severity?: "warning" | "error" | undefined;
121
121
  }): {
122
122
  findings: object[];
123
123
  coverage: object[];
@@ -14,7 +14,7 @@ export function runContext(config: {
14
14
  rootDir?: string;
15
15
  } | undefined, records?: Iterable<Record<string, any>>): RunContext;
16
16
  /**
17
- * Find the spans, divs and poetry fences marked with `lang=`, `font=` or
17
+ * Find the spans, divs and verse fences marked with `lang=`, `font=` or
18
18
  * `map=`, and every fault in their markup and their text.
19
19
  *
20
20
  * @param {string} source - Authored Markdown: a note body, or a whole file
@@ -44,8 +44,12 @@ export function scanRuns(source: string, ctx?: RunContext, syntax?: import("./co
44
44
  };
45
45
  /**
46
46
  * Rewrite text marked with `lang=`, `font=` and `map=` into the plain span,
47
- * div and poetry grammar the rest of each chain renders, one line for one line.
47
+ * div and verse grammar the rest of each chain renders, one line for one line.
48
48
  *
49
+ * - **A verse in columns is expanded first** into one span per column entry
50
+ * per verse line, by {@link module:engine/content-verse.expandVerse}, so
51
+ * each entry is mapped and set as a span with its keys is. That is the one
52
+ * rewrite that changes the number of lines.
49
53
  * - **The map, if any, is applied first.** Characters Markdown would read are
50
54
  * then backslash-escaped, so they reach every later pass as characters.
51
55
  * - **The construct carries** the language's tag as `lang`, the face's class
@@ -179,7 +183,7 @@ export type RunContext = {
179
183
  wordBooks?: ReadonlyMap<string, import("./run-words.mjs").WordBook> | undefined;
180
184
  };
181
185
  /**
182
- * One span, div or poetry fence carrying `lang=`, `font=` or `map=`. Its
186
+ * One span, div or verse fence carrying `lang=`, `font=` or `map=`. Its
183
187
  * `lang`, `font` and `map` are the ones it is set with: its own, else the
184
188
  * innermost div or fence around it that states one.
185
189
  */
@@ -187,7 +191,7 @@ export type Run = {
187
191
  /**
188
192
  * - Which construct.
189
193
  */
190
- kind: "span" | "block" | "poetry";
194
+ kind: "span" | "block" | "verse";
191
195
  /**
192
196
  * - The language as written.
193
197
  */
@@ -0,0 +1,240 @@
1
+ /** The authored verse lines, excluding stanza separators. */
2
+ export function verseLines(body: any): string[];
3
+ /**
4
+ * The least-indented verse is level zero. Every two additional leading spaces
5
+ * add one level; a leftover space is discarded and the level stops at eight.
6
+ * Blank lines divide stanzas rather than contributing to the baseline.
7
+ */
8
+ export function verseStanzas(body: any): {
9
+ text: string;
10
+ level: number;
11
+ }[][];
12
+ /** A verse is inline Markdown even when it starts with block syntax. */
13
+ export function inlineVerse(text: any): string;
14
+ /**
15
+ * Keep stanzas as Markdown paragraphs and make every verse line a hard break.
16
+ * The surrounding renderer still sees the entire note, so links and footnote
17
+ * definitions remain in scope. The span carries the indent for each line.
18
+ */
19
+ export function verseMarkdown(body: any, step?: number): string;
20
+ /**
21
+ * The number a line shows: `undefined` when the verse is not numbered, `""`
22
+ * when this line is not one of every `step`th.
23
+ *
24
+ * @param {number} line - The line's number, 1-based across the verse.
25
+ * @param {number} step - The verse's `numstep`.
26
+ * @returns {string|undefined} The number shown.
27
+ */
28
+ export function lineNumber(line: number, step: number): string | undefined;
29
+ /** Read verse code fences; start/end are inclusive zero-based source lines.
30
+ * @param {string} source - Authored Markdown.
31
+ */
32
+ export function scanVerse(source: string): {
33
+ blocks: {
34
+ end: number;
35
+ body: string;
36
+ start: number;
37
+ fence: string;
38
+ name: string;
39
+ attributes: {};
40
+ id: string;
41
+ classes: never[];
42
+ indent: string;
43
+ }[];
44
+ errors: {
45
+ line: number;
46
+ column: number;
47
+ message: string;
48
+ }[];
49
+ };
50
+ /** Whether a verse states a column. */
51
+ export function hasColumns(block: any): boolean;
52
+ /**
53
+ * A column's stacked entries, each with its offset in the written value.
54
+ *
55
+ * @param {string} value - A `col1=` or `col2=` value.
56
+ * @returns {Array<{raw: string, offset: number, lang?: string, map?: string, font?: string, offsets: {lang: number, map?: number, font?: number}}>}
57
+ * Each entry; one whose `lang` is `undefined` is malformed.
58
+ */
59
+ export function columnEntries(value: string): Array<{
60
+ raw: string;
61
+ offset: number;
62
+ lang?: string;
63
+ map?: string;
64
+ font?: string;
65
+ offsets: {
66
+ lang: number;
67
+ map?: number;
68
+ font?: number;
69
+ };
70
+ }>;
71
+ /**
72
+ * The columns of a verse, the left first, each entry located in the opening
73
+ * line.
74
+ *
75
+ * @param {object} block - A verse from {@link scanVerse}.
76
+ * @param {string} opening - Its opening line.
77
+ * @returns {Array<{key: string, entries: Array<ReturnType<typeof columnEntries>[number] & {column: number, columns: {lang: number, map?: number, font?: number}}>}>}
78
+ * 1-based columns.
79
+ */
80
+ export function verseColumns(block: object, opening: string): Array<{
81
+ key: string;
82
+ entries: Array<ReturnType<typeof columnEntries>[number] & {
83
+ column: number;
84
+ columns: {
85
+ lang: number;
86
+ map?: number;
87
+ font?: number;
88
+ };
89
+ }>;
90
+ }>;
91
+ /**
92
+ * The verse lines of a verse in columns: each blank-line group of span lines
93
+ * is one verse line, and a line holding only `***` breaks the stanza.
94
+ *
95
+ * @param {object} block - A verse from {@link scanVerse}.
96
+ * @param {string[]} lines - The source, by line.
97
+ * @returns {Array<{stanzaBreak: true, line: number}|{stanzaBreak?: false, parts: Array<{line: number, column: number, text: string, textColumn: number, lang?: string, span: boolean, extra: boolean, indent: number}>}>}
98
+ * 0-based lines, 1-based columns.
99
+ */
100
+ export function verseGroups(block: object, lines: string[]): Array<{
101
+ stanzaBreak: true;
102
+ line: number;
103
+ } | {
104
+ stanzaBreak?: false;
105
+ parts: Array<{
106
+ line: number;
107
+ column: number;
108
+ text: string;
109
+ textColumn: number;
110
+ lang?: string;
111
+ span: boolean;
112
+ extra: boolean;
113
+ indent: number;
114
+ }>;
115
+ }>;
116
+ /**
117
+ * Whether text runs right to left: its first letter is in a right-to-left
118
+ * block. A face's right-to-left code points sit in U+1E800–U+1EFFF.
119
+ *
120
+ * @param {string} text - Text, after any map.
121
+ * @returns {boolean} Whether it starts right to left.
122
+ */
123
+ export function rightToLeft(text: string): boolean;
124
+ /**
125
+ * Where a line of {@link expandVerseOrigins}'s output came from.
126
+ *
127
+ * @typedef {object} VerseOrigin
128
+ * @property {number} line - The source line, 0-based.
129
+ * @property {number} [textColumn] - For an entry's line, the 1-based source
130
+ * column of its text's first character.
131
+ * @property {boolean} [span] - Whether the source line is a span, whose `[`
132
+ * sits before its text.
133
+ * @property {number} [textLength] - The length of the entry's text.
134
+ * @property {{line: number, columns: {lang: number, map?: number, font?: number}}} [entry] -
135
+ * Where the entry is written in the opening line.
136
+ */
137
+ /**
138
+ * Write each verse line of a sound verse in columns as one span per column
139
+ * entry, the left column's entries first, each with the entry's language,
140
+ * mapping and font. Every target runs it before marked text is rewritten, so
141
+ * each entry is set exactly as a span with those keys is. The verse gains the
142
+ * class {@link COLUMNS_CLASS}; a verse already carrying it, a verse with a
143
+ * finding, and a verse without columns are left as written.
144
+ *
145
+ * @param {string} source - Markdown.
146
+ * @returns {{markdown: string, origins: VerseOrigin[]}} The Markdown, and
147
+ * where each of its lines came from.
148
+ */
149
+ export function expandVerseOrigins(source: string): {
150
+ markdown: string;
151
+ origins: VerseOrigin[];
152
+ };
153
+ /**
154
+ * {@link expandVerseOrigins}'s Markdown alone.
155
+ *
156
+ * @param {string} source - Markdown.
157
+ * @returns {string} The Markdown with each verse in columns expanded.
158
+ */
159
+ export function expandVerse(source: string): string;
160
+ /**
161
+ * Where a position in {@link expandVerseOrigins}'s output was written.
162
+ *
163
+ * A position in an entry's text is the same character of the source line; a
164
+ * position in its keys is the entry's part in the opening line that states
165
+ * the key it falls in.
166
+ *
167
+ * @param {{line: number, column: number}} at - 1-based, in the output.
168
+ * @param {string[]} expanded - The output, by line.
169
+ * @param {VerseOrigin[]} origins - From {@link expandVerseOrigins}.
170
+ * @returns {{line: number, column: number}} 1-based, in the source.
171
+ */
172
+ export function sourcePosition(at: {
173
+ line: number;
174
+ column: number;
175
+ }, expanded: string[], origins: VerseOrigin[]): {
176
+ line: number;
177
+ column: number;
178
+ };
179
+ /** Replace verse fences with HTML wrappers whose body remains inline Markdown.
180
+ * @param {string} source - Authored Markdown.
181
+ */
182
+ export function renderVerse(source: string): {
183
+ markdown: string;
184
+ errors: {
185
+ line: number;
186
+ column: number;
187
+ message: string;
188
+ }[];
189
+ };
190
+ /** The keys that set a verse in columns, the left first. */
191
+ export const COLUMN_KEYS: readonly string[];
192
+ /** The class the build gives a verse in columns once its lines are expanded. */
193
+ export const COLUMNS_CLASS: "verse-columns";
194
+ /**
195
+ * The keys a verse fence's attribute block takes. `lang`, `font` and `map`
196
+ * are read by `scanRuns` in content-scripts.mjs; `dir`, and on the site
197
+ * `data-pagefind-ignore`, are written by its rewrite of a fence set in one of
198
+ * the faces, and reach the element as they are written.
199
+ */
200
+ export const VERSE_KEYS: readonly string[];
201
+ /**
202
+ * The line that breaks a stanza in a verse in columns, `***`, also as the
203
+ * rewrite of marked text escapes it.
204
+ */
205
+ export const STANZA_BREAK: RegExp;
206
+ /**
207
+ * Where a line of {@link expandVerseOrigins}'s output came from.
208
+ */
209
+ export type VerseOrigin = {
210
+ /**
211
+ * - The source line, 0-based.
212
+ */
213
+ line: number;
214
+ /**
215
+ * - For an entry's line, the 1-based source
216
+ * column of its text's first character.
217
+ */
218
+ textColumn?: number | undefined;
219
+ /**
220
+ * - Whether the source line is a span, whose `[`
221
+ * sits before its text.
222
+ */
223
+ span?: boolean | undefined;
224
+ /**
225
+ * - The length of the entry's text.
226
+ */
227
+ textLength?: number | undefined;
228
+ /**
229
+ * -
230
+ * Where the entry is written in the opening line.
231
+ */
232
+ entry?: {
233
+ line: number;
234
+ columns: {
235
+ lang: number;
236
+ map?: number;
237
+ font?: number;
238
+ };
239
+ } | undefined;
240
+ };
@@ -13,6 +13,16 @@ export function pluralOf(word: {
13
13
  class?: unknown;
14
14
  plural?: unknown;
15
15
  }, grammar: Record<string, any> | undefined): string | null;
16
+ /**
17
+ * The plural a language's `data.grammar.plural` rule gives a form: the first
18
+ * literal ending replacement that fits, whatever the form's class.
19
+ *
20
+ * @param {string} form - The form.
21
+ * @param {Record<string, any>|undefined} grammar - Its language's
22
+ * `data.grammar`.
23
+ * @returns {string|null} The plural, or `null` where no rule fits.
24
+ */
25
+ export function pluralByRule(form: string, grammar: Record<string, any> | undefined): string | null;
16
26
  /**
17
27
  * The stable prose anchor of a word in one language's dictionary. The exact
18
28
  * language and NFC form identify it, including case and accents.
@@ -84,7 +84,7 @@ export function shortcodeFromBasename(destination: string): string;
84
84
  * {@link shortcodeFromBasename}.
85
85
  * @param {Record<string, unknown>} [options.frontmatter] - The consumer's
86
86
  * declared `docs.itemFields.frontmatter`.
87
- * @returns {Record<string, unknown>} The envelope, ready for `matter.stringify`.
87
+ * @returns {Record<string, unknown>} The envelope, ready for `writeMatter`.
88
88
  */
89
89
  export function itemFieldsEnvelope({ title, shortcode, frontmatter }: {
90
90
  title: string;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Every key that two records, or one record, spell with different case at the
3
+ * same path.
4
+ *
5
+ * @param {object[]} records - Content-index records.
6
+ * @returns {Array<{key: string, other: string, record: object, otherRecord: object}>}
7
+ * One entry per spelling that collides with another, naming the record that
8
+ * uses it and a record that uses the other spelling.
9
+ */
10
+ export function findKeyCaseCollisions(records: object[]): Array<{
11
+ key: string;
12
+ other: string;
13
+ record: object;
14
+ otherRecord: object;
15
+ }>;
16
+ /**
17
+ * The diagnostics for every case collision in the records, one per spelling,
18
+ * each naming the note and line of the spelling it collides with.
19
+ *
20
+ * @param {object[]} records - Content-index records.
21
+ * @param {object} [options]
22
+ * @param {string} [options.contentBase] - The directory the records' paths are
23
+ * relative to.
24
+ * @returns {string[]} Formatted `file:line:column: error: message` lines.
25
+ */
26
+ export function keyCaseCollisionDiagnostics(records: object[], { contentBase }?: {
27
+ contentBase?: string | undefined;
28
+ }): string[];
@@ -20,5 +20,3 @@ export function checkLanguageData(note: object, { index, types }?: {
20
20
  index?: object | undefined;
21
21
  types?: readonly string[] | undefined;
22
22
  }): object[];
23
- /** How a foreign row that its tongue's register does not bear out is reported. */
24
- export const FOREIGN_ROW_SEVERITY: "warning";
@@ -125,18 +125,21 @@ export function transliterate(ctx: object, keys: {
125
125
  /**
126
126
  * Every word of a language's lexicon with its native form under a map: the
127
127
  * stated exception, else the rows' spelling, each exactly as the build gives
128
- * it.
128
+ * it. With `derived`, the forms the language's rules derive follow the words,
129
+ * each case folded and naming the word it comes from in `from`, so the list
130
+ * is what the mapped-run word check accepts of the lexicon.
129
131
  *
130
132
  * @param {object} ctx - The run context.
131
- * @param {{lang: string, map: string, font?: string}} keys - The language, the
132
- * map and the face.
133
- * @returns {{entries: Array<{form: string, native: string, exception: boolean, class: string|null, gloss: string|null}>}|{problem: string, about: string}}
134
- * The words in `data.words` order, or the problem.
133
+ * @param {{lang: string, map: string, font?: string, derived?: boolean}} keys -
134
+ * The language, the map, the face, and whether to list derived forms.
135
+ * @returns {{entries: Array<{form: string, native: string, exception: boolean, class: string|null, gloss: string|null, from?: string}>}|{problem: string, about: string}}
136
+ * The words in `data.words` order, then any derived forms, or the problem.
135
137
  */
136
- export function lexiconEntries(ctx: object, { lang, map, font }: {
138
+ export function lexiconEntries(ctx: object, { lang, map, font, derived }: {
137
139
  lang: string;
138
140
  map: string;
139
141
  font?: string;
142
+ derived?: boolean;
140
143
  }): {
141
144
  entries: Array<{
142
145
  form: string;
@@ -144,6 +147,7 @@ export function lexiconEntries(ctx: object, { lang, map, font }: {
144
147
  exception: boolean;
145
148
  class: string | null;
146
149
  gloss: string | null;
150
+ from?: string;
147
151
  }>;
148
152
  } | {
149
153
  problem: string;
@@ -253,6 +257,15 @@ export type Lexicon = {
253
257
  * forms by the word's form, NFC; the first word of a form keeps it.
254
258
  */
255
259
  byForm: ReadonlyMap<string, readonly NativeForm[]>;
260
+ /**
261
+ * - The
262
+ * forms the language's rules derive from its words, folded, as the mapped-run
263
+ * word check accepts them (see {@link module :engine/word-forms.derivedForms}).
264
+ */
265
+ derived: ReadonlyArray<{
266
+ form: string;
267
+ from: LexiconWord;
268
+ }>;
256
269
  };
257
270
  /**
258
271
  * What a run, a passage or a listing is mapped by.
@@ -5,15 +5,24 @@
5
5
  * @property {string} full - Declared full name.
6
6
  * @property {true} indexed - The identity belongs to the indexed catalog.
7
7
  * @property {boolean} [fullUse] - The label is a full-name form rather than an alias.
8
+ * @property {string} [type] - The note's type, absent on an ambiguous label identity.
9
+ * @property {string|null} [subType] - The note's subtype, or null.
8
10
  * @property {NameMatch[]} [candidates] - Indexed identities sharing the label.
9
11
  * @property {string} [destination] - Canonical Address used by a resolved link.
10
12
  */
11
13
  /**
12
14
  * The prepared name and declared-alias lookup.
13
15
  * @typedef {object} NameCatalog
14
- * @property {(label: string, preferred?: {key: string}) => NameMatch|undefined} recognize - Match a whole visible label.
16
+ * @property {(label: string, preferred?: {key: string}, kind?: NameKind) => NameMatch|undefined} recognize - Match a whole visible label, among notes of `kind` when it supplies a type.
15
17
  * @property {(target: string) => NameMatch|undefined} resolve - Resolve an Address target without its anchor to its indexed identity.
16
18
  */
19
+ /**
20
+ * What a typed `.name` span says the named thing is. A null type matches any note;
21
+ * a type matches only notes of that type, and a subtype only notes of that subtype.
22
+ * @typedef {object} NameKind
23
+ * @property {string|null} type - Supplied type, or null.
24
+ * @property {string|null} subType - Supplied subtype, or null.
25
+ */
17
26
  /**
18
27
  * One captured marked-name occurrence.
19
28
  * @typedef {object} MarkedName
@@ -175,6 +184,14 @@ export type NameMatch = {
175
184
  * - The label is a full-name form rather than an alias.
176
185
  */
177
186
  fullUse?: boolean | undefined;
187
+ /**
188
+ * - The note's type, absent on an ambiguous label identity.
189
+ */
190
+ type?: string | undefined;
191
+ /**
192
+ * - The note's subtype, or null.
193
+ */
194
+ subType?: string | null | undefined;
178
195
  /**
179
196
  * - Indexed identities sharing the label.
180
197
  */
@@ -189,16 +206,30 @@ export type NameMatch = {
189
206
  */
190
207
  export type NameCatalog = {
191
208
  /**
192
- * - Match a whole visible label.
209
+ * - Match a whole visible label, among notes of `kind` when it supplies a type.
193
210
  */
194
211
  recognize: (label: string, preferred?: {
195
212
  key: string;
196
- }) => NameMatch | undefined;
213
+ }, kind?: NameKind) => NameMatch | undefined;
197
214
  /**
198
215
  * - Resolve an Address target without its anchor to its indexed identity.
199
216
  */
200
217
  resolve: (target: string) => NameMatch | undefined;
201
218
  };
219
+ /**
220
+ * What a typed `.name` span says the named thing is. A null type matches any note;
221
+ * a type matches only notes of that type, and a subtype only notes of that subtype.
222
+ */
223
+ export type NameKind = {
224
+ /**
225
+ * - Supplied type, or null.
226
+ */
227
+ type: string | null;
228
+ /**
229
+ * - Supplied subtype, or null.
230
+ */
231
+ subType: string | null;
232
+ };
202
233
  /**
203
234
  * One captured marked-name occurrence.
204
235
  */
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A page split at its frontmatter.
3
+ *
4
+ * @typedef {object} NoteMatter
5
+ * @property {unknown} data - The parsed frontmatter; an empty map where the
6
+ * page has none or the block is empty.
7
+ * @property {string} content - The body after the closing fence and its line
8
+ * break, or the whole text where there is no frontmatter.
9
+ * @property {string} matter - The YAML source between the fences; empty where
10
+ * there is no frontmatter.
11
+ */
12
+ /**
13
+ * Split a page's frontmatter from its body and parse it.
14
+ *
15
+ * @param {string} source - The complete Markdown source.
16
+ * @returns {NoteMatter} The parsed frontmatter, the body and the YAML source.
17
+ * @throws {Error} The `yaml` parse error, where the frontmatter is not YAML.
18
+ */
19
+ export function readMatter(source: string): NoteMatter;
20
+ /**
21
+ * A page with `data` as its frontmatter above `content`.
22
+ *
23
+ * @param {string} content - The body.
24
+ * @param {Record<string, unknown>} data - The frontmatter.
25
+ * @returns {string} The fenced YAML, then the body closed on a line break; the
26
+ * body alone where `data` serializes to an empty map.
27
+ */
28
+ export function writeMatter(content: string, data: Record<string, unknown>): string;
29
+ /**
30
+ * A page split at its frontmatter.
31
+ */
32
+ export type NoteMatter = {
33
+ /**
34
+ * - The parsed frontmatter; an empty map where the
35
+ * page has none or the block is empty.
36
+ */
37
+ data: unknown;
38
+ /**
39
+ * - The body after the closing fence and its line
40
+ * break, or the whole text where there is no frontmatter.
41
+ */
42
+ content: string;
43
+ /**
44
+ * - The YAML source between the fences; empty where
45
+ * there is no frontmatter.
46
+ */
47
+ matter: string;
48
+ };
@@ -423,7 +423,7 @@ export type DataFieldSpec = {
423
423
  * - The anchor kinds an
424
424
  * Address here may name with `#<anchor>`. Absent, an anchor is refused.
425
425
  */
426
- anchors?: readonly ("event" | "prose")[] | undefined;
426
+ anchors?: readonly ("prose" | "event")[] | undefined;
427
427
  /**
428
428
  * - The Address names exactly one event: an
429
429
  * anchor of kind `event`, or a note holding one event and no more.
@@ -37,7 +37,7 @@ export function coverage({ books, corpus, unprinted, mode, locate }: {
37
37
  }>;
38
38
  corpus: object[];
39
39
  unprinted?: object[] | undefined;
40
- mode?: "error" | "off" | "warning" | undefined;
40
+ mode?: "warning" | "error" | "off" | undefined;
41
41
  locate?: ((record: object) => {
42
42
  file?: string;
43
43
  line?: number;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Reads standard input to its end as UTF-8 text.
3
+ *
4
+ * The stream is read asynchronously, so a pipe whose writer is slower than the
5
+ * reader, or a non-blocking descriptor, delivers the whole input instead of
6
+ * failing with `EAGAIN`.
7
+ *
8
+ * @param {NodeJS.ReadableStream} [stream] The stream to drain; standard input by default.
9
+ * @returns {Promise<string>} The complete input.
10
+ */
11
+ export function readStdin(stream?: NodeJS.ReadableStream): Promise<string>;