@heroiclands/package-build 23.7.0 → 23.8.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 +27 -0
- package/README.md +32 -0
- package/bin/content-commands.mjs +20 -6
- package/ci/check-banners.mjs +37 -19
- package/container.mjs +48 -10
- package/docs/api.md +87 -43
- package/docs/authoring/frontmatter.md +30 -1
- package/docs/authoring/links-and-markup.md +229 -36
- package/docs/commands.md +12 -2
- package/docs/content-format.md +24 -1
- package/docs/design.md +31 -0
- package/docs/guides/e2e.md +37 -1
- package/docs/project-setup.md +30 -15
- package/docs/reference/format-details.md +418 -220
- package/docs/reference/note-types.md +90 -31
- package/e2e.mjs +89 -2
- package/engine/base-compiler.mjs +11 -18
- package/engine/compile-corpus.mjs +2 -1
- package/engine/content-alerts.mjs +4 -2
- package/engine/content-blocks.mjs +18 -36
- package/engine/content-format.yaml +3 -0
- package/engine/content-index.mjs +35 -22
- package/engine/content-poetry.mjs +9 -0
- package/engine/diagnostics.mjs +24 -11
- package/engine/frontmatter-lint.mjs +1 -1
- package/engine/generate.mjs +12 -22
- package/engine/helpers.mjs +8 -6
- package/engine/infobox.mjs +3 -0
- package/engine/journals.mjs +11 -1
- package/engine/language-data.mjs +236 -1
- package/engine/language-rows.mjs +235 -3
- package/engine/language-terms.mjs +524 -9
- package/engine/language-words.mjs +75 -0
- package/engine/markdownlint-run.mjs +12 -2
- package/engine/markup-validation.mjs +34 -0
- package/engine/note-addresses.mjs +1 -0
- package/engine/note-claims.mjs +19 -10
- package/engine/note-package.mjs +3 -5
- package/engine/note-vocabulary.mjs +29 -1
- package/engine/pdf-build.mjs +8 -1
- package/engine/prose-lint.mjs +12 -6
- package/engine/related-pages.mjs +22 -3
- package/engine/see-also.mjs +150 -0
- package/engine/site-build.mjs +5 -1
- package/engine/site-preview.mjs +4 -2
- package/engine/sql-tables.mjs +37 -12
- package/engine/standings.mjs +14 -12
- package/engine/word-rows.mjs +8 -3
- package/engine/word-terms.mjs +60 -2
- package/hugo-theme/layouts/partials/related.html +25 -8
- package/package.json +2 -1
- package/types/container.d.mts +4 -3
- package/types/e2e.d.mts +28 -0
- package/types/engine/base-compiler.d.mts +2 -2
- package/types/engine/compile-corpus.d.mts +2 -1
- package/types/engine/content-index.d.mts +20 -7
- package/types/engine/diagnostics.d.mts +7 -6
- package/types/engine/helpers.d.mts +7 -4
- package/types/engine/language-rows.d.mts +89 -3
- package/types/engine/language-terms.d.mts +106 -4
- package/types/engine/markdownlint-run.d.mts +3 -1
- package/types/engine/markup-validation.d.mts +14 -0
- package/types/engine/note-addresses.d.mts +10 -0
- package/types/engine/note-claims.d.mts +7 -1
- package/types/engine/note-package.d.mts +3 -1
- package/types/engine/note-vocabulary.d.mts +1 -0
- package/types/engine/related-pages.d.mts +10 -1
- package/types/engine/see-also.d.mts +55 -0
- package/types/engine/sql-tables.d.mts +7 -3
- package/types/engine/word-rows.d.mts +2 -1
- package/types/engine/word-terms.d.mts +11 -0
package/engine/language-data.mjs
CHANGED
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
* `data.tongues`, `translation` apart: `exonym:vylari` is declared by
|
|
26
26
|
* `exonym:<tongue>`;
|
|
27
27
|
* - an edge stands once in `data.sounds.edges`;
|
|
28
|
+
* - a sound stands once in each mutation of `data.sounds.mutations`;
|
|
29
|
+
* - a cluster stands in one group of `data.sounds.clusters.groups`, and a
|
|
30
|
+
* language stating groups states no `innerCluster` edge;
|
|
31
|
+
* - every vowel `data.sounds.syllables.harmony.partners` keys is a vowel of
|
|
32
|
+
* the harmony, and every partner one of its `front` vowels;
|
|
28
33
|
* - every Address in `data.scope` resolves, and every type it names is a note
|
|
29
34
|
* type;
|
|
30
35
|
* - every language a cognate's `forms` names is a language note;
|
|
@@ -32,7 +37,15 @@
|
|
|
32
37
|
* - a sound-change set's name stands once in the note, a step's label once
|
|
33
38
|
* in its set, and its `from` and `to` name language notes;
|
|
34
39
|
* - a frame's classes are bases of the master word classes;
|
|
35
|
-
* - a calling-name closing states a `form` or `doubled: true`, not both
|
|
40
|
+
* - a calling-name closing states a `form` or `doubled: true`, not both;
|
|
41
|
+
* - a form stands once in `data.older`;
|
|
42
|
+
* - the mutation `data.negation` names is one of `data.sounds.mutations`;
|
|
43
|
+
* - in `data.naming`, a class of name has one shape, which states its
|
|
44
|
+
* syllables, its consonants per vowel or both, each range's `min` no
|
|
45
|
+
* higher than its `max`; an edge stands once for each class of name; a kind
|
|
46
|
+
* of name has one set of draws; and a held-from row takes `elements` only
|
|
47
|
+
* where it takes an `element`, and is clipped or given a genitive only
|
|
48
|
+
* where it takes a name.
|
|
36
49
|
*
|
|
37
50
|
* @module
|
|
38
51
|
*/
|
|
@@ -43,6 +56,9 @@ import { addressKeyOf } from "./culture-reach.mjs";
|
|
|
43
56
|
import { AddressEntries } from "./address-values.mjs";
|
|
44
57
|
import { isAddressTuple, renderAddress } from "./address.mjs";
|
|
45
58
|
import {
|
|
59
|
+
ANY_NAME,
|
|
60
|
+
HELD_NAMES,
|
|
61
|
+
HELD_TAKES,
|
|
46
62
|
LANGUAGE_DATA_KEYS,
|
|
47
63
|
RESERVED_TONGUE,
|
|
48
64
|
SCOPE_KINDS,
|
|
@@ -287,6 +303,80 @@ export function checkLanguageData(note, { index, types } = {}) {
|
|
|
287
303
|
else edgeAt.set(row.edge, i);
|
|
288
304
|
});
|
|
289
305
|
|
|
306
|
+
if (isMap(sounds.clusters) && Array.isArray(sounds.clusters.groups))
|
|
307
|
+
listOf(sounds.edges).forEach((row, i) => {
|
|
308
|
+
if (!isMap(row) || row.edge !== "innerCluster") return;
|
|
309
|
+
findings.push(
|
|
310
|
+
finding(
|
|
311
|
+
["data", "sounds", "edges", i, "edge"],
|
|
312
|
+
"error",
|
|
313
|
+
"`data.sounds.clusters.groups` states the clusters inside a word, so no " +
|
|
314
|
+
"`innerCluster` edge does",
|
|
315
|
+
),
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
const clusterAt = new Map();
|
|
319
|
+
listOf(sounds.clusters?.groups).forEach((group, i) => {
|
|
320
|
+
if (!isMap(group)) return;
|
|
321
|
+
listOf(group.clusters).forEach((cluster, j) => {
|
|
322
|
+
if (typeof cluster !== "string") return;
|
|
323
|
+
if (clusterAt.has(cluster))
|
|
324
|
+
findings.push(
|
|
325
|
+
finding(
|
|
326
|
+
["data", "sounds", "clusters", "groups", i, "clusters", j],
|
|
327
|
+
"error",
|
|
328
|
+
`the cluster \`${cluster}\` stands twice, first in ` +
|
|
329
|
+
`\`data.sounds.clusters.groups[${clusterAt.get(cluster)}]\``,
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
else clusterAt.set(cluster, i);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const mutationAt = new Map();
|
|
337
|
+
listOf(sounds.mutations).forEach((row, i) => {
|
|
338
|
+
if (!isMap(row) || typeof row.mutation !== "string" || typeof row.sound !== "string")
|
|
339
|
+
return;
|
|
340
|
+
const key = JSON.stringify([row.mutation, row.sound]);
|
|
341
|
+
if (mutationAt.has(key))
|
|
342
|
+
findings.push(
|
|
343
|
+
finding(
|
|
344
|
+
["data", "sounds", "mutations", i, "sound"],
|
|
345
|
+
"error",
|
|
346
|
+
`the sound \`${row.sound}\` stands twice in the mutation \`${row.mutation}\`, ` +
|
|
347
|
+
`first at \`data.sounds.mutations[${mutationAt.get(key)}]\``,
|
|
348
|
+
),
|
|
349
|
+
);
|
|
350
|
+
else mutationAt.set(key, i);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const harmony = isMap(sounds.syllables?.harmony) ? sounds.syllables.harmony : {};
|
|
354
|
+
if (isMap(harmony.partners)) {
|
|
355
|
+
const front = new Set(listOf(harmony.front));
|
|
356
|
+
const vowels = new Set([...listOf(harmony.back), ...front, ...listOf(harmony.neutral)]);
|
|
357
|
+
for (const [vowel, partner] of Object.entries(harmony.partners)) {
|
|
358
|
+
const path = ["data", "sounds", "syllables", "harmony", "partners", vowel];
|
|
359
|
+
if (!vowels.has(vowel))
|
|
360
|
+
findings.push(
|
|
361
|
+
finding(
|
|
362
|
+
path,
|
|
363
|
+
"error",
|
|
364
|
+
`\`${vowel}\` is not a vowel of \`data.sounds.syllables.harmony\``,
|
|
365
|
+
true,
|
|
366
|
+
),
|
|
367
|
+
);
|
|
368
|
+
else if (typeof partner === "string" && !front.has(partner))
|
|
369
|
+
findings.push(
|
|
370
|
+
finding(
|
|
371
|
+
path,
|
|
372
|
+
"error",
|
|
373
|
+
`the partner \`${partner}\` of \`${vowel}\` is not one of the harmony's ` +
|
|
374
|
+
"`front` vowels",
|
|
375
|
+
),
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
290
380
|
listOf(sounds.frames).forEach((row, i) => {
|
|
291
381
|
if (!isMap(row)) return;
|
|
292
382
|
listOf(row.class).forEach((base, j) => {
|
|
@@ -351,6 +441,151 @@ export function checkLanguageData(note, { index, types } = {}) {
|
|
|
351
441
|
);
|
|
352
442
|
});
|
|
353
443
|
|
|
444
|
+
// ── Older forms and negation ─────────────────────────────────────────────
|
|
445
|
+
const olderAt = new Map();
|
|
446
|
+
listOf(data.older).forEach((row, i) => {
|
|
447
|
+
if (!isMap(row) || typeof row.form !== "string") return;
|
|
448
|
+
if (olderAt.has(row.form))
|
|
449
|
+
findings.push(
|
|
450
|
+
finding(
|
|
451
|
+
["data", "older", i, "form"],
|
|
452
|
+
"error",
|
|
453
|
+
`the form \`${row.form}\` stands twice, first at \`data.older[${olderAt.get(row.form)}]\``,
|
|
454
|
+
),
|
|
455
|
+
);
|
|
456
|
+
else olderAt.set(row.form, i);
|
|
457
|
+
});
|
|
458
|
+
const negation = isMap(data.negation) ? data.negation : {};
|
|
459
|
+
if (typeof negation.mutation === "string") {
|
|
460
|
+
const named = new Set(
|
|
461
|
+
listOf(sounds.mutations)
|
|
462
|
+
.filter((row) => isMap(row) && typeof row.mutation === "string")
|
|
463
|
+
.map((row) => row.mutation),
|
|
464
|
+
);
|
|
465
|
+
if (!named.has(negation.mutation))
|
|
466
|
+
findings.push(
|
|
467
|
+
finding(
|
|
468
|
+
["data", "negation", "mutation"],
|
|
469
|
+
"error",
|
|
470
|
+
`the mutation \`${negation.mutation}\` is not one of \`data.sounds.mutations\``,
|
|
471
|
+
),
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ── Naming ───────────────────────────────────────────────────────────────
|
|
476
|
+
const naming = isMap(data.naming) ? data.naming : {};
|
|
477
|
+
/**
|
|
478
|
+
* A finding at the second of two entries sharing a key.
|
|
479
|
+
*
|
|
480
|
+
* @param {string} list - The list under `data.naming`.
|
|
481
|
+
* @param {(row: object) => string|undefined} keyOf - The entry's key.
|
|
482
|
+
* @param {(row: object, first: number) => [Array<string|number>, string]} report
|
|
483
|
+
* - Where the second sits, relative to its entry, and what is said.
|
|
484
|
+
*/
|
|
485
|
+
const once = (list, keyOf, report) => {
|
|
486
|
+
const seen = new Map();
|
|
487
|
+
listOf(naming[list]).forEach((row, i) => {
|
|
488
|
+
if (!isMap(row)) return;
|
|
489
|
+
const key = keyOf(row);
|
|
490
|
+
if (key === undefined) return;
|
|
491
|
+
if (seen.has(key)) {
|
|
492
|
+
const [rest, message] = report(row, seen.get(key));
|
|
493
|
+
findings.push(finding(["data", "naming", list, i, ...rest], "error", message));
|
|
494
|
+
} else seen.set(key, i);
|
|
495
|
+
});
|
|
496
|
+
};
|
|
497
|
+
once(
|
|
498
|
+
"shapes",
|
|
499
|
+
(row) => (typeof row.of === "string" ? row.of : undefined),
|
|
500
|
+
(row, first) => [
|
|
501
|
+
["of"],
|
|
502
|
+
`the class of name \`${row.of}\` has its shape stated twice, first at ` +
|
|
503
|
+
`\`data.naming.shapes[${first}]\``,
|
|
504
|
+
],
|
|
505
|
+
);
|
|
506
|
+
once(
|
|
507
|
+
"edges",
|
|
508
|
+
(row) =>
|
|
509
|
+
typeof row.edge === "string" ?
|
|
510
|
+
JSON.stringify([row.edge, row.of ?? ANY_NAME])
|
|
511
|
+
: undefined,
|
|
512
|
+
(row, first) => [
|
|
513
|
+
["edge"],
|
|
514
|
+
`the edge \`${row.edge}\` of \`${row.of ?? ANY_NAME}\` stands twice, first at ` +
|
|
515
|
+
`\`data.naming.edges[${first}]\``,
|
|
516
|
+
],
|
|
517
|
+
);
|
|
518
|
+
once(
|
|
519
|
+
"draws",
|
|
520
|
+
(row) => (typeof row.kind === "string" ? row.kind : undefined),
|
|
521
|
+
(row, first) => [
|
|
522
|
+
["kind"],
|
|
523
|
+
`the kind \`${row.kind}\` has its draws stated twice, first at ` +
|
|
524
|
+
`\`data.naming.draws[${first}]\``,
|
|
525
|
+
],
|
|
526
|
+
);
|
|
527
|
+
listOf(naming.shapes).forEach((row, i) => {
|
|
528
|
+
if (!isMap(row)) return;
|
|
529
|
+
const at = (...rest) => ["data", "naming", "shapes", i, ...rest];
|
|
530
|
+
if (row.syllables == null && row.perVowel == null)
|
|
531
|
+
findings.push(
|
|
532
|
+
finding(
|
|
533
|
+
at(),
|
|
534
|
+
"error",
|
|
535
|
+
`\`data.naming.shapes[${i}]\` states neither \`syllables\` nor \`perVowel\``,
|
|
536
|
+
),
|
|
537
|
+
);
|
|
538
|
+
for (const key of ["syllables", "perVowel"]) {
|
|
539
|
+
const range = row[key];
|
|
540
|
+
if (!isMap(range) || typeof range.min !== "number" || typeof range.max !== "number")
|
|
541
|
+
continue;
|
|
542
|
+
if (range.min > range.max)
|
|
543
|
+
findings.push(
|
|
544
|
+
finding(
|
|
545
|
+
at(key),
|
|
546
|
+
"error",
|
|
547
|
+
`\`data.naming.shapes[${i}].${key}\` runs from ${range.min} to ${range.max}; ` +
|
|
548
|
+
"its `min` is above its `max`",
|
|
549
|
+
),
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
listOf(naming.heldFrom).forEach((row, i) => {
|
|
554
|
+
if (!isMap(row) || !HELD_TAKES.includes(row.takes)) return;
|
|
555
|
+
const at = (...rest) => ["data", "naming", "heldFrom", i, ...rest];
|
|
556
|
+
const label = `\`data.naming.heldFrom[${i}]\``;
|
|
557
|
+
if (row.takes === "element") {
|
|
558
|
+
if (!listOf(row.elements).length)
|
|
559
|
+
findings.push(
|
|
560
|
+
finding(
|
|
561
|
+
at(),
|
|
562
|
+
"error",
|
|
563
|
+
`${label} takes an \`element\` and names no \`elements\``,
|
|
564
|
+
),
|
|
565
|
+
);
|
|
566
|
+
} else if (row.elements != null)
|
|
567
|
+
findings.push(
|
|
568
|
+
finding(
|
|
569
|
+
at("elements"),
|
|
570
|
+
"error",
|
|
571
|
+
`\`elements\` lists what a place takes first, and ${label} takes a ` +
|
|
572
|
+
`\`${row.takes}\``,
|
|
573
|
+
true,
|
|
574
|
+
),
|
|
575
|
+
);
|
|
576
|
+
if (!HELD_NAMES.includes(row.takes))
|
|
577
|
+
for (const key of ["clip", "genitive"])
|
|
578
|
+
if (row[key] != null)
|
|
579
|
+
findings.push(
|
|
580
|
+
finding(
|
|
581
|
+
at(key),
|
|
582
|
+
"error",
|
|
583
|
+
`\`${key}\` shapes a name, and ${label} takes a \`${row.takes}\``,
|
|
584
|
+
true,
|
|
585
|
+
),
|
|
586
|
+
);
|
|
587
|
+
});
|
|
588
|
+
|
|
354
589
|
// ── Scope ────────────────────────────────────────────────────────────────
|
|
355
590
|
const scope = isMap(data.scope) ? data.scope : {};
|
|
356
591
|
for (const kind of SCOPE_KINDS) {
|
package/engine/language-rows.mjs
CHANGED
|
@@ -33,6 +33,13 @@
|
|
|
33
33
|
* `elements` and `roots` — one row per entry, the list keys always lists;
|
|
34
34
|
* - `scope` — one row per value, with the `kind` of `data.scope` it is under;
|
|
35
35
|
* - `clusters` — one row per language stating `data.sounds.clusters`;
|
|
36
|
+
* - `cluster_groups` and `mutations` — one row per entry of
|
|
37
|
+
* `data.sounds.clusters.groups` and `data.sounds.mutations`;
|
|
38
|
+
* - `older` — one row per `data.older` entry;
|
|
39
|
+
* - `negation` — one row per language stating `data.negation`;
|
|
40
|
+
* - `name_shapes`, `name_edges`, `name_draws` and `held_from` — one row per
|
|
41
|
+
* entry of the lists of `data.naming`, and `near_names` one per worked
|
|
42
|
+
* near name, with the language's `min_syllables`;
|
|
36
43
|
* - `cognates` — one row per form of each set, with the set's `gloss`.
|
|
37
44
|
*
|
|
38
45
|
* @module
|
|
@@ -40,7 +47,9 @@
|
|
|
40
47
|
|
|
41
48
|
import { isLanguageRecord, noteAddressOf } from "./word-rows.mjs";
|
|
42
49
|
import {
|
|
50
|
+
ANY_NAME,
|
|
43
51
|
CALLING_CLOSING_KEYS,
|
|
52
|
+
CLUSTER_GROUP_KEYS,
|
|
44
53
|
EDGE_KEYS,
|
|
45
54
|
ELEMENT_KEYS,
|
|
46
55
|
FINAL_KEYS,
|
|
@@ -48,6 +57,8 @@ import {
|
|
|
48
57
|
JOIN_KEYS,
|
|
49
58
|
LETTER_KEYS,
|
|
50
59
|
MARK_KEYS,
|
|
60
|
+
MUTATION_KEYS,
|
|
61
|
+
OLDER_KEYS,
|
|
51
62
|
READ_PAST_KEYS,
|
|
52
63
|
REGISTER_KEYS,
|
|
53
64
|
RETIRED_KEYS,
|
|
@@ -86,6 +97,38 @@ export const LETTER_COLUMNS = Object.freeze([...IDENTITY, ...LETTER_KEYS]);
|
|
|
86
97
|
export const EDGE_COLUMNS = Object.freeze([...IDENTITY, ...EDGE_KEYS]);
|
|
87
98
|
/** Every column of the `clusters` relation, in order. */
|
|
88
99
|
export const CLUSTER_COLUMNS = Object.freeze(["language", "max"]);
|
|
100
|
+
/** Every column of the `cluster_groups` relation, in order. */
|
|
101
|
+
export const CLUSTER_GROUP_COLUMNS = Object.freeze([...IDENTITY, ...CLUSTER_GROUP_KEYS]);
|
|
102
|
+
/** Every column of the `mutations` relation, in order. */
|
|
103
|
+
export const MUTATION_COLUMNS = Object.freeze([...IDENTITY, ...MUTATION_KEYS]);
|
|
104
|
+
/** Every column of the `older` relation, in order. */
|
|
105
|
+
export const OLDER_COLUMNS = Object.freeze([...IDENTITY, ...OLDER_KEYS]);
|
|
106
|
+
/** Every column of the `name_shapes` relation, in order. */
|
|
107
|
+
export const NAME_SHAPE_COLUMNS = Object.freeze([
|
|
108
|
+
...IDENTITY,
|
|
109
|
+
"of",
|
|
110
|
+
"syllables_min",
|
|
111
|
+
"syllables_max",
|
|
112
|
+
"per_vowel_min",
|
|
113
|
+
"per_vowel_max",
|
|
114
|
+
]);
|
|
115
|
+
/** Every column of the `name_edges` relation, in order. */
|
|
116
|
+
export const NAME_EDGE_COLUMNS = Object.freeze([...IDENTITY, "edge", "sounds", "vowel", "of"]);
|
|
117
|
+
/** Every column of the `name_draws` relation, in order. */
|
|
118
|
+
export const NAME_DRAW_COLUMNS = Object.freeze([...IDENTITY, "kind", "elements"]);
|
|
119
|
+
/** Every column of the `held_from` relation, in order. */
|
|
120
|
+
export const HELD_FROM_COLUMNS = Object.freeze([
|
|
121
|
+
...IDENTITY,
|
|
122
|
+
"source",
|
|
123
|
+
"takes",
|
|
124
|
+
"clip",
|
|
125
|
+
"genitive",
|
|
126
|
+
"elements",
|
|
127
|
+
]);
|
|
128
|
+
/** Every column of the `near_names` relation, in order. */
|
|
129
|
+
export const NEAR_NAME_COLUMNS = Object.freeze([...IDENTITY, "near", "given", "min_syllables"]);
|
|
130
|
+
/** Every column of the `negation` relation, in order: one row per language. */
|
|
131
|
+
export const NEGATION_COLUMNS = Object.freeze(["language", "particle", "mutation"]);
|
|
89
132
|
/** Every column of the `stress` relation, in order. */
|
|
90
133
|
export const STRESS_COLUMNS = Object.freeze(["language", "stress"]);
|
|
91
134
|
/** Every column of the `finals` relation, in order. */
|
|
@@ -106,6 +149,7 @@ export const SYLLABLE_COLUMNS = Object.freeze([
|
|
|
106
149
|
"harmony_front",
|
|
107
150
|
"harmony_neutral",
|
|
108
151
|
"harmony_neutralAs",
|
|
152
|
+
"harmony_partners",
|
|
109
153
|
]);
|
|
110
154
|
/** Every column of the `joining` relation, in order. */
|
|
111
155
|
export const JOINING_COLUMNS = Object.freeze([...IDENTITY, ...JOINING_KEYS]);
|
|
@@ -132,6 +176,7 @@ export const SOUND_CHANGE_COLUMNS = Object.freeze([
|
|
|
132
176
|
"name",
|
|
133
177
|
"from_language",
|
|
134
178
|
"to_language",
|
|
179
|
+
"takes",
|
|
135
180
|
"step_position",
|
|
136
181
|
...SOUND_CHANGE_STEP_KEYS,
|
|
137
182
|
]);
|
|
@@ -174,6 +219,21 @@ export const LANGUAGE_COLUMN_TYPES = Object.freeze({
|
|
|
174
219
|
letters: { position: "BIGINT", letters: LIST, tongue: LIST },
|
|
175
220
|
edges: { position: "BIGINT", sounds: LIST },
|
|
176
221
|
clusters: { max: "BIGINT" },
|
|
222
|
+
cluster_groups: { position: "BIGINT", clusters: LIST },
|
|
223
|
+
mutations: { position: "BIGINT" },
|
|
224
|
+
older: { position: "BIGINT" },
|
|
225
|
+
negation: {},
|
|
226
|
+
name_shapes: {
|
|
227
|
+
position: "BIGINT",
|
|
228
|
+
syllables_min: "BIGINT",
|
|
229
|
+
syllables_max: "BIGINT",
|
|
230
|
+
per_vowel_min: "DOUBLE",
|
|
231
|
+
per_vowel_max: "DOUBLE",
|
|
232
|
+
},
|
|
233
|
+
name_edges: { position: "BIGINT", sounds: LIST, vowel: "BOOLEAN" },
|
|
234
|
+
name_draws: { position: "BIGINT", elements: LIST },
|
|
235
|
+
held_from: { position: "BIGINT", genitive: LIST, elements: LIST },
|
|
236
|
+
near_names: { position: "BIGINT", min_syllables: "BIGINT" },
|
|
177
237
|
stress: {},
|
|
178
238
|
finals: { position: "BIGINT" },
|
|
179
239
|
syllables: {
|
|
@@ -188,6 +248,7 @@ export const LANGUAGE_COLUMN_TYPES = Object.freeze({
|
|
|
188
248
|
harmony_back: LIST,
|
|
189
249
|
harmony_front: LIST,
|
|
190
250
|
harmony_neutral: LIST,
|
|
251
|
+
harmony_partners: "STRUCT(vowel VARCHAR, partner VARCHAR)[]",
|
|
191
252
|
},
|
|
192
253
|
joining: { position: "BIGINT", first: LIST, second: LIST },
|
|
193
254
|
joins: { position: "BIGINT", letters: LIST },
|
|
@@ -470,6 +531,159 @@ export function clusterRows(records) {
|
|
|
470
531
|
return rows;
|
|
471
532
|
}
|
|
472
533
|
|
|
534
|
+
/**
|
|
535
|
+
* The `cluster_groups` rows: one per `data.sounds.clusters.groups` entry,
|
|
536
|
+
* `clusters` a list.
|
|
537
|
+
*
|
|
538
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
539
|
+
* @returns {object[]}
|
|
540
|
+
*/
|
|
541
|
+
export function clusterGroupRows(records) {
|
|
542
|
+
return listRows(records, (data) => soundsOf(data).clusters?.groups, CLUSTER_GROUP_KEYS, {
|
|
543
|
+
lists: ["clusters"],
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* The `mutations` rows: one per `data.sounds.mutations` entry.
|
|
549
|
+
*
|
|
550
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
551
|
+
* @returns {object[]}
|
|
552
|
+
*/
|
|
553
|
+
export function mutationRows(records) {
|
|
554
|
+
return listRows(records, (data) => soundsOf(data).mutations, MUTATION_KEYS);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* The `older` rows: one per `data.older` entry.
|
|
559
|
+
*
|
|
560
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
561
|
+
* @returns {object[]}
|
|
562
|
+
*/
|
|
563
|
+
export function olderRows(records) {
|
|
564
|
+
return listRows(records, (data) => data.older, OLDER_KEYS);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* The `negation` rows: one per language stating `data.negation`.
|
|
569
|
+
*
|
|
570
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
571
|
+
* @returns {object[]}
|
|
572
|
+
*/
|
|
573
|
+
export function negationRows(records) {
|
|
574
|
+
const rows = [];
|
|
575
|
+
for (const [language, record] of languagesOf(records)) {
|
|
576
|
+
const negation = record.data?.negation;
|
|
577
|
+
if (isMap(negation))
|
|
578
|
+
rows.push({
|
|
579
|
+
language,
|
|
580
|
+
particle: negation.particle ?? null,
|
|
581
|
+
mutation: negation.mutation ?? null,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
return rows;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/** A language's `data.naming`, or an empty map. */
|
|
588
|
+
const namingOf = (data) => (isMap(data.naming) ? data.naming : {});
|
|
589
|
+
|
|
590
|
+
/** A range's bound, or `null`. */
|
|
591
|
+
const bound = (range, key) => (isMap(range) && typeof range[key] === "number" ? range[key] : null);
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* The `name_shapes` rows: one per `data.naming.shapes` entry, each range as
|
|
595
|
+
* its two bounds.
|
|
596
|
+
*
|
|
597
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
598
|
+
* @returns {object[]}
|
|
599
|
+
*/
|
|
600
|
+
export function nameShapeRows(records) {
|
|
601
|
+
const rows = [];
|
|
602
|
+
for (const [language, record] of languagesOf(records)) {
|
|
603
|
+
const shapes = namingOf(isMap(record.data) ? record.data : {}).shapes;
|
|
604
|
+
if (!Array.isArray(shapes)) continue;
|
|
605
|
+
shapes.forEach((shape, position) => {
|
|
606
|
+
if (!isMap(shape)) return;
|
|
607
|
+
rows.push({
|
|
608
|
+
language,
|
|
609
|
+
position,
|
|
610
|
+
of: shape.of ?? null,
|
|
611
|
+
syllables_min: bound(shape.syllables, "min"),
|
|
612
|
+
syllables_max: bound(shape.syllables, "max"),
|
|
613
|
+
per_vowel_min: bound(shape.perVowel, "min"),
|
|
614
|
+
per_vowel_max: bound(shape.perVowel, "max"),
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
return rows;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* The `name_edges` rows: one per `data.naming.edges` entry, `sounds` a list,
|
|
623
|
+
* `vowel` `false` and `of` `name` where unstated.
|
|
624
|
+
*
|
|
625
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
626
|
+
* @returns {object[]}
|
|
627
|
+
*/
|
|
628
|
+
export function nameEdgeRows(records) {
|
|
629
|
+
return listRows(records, (data) => namingOf(data).edges, ["edge", "sounds", "vowel", "of"], {
|
|
630
|
+
lists: ["sounds"],
|
|
631
|
+
}).map((row) => ({ ...row, vowel: row.vowel === true, of: row.of ?? ANY_NAME }));
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* The `name_draws` rows: one per `data.naming.draws` entry.
|
|
636
|
+
*
|
|
637
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
638
|
+
* @returns {object[]}
|
|
639
|
+
*/
|
|
640
|
+
export function nameDrawRows(records) {
|
|
641
|
+
return listRows(records, (data) => namingOf(data).draws, ["kind", "elements"], {
|
|
642
|
+
lists: ["elements"],
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* The `held_from` rows: one per `data.naming.heldFrom` entry.
|
|
648
|
+
*
|
|
649
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
650
|
+
* @returns {object[]}
|
|
651
|
+
*/
|
|
652
|
+
export function heldFromRows(records) {
|
|
653
|
+
return listRows(
|
|
654
|
+
records,
|
|
655
|
+
(data) => namingOf(data).heldFrom,
|
|
656
|
+
["source", "takes", "clip", "genitive", "elements"],
|
|
657
|
+
{ lists: ["genitive", "elements"] },
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* The `near_names` rows: one per worked example of `data.naming.nearName`,
|
|
663
|
+
* each with the language's `min_syllables`.
|
|
664
|
+
*
|
|
665
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
666
|
+
* @returns {object[]}
|
|
667
|
+
*/
|
|
668
|
+
export function nearNameRows(records) {
|
|
669
|
+
const rows = [];
|
|
670
|
+
for (const [language, record] of languagesOf(records)) {
|
|
671
|
+
const near = namingOf(isMap(record.data) ? record.data : {}).nearName;
|
|
672
|
+
if (!isMap(near) || !Array.isArray(near.examples)) continue;
|
|
673
|
+
near.examples.forEach((example, position) => {
|
|
674
|
+
if (!isMap(example)) return;
|
|
675
|
+
rows.push({
|
|
676
|
+
language,
|
|
677
|
+
position,
|
|
678
|
+
near: example.near ?? null,
|
|
679
|
+
given: example.given ?? null,
|
|
680
|
+
min_syllables: near.minSyllables ?? null,
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
return rows;
|
|
685
|
+
}
|
|
686
|
+
|
|
473
687
|
/**
|
|
474
688
|
* The `read_past` rows: one per `data.sounds.readPast` entry.
|
|
475
689
|
*
|
|
@@ -482,8 +696,8 @@ export function readPastRows(records) {
|
|
|
482
696
|
|
|
483
697
|
/**
|
|
484
698
|
* The `sound_changes` rows: one per step of each `data.soundChanges` set, with
|
|
485
|
-
* the set's `position`, `name`, `from_language` and `
|
|
486
|
-
* step's `step_position` within it.
|
|
699
|
+
* the set's `position`, `name`, `from_language`, `to_language` and `takes`,
|
|
700
|
+
* and the step's `step_position` within it.
|
|
487
701
|
*
|
|
488
702
|
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
489
703
|
* @returns {object[]}
|
|
@@ -503,6 +717,7 @@ export function soundChangeRows(records) {
|
|
|
503
717
|
name: set.name ?? null,
|
|
504
718
|
from_language: set.from ?? null,
|
|
505
719
|
to_language: set.to ?? null,
|
|
720
|
+
takes: set.takes ?? null,
|
|
506
721
|
step_position: stepPosition,
|
|
507
722
|
};
|
|
508
723
|
for (const key of SOUND_CHANGE_STEP_KEYS) row[key] = step[key] ?? null;
|
|
@@ -592,7 +807,8 @@ export function finalRows(records) {
|
|
|
592
807
|
/**
|
|
593
808
|
* The `syllables` rows: one per language stating `data.sounds.syllables`, a
|
|
594
809
|
* list rule always a list (`any` a list of one), the harmony's sets each a
|
|
595
|
-
* column of its own
|
|
810
|
+
* column of its own, its partners a list of `{ vowel, partner }` in the order
|
|
811
|
+
* stated.
|
|
596
812
|
*
|
|
597
813
|
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
598
814
|
* @returns {object[]}
|
|
@@ -618,6 +834,13 @@ export function syllableRows(records) {
|
|
|
618
834
|
harmony_front: asList(harmony.front),
|
|
619
835
|
harmony_neutral: asList(harmony.neutral),
|
|
620
836
|
harmony_neutralAs: harmony.neutralAs ?? null,
|
|
837
|
+
harmony_partners:
|
|
838
|
+
isMap(harmony.partners) && Object.keys(harmony.partners).length ?
|
|
839
|
+
Object.entries(harmony.partners).map(([vowel, partner]) => ({
|
|
840
|
+
vowel,
|
|
841
|
+
partner: partner == null ? null : String(partner),
|
|
842
|
+
}))
|
|
843
|
+
: null,
|
|
621
844
|
});
|
|
622
845
|
}
|
|
623
846
|
return rows;
|
|
@@ -744,6 +967,8 @@ export const LANGUAGE_RELATIONS = Object.freeze([
|
|
|
744
967
|
["letters", letterRows, LETTER_COLUMNS],
|
|
745
968
|
["edges", edgeRows, EDGE_COLUMNS],
|
|
746
969
|
["clusters", clusterRows, CLUSTER_COLUMNS],
|
|
970
|
+
["cluster_groups", clusterGroupRows, CLUSTER_GROUP_COLUMNS],
|
|
971
|
+
["mutations", mutationRows, MUTATION_COLUMNS],
|
|
747
972
|
["stress", stressRows, STRESS_COLUMNS],
|
|
748
973
|
["finals", finalRows, FINAL_COLUMNS],
|
|
749
974
|
["syllables", syllableRows, SYLLABLE_COLUMNS],
|
|
@@ -758,4 +983,11 @@ export const LANGUAGE_RELATIONS = Object.freeze([
|
|
|
758
983
|
["roots", rootRows, ROOT_COLUMNS],
|
|
759
984
|
["cognates", cognateRows, COGNATE_COLUMNS],
|
|
760
985
|
["calling_names", callingNameRows, CALLING_NAME_COLUMNS],
|
|
986
|
+
["older", olderRows, OLDER_COLUMNS],
|
|
987
|
+
["negation", negationRows, NEGATION_COLUMNS],
|
|
988
|
+
["name_shapes", nameShapeRows, NAME_SHAPE_COLUMNS],
|
|
989
|
+
["name_edges", nameEdgeRows, NAME_EDGE_COLUMNS],
|
|
990
|
+
["name_draws", nameDrawRows, NAME_DRAW_COLUMNS],
|
|
991
|
+
["held_from", heldFromRows, HELD_FROM_COLUMNS],
|
|
992
|
+
["near_names", nearNameRows, NEAR_NAME_COLUMNS],
|
|
761
993
|
]);
|