@heroiclands/package-build 24.0.1 → 24.0.3
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 +32 -0
- package/assets/preview/gitea.ini +1 -1
- package/assets/styles/content-base.css +33 -0
- package/bin/content-commands.mjs +17 -17
- package/content-config.mjs +1 -52
- package/docs/api.md +20 -13
- package/docs/authoring/links-and-markup.md +165 -17
- package/docs/commands.md +20 -20
- package/docs/configuration.md +29 -69
- package/docs/content-format.md +4 -2
- package/docs/guides/site.md +3 -1
- package/docs/guides/writing-in-invented-scripts.md +68 -6
- package/docs/reference/format-details.md +160 -118
- package/docs/reference/note-types.md +49 -29
- package/docs/source-preview.md +2 -2
- package/engine/content-blocks.mjs +6 -2
- package/engine/content-charset.mjs +1 -0
- package/engine/content-format.yaml +2 -0
- package/engine/content-index.mjs +5 -1
- package/engine/content-render.mjs +2 -1
- package/engine/content-scripts.mjs +268 -21
- package/engine/content-scroll.mjs +389 -0
- package/engine/dictionary-layout.mjs +16 -3
- package/engine/font-outlines.mjs +394 -0
- package/engine/foreign-catalog.mjs +11 -122
- package/engine/language-data.mjs +154 -63
- package/engine/language-rows.mjs +35 -9
- package/engine/language-terms.mjs +39 -9
- package/engine/map-lexicon.mjs +31 -6
- package/engine/note-vocabulary.mjs +58 -6
- package/engine/pdf-build.mjs +9 -178
- package/engine/pdf-fonts.mjs +1 -1
- package/engine/pdf-render.mjs +52 -11
- package/engine/run-rows.mjs +45 -3
- package/engine/script-direction.mjs +67 -0
- package/engine/script-emphasis-terms.mjs +11 -3
- package/engine/script-font-delivery.mjs +42 -9
- package/engine/script-fonts.mjs +7 -0
- package/engine/site-reach.mjs +23 -0
- package/engine/site-render.mjs +18 -3
- package/engine/verse-metre.mjs +1 -0
- package/engine/written-in.mjs +208 -0
- package/package.json +2 -1
- package/site-theme/static/css/style.css +7 -0
- package/types/content-config.d.mts +1 -19
- package/types/engine/content-scripts.d.mts +31 -6
- package/types/engine/content-scroll.d.mts +179 -0
- package/types/engine/dictionary-layout.d.mts +5 -0
- package/types/engine/font-outlines.d.mts +67 -0
- package/types/engine/foreign-catalog.d.mts +0 -38
- package/types/engine/language-data.d.mts +13 -0
- package/types/engine/language-rows.d.mts +5 -4
- package/types/engine/language-terms.d.mts +7 -0
- package/types/engine/map-lexicon.d.mts +4 -3
- package/types/engine/pdf-build.d.mts +5 -27
- package/types/engine/pdf-fonts.d.mts +10 -0
- package/types/engine/run-rows.d.mts +28 -6
- package/types/engine/script-direction.d.mts +10 -0
- package/types/engine/script-emphasis-terms.d.mts +10 -3
- package/types/engine/script-font-delivery.d.mts +5 -3
- package/types/engine/script-fonts.d.mts +9 -0
- package/types/engine/site-reach.d.mts +11 -0
- package/types/engine/written-in.d.mts +76 -0
- package/engine/asset-replacement.mjs +0 -93
- package/types/engine/asset-replacement.d.mts +0 -22
package/engine/language-data.mjs
CHANGED
|
@@ -21,9 +21,11 @@
|
|
|
21
21
|
* - no skill of another subType states these keys;
|
|
22
22
|
* - a tongue tag is lowercase letters and digits, optionally closing on
|
|
23
23
|
* `:<tongue>`, and stands once; a tongue's `language` names a language note;
|
|
24
|
-
* - every tag a register row
|
|
25
|
-
* `
|
|
26
|
-
*
|
|
24
|
+
* - every tag a register row names is declared in `data.tongues`,
|
|
25
|
+
* `translation` apart: `exonym:vylari` is declared by `exonym:<tongue>`;
|
|
26
|
+
* - every tongue a letter row names is such a tag or the Address of a
|
|
27
|
+
* language note, and a spelling guide's, on a `doc` note, an Address (see
|
|
28
|
+
* {@link checkGuideSounds});
|
|
27
29
|
* - an edge stands once in `data.sounds.edges`;
|
|
28
30
|
* - a sound stands once in each mutation of `data.sounds.mutations`;
|
|
29
31
|
* - a cluster stands in one group of `data.sounds.clusters.groups`, and a
|
|
@@ -432,6 +434,153 @@ function checkParticiples(participles, affixes, finding) {
|
|
|
432
434
|
return out;
|
|
433
435
|
}
|
|
434
436
|
|
|
437
|
+
/**
|
|
438
|
+
* The Address checks a note's data reads through, each finding located by
|
|
439
|
+
* `finding`.
|
|
440
|
+
*
|
|
441
|
+
* @param {object|undefined} index - The index; without `addressHit`, nothing
|
|
442
|
+
* resolves and nothing is reported.
|
|
443
|
+
* @param {(path: Array<string|number>, severity: string, message: string, key?: boolean) => object} finding -
|
|
444
|
+
* A finding at a path of the note's frontmatter.
|
|
445
|
+
* @returns {{resolve: Function, language: Function}} The checks.
|
|
446
|
+
*/
|
|
447
|
+
function resolvers(index, finding) {
|
|
448
|
+
/**
|
|
449
|
+
* The note an Address names, or a finding where it names none.
|
|
450
|
+
*
|
|
451
|
+
* @param {unknown} value - The Address as the note holds it.
|
|
452
|
+
* @param {Array<string|number>} path - Where it sits.
|
|
453
|
+
* @param {string|undefined} type - The type a bare shortcode defaults to.
|
|
454
|
+
* @param {boolean} [key] - Whether the Address is a map key.
|
|
455
|
+
* @returns {{target?: object, key?: string, finding?: object}}
|
|
456
|
+
*/
|
|
457
|
+
function resolve(value, path, type, key = false) {
|
|
458
|
+
if (!index?.addressHit || value === undefined || value === null) return {};
|
|
459
|
+
const canonical = addressKeyOf(value, index, type);
|
|
460
|
+
if (!canonical)
|
|
461
|
+
return {
|
|
462
|
+
finding: finding(
|
|
463
|
+
path,
|
|
464
|
+
"error",
|
|
465
|
+
`\`${label(path)}\` is not an Address: ${JSON.stringify(
|
|
466
|
+
isAddressTuple(value) ? renderAddress(value) : value,
|
|
467
|
+
)}`,
|
|
468
|
+
key,
|
|
469
|
+
),
|
|
470
|
+
};
|
|
471
|
+
const target = index.addressHit(canonical);
|
|
472
|
+
if (!target)
|
|
473
|
+
return {
|
|
474
|
+
finding: finding(
|
|
475
|
+
path,
|
|
476
|
+
"error",
|
|
477
|
+
`\`${label(path)}\` names ${canonical}, which does not resolve in this ` +
|
|
478
|
+
"package or its declared dependencies",
|
|
479
|
+
key,
|
|
480
|
+
),
|
|
481
|
+
};
|
|
482
|
+
return { target, key: canonical };
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** As {@link resolve}, and the target must be a language note. */
|
|
486
|
+
function language(value, path, key = false) {
|
|
487
|
+
const result = resolve(value, path, "skill", key);
|
|
488
|
+
if (result.finding) return [result.finding];
|
|
489
|
+
if (!result.target) return [];
|
|
490
|
+
const target = result.target.fm ?? result.target;
|
|
491
|
+
if (target.type === "skill" && target.subType === LANGUAGE_SUBTYPE) return [];
|
|
492
|
+
return [
|
|
493
|
+
finding(
|
|
494
|
+
path,
|
|
495
|
+
"error",
|
|
496
|
+
`\`${label(path)}\` names ${result.key}, which is not a skill whose subType is ` +
|
|
497
|
+
"language",
|
|
498
|
+
key,
|
|
499
|
+
),
|
|
500
|
+
];
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return { resolve, language };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* The findings on each `tongue` of a note's `data.sounds.letters` rows. A value
|
|
508
|
+
* is a tag the note's `data.tongues` declares, or else the Address of a
|
|
509
|
+
* language note; a `doc` note declares no tags, so its rows name language
|
|
510
|
+
* notes only. Without an index no Address resolves: a language note's value
|
|
511
|
+
* that is not a declared tag is then a finding, and a guide's is not checked.
|
|
512
|
+
*
|
|
513
|
+
* @param {unknown} letters - The rows.
|
|
514
|
+
* @param {object} context
|
|
515
|
+
* @param {Set<string>|undefined} context.declared - The tags `data.tongues`
|
|
516
|
+
* declares; `undefined` on a note that declares none.
|
|
517
|
+
* @param {object|undefined} context.index - The index Addresses resolve in.
|
|
518
|
+
* @param {(path: Array<string|number>, severity: string, message: string, key?: boolean) => object} context.finding -
|
|
519
|
+
* A finding at a path of the note's frontmatter.
|
|
520
|
+
* @returns {object[]} The findings, each at its value.
|
|
521
|
+
*/
|
|
522
|
+
function letterTongueFindings(letters, { declared, index, finding }) {
|
|
523
|
+
const { resolve, language } = resolvers(index, finding);
|
|
524
|
+
const out = [];
|
|
525
|
+
listOf(letters).forEach((row, i) => {
|
|
526
|
+
if (!isMap(row) || row.tongue === undefined || row.tongue === null) return;
|
|
527
|
+
const at = ["data", "sounds", "letters", i, "tongue"];
|
|
528
|
+
const values = Array.isArray(row.tongue) ? row.tongue : [row.tongue];
|
|
529
|
+
values.forEach((value, j) => {
|
|
530
|
+
if (typeof value !== "string") return;
|
|
531
|
+
if (declared && tongueDeclared(value, declared)) return;
|
|
532
|
+
const path = Array.isArray(row.tongue) ? [...at, j] : at;
|
|
533
|
+
const resolved = resolve(value, path, "skill");
|
|
534
|
+
if (resolved.target) {
|
|
535
|
+
out.push(...language(value, path));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (declared)
|
|
539
|
+
out.push(
|
|
540
|
+
finding(
|
|
541
|
+
path,
|
|
542
|
+
"error",
|
|
543
|
+
`the tongue \`${value}\` is not declared in \`data.tongues\`` +
|
|
544
|
+
(index?.addressHit ? " and names no language note" : ""),
|
|
545
|
+
),
|
|
546
|
+
);
|
|
547
|
+
else if (resolved.finding)
|
|
548
|
+
out.push(
|
|
549
|
+
finding(
|
|
550
|
+
path,
|
|
551
|
+
"error",
|
|
552
|
+
`\`${label(path)}\` names \`${value}\`, which is no language note's ` +
|
|
553
|
+
"Address; a spelling guide names each tongue by its language note",
|
|
554
|
+
),
|
|
555
|
+
);
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
return out;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Check a `doc` note's `data.sounds`, a spelling guide's letters and marks:
|
|
563
|
+
* each letter row's `tongue` names language notes. The data-key check owns
|
|
564
|
+
* the keys, so a sound rule a guide does not state is refused there.
|
|
565
|
+
*
|
|
566
|
+
* @param {object} note - A `doc` note: `file`, `raw` and parsed `fm`.
|
|
567
|
+
* @param {object} [context]
|
|
568
|
+
* @param {object} [context.index] - The index, for whether an Address resolves.
|
|
569
|
+
* @returns {object[]} The findings.
|
|
570
|
+
*/
|
|
571
|
+
export function checkGuideSounds(note, { index } = {}) {
|
|
572
|
+
const sounds = note?.fm?.data?.sounds;
|
|
573
|
+
if (!isMap(sounds)) return [];
|
|
574
|
+
const raw = note.raw ?? "";
|
|
575
|
+
const finding = (path, severity, message, key = false) => ({
|
|
576
|
+
file: note.file,
|
|
577
|
+
...positionOfFrontmatterPath(raw, path, { key }),
|
|
578
|
+
severity,
|
|
579
|
+
message,
|
|
580
|
+
});
|
|
581
|
+
return letterTongueFindings(sounds.letters, { declared: undefined, index, finding });
|
|
582
|
+
}
|
|
583
|
+
|
|
435
584
|
/**
|
|
436
585
|
* Check a `skill` note's language data against what it means.
|
|
437
586
|
*
|
|
@@ -542,60 +691,7 @@ export function checkLanguageData(note, { index, types } = {}) {
|
|
|
542
691
|
...checkAffixClasses(data.affixes, finding),
|
|
543
692
|
);
|
|
544
693
|
|
|
545
|
-
|
|
546
|
-
* The note an Address names, or a finding where it names none.
|
|
547
|
-
*
|
|
548
|
-
* @param {unknown} value - The Address as the note holds it.
|
|
549
|
-
* @param {Array<string|number>} path - Where it sits.
|
|
550
|
-
* @param {string|undefined} type - The type a bare shortcode defaults to.
|
|
551
|
-
* @param {boolean} [key] - Whether the Address is a map key.
|
|
552
|
-
* @returns {{target?: object, key?: string, finding?: object}}
|
|
553
|
-
*/
|
|
554
|
-
function resolve(value, path, type, key = false) {
|
|
555
|
-
if (!index?.addressHit || value === undefined || value === null) return {};
|
|
556
|
-
const canonical = addressKeyOf(value, index, type);
|
|
557
|
-
if (!canonical)
|
|
558
|
-
return {
|
|
559
|
-
finding: finding(
|
|
560
|
-
path,
|
|
561
|
-
"error",
|
|
562
|
-
`\`${label(path)}\` is not an Address: ${JSON.stringify(
|
|
563
|
-
isAddressTuple(value) ? renderAddress(value) : value,
|
|
564
|
-
)}`,
|
|
565
|
-
key,
|
|
566
|
-
),
|
|
567
|
-
};
|
|
568
|
-
const target = index.addressHit(canonical);
|
|
569
|
-
if (!target)
|
|
570
|
-
return {
|
|
571
|
-
finding: finding(
|
|
572
|
-
path,
|
|
573
|
-
"error",
|
|
574
|
-
`\`${label(path)}\` names ${canonical}, which does not resolve in this ` +
|
|
575
|
-
"package or its declared dependencies",
|
|
576
|
-
key,
|
|
577
|
-
),
|
|
578
|
-
};
|
|
579
|
-
return { target, key: canonical };
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/** As {@link resolve}, and the target must be a language note. */
|
|
583
|
-
function language(value, path, key = false) {
|
|
584
|
-
const result = resolve(value, path, "skill", key);
|
|
585
|
-
if (result.finding) return [result.finding];
|
|
586
|
-
if (!result.target) return [];
|
|
587
|
-
const target = result.target.fm ?? result.target;
|
|
588
|
-
if (target.type === "skill" && target.subType === LANGUAGE_SUBTYPE) return [];
|
|
589
|
-
return [
|
|
590
|
-
finding(
|
|
591
|
-
path,
|
|
592
|
-
"error",
|
|
593
|
-
`\`${label(path)}\` names ${result.key}, which is not a skill whose subType is ` +
|
|
594
|
-
"language",
|
|
595
|
-
key,
|
|
596
|
-
),
|
|
597
|
-
];
|
|
598
|
-
}
|
|
694
|
+
const { resolve, language } = resolvers(index, finding);
|
|
599
695
|
|
|
600
696
|
// ── Tongues ──────────────────────────────────────────────────────────────
|
|
601
697
|
const declared = new Set();
|
|
@@ -668,12 +764,7 @@ export function checkLanguageData(note, { index, types } = {}) {
|
|
|
668
764
|
|
|
669
765
|
// ── Sounds ───────────────────────────────────────────────────────────────
|
|
670
766
|
const sounds = isMap(data.sounds) ? data.sounds : {};
|
|
671
|
-
|
|
672
|
-
if (!isMap(row) || row.tongue === undefined || row.tongue === null) return;
|
|
673
|
-
const path = ["data", "sounds", "letters", i, "tongue"];
|
|
674
|
-
if (Array.isArray(row.tongue)) undeclared(row.tongue, (j) => [...path, j]);
|
|
675
|
-
else undeclared([row.tongue], () => path);
|
|
676
|
-
});
|
|
767
|
+
findings.push(...letterTongueFindings(sounds.letters, { declared, index, finding }));
|
|
677
768
|
const edgeAt = new Map();
|
|
678
769
|
listOf(sounds.edges).forEach((row, i) => {
|
|
679
770
|
if (!isMap(row) || typeof row.edge !== "string") return;
|
package/engine/language-rows.mjs
CHANGED
|
@@ -253,7 +253,7 @@ export const LANGUAGE_COLUMN_TYPES = Object.freeze({
|
|
|
253
253
|
joining: { position: "BIGINT", first: LIST, second: LIST },
|
|
254
254
|
joins: { position: "BIGINT", letters: LIST },
|
|
255
255
|
marks: { position: "BIGINT", letters: LIST },
|
|
256
|
-
skeleton: { position: "BIGINT", second: LIST },
|
|
256
|
+
skeleton: { position: "BIGINT", second: LIST, third: LIST },
|
|
257
257
|
frames: {
|
|
258
258
|
position: "BIGINT",
|
|
259
259
|
class: LIST,
|
|
@@ -309,11 +309,14 @@ function languagesOf(records) {
|
|
|
309
309
|
* @param {string} [options.prefix] - The prefix of a key sharing an identity
|
|
310
310
|
* column's name.
|
|
311
311
|
* @param {readonly string[]} [options.lists] - The keys always set as lists.
|
|
312
|
+
* @param {(records: Iterable<Record<string, any>>) => Array<[string, Record<string, any>]>} [options.sources] -
|
|
313
|
+
* The records stating the list, with their Addresses; the language records
|
|
314
|
+
* where unstated.
|
|
312
315
|
* @returns {object[]} One row per entry, in record then entry order.
|
|
313
316
|
*/
|
|
314
|
-
function listRows(records, read, keys, { prefix = "", lists = [] } = {}) {
|
|
317
|
+
function listRows(records, read, keys, { prefix = "", lists = [], sources = languagesOf } = {}) {
|
|
315
318
|
const rows = [];
|
|
316
|
-
for (const [language, record] of
|
|
319
|
+
for (const [language, record] of sources(records)) {
|
|
317
320
|
const entries = read(isMap(record.data) ? record.data : {});
|
|
318
321
|
if (!Array.isArray(entries)) continue;
|
|
319
322
|
entries.forEach((entry, position) => {
|
|
@@ -328,6 +331,24 @@ function listRows(records, read, keys, { prefix = "", lists = [] } = {}) {
|
|
|
328
331
|
return rows;
|
|
329
332
|
}
|
|
330
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Each record stating letters and marks, with its Address: every language
|
|
336
|
+
* record, and every `doc` record whose `data.sounds` is a spelling guide's.
|
|
337
|
+
*
|
|
338
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
339
|
+
* @returns {Array<[string, Record<string, any>]>}
|
|
340
|
+
*/
|
|
341
|
+
function soundSourcesOf(records) {
|
|
342
|
+
const list = [...records];
|
|
343
|
+
const out = languagesOf(list);
|
|
344
|
+
for (const record of list) {
|
|
345
|
+
if (record?.type !== "doc" || record.documents || !isMap(record.data?.sounds)) continue;
|
|
346
|
+
const address = noteAddressOf(record);
|
|
347
|
+
if (address) out.push([address, record]);
|
|
348
|
+
}
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
|
|
331
352
|
/** A language's `data.sounds`, or an empty map. */
|
|
332
353
|
const soundsOf = (data) => (isMap(data.sounds) ? data.sounds : {});
|
|
333
354
|
|
|
@@ -494,8 +515,8 @@ export function scopeRows(records) {
|
|
|
494
515
|
}
|
|
495
516
|
|
|
496
517
|
/**
|
|
497
|
-
* The `letters` rows: one per `data.sounds.letters` entry
|
|
498
|
-
* `tongue` lists.
|
|
518
|
+
* The `letters` rows: one per `data.sounds.letters` entry of a language note
|
|
519
|
+
* or a spelling guide, `letters` and `tongue` lists.
|
|
499
520
|
*
|
|
500
521
|
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
501
522
|
* @returns {object[]}
|
|
@@ -503,6 +524,7 @@ export function scopeRows(records) {
|
|
|
503
524
|
export function letterRows(records) {
|
|
504
525
|
return listRows(records, (data) => soundsOf(data).letters, LETTER_KEYS, {
|
|
505
526
|
lists: ["letters", "tongue"],
|
|
527
|
+
sources: soundSourcesOf,
|
|
506
528
|
});
|
|
507
529
|
}
|
|
508
530
|
|
|
@@ -875,24 +897,28 @@ export function joinRows(records) {
|
|
|
875
897
|
}
|
|
876
898
|
|
|
877
899
|
/**
|
|
878
|
-
* The `marks` rows: one per `data.sounds.marks` entry
|
|
900
|
+
* The `marks` rows: one per `data.sounds.marks` entry of a language note or a
|
|
901
|
+
* spelling guide.
|
|
879
902
|
*
|
|
880
903
|
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
881
904
|
* @returns {object[]}
|
|
882
905
|
*/
|
|
883
906
|
export function markRows(records) {
|
|
884
|
-
return listRows(records, (data) => soundsOf(data).marks, MARK_KEYS, {
|
|
907
|
+
return listRows(records, (data) => soundsOf(data).marks, MARK_KEYS, {
|
|
908
|
+
lists: ["letters"],
|
|
909
|
+
sources: soundSourcesOf,
|
|
910
|
+
});
|
|
885
911
|
}
|
|
886
912
|
|
|
887
913
|
/**
|
|
888
|
-
* The `skeleton` rows: one per `data.sounds.skeleton` entry, `second`
|
|
914
|
+
* The `skeleton` rows: one per `data.sounds.skeleton` entry, `second` and `third` lists.
|
|
889
915
|
*
|
|
890
916
|
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
891
917
|
* @returns {object[]}
|
|
892
918
|
*/
|
|
893
919
|
export function skeletonRows(records) {
|
|
894
920
|
return listRows(records, (data) => soundsOf(data).skeleton, SKELETON_KEYS, {
|
|
895
|
-
lists: ["second"],
|
|
921
|
+
lists: ["second", "third"],
|
|
896
922
|
});
|
|
897
923
|
}
|
|
898
924
|
|
|
@@ -130,7 +130,7 @@ export const CLUSTER_USES = Object.freeze(["common", "sparing"]);
|
|
|
130
130
|
export const STRESS_TWO = Object.freeze(["first", "last"]);
|
|
131
131
|
|
|
132
132
|
/** The syllable a word of three or more stresses, counted from its end. */
|
|
133
|
-
export const STRESS_LONGER = Object.freeze(["antepenultimate", "penultimate", "final"]);
|
|
133
|
+
export const STRESS_LONGER = Object.freeze(["first", "antepenultimate", "penultimate", "final"]);
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
136
|
* The diacritics that mark a stress the rule would not choose, with the
|
|
@@ -152,7 +152,7 @@ export const JOIN_MEETINGS = Object.freeze(["C+C", "V+C", "V+V"]);
|
|
|
152
152
|
export const JOIN_RULES = Object.freeze(["merge", "insert", "drop", "double"]);
|
|
153
153
|
|
|
154
154
|
/** What a written mark says of its letter. */
|
|
155
|
-
export const MARK_KINDS = Object.freeze(["stress", "length"]);
|
|
155
|
+
export const MARK_KINDS = Object.freeze(["stress", "length", "apart"]);
|
|
156
156
|
|
|
157
157
|
/** How many of a mark a word may carry: `one` at most, or `any` number. */
|
|
158
158
|
export const MARK_LIMITS = Object.freeze(["one", "any"]);
|
|
@@ -377,13 +377,20 @@ export const LETTER_FIELDS = Object.freeze([
|
|
|
377
377
|
shape: "a string, such as `lips` or `tongue-tip`",
|
|
378
378
|
describe: "Where in the mouth they are made.",
|
|
379
379
|
}),
|
|
380
|
+
Object.freeze({
|
|
381
|
+
name: "ipa",
|
|
382
|
+
kind: "string",
|
|
383
|
+
shape: "a string in the International Phonetic Alphabet, such as `ʃ`",
|
|
384
|
+
describe: "The sounds they write, in the International Phonetic Alphabet.",
|
|
385
|
+
}),
|
|
380
386
|
Object.freeze({
|
|
381
387
|
name: "tongue",
|
|
382
388
|
kind: "string-or-list",
|
|
383
|
-
shape: "a tag, or a list of them",
|
|
389
|
+
shape: "a tag or a language note's Address, or a list of them",
|
|
384
390
|
entries: STRINGS,
|
|
385
391
|
describe:
|
|
386
|
-
"The tongues that write them,
|
|
392
|
+
"The tongues that write them, each a tag `data.tongues` declares or the Address of a " +
|
|
393
|
+
"language note; every one where absent. A `doc` note's rows name language notes only.",
|
|
387
394
|
}),
|
|
388
395
|
]);
|
|
389
396
|
|
|
@@ -529,6 +536,12 @@ export const FINAL_FIELDS = Object.freeze([
|
|
|
529
536
|
shape: "a string",
|
|
530
537
|
describe: "What it sounds like.",
|
|
531
538
|
}),
|
|
539
|
+
Object.freeze({
|
|
540
|
+
name: "ipa",
|
|
541
|
+
kind: "string",
|
|
542
|
+
shape: "a string in the International Phonetic Alphabet, such as `ʃ`",
|
|
543
|
+
describe: "Its sound, in the International Phonetic Alphabet.",
|
|
544
|
+
}),
|
|
532
545
|
]);
|
|
533
546
|
|
|
534
547
|
/**
|
|
@@ -718,7 +731,8 @@ export const STRESS_FIELDS = Object.freeze([
|
|
|
718
731
|
required: true,
|
|
719
732
|
allowed: STRESS_LONGER,
|
|
720
733
|
shape: `one of ${STRESS_LONGER.join(", ")}`,
|
|
721
|
-
describe:
|
|
734
|
+
describe:
|
|
735
|
+
"The stressed syllable of a word of three or more: `first`, or counted from its end.",
|
|
722
736
|
}),
|
|
723
737
|
Object.freeze({
|
|
724
738
|
name: "mark",
|
|
@@ -817,6 +831,12 @@ export const SKELETON_FIELDS = Object.freeze([
|
|
|
817
831
|
required: true,
|
|
818
832
|
describe: "The second consonants of a root the row holds for; `any` for every other.",
|
|
819
833
|
}),
|
|
834
|
+
Object.freeze({
|
|
835
|
+
name: "third",
|
|
836
|
+
...SOUNDS_OR_ANY,
|
|
837
|
+
describe:
|
|
838
|
+
"The third consonants of a root the row holds for; every third consonant where absent.",
|
|
839
|
+
}),
|
|
820
840
|
Object.freeze({
|
|
821
841
|
name: "between",
|
|
822
842
|
kind: "string",
|
|
@@ -914,10 +934,10 @@ export const SOUND_FIELDS = Object.freeze([
|
|
|
914
934
|
Object.freeze({
|
|
915
935
|
name: "letters",
|
|
916
936
|
kind: "list",
|
|
917
|
-
shape: "list of `{ letters, kind, like
|
|
937
|
+
shape: "list of `{ letters, kind, like?, place?, ipa?, tongue? }`",
|
|
918
938
|
entries: Object.freeze({
|
|
919
939
|
kind: "map",
|
|
920
|
-
shape: "a map — `{ letters, kind, like
|
|
940
|
+
shape: "a map — `{ letters, kind, like?, place?, ipa?, tongue? }`",
|
|
921
941
|
fields: LETTER_FIELDS,
|
|
922
942
|
}),
|
|
923
943
|
describe: "The letters the language writes, and the sounds they stand for.",
|
|
@@ -950,10 +970,10 @@ export const SOUND_FIELDS = Object.freeze([
|
|
|
950
970
|
Object.freeze({
|
|
951
971
|
name: "finals",
|
|
952
972
|
kind: "list",
|
|
953
|
-
shape: "list of `{ final, like }`",
|
|
973
|
+
shape: "list of `{ final, like, ipa? }`",
|
|
954
974
|
entries: Object.freeze({
|
|
955
975
|
kind: "map",
|
|
956
|
-
shape: "a map — `{ final, like }`",
|
|
976
|
+
shape: "a map — `{ final, like, ipa? }`",
|
|
957
977
|
fields: FINAL_FIELDS,
|
|
958
978
|
}),
|
|
959
979
|
describe: "The syllable finals, for a language whose syllables are parsed by them.",
|
|
@@ -1047,6 +1067,16 @@ export const SOUND_FIELDS = Object.freeze([
|
|
|
1047
1067
|
}),
|
|
1048
1068
|
]);
|
|
1049
1069
|
|
|
1070
|
+
/**
|
|
1071
|
+
* The keys of a spelling guide's `data.sounds`: a `doc` note stating the
|
|
1072
|
+
* letters and marks every tongue of a setting is written by.
|
|
1073
|
+
*
|
|
1074
|
+
* @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
|
|
1075
|
+
*/
|
|
1076
|
+
export const GUIDE_SOUND_FIELDS = Object.freeze(
|
|
1077
|
+
SOUND_FIELDS.filter((field) => field.name === "letters" || field.name === "marks"),
|
|
1078
|
+
);
|
|
1079
|
+
|
|
1050
1080
|
/**
|
|
1051
1081
|
* The keys of one `data.soundChanges` entry.
|
|
1052
1082
|
*
|
package/engine/map-lexicon.mjs
CHANGED
|
@@ -55,6 +55,7 @@ import { positionOfFrontmatterPath, positionOfYamlPath } from "./diagnostics.mjs
|
|
|
55
55
|
import { isNoteRecord } from "./index-records.mjs";
|
|
56
56
|
import { nearest } from "./near-miss.mjs";
|
|
57
57
|
import { WRITTEN_ENDINGS } from "./language-terms.mjs";
|
|
58
|
+
import { fromWrittenIn } from "./written-in.mjs";
|
|
58
59
|
import { writingsOf } from "./logographic.mjs";
|
|
59
60
|
import { FONTS_PACKAGE } from "./script-fonts.mjs";
|
|
60
61
|
import { GENERIC_FAMILIES, applyMap } from "./text-maps.mjs";
|
|
@@ -386,14 +387,37 @@ export function fontProblem(map, font) {
|
|
|
386
387
|
}
|
|
387
388
|
|
|
388
389
|
/**
|
|
389
|
-
* The mapping a command uses, or what is wrong with what it was given.
|
|
390
|
-
*
|
|
390
|
+
* The mapping a command uses, or what is wrong with what it was given. The
|
|
391
|
+
* language's `data.writtenIn` fills the map and the face it leaves out, as it
|
|
392
|
+
* fills a run's; after that, a map listing one face defaults to it, and one
|
|
393
|
+
* listing more needs `font`.
|
|
391
394
|
*
|
|
392
395
|
* @param {object} ctx - The run context.
|
|
393
|
-
* @param {{map
|
|
396
|
+
* @param {{map?: string, font?: string, lang?: string}} keys - What was asked for.
|
|
394
397
|
* @returns {{mapping: Mapping}|{problem: string, about: "map"|"lang"|"font"}} The mapping or the problem.
|
|
395
398
|
*/
|
|
396
|
-
function commandMapping(ctx,
|
|
399
|
+
function commandMapping(ctx, keys) {
|
|
400
|
+
const { lang } = keys;
|
|
401
|
+
const filled = fromWrittenIn(keys, ctx?.writtenIn, ctx?.maps);
|
|
402
|
+
const map = keys.map ?? filled.map;
|
|
403
|
+
const font = keys.font ?? filled.font;
|
|
404
|
+
if (map === undefined) {
|
|
405
|
+
if (lang !== undefined && !Object.hasOwn(ctx?.languages ?? {}, lang)) {
|
|
406
|
+
const near = nearest(lang, Object.keys(ctx?.languages ?? {}));
|
|
407
|
+
return {
|
|
408
|
+
about: "lang",
|
|
409
|
+
problem:
|
|
410
|
+
`\`${lang}\` is not a declared language (\`languages\` in ${CONFIG})` +
|
|
411
|
+
(near ? `; did you mean \`${near}\`?` : ""),
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
about: "map",
|
|
416
|
+
problem:
|
|
417
|
+
`\`${lang}\` names no script: its note states no \`data.writtenIn\`, so name a ` +
|
|
418
|
+
"map with `--map`",
|
|
419
|
+
};
|
|
420
|
+
}
|
|
397
421
|
const textMap = ctx?.maps?.get(map);
|
|
398
422
|
if (!textMap) {
|
|
399
423
|
const names = [...(ctx?.maps?.keys() ?? [])];
|
|
@@ -543,8 +567,9 @@ export function transliterate(ctx, keys, text) {
|
|
|
543
567
|
* writing, the signs a run writes directly for that sense.
|
|
544
568
|
*
|
|
545
569
|
* @param {object} ctx - The run context.
|
|
546
|
-
* @param {{lang: string, map
|
|
547
|
-
* The language, the map, the face, and whether to list derived forms.
|
|
570
|
+
* @param {{lang: string, map?: string, font?: string, derived?: boolean}} keys -
|
|
571
|
+
* The language, the map, the face, and whether to list derived forms. A map
|
|
572
|
+
* or a face left out is the one the language's `data.writtenIn` names.
|
|
548
573
|
* @returns {{entries: Array<{form: string, native: string, exception?: boolean, source?: "own"|"parts"|"none", ambiguous?: true, class: string|null, gloss: string|null, from?: string}>}|{problem: string, about: string}}
|
|
549
574
|
* The words in `data.words` order, then any derived forms, or the problem.
|
|
550
575
|
*/
|
|
@@ -139,8 +139,8 @@ import {
|
|
|
139
139
|
checkLanguageWords,
|
|
140
140
|
} from "./language-words.mjs";
|
|
141
141
|
import { checkGlossary } from "./glossary-entries.mjs";
|
|
142
|
-
import { LANGUAGE_DATA_FIELDS } from "./language-terms.mjs";
|
|
143
|
-
import { checkLanguageData } from "./language-data.mjs";
|
|
142
|
+
import { GUIDE_SOUND_FIELDS, LANGUAGE_DATA_FIELDS } from "./language-terms.mjs";
|
|
143
|
+
import { checkGuideSounds, checkLanguageData } from "./language-data.mjs";
|
|
144
144
|
import { checkAffiliationReach } from "./culture-reach.mjs";
|
|
145
145
|
import { checkLanguageNames, requiredGivenGenders } from "./language-names.mjs";
|
|
146
146
|
import { SOCIAL_TIES, SOCIAL_TIE_TARGET_TYPES } from "./social-tie-terms.mjs";
|
|
@@ -163,7 +163,12 @@ import { checkMapField } from "./text-maps.mjs";
|
|
|
163
163
|
import { checkConceptList, checkCoreFlag } from "./core-vocabulary.mjs";
|
|
164
164
|
import { METRE_KINDS, checkMetresField } from "./metre-declaration.mjs";
|
|
165
165
|
import { checkLanguageEmphasis, checkScriptField } from "./script-emphasis.mjs";
|
|
166
|
-
import {
|
|
166
|
+
import { WRITTEN_IN_KEY, checkWrittenIn } from "./written-in.mjs";
|
|
167
|
+
import {
|
|
168
|
+
EMPHASIS_CONVENTIONS,
|
|
169
|
+
LANGUAGE_EMPHASES,
|
|
170
|
+
SCRIPT_DIRECTIONS,
|
|
171
|
+
} from "./script-emphasis-terms.mjs";
|
|
167
172
|
|
|
168
173
|
/**
|
|
169
174
|
* One `data:` key a note type may carry.
|
|
@@ -2182,10 +2187,36 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
2182
2187
|
"and `words` marks it with a word, so either is a finding. Only a skill of " +
|
|
2183
2188
|
"`subType: language` states it.",
|
|
2184
2189
|
},
|
|
2190
|
+
{
|
|
2191
|
+
name: WRITTEN_IN_KEY,
|
|
2192
|
+
kind: "map",
|
|
2193
|
+
shape: "a map — `{ map, font }`",
|
|
2194
|
+
check: checkWrittenIn,
|
|
2195
|
+
fields: Object.freeze([
|
|
2196
|
+
{
|
|
2197
|
+
name: "map",
|
|
2198
|
+
kind: "string",
|
|
2199
|
+
shape: "a mapping note's `data.map.name`",
|
|
2200
|
+
required: true,
|
|
2201
|
+
describe: "The map the language's text is written by.",
|
|
2202
|
+
},
|
|
2203
|
+
{
|
|
2204
|
+
name: "font",
|
|
2205
|
+
kind: "string",
|
|
2206
|
+
shape: "an installed face's slug",
|
|
2207
|
+
required: true,
|
|
2208
|
+
describe: "The face the text is set in, one the map is drawn in.",
|
|
2209
|
+
},
|
|
2210
|
+
]),
|
|
2211
|
+
describe:
|
|
2212
|
+
"The script the language is written in, as the map and the face a run marked " +
|
|
2213
|
+
"with its name alone takes; a run's own `map=` or `font=` overrides it. Only a " +
|
|
2214
|
+
"skill of `subType: language` states it.",
|
|
2215
|
+
},
|
|
2185
2216
|
{
|
|
2186
2217
|
name: "script",
|
|
2187
2218
|
kind: "map",
|
|
2188
|
-
shape: "a map — `{ fonts, emphasis, strongEmphasis? }`",
|
|
2219
|
+
shape: "a map — `{ fonts, emphasis, strongEmphasis?, direction? }`",
|
|
2189
2220
|
check: checkScriptField,
|
|
2190
2221
|
fields: Object.freeze([
|
|
2191
2222
|
{
|
|
@@ -2220,10 +2251,20 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
2220
2251
|
allowed: EMPHASIS_CONVENTIONS,
|
|
2221
2252
|
describe: "The convention `**…**` takes instead of `emphasis`'s.",
|
|
2222
2253
|
},
|
|
2254
|
+
{
|
|
2255
|
+
name: "direction",
|
|
2256
|
+
kind: "string",
|
|
2257
|
+
shape: "`vertical-rl`",
|
|
2258
|
+
allowed: SCRIPT_DIRECTIONS,
|
|
2259
|
+
describe:
|
|
2260
|
+
"How the script is written: `vertical-rl` is columns, top to bottom, " +
|
|
2261
|
+
"read from the right, which a `scroll` fence sets. A script stating " +
|
|
2262
|
+
"none is written in lines.",
|
|
2263
|
+
},
|
|
2223
2264
|
]),
|
|
2224
2265
|
describe:
|
|
2225
|
-
"A script note's faces
|
|
2226
|
-
"`subType: script` states it.",
|
|
2266
|
+
"A script note's faces, how it marks emphasis and the direction it is " +
|
|
2267
|
+
"written in. Only a skill of `subType: script` states it.",
|
|
2227
2268
|
},
|
|
2228
2269
|
]),
|
|
2229
2270
|
}),
|
|
@@ -2323,6 +2364,17 @@ export const NOTE_VOCABULARY = Object.freeze({
|
|
|
2323
2364
|
"The culture a setting guide introduces, as a culture lore note. " +
|
|
2324
2365
|
"Required on `settingguide`; no other subType states it.",
|
|
2325
2366
|
},
|
|
2367
|
+
{
|
|
2368
|
+
name: "sounds",
|
|
2369
|
+
kind: "map",
|
|
2370
|
+
shape: "a map — `{ letters?, marks? }`",
|
|
2371
|
+
fields: GUIDE_SOUND_FIELDS,
|
|
2372
|
+
check: checkGuideSounds,
|
|
2373
|
+
describe:
|
|
2374
|
+
"A spelling guide's letters and the marks written on them, for every " +
|
|
2375
|
+
"tongue of the setting: each letter row's `tongue` names the language notes " +
|
|
2376
|
+
"that write it.",
|
|
2377
|
+
},
|
|
2326
2378
|
{
|
|
2327
2379
|
name: "map",
|
|
2328
2380
|
kind: "map",
|