@heroiclands/package-build 23.6.0 → 23.7.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 +42 -0
- package/docs/authoring/links-and-markup.md +266 -20
- package/docs/content-format.md +1 -1
- package/docs/reference/format-details.md +526 -70
- package/docs/reference/note-types.md +152 -38
- package/engine/content-format.yaml +10 -0
- package/engine/content-index.mjs +21 -14
- package/engine/dictionary-layout.mjs +9 -3
- package/engine/dictionary-rows.mjs +15 -25
- package/engine/index-records.mjs +1 -0
- package/engine/language-data.mjs +378 -0
- package/engine/language-rows.mjs +761 -0
- package/engine/language-terms.mjs +1203 -0
- package/engine/language-words.mjs +57 -9
- package/engine/note-addresses.mjs +38 -4
- package/engine/note-vocabulary.mjs +9 -2
- package/engine/pack-config.mjs +6 -1
- package/engine/sql-tables.mjs +71 -7
- package/engine/word-citations.mjs +206 -0
- package/engine/word-classes.mjs +1 -1
- package/engine/word-rows.mjs +193 -34
- package/engine/word-terms.mjs +83 -11
- package/package.json +1 -1
- package/types/engine/dictionary-layout.d.mts +7 -0
- package/types/engine/language-data.d.mts +22 -0
- package/types/engine/language-rows.d.mts +227 -0
- package/types/engine/language-terms.d.mts +265 -0
- package/types/engine/note-addresses.d.mts +8 -10
- package/types/engine/sql-tables.d.mts +2 -1
- package/types/engine/word-citations.d.mts +43 -0
- package/types/engine/word-rows.d.mts +78 -7
- package/types/engine/word-terms.d.mts +20 -0
package/engine/word-rows.mjs
CHANGED
|
@@ -24,22 +24,33 @@
|
|
|
24
24
|
* binds.
|
|
25
25
|
*
|
|
26
26
|
* A word row holds the words its own note states, and nothing is copied for a
|
|
27
|
-
* daughter language. `
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* daughter language. Its `gloss` is the word's senses joined by `; `, its
|
|
28
|
+
* `senses` each sense in order, and its `attested` the notes whose prose cites
|
|
29
|
+
* it ({@link module:engine/word-citations}), or `NULL` where none does.
|
|
30
|
+
* `language_lineage` is one row per language and each of its ancestors through
|
|
31
|
+
* `data.family`, itself included at depth `0`, so a language's words with its
|
|
32
|
+
* inherited ones are a join of the two.
|
|
30
33
|
*
|
|
31
34
|
* @module
|
|
32
35
|
*/
|
|
33
36
|
|
|
34
37
|
import { renderAddress } from "./address.mjs";
|
|
35
38
|
import { glossaryLanguage } from "./glossary-language.mjs";
|
|
36
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
AFFIX_KEYS,
|
|
41
|
+
FIELD_KEYS,
|
|
42
|
+
GLOSSARY_ENTRY_KEYS,
|
|
43
|
+
LANGUAGE_SUBTYPE,
|
|
44
|
+
WORD_KEYS,
|
|
45
|
+
glossSenses,
|
|
46
|
+
glossText,
|
|
47
|
+
} from "./word-terms.mjs";
|
|
37
48
|
|
|
38
49
|
/** The columns a word row carries before the word's own keys. */
|
|
39
50
|
const WORD_IDENTITY_COLUMNS = Object.freeze(["language", "position"]);
|
|
40
51
|
|
|
41
52
|
/** The columns a word row derives after the word's own keys. */
|
|
42
|
-
const WORD_DERIVED_COLUMNS = Object.freeze(["built_text", "built_parts"]);
|
|
53
|
+
const WORD_DERIVED_COLUMNS = Object.freeze(["built_text", "built_parts", "senses", "attested"]);
|
|
43
54
|
|
|
44
55
|
/** Every column of the `words` relation, in order. */
|
|
45
56
|
export const WORD_COLUMNS = Object.freeze([
|
|
@@ -51,6 +62,12 @@ export const WORD_COLUMNS = Object.freeze([
|
|
|
51
62
|
/** Every column of the `glossary` relation, in order. */
|
|
52
63
|
export const GLOSSARY_COLUMNS = Object.freeze(["note", "position", ...GLOSSARY_ENTRY_KEYS]);
|
|
53
64
|
|
|
65
|
+
/** Every column of the `affixes` relation, in order. */
|
|
66
|
+
export const AFFIX_COLUMNS = Object.freeze([...WORD_IDENTITY_COLUMNS, ...AFFIX_KEYS]);
|
|
67
|
+
|
|
68
|
+
/** Every column of the `fields` relation, in order. */
|
|
69
|
+
export const FIELD_COLUMNS = Object.freeze([...WORD_IDENTITY_COLUMNS, ...FIELD_KEYS]);
|
|
70
|
+
|
|
54
71
|
/** Every column of the `language_lineage` relation, in order. */
|
|
55
72
|
export const LINEAGE_COLUMNS = Object.freeze(["language", "ancestor", "depth"]);
|
|
56
73
|
|
|
@@ -64,32 +81,89 @@ const isMap = (value) => Boolean(value) && typeof value === "object" && !Array.i
|
|
|
64
81
|
const bare = (form) => String(form).replace(/^-+|-+$/g, "");
|
|
65
82
|
|
|
66
83
|
/**
|
|
67
|
-
* A lookup from a built piece to its gloss, over
|
|
68
|
-
*
|
|
84
|
+
* A lookup from a built piece to its gloss, over the words (stems included)
|
|
85
|
+
* and affixes of one or more languages.
|
|
69
86
|
*
|
|
70
|
-
*
|
|
87
|
+
* Languages are given nearest first: a language's own words, then its own
|
|
88
|
+
* affixes, then the next language's, so a daughter's entry wins over its
|
|
89
|
+
* parent's. A piece matches an entry written exactly as it is, and failing
|
|
71
90
|
* that one written with the same letters and different joining hyphens, so
|
|
72
|
-
* `wulf-` finds the word `wulf`.
|
|
91
|
+
* `wulf-` finds the word `wulf`. Within a language a word's own gloss wins over
|
|
92
|
+
* an affix's.
|
|
73
93
|
*
|
|
74
|
-
* @param {object}
|
|
94
|
+
* @param {...object} datas - Each language note's `data`, nearest first.
|
|
75
95
|
* @returns {(piece: string) => string|null} The gloss, or `null` where none resolves.
|
|
76
96
|
*/
|
|
77
|
-
export function pieceGlosses(
|
|
97
|
+
export function pieceGlosses(...datas) {
|
|
78
98
|
const exact = new Map();
|
|
79
99
|
const loose = new Map();
|
|
80
100
|
const take = (entries) => {
|
|
81
101
|
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
82
|
-
if (!isMap(entry) || typeof entry.form !== "string"
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
if (!
|
|
102
|
+
if (!isMap(entry) || typeof entry.form !== "string") continue;
|
|
103
|
+
const gloss = glossText(entry.gloss);
|
|
104
|
+
if (gloss === null) continue;
|
|
105
|
+
if (!exact.has(entry.form)) exact.set(entry.form, gloss);
|
|
106
|
+
if (!loose.has(bare(entry.form))) loose.set(bare(entry.form), gloss);
|
|
86
107
|
}
|
|
87
108
|
};
|
|
88
|
-
|
|
89
|
-
|
|
109
|
+
for (const data of datas) {
|
|
110
|
+
take(data?.words);
|
|
111
|
+
take(data?.affixes);
|
|
112
|
+
}
|
|
90
113
|
return (piece) => exact.get(piece) ?? loose.get(bare(piece)) ?? null;
|
|
91
114
|
}
|
|
92
115
|
|
|
116
|
+
/**
|
|
117
|
+
* The chain of languages from one up through `data.family`: the language
|
|
118
|
+
* itself, then its parent, and so on. The chain stops at a parent that is no
|
|
119
|
+
* language, and before a language it has already reached, so a cycle the lint
|
|
120
|
+
* refuses still yields a finite chain. `language_lineage`, the dictionary and
|
|
121
|
+
* the resolution of built pieces all walk it.
|
|
122
|
+
*
|
|
123
|
+
* @param {string|undefined} start - The language's key.
|
|
124
|
+
* @param {object} reader
|
|
125
|
+
* @param {(key: string) => boolean} reader.has - Whether a key is a language.
|
|
126
|
+
* @param {(key: string) => string|undefined} reader.parentOf - A language's
|
|
127
|
+
* parent key, where it states one.
|
|
128
|
+
* @returns {string[]} The keys, nearest first.
|
|
129
|
+
*/
|
|
130
|
+
export function lineageChain(start, { has, parentOf }) {
|
|
131
|
+
const chain = [];
|
|
132
|
+
const seen = new Set();
|
|
133
|
+
for (let current = start; current && has(current) && !seen.has(current);) {
|
|
134
|
+
seen.add(current);
|
|
135
|
+
chain.push(current);
|
|
136
|
+
current = parentOf(current) || undefined;
|
|
137
|
+
}
|
|
138
|
+
return chain;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** A language record's `data.family` where it is a plain Address string. */
|
|
142
|
+
const familyKey = (record) => {
|
|
143
|
+
const family = record?.data?.family;
|
|
144
|
+
return typeof family === "string" && family ? family : undefined;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* A lookup of language records by Address, and the chain walk over them.
|
|
149
|
+
*
|
|
150
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
151
|
+
* @returns {{languages: Map<string, object>, chain: (key: string) => string[]}}
|
|
152
|
+
*/
|
|
153
|
+
export function languageLineages(records) {
|
|
154
|
+
const languages = new Map();
|
|
155
|
+
for (const record of records)
|
|
156
|
+
if (isLanguageRecord(record)) {
|
|
157
|
+
const key = noteAddressOf(record);
|
|
158
|
+
if (key && !languages.has(key)) languages.set(key, record);
|
|
159
|
+
}
|
|
160
|
+
const reader = {
|
|
161
|
+
has: (key) => languages.has(key),
|
|
162
|
+
parentOf: (key) => familyKey(languages.get(key)),
|
|
163
|
+
};
|
|
164
|
+
return { languages, chain: (key) => lineageChain(key, reader) };
|
|
165
|
+
}
|
|
166
|
+
|
|
93
167
|
/**
|
|
94
168
|
* Whether a record is a language note's.
|
|
95
169
|
*
|
|
@@ -107,7 +181,7 @@ export function isLanguageRecord(record) {
|
|
|
107
181
|
* @param {Record<string, any>} record - An index record.
|
|
108
182
|
* @returns {string|null} The Address, or `null` for a record naming no note.
|
|
109
183
|
*/
|
|
110
|
-
function noteAddressOf(record) {
|
|
184
|
+
export function noteAddressOf(record) {
|
|
111
185
|
if (!record?.package || !record.type || !record.shortcode) return null;
|
|
112
186
|
return renderAddress({
|
|
113
187
|
package: String(record.package),
|
|
@@ -117,6 +191,31 @@ function noteAddressOf(record) {
|
|
|
117
191
|
});
|
|
118
192
|
}
|
|
119
193
|
|
|
194
|
+
/**
|
|
195
|
+
* A lookup of the notes citing a word, from records that carry `citations`.
|
|
196
|
+
*
|
|
197
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
198
|
+
* @returns {(language: string, form: string) => string[]} The citing notes;
|
|
199
|
+
* empty where none cites the word.
|
|
200
|
+
*/
|
|
201
|
+
export function citationLookup(records) {
|
|
202
|
+
const byLanguage = new Map();
|
|
203
|
+
for (const record of records) {
|
|
204
|
+
if (!isLanguageRecord(record) || !Array.isArray(record.citations)) continue;
|
|
205
|
+
const language = noteAddressOf(record);
|
|
206
|
+
if (!language) continue;
|
|
207
|
+
const words = new Map();
|
|
208
|
+
for (const entry of record.citations)
|
|
209
|
+
if (isMap(entry) && typeof entry.form === "string" && Array.isArray(entry.notes))
|
|
210
|
+
words.set(
|
|
211
|
+
entry.form,
|
|
212
|
+
entry.notes.filter((note) => typeof note === "string"),
|
|
213
|
+
);
|
|
214
|
+
byLanguage.set(language, words);
|
|
215
|
+
}
|
|
216
|
+
return (language, form) => byLanguage.get(language)?.get(form) ?? [];
|
|
217
|
+
}
|
|
218
|
+
|
|
120
219
|
/**
|
|
121
220
|
* The `words` rows of a set of index records.
|
|
122
221
|
*
|
|
@@ -124,9 +223,23 @@ function noteAddressOf(record) {
|
|
|
124
223
|
*
|
|
125
224
|
* @param {Iterable<Record<string, any>>} records - Index records, every
|
|
126
225
|
* Address a string, already narrowed to the audience being published to.
|
|
226
|
+
* @param {Iterable<Record<string, any>>} [all] - Every record of the package,
|
|
227
|
+
* for resolving a built piece through an ancestor the audience does not see.
|
|
127
228
|
* @returns {object[]} One row per word, in record then entry order.
|
|
128
229
|
*/
|
|
129
|
-
export function wordRows(records) {
|
|
230
|
+
export function wordRows(records, all = records) {
|
|
231
|
+
const { languages, chain } = languageLineages(all);
|
|
232
|
+
const citedBy = citationLookup(all);
|
|
233
|
+
const visible = new Set();
|
|
234
|
+
for (const record of records) {
|
|
235
|
+
const key = noteAddressOf(record);
|
|
236
|
+
if (key) visible.add(key);
|
|
237
|
+
}
|
|
238
|
+
const known = new Set();
|
|
239
|
+
for (const record of all) {
|
|
240
|
+
const key = noteAddressOf(record);
|
|
241
|
+
if (key) known.add(key);
|
|
242
|
+
}
|
|
130
243
|
const rows = [];
|
|
131
244
|
for (const record of records) {
|
|
132
245
|
if (!isLanguageRecord(record)) continue;
|
|
@@ -134,7 +247,7 @@ export function wordRows(records) {
|
|
|
134
247
|
if (!Array.isArray(words) || !words.length) continue;
|
|
135
248
|
const language = noteAddressOf(record);
|
|
136
249
|
if (!language) continue;
|
|
137
|
-
const glossOf = pieceGlosses(
|
|
250
|
+
const glossOf = pieceGlosses(...chain(language).map((key) => languages.get(key).data));
|
|
138
251
|
words.forEach((entry, position) => {
|
|
139
252
|
if (!isMap(entry)) return;
|
|
140
253
|
const row = { language, position };
|
|
@@ -144,12 +257,70 @@ export function wordRows(records) {
|
|
|
144
257
|
row.built_text = built.length ? built.join(" + ") : null;
|
|
145
258
|
row.built_parts =
|
|
146
259
|
built.length ? built.map((form) => ({ form, gloss: glossOf(form) })) : null;
|
|
260
|
+
row.gloss = glossText(entry.gloss);
|
|
261
|
+
row.senses = glossSenses(entry.gloss);
|
|
262
|
+
// A citing note the audience does not see is left out, as the note is.
|
|
263
|
+
const attested = citedBy(language, entry.form).filter(
|
|
264
|
+
(note) => visible.has(note) || !known.has(note),
|
|
265
|
+
);
|
|
266
|
+
row.attested = attested.length ? attested : null;
|
|
147
267
|
rows.push(row);
|
|
148
268
|
});
|
|
149
269
|
}
|
|
150
270
|
return rows;
|
|
151
271
|
}
|
|
152
272
|
|
|
273
|
+
/**
|
|
274
|
+
* The rows of one list a language note states beside its words: one per entry,
|
|
275
|
+
* keyed by the note, with every key of the entry a column.
|
|
276
|
+
*
|
|
277
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
278
|
+
* @param {string} list - The `data` key holding the entries.
|
|
279
|
+
* @param {readonly string[]} keys - The keys an entry carries.
|
|
280
|
+
* @returns {object[]} One row per entry, in record then entry order.
|
|
281
|
+
*/
|
|
282
|
+
function languageListRows(records, list, keys) {
|
|
283
|
+
const rows = [];
|
|
284
|
+
for (const record of records) {
|
|
285
|
+
if (!isLanguageRecord(record)) continue;
|
|
286
|
+
const entries = record.data?.[list];
|
|
287
|
+
if (!Array.isArray(entries) || !entries.length) continue;
|
|
288
|
+
const language = noteAddressOf(record);
|
|
289
|
+
if (!language) continue;
|
|
290
|
+
entries.forEach((entry, position) => {
|
|
291
|
+
if (!isMap(entry)) return;
|
|
292
|
+
const row = { language, position };
|
|
293
|
+
for (const key of keys) row[key] = entry[key] ?? null;
|
|
294
|
+
rows.push(row);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return rows;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* The `affixes` rows of a set of index records: one per `data.affixes` entry
|
|
302
|
+
* of a language note, which is the row's `language`.
|
|
303
|
+
*
|
|
304
|
+
* @param {Iterable<Record<string, any>>} records - Index records, every
|
|
305
|
+
* Address a string, already narrowed to the audience being published to.
|
|
306
|
+
* @returns {object[]} One row per affix, in record then entry order.
|
|
307
|
+
*/
|
|
308
|
+
export function affixRows(records) {
|
|
309
|
+
return languageListRows(records, "affixes", AFFIX_KEYS);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* The `fields` rows of a set of index records: one per `data.fields` entry of
|
|
314
|
+
* a language note, which is the row's `language`.
|
|
315
|
+
*
|
|
316
|
+
* @param {Iterable<Record<string, any>>} records - Index records, every
|
|
317
|
+
* Address a string, already narrowed to the audience being published to.
|
|
318
|
+
* @returns {object[]} One row per field, in record then entry order.
|
|
319
|
+
*/
|
|
320
|
+
export function fieldRows(records) {
|
|
321
|
+
return languageListRows(records, "fields", FIELD_KEYS);
|
|
322
|
+
}
|
|
323
|
+
|
|
153
324
|
/**
|
|
154
325
|
* The `glossary` rows of a set of index records: one per entry of a culture's
|
|
155
326
|
* or an affiliation's `data.glossary`, with `name_sort` the name and `common`
|
|
@@ -209,25 +380,13 @@ export function glossaryRows(records, all = records) {
|
|
|
209
380
|
* @returns {object[]} One row per language and ancestor, in record then depth order.
|
|
210
381
|
*/
|
|
211
382
|
export function lineageRows(records, all = records) {
|
|
212
|
-
const
|
|
213
|
-
for (const record of all)
|
|
214
|
-
if (isLanguageRecord(record)) {
|
|
215
|
-
const key = noteAddressOf(record);
|
|
216
|
-
if (key && !languages.has(key)) languages.set(key, record);
|
|
217
|
-
}
|
|
383
|
+
const { chain } = languageLineages(all);
|
|
218
384
|
const rows = [];
|
|
219
385
|
for (const record of records) {
|
|
220
386
|
if (!isLanguageRecord(record)) continue;
|
|
221
387
|
const language = noteAddressOf(record);
|
|
222
388
|
if (!language) continue;
|
|
223
|
-
|
|
224
|
-
let current = language;
|
|
225
|
-
for (let depth = 0; current && languages.has(current) && !seen.has(current); depth++) {
|
|
226
|
-
seen.add(current);
|
|
227
|
-
rows.push({ language, ancestor: current, depth });
|
|
228
|
-
const family = languages.get(current).data?.family;
|
|
229
|
-
current = typeof family === "string" && family ? family : undefined;
|
|
230
|
-
}
|
|
389
|
+
chain(language).forEach((ancestor, depth) => rows.push({ language, ancestor, depth }));
|
|
231
390
|
}
|
|
232
391
|
return rows;
|
|
233
392
|
}
|
package/engine/word-terms.mjs
CHANGED
|
@@ -88,6 +88,15 @@ export const WORD_FIELDS = Object.freeze([
|
|
|
88
88
|
shape: "the word as cited, bare",
|
|
89
89
|
describe: "The word as cited, with no backticks.",
|
|
90
90
|
}),
|
|
91
|
+
Object.freeze({
|
|
92
|
+
name: "variants",
|
|
93
|
+
kind: "list",
|
|
94
|
+
shape: "a list of forms",
|
|
95
|
+
entries: Object.freeze({ kind: "string", shape: "a form, bare" }),
|
|
96
|
+
describe:
|
|
97
|
+
"The word's other forms, such as a long form beside a short one; a marked use of " +
|
|
98
|
+
"one cites the word.",
|
|
99
|
+
}),
|
|
91
100
|
Object.freeze({
|
|
92
101
|
name: "name_sort",
|
|
93
102
|
kind: "string",
|
|
@@ -112,10 +121,12 @@ export const WORD_FIELDS = Object.freeze([
|
|
|
112
121
|
}),
|
|
113
122
|
Object.freeze({
|
|
114
123
|
name: "gloss",
|
|
115
|
-
kind: "string",
|
|
124
|
+
kind: "string-or-list",
|
|
116
125
|
required: true,
|
|
117
|
-
shape: "a string",
|
|
118
|
-
|
|
126
|
+
shape: "a string, or a list of senses",
|
|
127
|
+
entries: Object.freeze({ kind: "string", shape: "a sense" }),
|
|
128
|
+
describe:
|
|
129
|
+
"The meaning, in brief; a list states one sense per entry, which a dictionary numbers.",
|
|
119
130
|
}),
|
|
120
131
|
Object.freeze({
|
|
121
132
|
name: "source",
|
|
@@ -134,11 +145,11 @@ export const WORD_FIELDS = Object.freeze([
|
|
|
134
145
|
"write them; absent for a root word.",
|
|
135
146
|
}),
|
|
136
147
|
Object.freeze({
|
|
137
|
-
name: "
|
|
138
|
-
kind: "
|
|
139
|
-
shape: "a
|
|
140
|
-
|
|
141
|
-
|
|
148
|
+
name: "note",
|
|
149
|
+
kind: "string",
|
|
150
|
+
shape: "a string",
|
|
151
|
+
describe:
|
|
152
|
+
"What a source says of the word beyond its pieces, such as a cognate it is read with.",
|
|
142
153
|
}),
|
|
143
154
|
Object.freeze({
|
|
144
155
|
name: "pronunciation",
|
|
@@ -163,14 +174,41 @@ export const WORD_FIELDS = Object.freeze([
|
|
|
163
174
|
Object.freeze({
|
|
164
175
|
name: "frame",
|
|
165
176
|
kind: "string",
|
|
166
|
-
shape: "a
|
|
167
|
-
describe: "The frame a root is set in.",
|
|
177
|
+
shape: "a frame of the language's `sounds.frames`",
|
|
178
|
+
describe: "The frame a root is set in, named in the language's `sounds.frames`.",
|
|
168
179
|
}),
|
|
169
180
|
]);
|
|
170
181
|
|
|
171
182
|
/** The keys of {@link WORD_FIELDS}, in declared order. */
|
|
172
183
|
export const WORD_KEYS = Object.freeze(WORD_FIELDS.map((field) => field.name));
|
|
173
184
|
|
|
185
|
+
/**
|
|
186
|
+
* A word's senses: one for a string gloss, one per entry for a list, each
|
|
187
|
+
* trimmed, blanks dropped.
|
|
188
|
+
*
|
|
189
|
+
* @param {unknown} gloss - The word's `gloss`.
|
|
190
|
+
* @returns {string[]} The senses, in order.
|
|
191
|
+
*/
|
|
192
|
+
export function glossSenses(gloss) {
|
|
193
|
+
const list = Array.isArray(gloss) ? gloss : [gloss];
|
|
194
|
+
return list
|
|
195
|
+
.filter((sense) => typeof sense === "string")
|
|
196
|
+
.map((sense) => sense.trim())
|
|
197
|
+
.filter(Boolean);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* A word's gloss as one line of text: its senses joined by `; `, or `null`
|
|
202
|
+
* where it states none.
|
|
203
|
+
*
|
|
204
|
+
* @param {unknown} gloss - The word's `gloss`.
|
|
205
|
+
* @returns {string|null}
|
|
206
|
+
*/
|
|
207
|
+
export function glossText(gloss) {
|
|
208
|
+
const senses = glossSenses(gloss);
|
|
209
|
+
return senses.length ? senses.join("; ") : null;
|
|
210
|
+
}
|
|
211
|
+
|
|
174
212
|
/**
|
|
175
213
|
* The keys one entry of a language note's `data.fields` carries.
|
|
176
214
|
*
|
|
@@ -217,7 +255,29 @@ export const AFFIX_FIELDS = Object.freeze([
|
|
|
217
255
|
name: "attaches",
|
|
218
256
|
kind: "string",
|
|
219
257
|
shape: "a string",
|
|
220
|
-
describe: "How it attaches, such as `replaces a closing vowel`.",
|
|
258
|
+
describe: "How it attaches, in words, such as `replaces a closing vowel`.",
|
|
259
|
+
}),
|
|
260
|
+
Object.freeze({
|
|
261
|
+
name: "replaces",
|
|
262
|
+
kind: "string",
|
|
263
|
+
shape: "`vowel`, or the letters, such as `a`",
|
|
264
|
+
describe:
|
|
265
|
+
"What it takes the place of at the close of the stem: `vowel` for any closing " +
|
|
266
|
+
"vowel, or the letters it replaces; added to the whole stem where absent.",
|
|
267
|
+
}),
|
|
268
|
+
Object.freeze({
|
|
269
|
+
name: "lengthens",
|
|
270
|
+
kind: "boolean",
|
|
271
|
+
shape: "`true` or `false`",
|
|
272
|
+
describe:
|
|
273
|
+
"Whether the stem's last vowel lengthens before the affix; `false` where unstated. An " +
|
|
274
|
+
"affix that `replaces` the stem's close does not also lengthen it.",
|
|
275
|
+
}),
|
|
276
|
+
Object.freeze({
|
|
277
|
+
name: "front",
|
|
278
|
+
kind: "string",
|
|
279
|
+
shape: "the affix as written, such as `-ür`",
|
|
280
|
+
describe: "Its form after front vowels, where the language keeps vowel harmony.",
|
|
221
281
|
}),
|
|
222
282
|
Object.freeze({
|
|
223
283
|
name: "examples",
|
|
@@ -226,8 +286,20 @@ export const AFFIX_FIELDS = Object.freeze([
|
|
|
226
286
|
entries: Object.freeze({ kind: "string", shape: "a string" }),
|
|
227
287
|
describe: "The names the affix is read from.",
|
|
228
288
|
}),
|
|
289
|
+
Object.freeze({
|
|
290
|
+
name: "tones",
|
|
291
|
+
kind: "string",
|
|
292
|
+
shape: "a tone pattern, such as `L`",
|
|
293
|
+
describe: "The tone pattern the affix carries, written as a word's `tones` is.",
|
|
294
|
+
}),
|
|
229
295
|
]);
|
|
230
296
|
|
|
297
|
+
/** The keys of {@link WORD_FIELD_FIELDS}, in declared order. */
|
|
298
|
+
export const FIELD_KEYS = Object.freeze(WORD_FIELD_FIELDS.map((field) => field.name));
|
|
299
|
+
|
|
300
|
+
/** The keys of {@link AFFIX_FIELDS}, in declared order. */
|
|
301
|
+
export const AFFIX_KEYS = Object.freeze(AFFIX_FIELDS.map((field) => field.name));
|
|
302
|
+
|
|
231
303
|
/**
|
|
232
304
|
* The keys one entry of a culture's or an affiliation's `data.glossary`
|
|
233
305
|
* carries.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "23.
|
|
3
|
+
"version": "23.7.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",
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* @property {object} [addressContext] - Address resolution context.
|
|
7
7
|
* @property {(address: string) => string|undefined} [labelOf] - A note's
|
|
8
8
|
* `name.full`, by its Address.
|
|
9
|
+
* @property {boolean|"only"} [showKey=true] - Whether the key is printed;
|
|
10
|
+
* `"only"` prints the key and nothing else.
|
|
9
11
|
* @property {string} [pageLanguage] - The language of the page the fence is
|
|
10
12
|
* on, which no entry is tagged with.
|
|
11
13
|
*/
|
|
@@ -61,6 +63,11 @@ export type LayoutOptions = {
|
|
|
61
63
|
* `name.full`, by its Address.
|
|
62
64
|
*/
|
|
63
65
|
labelOf?: ((address: string) => string | undefined) | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* - Whether the key is printed;
|
|
68
|
+
* `"only"` prints the key and nothing else.
|
|
69
|
+
*/
|
|
70
|
+
showKey?: boolean | "only" | undefined;
|
|
64
71
|
/**
|
|
65
72
|
* - The language of the page the fence is
|
|
66
73
|
* on, which no entry is tagged with.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a tag a row names is one `data.tongues` declares.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} tag - The tag as the row writes it.
|
|
5
|
+
* @param {Set<string>} declared - The declared tags.
|
|
6
|
+
* @returns {boolean}
|
|
7
|
+
*/
|
|
8
|
+
export function tongueDeclared(tag: string, declared: Set<string>): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Check a `skill` note's language data against what it means.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} note - A `skill` note: `file`, `raw` and parsed `fm`.
|
|
13
|
+
* @param {object} [context]
|
|
14
|
+
* @param {object} [context.index] - The index, for whether an Address
|
|
15
|
+
* resolves. Without one, only what the note says of itself is checked.
|
|
16
|
+
* @param {readonly string[]} [context.types] - The note types a scope may name.
|
|
17
|
+
* @returns {object[]} The findings.
|
|
18
|
+
*/
|
|
19
|
+
export function checkLanguageData(note: object, { index, types }?: {
|
|
20
|
+
index?: object | undefined;
|
|
21
|
+
types?: readonly string[] | undefined;
|
|
22
|
+
}): object[];
|