@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
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
* Keys that differ only in case, found in the content index's records.
|
|
16
|
+
*
|
|
17
|
+
* The notes relation is read with `read_json_auto`, which infers one struct
|
|
18
|
+
* from every record and treats the names of a struct's fields without regard
|
|
19
|
+
* to case. A map whose keys an author chooses therefore cannot hold `carved`
|
|
20
|
+
* in one note and `Carved` in another: the relation refuses to load. This
|
|
21
|
+
* module names each such pair, with the note and the line of both keys, so the
|
|
22
|
+
* author renames one.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import fs from "node:fs";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
import { formatDiagnostic, positionInFrontmatter } from "./diagnostics.mjs";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Walk a record, noting every key under its case-folded path.
|
|
33
|
+
*
|
|
34
|
+
* @param {unknown} value - The value at `trail`.
|
|
35
|
+
* @param {string} trail - The case-folded path to it.
|
|
36
|
+
* @param {object} record - The record being walked.
|
|
37
|
+
* @param {Map<string, Map<string, Set<object>>>} groups - Case-folded path to
|
|
38
|
+
* each spelling found there and the records that use it.
|
|
39
|
+
*/
|
|
40
|
+
function walk(value, trail, record, groups) {
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
for (const entry of value) walk(entry, trail, record, groups);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (value === null || typeof value !== "object") return;
|
|
46
|
+
for (const [key, child] of Object.entries(value)) {
|
|
47
|
+
const at = `${trail}.${key.toLowerCase()}`;
|
|
48
|
+
let spellings = groups.get(at);
|
|
49
|
+
if (!spellings) groups.set(at, (spellings = new Map()));
|
|
50
|
+
let users = spellings.get(key);
|
|
51
|
+
if (!users) spellings.set(key, (users = new Set()));
|
|
52
|
+
users.add(record);
|
|
53
|
+
walk(child, at, record, groups);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Every key that two records, or one record, spell with different case at the
|
|
59
|
+
* same path.
|
|
60
|
+
*
|
|
61
|
+
* @param {object[]} records - Content-index records.
|
|
62
|
+
* @returns {Array<{key: string, other: string, record: object, otherRecord: object}>}
|
|
63
|
+
* One entry per spelling that collides with another, naming the record that
|
|
64
|
+
* uses it and a record that uses the other spelling.
|
|
65
|
+
*/
|
|
66
|
+
export function findKeyCaseCollisions(records) {
|
|
67
|
+
const groups = new Map();
|
|
68
|
+
for (const record of records) walk(record, "", record, groups);
|
|
69
|
+
const found = [];
|
|
70
|
+
for (const spellings of groups.values()) {
|
|
71
|
+
if (spellings.size < 2) continue;
|
|
72
|
+
const keys = [...spellings.keys()].sort();
|
|
73
|
+
const [first, ...rest] = keys;
|
|
74
|
+
for (const key of keys) {
|
|
75
|
+
const other = key === first ? rest[0] : first;
|
|
76
|
+
found.push({
|
|
77
|
+
key,
|
|
78
|
+
other,
|
|
79
|
+
record: [...spellings.get(key)][0],
|
|
80
|
+
otherRecord: [...spellings.get(other)][0],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return found;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The note a record came from and where a key sits in it.
|
|
89
|
+
*
|
|
90
|
+
* @param {object} record - A content-index record.
|
|
91
|
+
* @param {string} key - The key to find.
|
|
92
|
+
* @param {string|undefined} contentBase - The directory `record.file.path` is
|
|
93
|
+
* relative to.
|
|
94
|
+
* @returns {{file: string, line?: number, column?: number}} The path relative
|
|
95
|
+
* to the working directory, with the position where the key is found.
|
|
96
|
+
*/
|
|
97
|
+
function noteLocation(record, key, contentBase) {
|
|
98
|
+
const relative = record.file?.path ?? "";
|
|
99
|
+
const absolute = contentBase ? path.join(contentBase, relative) : undefined;
|
|
100
|
+
const file = absolute ? path.relative(process.cwd(), absolute) : relative;
|
|
101
|
+
let position = {};
|
|
102
|
+
if (absolute) {
|
|
103
|
+
try {
|
|
104
|
+
position = positionInFrontmatter(fs.readFileSync(absolute, "utf8"), key, undefined);
|
|
105
|
+
} catch {
|
|
106
|
+
position = {};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { file, ...position };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The diagnostics for every case collision in the records, one per spelling,
|
|
114
|
+
* each naming the note and line of the spelling it collides with.
|
|
115
|
+
*
|
|
116
|
+
* @param {object[]} records - Content-index records.
|
|
117
|
+
* @param {object} [options]
|
|
118
|
+
* @param {string} [options.contentBase] - The directory the records' paths are
|
|
119
|
+
* relative to.
|
|
120
|
+
* @returns {string[]} Formatted `file:line:column: error: message` lines.
|
|
121
|
+
*/
|
|
122
|
+
export function keyCaseCollisionDiagnostics(records, { contentBase } = {}) {
|
|
123
|
+
return findKeyCaseCollisions(records).map(({ key, other, record, otherRecord }) => {
|
|
124
|
+
const here = noteLocation(record, key, contentBase);
|
|
125
|
+
const there = noteLocation(otherRecord, other, contentBase);
|
|
126
|
+
const where = [there.file, there.line, there.column].filter((part) => part !== undefined);
|
|
127
|
+
return formatDiagnostic({
|
|
128
|
+
...here,
|
|
129
|
+
severity: "error",
|
|
130
|
+
message:
|
|
131
|
+
`\`${key}\` and \`${other}\` name one key: the content index reads keys ` +
|
|
132
|
+
`without regard to case, so \`${other}\` in ${where.join(":")} must be spelled ` +
|
|
133
|
+
`the same way`,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
package/engine/language-data.mjs
CHANGED
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
* and NFC (see {@link foreignRowFindings});
|
|
40
40
|
* - a sound-change set's name stands once in the note, a step's label once
|
|
41
41
|
* in its set, and its `from` and `to` name language notes;
|
|
42
|
-
* - a frame's classes
|
|
42
|
+
* - a frame's classes, and the classes an affix's `attachesTo` names, are
|
|
43
|
+
* bases of the master word classes, each named once;
|
|
43
44
|
* - a calling-name closing states a `form` or `doubled: true`, not both;
|
|
44
45
|
* - a form stands once in `data.older`;
|
|
45
46
|
* - the mutation `data.negation` names is one of `data.sounds.mutations`;
|
|
@@ -69,6 +70,7 @@ import {
|
|
|
69
70
|
} from "./language-terms.mjs";
|
|
70
71
|
import { LANGUAGE_SUBTYPE } from "./word-terms.mjs";
|
|
71
72
|
import { WORD_CLASS_BASES } from "./word-classes.mjs";
|
|
73
|
+
import { nearest } from "./near-miss.mjs";
|
|
72
74
|
|
|
73
75
|
/** The bases a frame's `class` names. */
|
|
74
76
|
const CLASS_BASES = new Set(WORD_CLASS_BASES.map((item) => item.token));
|
|
@@ -117,9 +119,6 @@ export function tongueDeclared(tag, declared) {
|
|
|
117
119
|
/** The tag families whose rows name a name of another tongue's register. */
|
|
118
120
|
const FOREIGN_FAMILIES = new Set(["other", "exonym"]);
|
|
119
121
|
|
|
120
|
-
/** How a foreign row that its tongue's register does not bear out is reported. */
|
|
121
|
-
export const FOREIGN_ROW_SEVERITY = "warning";
|
|
122
|
-
|
|
123
122
|
/** A name as a foreign row is compared: NFC and lowercase. */
|
|
124
123
|
const foldName = (name) => name.normalize("NFC").toLowerCase();
|
|
125
124
|
|
|
@@ -190,7 +189,7 @@ function foreignRowFindings(rows, index, finding) {
|
|
|
190
189
|
findings.push(
|
|
191
190
|
finding(
|
|
192
191
|
["data", "register", i, "tongue"],
|
|
193
|
-
|
|
192
|
+
"error",
|
|
194
193
|
languages.length ?
|
|
195
194
|
`the tongue \`${tongue}\` in \`${row.tongue}\` names ${languages.length} ` +
|
|
196
195
|
`language notes, ${languages.join(" and ")}; the \`data.tongues\` entries ` +
|
|
@@ -217,7 +216,7 @@ function foreignRowFindings(rows, index, finding) {
|
|
|
217
216
|
findings.push(
|
|
218
217
|
finding(
|
|
219
218
|
["data", "register", i, "name"],
|
|
220
|
-
|
|
219
|
+
"error",
|
|
221
220
|
`\`${row.name}\` is registered as \`${row.tongue}\`, but the register of ` +
|
|
222
221
|
`${title} (${fm.type}-${fm.shortcode}) lists no \`${tongue}\` name ` +
|
|
223
222
|
`\`${row.name}\`; correct the spelling, or register the name there first`,
|
|
@@ -241,6 +240,198 @@ function formEntries(forms) {
|
|
|
241
240
|
return [];
|
|
242
241
|
}
|
|
243
242
|
|
|
243
|
+
/** A suggestion clause for a near miss, or nothing. */
|
|
244
|
+
const suggestion = (value, candidates) => {
|
|
245
|
+
const near = nearest(value, candidates);
|
|
246
|
+
return near ? `; did you mean \`${near}\`?` : "";
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/** A suggestion clause for a word class written out or mistyped, or nothing. */
|
|
250
|
+
const classSuggestion = (value, bases) => {
|
|
251
|
+
const spelled = WORD_CLASS_BASES.find((base) => base.expansion === value.trim().toLowerCase());
|
|
252
|
+
return spelled ? `; did you mean \`${spelled.token}\`?` : suggestion(value, bases);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Each entry of a list of strings that repeats an earlier one, as a finding.
|
|
257
|
+
*
|
|
258
|
+
* @param {unknown[]} list - The list.
|
|
259
|
+
* @param {Array<string|number>} path - Where it sits.
|
|
260
|
+
* @param {string} verb - What the entry does: `names` or `states`.
|
|
261
|
+
* @param {Function} finding - The note's finding builder.
|
|
262
|
+
* @returns {object[]} The findings.
|
|
263
|
+
*/
|
|
264
|
+
function repeats(list, path, verb, finding) {
|
|
265
|
+
const seen = new Set();
|
|
266
|
+
const out = [];
|
|
267
|
+
list.forEach((item, i) => {
|
|
268
|
+
if (typeof item !== "string") return;
|
|
269
|
+
const key = item.trim();
|
|
270
|
+
if (seen.has(key))
|
|
271
|
+
out.push(
|
|
272
|
+
finding(
|
|
273
|
+
[...path, i],
|
|
274
|
+
"error",
|
|
275
|
+
`\`${label([...path, i])}\` ${verb} \`${key}\` a second time`,
|
|
276
|
+
),
|
|
277
|
+
);
|
|
278
|
+
seen.add(key);
|
|
279
|
+
});
|
|
280
|
+
return out;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Check `data.grammar.agreement` against what it means: classes of the master
|
|
285
|
+
* list, each once, and at least two distinct endings to agree among.
|
|
286
|
+
*
|
|
287
|
+
* @param {unknown} agreement - The authored value.
|
|
288
|
+
* @param {Function} finding - The note's finding builder.
|
|
289
|
+
* @returns {object[]} The findings.
|
|
290
|
+
*/
|
|
291
|
+
function checkAgreement(agreement, finding) {
|
|
292
|
+
if (!isMap(agreement)) return [];
|
|
293
|
+
const out = [];
|
|
294
|
+
const at = ["data", "grammar", "agreement"];
|
|
295
|
+
const bases = WORD_CLASS_BASES.map((base) => base.token);
|
|
296
|
+
if (Array.isArray(agreement.classes)) {
|
|
297
|
+
if (!agreement.classes.length)
|
|
298
|
+
out.push(
|
|
299
|
+
finding(
|
|
300
|
+
[...at, "classes"],
|
|
301
|
+
"error",
|
|
302
|
+
"`data.grammar.agreement.classes` must name at least one word class",
|
|
303
|
+
),
|
|
304
|
+
);
|
|
305
|
+
agreement.classes.forEach((item, i) => {
|
|
306
|
+
if (typeof item !== "string" || bases.includes(item.trim())) return;
|
|
307
|
+
out.push(
|
|
308
|
+
finding(
|
|
309
|
+
[...at, "classes", i],
|
|
310
|
+
"error",
|
|
311
|
+
`\`${label([...at, "classes", i])}\` is \`${item}\`, which is not a word class` +
|
|
312
|
+
classSuggestion(item, bases),
|
|
313
|
+
),
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
out.push(...repeats(agreement.classes, [...at, "classes"], "names", finding));
|
|
317
|
+
}
|
|
318
|
+
if (Array.isArray(agreement.endings)) {
|
|
319
|
+
const distinct = new Set(
|
|
320
|
+
agreement.endings.filter((item) => typeof item === "string").map((item) => item.trim()),
|
|
321
|
+
);
|
|
322
|
+
if (distinct.size < 2)
|
|
323
|
+
out.push(
|
|
324
|
+
finding(
|
|
325
|
+
[...at, "endings"],
|
|
326
|
+
"error",
|
|
327
|
+
"`data.grammar.agreement.endings` must state at least two endings for a word to agree among",
|
|
328
|
+
),
|
|
329
|
+
);
|
|
330
|
+
out.push(...repeats(agreement.endings, [...at, "endings"], "states", finding));
|
|
331
|
+
}
|
|
332
|
+
return out;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Check each `data.affixes[].attachesTo` against what it means: bases of the
|
|
337
|
+
* master word classes, each once, and at least one.
|
|
338
|
+
*
|
|
339
|
+
* @param {unknown} affixes - The note's `data.affixes`.
|
|
340
|
+
* @param {Function} finding - The note's finding builder.
|
|
341
|
+
* @returns {object[]} The findings.
|
|
342
|
+
*/
|
|
343
|
+
function checkAffixClasses(affixes, finding) {
|
|
344
|
+
const out = [];
|
|
345
|
+
const bases = WORD_CLASS_BASES.map((base) => base.token);
|
|
346
|
+
listOf(affixes).forEach((affix, i) => {
|
|
347
|
+
if (!isMap(affix) || !Array.isArray(affix.attachesTo)) return;
|
|
348
|
+
const at = ["data", "affixes", i, "attachesTo"];
|
|
349
|
+
if (!affix.attachesTo.length)
|
|
350
|
+
out.push(finding(at, "error", `\`${label(at)}\` must name at least one word class`));
|
|
351
|
+
affix.attachesTo.forEach((item, j) => {
|
|
352
|
+
if (typeof item !== "string" || bases.includes(item.trim())) return;
|
|
353
|
+
out.push(
|
|
354
|
+
finding(
|
|
355
|
+
[...at, j],
|
|
356
|
+
"error",
|
|
357
|
+
`\`${label([...at, j])}\` is \`${item}\`, which is not a word class` +
|
|
358
|
+
classSuggestion(item, bases),
|
|
359
|
+
),
|
|
360
|
+
);
|
|
361
|
+
});
|
|
362
|
+
out.push(...repeats(affix.attachesTo, at, "names", finding));
|
|
363
|
+
});
|
|
364
|
+
return out;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Check `data.grammar.participles` against what it means: root endings with
|
|
369
|
+
* letters, and affixes that are suffixes the language's `data.affixes` states.
|
|
370
|
+
*
|
|
371
|
+
* @param {unknown} participles - The authored value.
|
|
372
|
+
* @param {unknown} affixes - The note's `data.affixes`.
|
|
373
|
+
* @param {Function} finding - The note's finding builder.
|
|
374
|
+
* @returns {object[]} The findings.
|
|
375
|
+
*/
|
|
376
|
+
function checkParticiples(participles, affixes, finding) {
|
|
377
|
+
if (!isMap(participles)) return [];
|
|
378
|
+
const out = [];
|
|
379
|
+
const at = ["data", "grammar", "participles"];
|
|
380
|
+
if (Array.isArray(participles.root)) {
|
|
381
|
+
if (!participles.root.length)
|
|
382
|
+
out.push(
|
|
383
|
+
finding(
|
|
384
|
+
[...at, "root"],
|
|
385
|
+
"error",
|
|
386
|
+
"`data.grammar.participles.root` must name at least one ending a verb drops to give its root",
|
|
387
|
+
),
|
|
388
|
+
);
|
|
389
|
+
participles.root.forEach((item, i) => {
|
|
390
|
+
if (typeof item === "string" && !item.trim())
|
|
391
|
+
out.push(
|
|
392
|
+
finding(
|
|
393
|
+
[...at, "root", i],
|
|
394
|
+
"error",
|
|
395
|
+
`\`${label([...at, "root", i])}\` must name the letters a verb drops to give its root`,
|
|
396
|
+
),
|
|
397
|
+
);
|
|
398
|
+
});
|
|
399
|
+
out.push(...repeats(participles.root, [...at, "root"], "states", finding));
|
|
400
|
+
}
|
|
401
|
+
if (Array.isArray(participles.affixes)) {
|
|
402
|
+
if (!participles.affixes.length)
|
|
403
|
+
out.push(
|
|
404
|
+
finding(
|
|
405
|
+
[...at, "affixes"],
|
|
406
|
+
"error",
|
|
407
|
+
"`data.grammar.participles.affixes` must name at least one suffix of `data.affixes`",
|
|
408
|
+
),
|
|
409
|
+
);
|
|
410
|
+
const stated = listOf(affixes)
|
|
411
|
+
.filter((affix) => isMap(affix) && typeof affix.form === "string")
|
|
412
|
+
.map((affix) => affix.form.trim());
|
|
413
|
+
const suffixes = stated.filter((form) => /^[-–]/u.test(form) && !/[-–]$/u.test(form));
|
|
414
|
+
participles.affixes.forEach((item, i) => {
|
|
415
|
+
if (typeof item !== "string") return;
|
|
416
|
+
const form = item.trim();
|
|
417
|
+
const where = `\`${label([...at, "affixes", i])}\` is \`${item}\``;
|
|
418
|
+
if (suffixes.includes(form)) return;
|
|
419
|
+
out.push(
|
|
420
|
+
finding(
|
|
421
|
+
[...at, "affixes", i],
|
|
422
|
+
"error",
|
|
423
|
+
stated.includes(form) ?
|
|
424
|
+
`${where}, a ${/^[-–]/u.test(form) ? "infix" : "prefix"}; a participle is a suffix on the root`
|
|
425
|
+
: `${where}, which is not a suffix of \`data.affixes\`` +
|
|
426
|
+
suggestion(form, suffixes),
|
|
427
|
+
),
|
|
428
|
+
);
|
|
429
|
+
});
|
|
430
|
+
out.push(...repeats(participles.affixes, [...at, "affixes"], "names", finding));
|
|
431
|
+
}
|
|
432
|
+
return out;
|
|
433
|
+
}
|
|
434
|
+
|
|
244
435
|
/**
|
|
245
436
|
* Check a `skill` note's language data against what it means.
|
|
246
437
|
*
|
|
@@ -345,6 +536,12 @@ export function checkLanguageData(note, { index, types } = {}) {
|
|
|
345
536
|
);
|
|
346
537
|
}
|
|
347
538
|
|
|
539
|
+
findings.push(
|
|
540
|
+
...checkAgreement(grammar.agreement, finding),
|
|
541
|
+
...checkParticiples(grammar.participles, data.affixes, finding),
|
|
542
|
+
...checkAffixClasses(data.affixes, finding),
|
|
543
|
+
);
|
|
544
|
+
|
|
348
545
|
/**
|
|
349
546
|
* The note an Address names, or a finding where it names none.
|
|
350
547
|
*
|
package/engine/language-rows.mjs
CHANGED
|
@@ -130,7 +130,7 @@ export const NEAR_NAME_COLUMNS = Object.freeze([...IDENTITY, "near", "given", "m
|
|
|
130
130
|
/** Every column of the `negation` relation, in order: one row per language. */
|
|
131
131
|
export const NEGATION_COLUMNS = Object.freeze(["language", "particle", "mutation"]);
|
|
132
132
|
/** Every column of the `stress` relation, in order. */
|
|
133
|
-
export const STRESS_COLUMNS = Object.freeze(["language", "
|
|
133
|
+
export const STRESS_COLUMNS = Object.freeze(["language", "two", "longer", "mark"]);
|
|
134
134
|
/** Every column of the `finals` relation, in order. */
|
|
135
135
|
export const FINAL_COLUMNS = Object.freeze([...IDENTITY, ...FINAL_KEYS]);
|
|
136
136
|
/** Every column of the `syllables` relation, in order: one row per language. */
|
|
@@ -789,7 +789,13 @@ export function stressRows(records) {
|
|
|
789
789
|
const rows = [];
|
|
790
790
|
for (const [language, record] of languagesOf(records)) {
|
|
791
791
|
const stress = soundsOf(isMap(record.data) ? record.data : {}).stress;
|
|
792
|
-
if (
|
|
792
|
+
if (isMap(stress))
|
|
793
|
+
rows.push({
|
|
794
|
+
language,
|
|
795
|
+
two: stress.two ?? null,
|
|
796
|
+
longer: stress.longer ?? null,
|
|
797
|
+
mark: stress.mark ?? null,
|
|
798
|
+
});
|
|
793
799
|
}
|
|
794
800
|
return rows;
|
|
795
801
|
}
|
|
@@ -126,13 +126,21 @@ export const HELD_CLIPS = Object.freeze(["first"]);
|
|
|
126
126
|
/** How often a group of clusters stands inside a word. */
|
|
127
127
|
export const CLUSTER_USES = Object.freeze(["common", "sparing"]);
|
|
128
128
|
|
|
129
|
-
/**
|
|
130
|
-
export const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
129
|
+
/** The syllable a word of two stresses. */
|
|
130
|
+
export const STRESS_TWO = Object.freeze(["first", "last"]);
|
|
131
|
+
|
|
132
|
+
/** The syllable a word of three or more stresses, counted from its end. */
|
|
133
|
+
export const STRESS_LONGER = Object.freeze(["antepenultimate", "penultimate", "final"]);
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The diacritics that mark a stress the rule would not choose, with the
|
|
137
|
+
* combining character each is written with.
|
|
138
|
+
*/
|
|
139
|
+
export const STRESS_MARKS = Object.freeze({
|
|
140
|
+
acute: "\u0301",
|
|
141
|
+
grave: "\u0300",
|
|
142
|
+
circumflex: "\u0302",
|
|
143
|
+
});
|
|
136
144
|
|
|
137
145
|
/** Which sounds meet where two parts of a word join. */
|
|
138
146
|
export const JOIN_MEETINGS = Object.freeze(["C+C", "V+C", "V+V"]);
|
|
@@ -690,6 +698,39 @@ export const HARMONY_FIELDS = Object.freeze([
|
|
|
690
698
|
}),
|
|
691
699
|
]);
|
|
692
700
|
|
|
701
|
+
/**
|
|
702
|
+
* The keys of a language's `data.sounds.stress`.
|
|
703
|
+
*
|
|
704
|
+
* @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
|
|
705
|
+
*/
|
|
706
|
+
export const STRESS_FIELDS = Object.freeze([
|
|
707
|
+
Object.freeze({
|
|
708
|
+
name: "two",
|
|
709
|
+
kind: "string",
|
|
710
|
+
required: true,
|
|
711
|
+
allowed: STRESS_TWO,
|
|
712
|
+
shape: `one of ${STRESS_TWO.join(", ")}`,
|
|
713
|
+
describe: "The stressed syllable of a word of two syllables.",
|
|
714
|
+
}),
|
|
715
|
+
Object.freeze({
|
|
716
|
+
name: "longer",
|
|
717
|
+
kind: "string",
|
|
718
|
+
required: true,
|
|
719
|
+
allowed: STRESS_LONGER,
|
|
720
|
+
shape: `one of ${STRESS_LONGER.join(", ")}`,
|
|
721
|
+
describe: "The stressed syllable of a word of three or more, counted from its end.",
|
|
722
|
+
}),
|
|
723
|
+
Object.freeze({
|
|
724
|
+
name: "mark",
|
|
725
|
+
kind: "string",
|
|
726
|
+
allowed: Object.freeze(Object.keys(STRESS_MARKS)),
|
|
727
|
+
shape: `one of ${Object.keys(STRESS_MARKS).join(", ")}`,
|
|
728
|
+
describe:
|
|
729
|
+
"The diacritic that marks a stress the rule would not choose; it moves the stress to " +
|
|
730
|
+
"the syllable it sits on.",
|
|
731
|
+
}),
|
|
732
|
+
]);
|
|
733
|
+
|
|
693
734
|
/**
|
|
694
735
|
* The keys of a language's `data.sounds.syllables`: what a syllable may open
|
|
695
736
|
* and close on, and what a word may.
|
|
@@ -901,10 +942,10 @@ export const SOUND_FIELDS = Object.freeze([
|
|
|
901
942
|
}),
|
|
902
943
|
Object.freeze({
|
|
903
944
|
name: "stress",
|
|
904
|
-
kind: "
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
describe: "Where a word's stress falls.",
|
|
945
|
+
kind: "map",
|
|
946
|
+
shape: "a map — `{ two, longer, mark? }`",
|
|
947
|
+
fields: STRESS_FIELDS,
|
|
948
|
+
describe: "Where a word's stress falls, as a rule a program reads.",
|
|
908
949
|
}),
|
|
909
950
|
Object.freeze({
|
|
910
951
|
name: "finals",
|
|
@@ -1623,6 +1664,52 @@ export const VERB_PERSON_FIELDS = Object.freeze(
|
|
|
1623
1664
|
),
|
|
1624
1665
|
);
|
|
1625
1666
|
|
|
1667
|
+
/**
|
|
1668
|
+
* The endings `data.grammar.native` writes under a logographic map: the noun
|
|
1669
|
+
* plural, then the six persons of the verb pattern.
|
|
1670
|
+
*/
|
|
1671
|
+
export const WRITTEN_ENDINGS = Object.freeze([
|
|
1672
|
+
"plural",
|
|
1673
|
+
...VERB_PERSON_FIELDS.map((person) => person.name),
|
|
1674
|
+
]);
|
|
1675
|
+
|
|
1676
|
+
/** The keys of one `data.grammar.native` entry. */
|
|
1677
|
+
const GRAMMAR_NATIVE_FIELDS = Object.freeze([
|
|
1678
|
+
Object.freeze({
|
|
1679
|
+
name: "ending",
|
|
1680
|
+
kind: "string",
|
|
1681
|
+
required: true,
|
|
1682
|
+
allowed: WRITTEN_ENDINGS,
|
|
1683
|
+
shape: `one of ${WRITTEN_ENDINGS.join(", ")}`,
|
|
1684
|
+
describe:
|
|
1685
|
+
"The ending written: `plural` for a noun's plural, or a person of `verbs.persons` " +
|
|
1686
|
+
"for a verb's present form.",
|
|
1687
|
+
}),
|
|
1688
|
+
Object.freeze({
|
|
1689
|
+
name: "map",
|
|
1690
|
+
kind: "string",
|
|
1691
|
+
required: true,
|
|
1692
|
+
shape: "a logographic mapping note's `data.map.name`",
|
|
1693
|
+
describe: "The logographic map the ending is written for.",
|
|
1694
|
+
}),
|
|
1695
|
+
Object.freeze({
|
|
1696
|
+
name: "font",
|
|
1697
|
+
kind: "string",
|
|
1698
|
+
shape: "a face the map lists",
|
|
1699
|
+
describe:
|
|
1700
|
+
"The one face the writing is for; unstated, it is for every face the map lists, " +
|
|
1701
|
+
"and a writing stated for a face wins over it there.",
|
|
1702
|
+
}),
|
|
1703
|
+
Object.freeze({
|
|
1704
|
+
name: "text",
|
|
1705
|
+
kind: "string",
|
|
1706
|
+
required: true,
|
|
1707
|
+
allowEmpty: true,
|
|
1708
|
+
shape: "the ending in the script, as the map's characters; empty writes the stem alone",
|
|
1709
|
+
describe: "The signs written after the stem's writing.",
|
|
1710
|
+
}),
|
|
1711
|
+
]);
|
|
1712
|
+
|
|
1626
1713
|
/** The two alternatives of a supplied optional verb pattern. */
|
|
1627
1714
|
const VERB_PATTERN_FIELDS = Object.freeze([
|
|
1628
1715
|
Object.freeze({
|
|
@@ -1651,7 +1738,56 @@ const VERB_PATTERN_FIELDS = Object.freeze([
|
|
|
1651
1738
|
}),
|
|
1652
1739
|
]);
|
|
1653
1740
|
|
|
1654
|
-
/** The
|
|
1741
|
+
/** The two keys of a language's optional agreement rule. */
|
|
1742
|
+
const AGREEMENT_FIELDS = Object.freeze([
|
|
1743
|
+
Object.freeze({
|
|
1744
|
+
name: "classes",
|
|
1745
|
+
kind: "list",
|
|
1746
|
+
required: true,
|
|
1747
|
+
shape: "a list of word class bases, such as `[adj, pron]`",
|
|
1748
|
+
entries: Object.freeze({ kind: "string", shape: "a word class base" }),
|
|
1749
|
+
describe: "The word classes that agree, each a base of the master word classes.",
|
|
1750
|
+
}),
|
|
1751
|
+
Object.freeze({
|
|
1752
|
+
name: "endings",
|
|
1753
|
+
kind: "list",
|
|
1754
|
+
required: true,
|
|
1755
|
+
shape: "a list of at least two literal endings, such as `[os, a, on]`",
|
|
1756
|
+
entries: Object.freeze({ kind: "string", shape: "a literal ending string" }),
|
|
1757
|
+
describe:
|
|
1758
|
+
"The singular endings an agreeing word swaps among, one for each gender; the " +
|
|
1759
|
+
"`plural` rule gives each one's plural. A word closing on none does not agree.",
|
|
1760
|
+
}),
|
|
1761
|
+
]);
|
|
1762
|
+
|
|
1763
|
+
/** The two keys of a language's optional participle rule. */
|
|
1764
|
+
const PARTICIPLE_FIELDS = Object.freeze([
|
|
1765
|
+
Object.freeze({
|
|
1766
|
+
name: "root",
|
|
1767
|
+
kind: "list",
|
|
1768
|
+
required: true,
|
|
1769
|
+
shape: "a list of closing letters, such as `[athen, ethen]`",
|
|
1770
|
+
entries: Object.freeze({ kind: "string", shape: "the closing letters of a citation form" }),
|
|
1771
|
+
describe:
|
|
1772
|
+
"The infinitive endings a verb's citation form drops to give its root, the longest " +
|
|
1773
|
+
"that matches first. A verb closing on none has no participle.",
|
|
1774
|
+
}),
|
|
1775
|
+
Object.freeze({
|
|
1776
|
+
name: "affixes",
|
|
1777
|
+
kind: "list",
|
|
1778
|
+
required: true,
|
|
1779
|
+
shape: "a list of suffixes of `data.affixes`, such as `[-andar, -thos]`",
|
|
1780
|
+
entries: Object.freeze({
|
|
1781
|
+
kind: "string",
|
|
1782
|
+
shape: "the `form` of a suffix of `data.affixes`",
|
|
1783
|
+
}),
|
|
1784
|
+
describe:
|
|
1785
|
+
"The participle suffixes, each attaching to the root as its own `replaces` says: " +
|
|
1786
|
+
"`vowel` or letters take the place of the root's close, and none adds it whole.",
|
|
1787
|
+
}),
|
|
1788
|
+
]);
|
|
1789
|
+
|
|
1790
|
+
/** The optional noun, verb, agreement and participle patterns of one language. */
|
|
1655
1791
|
const GRAMMAR_FIELDS = Object.freeze([
|
|
1656
1792
|
innerList(
|
|
1657
1793
|
"plural",
|
|
@@ -1680,6 +1816,39 @@ const GRAMMAR_FIELDS = Object.freeze([
|
|
|
1680
1816
|
fields: VERB_PATTERN_FIELDS,
|
|
1681
1817
|
describe: "One optional regular present pattern, printed once at the dictionary head.",
|
|
1682
1818
|
}),
|
|
1819
|
+
Object.freeze({
|
|
1820
|
+
name: "agreement",
|
|
1821
|
+
kind: "map",
|
|
1822
|
+
shape: "a map — `{ classes, endings }`",
|
|
1823
|
+
fields: AGREEMENT_FIELDS,
|
|
1824
|
+
describe:
|
|
1825
|
+
"How words of the named classes agree with a noun: a word closing on one of the " +
|
|
1826
|
+
"`endings` also takes each other one, and each form takes the `plural` rule. Read " +
|
|
1827
|
+
"by the mapped-run word check.",
|
|
1828
|
+
}),
|
|
1829
|
+
Object.freeze({
|
|
1830
|
+
name: "participles",
|
|
1831
|
+
kind: "map",
|
|
1832
|
+
shape: "a map — `{ root, affixes }`",
|
|
1833
|
+
fields: PARTICIPLE_FIELDS,
|
|
1834
|
+
describe:
|
|
1835
|
+
"Participles on the verb root: a verb closing on a `root` ending drops it, and each " +
|
|
1836
|
+
"named suffix attaches to what is left; a participle closing on an `agreement` " +
|
|
1837
|
+
"ending agrees. Read by the mapped-run word check.",
|
|
1838
|
+
}),
|
|
1839
|
+
Object.freeze({
|
|
1840
|
+
name: "native",
|
|
1841
|
+
kind: "list",
|
|
1842
|
+
shape: "a list of `{ ending, map, font?, text }`",
|
|
1843
|
+
entries: Object.freeze({
|
|
1844
|
+
kind: "map",
|
|
1845
|
+
shape: "a map — `{ ending, map, font?, text }`",
|
|
1846
|
+
fields: GRAMMAR_NATIVE_FIELDS,
|
|
1847
|
+
}),
|
|
1848
|
+
describe:
|
|
1849
|
+
"Under a logographic map, the signs a noun's plural or a verb's present form is " +
|
|
1850
|
+
"written with after its stem's writing, one entry per ending, map and face.",
|
|
1851
|
+
}),
|
|
1683
1852
|
]);
|
|
1684
1853
|
|
|
1685
1854
|
/**
|
|
@@ -1693,12 +1862,13 @@ export const LANGUAGE_DATA_FIELDS = Object.freeze([
|
|
|
1693
1862
|
Object.freeze({
|
|
1694
1863
|
name: "grammar",
|
|
1695
1864
|
kind: "map",
|
|
1696
|
-
shape: "a map — `{ plural?, verbs? }`",
|
|
1865
|
+
shape: "a map — `{ plural?, verbs?, agreement?, participles?, native? }`",
|
|
1697
1866
|
fields: GRAMMAR_FIELDS,
|
|
1698
1867
|
describe:
|
|
1699
|
-
"Optional noun plurals
|
|
1700
|
-
"
|
|
1701
|
-
"
|
|
1868
|
+
"Optional noun plurals, one verb pattern, agreement, participles and the endings' " +
|
|
1869
|
+
"logographic writings, for dictionary presentation, the mapped-run word check and " +
|
|
1870
|
+
"logographic maps. Only the shape of supplied keys is " +
|
|
1871
|
+
"checked; no word must follow or state a rule. A language skill note only.",
|
|
1702
1872
|
}),
|
|
1703
1873
|
listOfMaps(
|
|
1704
1874
|
"tongues",
|