@heroiclands/package-build 23.12.8 → 24.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/CHANGELOG.md +84 -0
- package/README.md +3 -2
- package/assets/preview/gitea.ini +12 -7
- package/assets/styles/content-base.css +293 -24
- package/bin/content-commands.mjs +370 -213
- package/bin/package-build-preview.mjs +2 -2
- package/bin/package-build.mjs +3 -2
- package/ci/check-banners.mjs +7 -7
- package/config.mjs +21 -6
- package/content-config.mjs +199 -80
- package/docs/api.md +110 -89
- package/docs/authoring/links-and-markup.md +469 -97
- package/docs/commands.md +259 -78
- package/docs/configuration.md +211 -103
- package/docs/content-format.md +4 -4
- package/docs/diagnostics.md +44 -2
- package/docs/getting-started.md +1 -1
- package/docs/guides/icons.md +1 -1
- package/docs/guides/module-project.md +5 -8
- package/docs/guides/release-and-deploy.md +2 -2
- package/docs/guides/site.md +112 -23
- package/docs/guides/system-project.md +1 -1
- package/docs/guides/writing-in-invented-scripts.md +1247 -0
- package/docs/index.md +1 -0
- package/docs/project-setup.md +28 -30
- package/docs/reference/format-details.md +677 -319
- package/docs/reference/note-types.md +47 -10
- package/docs/reference/theme-pages.md +178 -108
- package/docs/source-preview.md +16 -2
- package/engine/anchors.mjs +2 -2
- package/engine/asset-shipping.mjs +22 -10
- package/engine/base-compiler.mjs +62 -104
- package/engine/changelog-lint.mjs +1 -1
- package/engine/code-fences.mjs +3 -3
- package/engine/content-address.mjs +1 -1
- package/engine/content-alerts.mjs +8 -4
- package/engine/content-blocks.mjs +29 -14
- package/engine/content-embeds.mjs +1 -1
- package/engine/content-expand.mjs +376 -0
- package/engine/content-figures.mjs +18 -11
- package/engine/content-format.yaml +8 -0
- package/engine/content-glyphs.mjs +213 -0
- package/engine/content-images.mjs +6 -7
- package/engine/content-index.mjs +1 -1
- package/engine/content-links.mjs +3 -3
- package/engine/content-lint.mjs +1 -1
- package/engine/content-render.mjs +453 -0
- package/engine/content-scripts.mjs +376 -43
- package/engine/content-spans.mjs +6 -3
- package/engine/content-styles.mjs +91 -0
- package/engine/content-symbols.mjs +186 -0
- package/engine/content-verse.mjs +738 -0
- package/engine/core-report.mjs +169 -0
- package/engine/core-vocabulary.mjs +208 -0
- package/engine/dictionary-html.mjs +119 -0
- package/engine/dictionary-rows.mjs +15 -4
- package/engine/field-reference.mjs +5 -5
- package/engine/first-use.mjs +17 -27
- package/engine/frontmatter-lint.mjs +1 -1
- package/engine/heading-attributes.mjs +89 -0
- package/engine/helpers.mjs +61 -101
- package/engine/homepage.mjs +17 -29
- package/engine/html-minify.mjs +70 -0
- package/engine/index.mjs +8 -5
- package/engine/key-case-collisions.mjs +136 -0
- package/engine/language-data.mjs +203 -6
- package/engine/language-rows.mjs +8 -2
- package/engine/language-terms.mjs +186 -16
- package/engine/language-words.mjs +12 -0
- package/engine/logographic.mjs +255 -0
- package/engine/map-lexicon.mjs +262 -47
- package/engine/map-places.mjs +5 -4
- package/engine/markdown-expressions.mjs +0 -2
- package/engine/markdownlint-run.mjs +9 -3
- package/engine/markup-validation.mjs +21 -0
- package/engine/metre-declaration.mjs +134 -0
- package/engine/names.mjs +36 -9
- package/engine/note-matter.mjs +66 -0
- package/engine/note-vocabulary.mjs +187 -3
- package/engine/pdf-build.mjs +69 -133
- package/engine/pdf-render.mjs +205 -58
- package/engine/post-fields.mjs +157 -0
- package/engine/preview-index.mjs +130 -0
- package/engine/preview-source.mjs +450 -0
- package/engine/project-init.mjs +5 -12
- package/engine/prose-config.mjs +3 -3
- package/engine/read-stdin.mjs +29 -0
- package/engine/run-rows.mjs +2 -2
- package/engine/run-words.mjs +6 -112
- package/engine/script-emphasis-terms.mjs +38 -0
- package/engine/script-emphasis.mjs +295 -0
- package/engine/script-font-delivery.mjs +4 -4
- package/engine/site-build.mjs +260 -315
- package/engine/site-config.mjs +26 -238
- package/engine/site-files.mjs +175 -0
- package/engine/site-index.mjs +9 -4
- package/engine/site-preview.mjs +52 -525
- package/engine/site-render.mjs +593 -0
- package/engine/site-root.mjs +22 -13
- package/engine/site-search.mjs +161 -0
- package/engine/site-serve.mjs +273 -0
- package/engine/site-theme-banner.mjs +154 -0
- package/engine/site-theme-urls.mjs +125 -0
- package/engine/site-theme.mjs +926 -0
- package/engine/sql-tables.mjs +20 -3
- package/engine/text-maps.mjs +57 -10
- package/engine/typographer.mjs +248 -0
- package/engine/verse-metre.mjs +357 -0
- package/engine/wikilink-syntax.mjs +29 -47
- package/engine/wikilinks.mjs +758 -279
- package/engine/word-forms.mjs +364 -0
- package/engine/word-rows.mjs +12 -2
- package/engine/word-terms.mjs +38 -4
- package/manifest.mjs +3 -4
- package/package.json +11 -8
- package/{hugo-theme → site-theme}/data/banners.yaml +6 -10
- package/site-theme/layouts/base.hbs +31 -0
- package/site-theme/layouts/blog-list.hbs +30 -0
- package/site-theme/layouts/blog-post.hbs +40 -0
- package/site-theme/layouts/home.hbs +35 -0
- package/site-theme/layouts/list.hbs +20 -0
- package/site-theme/layouts/not-found.hbs +31 -0
- package/site-theme/layouts/page.hbs +7 -0
- package/site-theme/partials/copy-buttons.hbs +37 -0
- package/site-theme/partials/draft-notice.hbs +5 -0
- package/site-theme/partials/exturl.hbs +2 -0
- package/{hugo-theme/layouts/partials/footer.html → site-theme/partials/footer.hbs} +10 -7
- package/site-theme/partials/header.hbs +44 -0
- package/site-theme/partials/hero-banner.hbs +11 -0
- package/site-theme/partials/infobox-entry.hbs +2 -0
- package/site-theme/partials/infobox-section.hbs +31 -0
- package/site-theme/partials/infobox-value.hbs +2 -0
- package/site-theme/partials/infobox.hbs +18 -0
- package/site-theme/partials/list-members.hbs +9 -0
- package/site-theme/partials/nav-links.hbs +5 -0
- package/site-theme/partials/page-body.hbs +48 -0
- package/site-theme/partials/page-list.hbs +14 -0
- package/site-theme/partials/related.hbs +24 -0
- package/site-theme/partials/search-meta.hbs +6 -0
- package/site-theme/partials/search-names.hbs +2 -0
- package/site-theme/partials/search.hbs +52 -0
- package/site-theme/partials/tags.hbs +4 -0
- package/site-theme/partials/toc.hbs +7 -0
- package/{hugo-theme → site-theme}/static/css/style.css +106 -14
- package/stage.mjs +1 -2
- package/types/config.d.mts +8 -4
- package/types/content-config.d.mts +15 -15
- package/types/engine/asset-shipping.d.mts +5 -2
- package/types/engine/base-compiler.d.mts +20 -2
- package/types/engine/code-fences.d.mts +3 -3
- package/types/engine/content-address.d.mts +1 -1
- package/types/engine/content-alerts.d.mts +8 -2
- package/types/engine/content-blocks.d.mts +11 -4
- package/types/engine/content-expand.d.mts +288 -0
- package/types/engine/content-figures.d.mts +11 -3
- package/types/engine/content-glyphs.d.mts +85 -0
- package/types/engine/content-images.d.mts +6 -7
- package/types/engine/content-links.d.mts +1 -1
- package/types/engine/content-render.d.mts +199 -0
- package/types/engine/content-scripts.d.mts +70 -4
- package/types/engine/content-spans.d.mts +1 -1
- package/types/engine/content-styles.d.mts +33 -0
- package/types/engine/content-symbols.d.mts +35 -0
- package/types/engine/content-verse.d.mts +240 -0
- package/types/engine/core-report.d.mts +45 -0
- package/types/engine/core-vocabulary.d.mts +53 -0
- package/types/engine/dictionary-html.d.mts +8 -0
- package/types/engine/dictionary-rows.d.mts +10 -0
- package/types/engine/field-reference.d.mts +2 -2
- package/types/engine/heading-attributes.d.mts +20 -0
- package/types/engine/helpers.d.mts +3 -10
- package/types/engine/homepage.d.mts +11 -24
- package/types/engine/html-minify.d.mts +9 -0
- package/types/engine/index.d.mts +2 -1
- package/types/engine/key-case-collisions.d.mts +28 -0
- package/types/engine/language-data.d.mts +0 -2
- package/types/engine/language-terms.d.mts +24 -2
- package/types/engine/logographic.d.mts +45 -0
- package/types/engine/map-lexicon.d.mts +66 -13
- package/types/engine/map-places.d.mts +8 -0
- package/types/engine/markdown-expressions.d.mts +0 -2
- package/types/engine/metre-declaration.d.mts +39 -0
- package/types/engine/names.d.mts +34 -3
- package/types/engine/note-matter.d.mts +48 -0
- package/types/engine/pdf-render.d.mts +6 -2
- package/types/engine/post-fields.d.mts +44 -0
- package/types/engine/preview-index.d.mts +72 -0
- package/types/engine/preview-source.d.mts +51 -0
- package/types/engine/read-stdin.d.mts +11 -0
- package/types/engine/run-rows.d.mts +2 -2
- package/types/engine/run-words.d.mts +0 -15
- package/types/engine/script-emphasis-terms.d.mts +21 -0
- package/types/engine/script-emphasis.d.mts +84 -0
- package/types/engine/script-font-delivery.d.mts +4 -4
- package/types/engine/site-build.d.mts +71 -113
- package/types/engine/site-config.d.mts +14 -134
- package/types/engine/site-files.d.mts +73 -0
- package/types/engine/site-index.d.mts +11 -3
- package/types/engine/site-preview.d.mts +15 -19
- package/types/engine/site-render.d.mts +171 -0
- package/types/engine/site-root.d.mts +12 -4
- package/types/engine/site-search.d.mts +54 -0
- package/types/engine/site-serve.d.mts +44 -0
- package/types/engine/site-theme-banner.d.mts +51 -0
- package/types/engine/site-theme-urls.d.mts +60 -0
- package/types/engine/site-theme.d.mts +127 -0
- package/types/engine/sql-tables.d.mts +4 -1
- package/types/engine/text-maps.d.mts +17 -3
- package/types/engine/typographer.d.mts +11 -0
- package/types/engine/verse-metre.d.mts +131 -0
- package/types/engine/wikilink-syntax.d.mts +30 -48
- package/types/engine/wikilinks.d.mts +330 -14
- package/types/engine/word-forms.d.mts +83 -0
- package/types/manifest.d.mts +3 -4
- package/types/stage.d.mts +1 -2
- package/types/vendor/markdownlint.d.mts +8 -0
- package/vendor/markdownlint.mjs +20388 -0
- package/engine/content-poetry.mjs +0 -240
- package/engine/web-wikilinks.mjs +0 -594
- package/hugo-theme/layouts/404.html +0 -85
- package/hugo-theme/layouts/_default/baseof.html +0 -67
- package/hugo-theme/layouts/_default/list.html +0 -139
- package/hugo-theme/layouts/_default/single.html +0 -4
- package/hugo-theme/layouts/blog/list.html +0 -54
- package/hugo-theme/layouts/blog-post/single.html +0 -36
- package/hugo-theme/layouts/index.html +0 -75
- package/hugo-theme/layouts/partials/banner-url.html +0 -141
- package/hugo-theme/layouts/partials/cdn-url.html +0 -36
- package/hugo-theme/layouts/partials/dictionary-lists.html +0 -58
- package/hugo-theme/layouts/partials/draft-notice.html +0 -41
- package/hugo-theme/layouts/partials/draft-state.html +0 -26
- package/hugo-theme/layouts/partials/header.html +0 -35
- package/hugo-theme/layouts/partials/hero-banner.html +0 -85
- package/hugo-theme/layouts/partials/infobox/entry.html +0 -18
- package/hugo-theme/layouts/partials/infobox/items.html +0 -26
- package/hugo-theme/layouts/partials/infobox/section.html +0 -56
- package/hugo-theme/layouts/partials/infobox/value.html +0 -43
- package/hugo-theme/layouts/partials/infobox.html +0 -57
- package/hugo-theme/layouts/partials/page-body.html +0 -152
- package/hugo-theme/layouts/partials/related.html +0 -138
- package/hugo-theme/layouts/partials/search.html +0 -84
- package/hugo-theme/layouts/partials/site-url.html +0 -32
- package/hugo-theme/layouts/partials/tags.html +0 -30
- package/hugo-theme/layouts/partials/toc.html +0 -28
- package/hugo-theme/layouts/partials/warn-missing-banner.html +0 -3
- package/hugo-theme/layouts/shortcodes/cartouche.html +0 -64
- package/hugo-theme/layouts/shortcodes/exturl.html +0 -12
- package/hugo-theme/layouts/shortcodes/glyph.html +0 -43
- package/hugo-theme/layouts/shortcodes/pagelist.html +0 -63
- package/hugo-theme/theme.toml +0 -11
- package/types/engine/content-poetry.d.mts +0 -58
- package/types/engine/web-wikilinks.d.mts +0 -107
- /package/{hugo-theme → site-theme}/data/banner-kinds.yaml +0 -0
- /package/{hugo-theme → site-theme}/static/css/sohl-icons.css +0 -0
- /package/{hugo-theme → site-theme}/static/fonts/game-icons-kb.woff2 +0 -0
- /package/{hugo-theme → site-theme}/static/img/banners/takheperu-hieroglyphs.svg +0 -0
- /package/{hugo-theme → site-theme}/static/img/cartouches/takheperu.svg +0 -0
- /package/{hugo-theme → site-theme}/static/js/inline-spoilers.js +0 -0
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* each declared in `data.classModifiers`, and a declared modifier no word
|
|
25
25
|
* uses is a warning;
|
|
26
26
|
* - a word's `field` is one of `data.fields`;
|
|
27
|
+
* - a word's `concept` names entries of the concept list, each once;
|
|
27
28
|
* - a `borrowed` source names its `language`;
|
|
28
29
|
* - no form stands twice in one language;
|
|
29
30
|
* - a word's `gloss` states at least one sense;
|
|
@@ -61,6 +62,7 @@ import {
|
|
|
61
62
|
pronunciationProblem,
|
|
62
63
|
} from "./word-terms.mjs";
|
|
63
64
|
import { WORD_CLASS_MODIFIERS, parseWordClass } from "./word-classes.mjs";
|
|
65
|
+
import { conceptIdsIn, conceptProblems } from "./core-vocabulary.mjs";
|
|
64
66
|
import { lineageChain, pieceGlosses } from "./word-rows.mjs";
|
|
65
67
|
|
|
66
68
|
const MODIFIER_TOKENS = new Set(WORD_CLASS_MODIFIERS.map((item) => item.token));
|
|
@@ -192,6 +194,7 @@ export function checkLanguageWords(note, { index } = {}) {
|
|
|
192
194
|
}
|
|
193
195
|
const used = new Set();
|
|
194
196
|
const forms = new Map();
|
|
197
|
+
const concepts = conceptIdsIn(index);
|
|
195
198
|
listOf(data.words).forEach((word, i) => {
|
|
196
199
|
if (!isMap(word)) return;
|
|
197
200
|
const at = (key) => ["data", "words", i, key];
|
|
@@ -226,6 +229,15 @@ export function checkLanguageWords(note, { index } = {}) {
|
|
|
226
229
|
),
|
|
227
230
|
);
|
|
228
231
|
|
|
232
|
+
for (const problem of conceptProblems(word.concept, concepts))
|
|
233
|
+
findings.push(
|
|
234
|
+
finding(
|
|
235
|
+
problem.entry === null ? at("concept") : [...at("concept"), problem.entry],
|
|
236
|
+
"error",
|
|
237
|
+
problem.message,
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
|
|
229
241
|
if (typeof word.form === "string") {
|
|
230
242
|
const key = word.form.normalize("NFC");
|
|
231
243
|
if (forms.has(key))
|
|
@@ -0,0 +1,255 @@
|
|
|
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
|
+
* **A logographic map writes meanings, so a word's writing comes from the
|
|
16
|
+
* lexicon and never from its sound.** A mapping note stating
|
|
17
|
+
* `data.map.logographic: true` writes each word of a run in this order:
|
|
18
|
+
*
|
|
19
|
+
* 1. **The word's own `native` entry** for the map, and for the run's face
|
|
20
|
+
* where an entry names one.
|
|
21
|
+
* 2. **Otherwise its parts.** A word stating `built` is written by the
|
|
22
|
+
* writings of its pieces joined in order: a piece is a word of the
|
|
23
|
+
* lexicon, matched exactly and then with its joining hyphens taken off, or
|
|
24
|
+
* an affix of `data.affixes` with its own `native`; a word piece resolves
|
|
25
|
+
* by this same rule. A noun's plural and a verb's present forms are written
|
|
26
|
+
* by the stem's writing followed by the ending's, which
|
|
27
|
+
* `data.grammar.native` states by the ending's name: `plural`, or one of
|
|
28
|
+
* the six persons. A hyphenated word no entry states is written by its
|
|
29
|
+
* parts, each by this rule.
|
|
30
|
+
* 3. **Otherwise the word has no writing**, which a run reports at the word.
|
|
31
|
+
*
|
|
32
|
+
* A run's word is matched case folded, so one roman form may stand for more
|
|
33
|
+
* than one entry. Where those entries are written alike the word takes that
|
|
34
|
+
* writing; where they differ, or one has a writing and another none, the word
|
|
35
|
+
* is ambiguous, and the finding names every entry's gloss. A part matched to
|
|
36
|
+
* entries written differently makes the word ambiguous the same way.
|
|
37
|
+
*
|
|
38
|
+
* @module
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { pluralOf } from "./dictionary-rows.mjs";
|
|
42
|
+
import { verbForm, verbPattern } from "./verb-pattern.mjs";
|
|
43
|
+
import { parseWordClass } from "./word-classes.mjs";
|
|
44
|
+
import { foldWord } from "./word-forms.mjs";
|
|
45
|
+
import { glossText } from "./word-terms.mjs";
|
|
46
|
+
|
|
47
|
+
/** Whether a value is a plain map. */
|
|
48
|
+
const isMap = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
49
|
+
|
|
50
|
+
/** A list value, or an empty one. */
|
|
51
|
+
const listOf = (value) => (Array.isArray(value) ? value : []);
|
|
52
|
+
|
|
53
|
+
/** A form with its joining hyphens taken off. */
|
|
54
|
+
const bare = (form) => form.replace(/^[-–]+|[-–]+$/gu, "");
|
|
55
|
+
|
|
56
|
+
/** The characters a hyphenated word is split at. */
|
|
57
|
+
const JOINS = /[–-]/u;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Where a word's writing comes from, or why it has none.
|
|
61
|
+
*
|
|
62
|
+
* @typedef {{text: string, source: "own"|"parts"}
|
|
63
|
+
* | {missing: true, noLexicon?: true}
|
|
64
|
+
* | {ambiguous: {form: string, glosses: string[]}}} Writing
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The text a list of native entries states for a map in a face: the entry for
|
|
69
|
+
* the face, else the entry for no face.
|
|
70
|
+
*
|
|
71
|
+
* @param {unknown} natives - A `native` list as authored.
|
|
72
|
+
* @param {string} map - The map's name.
|
|
73
|
+
* @param {string|undefined} font - The face.
|
|
74
|
+
* @returns {string|undefined} The text.
|
|
75
|
+
*/
|
|
76
|
+
export function nativeText(natives, map, font) {
|
|
77
|
+
const entries = listOf(natives).filter(
|
|
78
|
+
(entry) => isMap(entry) && entry.map === map && typeof entry.text === "string",
|
|
79
|
+
);
|
|
80
|
+
const own = font === undefined ? undefined : entries.find((entry) => entry.font === font);
|
|
81
|
+
return (own ?? entries.find((entry) => typeof entry.font !== "string"))?.text;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* One writing for several candidates: the shared one where every candidate is
|
|
86
|
+
* written alike, else an ambiguity naming each candidate's gloss.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} form - The form the candidates were matched by.
|
|
89
|
+
* @param {Array<{writing: Writing, gloss: string}>} candidates - At least one.
|
|
90
|
+
* @returns {Writing} The writing.
|
|
91
|
+
*/
|
|
92
|
+
function oneOf(form, candidates) {
|
|
93
|
+
const passed = candidates.find(({ writing }) => "ambiguous" in writing);
|
|
94
|
+
if (passed) return passed.writing;
|
|
95
|
+
const texts = new Set(
|
|
96
|
+
candidates.map(({ writing }) => ("text" in writing ? writing.text : undefined)),
|
|
97
|
+
);
|
|
98
|
+
if (texts.size === 1) return candidates[0].writing;
|
|
99
|
+
return { ambiguous: { form, glosses: candidates.map(({ gloss }) => gloss) } };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The writings of a language's words under one logographic map in one face.
|
|
104
|
+
*
|
|
105
|
+
* @param {Record<string, any>|undefined} data - The language note's `data`.
|
|
106
|
+
* @param {string} map - The map's name.
|
|
107
|
+
* @param {string|undefined} font - The face.
|
|
108
|
+
* @returns {{word: (word: string) => Writing, entry: (index: number) => Writing}}
|
|
109
|
+
* The writing of a run's word, and of the `data.words` entry at an index.
|
|
110
|
+
*/
|
|
111
|
+
export function writingsOf(data, map, font) {
|
|
112
|
+
const words = listOf(data?.words);
|
|
113
|
+
const affixes = listOf(data?.affixes).filter(isMap);
|
|
114
|
+
const grammar = isMap(data?.grammar) ? data.grammar : undefined;
|
|
115
|
+
const verbs = verbPattern(grammar?.verbs);
|
|
116
|
+
const replaces = verbs?.replaces === undefined ? undefined : foldWord(verbs.replaces);
|
|
117
|
+
const endings = grammar?.native;
|
|
118
|
+
const indexed = words
|
|
119
|
+
.map((word, index) => ({ word, index }))
|
|
120
|
+
.filter(({ word }) => isMap(word) && typeof word.form === "string");
|
|
121
|
+
const glossOf = (entry) => glossText(entry.gloss) ?? String(entry.form ?? "");
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {number} index - A `data.words` index.
|
|
125
|
+
* @param {Set<number>} visiting - The entries whose parts are being read.
|
|
126
|
+
* @returns {Writing}
|
|
127
|
+
*/
|
|
128
|
+
const entry = (index, visiting) => {
|
|
129
|
+
const word = words[index];
|
|
130
|
+
const own = nativeText(word?.native, map, font);
|
|
131
|
+
if (own !== undefined) return { text: own, source: "own" };
|
|
132
|
+
const pieces = listOf(word?.built).filter((piece) => typeof piece === "string");
|
|
133
|
+
if (!pieces.length || visiting.has(index)) return { missing: true };
|
|
134
|
+
const inner = new Set(visiting).add(index);
|
|
135
|
+
let text = "";
|
|
136
|
+
for (const piece of pieces) {
|
|
137
|
+
const found = part(piece, inner);
|
|
138
|
+
if (!("text" in found)) return found;
|
|
139
|
+
text += found.text;
|
|
140
|
+
}
|
|
141
|
+
return { text, source: "parts" };
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/** The candidates of a list of matched words and affixes. */
|
|
145
|
+
const candidatesOf = (matchedWords, matchedAffixes, visiting) => [
|
|
146
|
+
...matchedWords.map(({ word, index }) => ({
|
|
147
|
+
writing: entry(index, visiting),
|
|
148
|
+
gloss: glossOf(word),
|
|
149
|
+
})),
|
|
150
|
+
...matchedAffixes.map((affix) => {
|
|
151
|
+
const text = nativeText(affix.native, map, font);
|
|
152
|
+
return {
|
|
153
|
+
writing: /** @type {Writing} */ (
|
|
154
|
+
text === undefined ? { missing: true } : { text, source: "own" }
|
|
155
|
+
),
|
|
156
|
+
gloss: glossOf(affix),
|
|
157
|
+
};
|
|
158
|
+
}),
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* A `built` piece: a word written exactly so, else an affix, else either
|
|
163
|
+
* with its joining hyphens taken off.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} piece - The piece.
|
|
166
|
+
* @param {Set<number>} visiting - The entries whose parts are being read.
|
|
167
|
+
* @returns {Writing}
|
|
168
|
+
*/
|
|
169
|
+
const part = (piece, visiting) => {
|
|
170
|
+
const affixForms = (affix) =>
|
|
171
|
+
[affix.form, affix.front].filter((form) => typeof form === "string");
|
|
172
|
+
const stages = [
|
|
173
|
+
[indexed.filter(({ word }) => word.form === piece), []],
|
|
174
|
+
[[], affixes.filter((affix) => affixForms(affix).includes(piece))],
|
|
175
|
+
[indexed.filter(({ word }) => bare(word.form) === bare(piece)), []],
|
|
176
|
+
[[], affixes.filter((affix) => affixForms(affix).some((f) => bare(f) === bare(piece)))],
|
|
177
|
+
];
|
|
178
|
+
for (const [matchedWords, matchedAffixes] of stages) {
|
|
179
|
+
const candidates = candidatesOf(matchedWords, matchedAffixes, visiting);
|
|
180
|
+
if (candidates.length) return oneOf(piece, candidates);
|
|
181
|
+
}
|
|
182
|
+
return { missing: true };
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The ending's writing a derived form takes after its stem's.
|
|
187
|
+
*
|
|
188
|
+
* @param {string} ending - A name of
|
|
189
|
+
* {@link module:engine/language-terms.WRITTEN_ENDINGS}.
|
|
190
|
+
* @returns {string|undefined} The text.
|
|
191
|
+
*/
|
|
192
|
+
const endingText = (ending) =>
|
|
193
|
+
nativeText(
|
|
194
|
+
listOf(endings).filter((one) => isMap(one) && one.ending === ending),
|
|
195
|
+
map,
|
|
196
|
+
font,
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* A word of a run, matched case folded.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} written - The word as written.
|
|
203
|
+
* @returns {Writing}
|
|
204
|
+
*/
|
|
205
|
+
const word = (written) => {
|
|
206
|
+
const folded = foldWord(written);
|
|
207
|
+
const none = new Set();
|
|
208
|
+
const stated = indexed.filter(({ word: one }) => foldWord(one.form) === folded);
|
|
209
|
+
if (stated.length) return oneOf(written, candidatesOf(stated, [], none));
|
|
210
|
+
const stripped = indexed.filter(({ word: one }) => bare(foldWord(one.form)) === folded);
|
|
211
|
+
if (stripped.length) return oneOf(written, candidatesOf(stripped, [], none));
|
|
212
|
+
const derived = [];
|
|
213
|
+
for (const { word: one, index } of indexed) {
|
|
214
|
+
const base = parseWordClass(one.class).base;
|
|
215
|
+
const plural = pluralOf(one, grammar);
|
|
216
|
+
const forms = [
|
|
217
|
+
...(plural ? [{ form: plural, ending: "plural" }] : []),
|
|
218
|
+
...(verbs && base === "v" ?
|
|
219
|
+
verbs.endings.map(({ name, ending }) => ({
|
|
220
|
+
form: verbForm(foldWord(one.form), foldWord(ending), replaces),
|
|
221
|
+
ending: name,
|
|
222
|
+
}))
|
|
223
|
+
: []),
|
|
224
|
+
];
|
|
225
|
+
for (const { form, ending } of forms) {
|
|
226
|
+
if (foldWord(form) !== folded) continue;
|
|
227
|
+
const stem = entry(index, none);
|
|
228
|
+
const tail = endingText(ending);
|
|
229
|
+
derived.push({
|
|
230
|
+
writing: /** @type {Writing} */ (
|
|
231
|
+
!("text" in stem) ? stem
|
|
232
|
+
: tail === undefined ? { missing: true }
|
|
233
|
+
: { text: stem.text + tail, source: "parts" }
|
|
234
|
+
),
|
|
235
|
+
gloss: glossOf(one),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (derived.length) return oneOf(written, derived);
|
|
240
|
+
if (JOINS.test(written)) {
|
|
241
|
+
let text = "";
|
|
242
|
+
for (const piece of written.split(JOINS)) {
|
|
243
|
+
if (!piece) continue;
|
|
244
|
+
const found = word(piece);
|
|
245
|
+
if ("missing" in found) return { missing: true };
|
|
246
|
+
if ("ambiguous" in found) return found;
|
|
247
|
+
text += found.text;
|
|
248
|
+
}
|
|
249
|
+
return { text, source: "parts" };
|
|
250
|
+
}
|
|
251
|
+
return { missing: true };
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
return { word, entry: (index) => entry(index, new Set()) };
|
|
255
|
+
}
|