@heroiclands/package-build 23.6.0 → 23.6.1
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 +16 -0
- package/docs/authoring/links-and-markup.md +45 -14
- package/docs/content-format.md +1 -1
- package/docs/reference/format-details.md +18 -6
- package/docs/reference/note-types.md +1 -0
- package/engine/dictionary-layout.mjs +4 -2
- package/engine/dictionary-rows.mjs +10 -13
- package/engine/language-words.mjs +17 -3
- package/engine/pack-config.mjs +6 -1
- package/engine/sql-tables.mjs +45 -4
- package/engine/word-classes.mjs +1 -1
- package/engine/word-rows.mjs +135 -25
- package/engine/word-terms.mjs +12 -0
- package/package.json +1 -1
- package/types/engine/dictionary-layout.d.mts +5 -0
- package/types/engine/sql-tables.d.mts +2 -1
- package/types/engine/word-rows.d.mts +62 -7
- package/types/engine/word-terms.d.mts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 23.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
**Dictionaries and glossaries**
|
|
8
|
+
|
|
9
|
+
- A language's Suffixes and Fields tables can be set from its frontmatter, by querying `affixes` and `fields` in an `sql` fence.
|
|
10
|
+
- An affix can state its own tone pattern in `tones`.
|
|
11
|
+
- `key=false` leaves the key off a dictionary or glossary fence, so a page with one fence per heading states the key once.
|
|
12
|
+
- A daughter language's words built from its parent's words and affixes no longer warn that a piece matches nothing, and their glosses print.
|
|
13
|
+
- Dictionaries abbreviate particle as _ptcl._, since _part._ reads as participle.
|
|
14
|
+
|
|
15
|
+
**Diagnostics**
|
|
16
|
+
|
|
17
|
+
- File paths stay relative to the working directory when `PACKAGE_BUILD_CONFIG` names the configuration through a symlinked path, as macOS temporary directories are.
|
|
18
|
+
|
|
3
19
|
## 23.6.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
|
@@ -309,18 +309,20 @@ A column's heading is its SQL alias, so `AS "Name"` is what a reader sees. A cel
|
|
|
309
309
|
|
|
310
310
|
### What a query can read
|
|
311
311
|
|
|
312
|
-
| Relation | What it holds
|
|
313
|
-
| ------------------ |
|
|
314
|
-
| `notes` | Every note except a stub. This is the one to use unless a stub is wanted.
|
|
315
|
-
| `entries` | Every note, stubs included.
|
|
316
|
-
| `events` | One row per event a `lore`, `place` or `affiliation` note writes in `data.events`, with `whenSort` to order them by date.
|
|
317
|
-
| `words` | One row per word a language note writes in `data.words`, with that note as its `language`, its `position` and what it is built from.
|
|
318
|
-
| `
|
|
319
|
-
| `
|
|
320
|
-
| `
|
|
321
|
-
| `
|
|
322
|
-
| `
|
|
323
|
-
|
|
|
312
|
+
| Relation | What it holds |
|
|
313
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
314
|
+
| `notes` | Every note except a stub. This is the one to use unless a stub is wanted. |
|
|
315
|
+
| `entries` | Every note, stubs included. |
|
|
316
|
+
| `events` | One row per event a `lore`, `place` or `affiliation` note writes in `data.events`, with `whenSort` to order them by date. |
|
|
317
|
+
| `words` | One row per word a language note writes in `data.words`, with that note as its `language`, its `position` and what it is built from. |
|
|
318
|
+
| `affixes` | One row per affix a language note writes in `data.affixes`, with that note as its `language`, its `position`, and each key the affix states: `form`, `gloss`, `attaches`, `examples` and `tones`. |
|
|
319
|
+
| `fields` | One row per field a language note writes in `data.fields`, with that note as its `language`, its `position`, and the field's `name` and what it `holds`. |
|
|
320
|
+
| `glossary` | One row per entry a culture or an affiliation writes in `data.glossary`, with the `note` it belongs to and the `language` it resolves to. |
|
|
321
|
+
| `language_lineage` | One row per language note and each language it inherits words from through `data.family`, itself included at `depth` 0. Join it to `words` for a language's words with inherited ones. |
|
|
322
|
+
| `dictionary` | One row per word per language it is listed under, inherited words included, with its `depth`. See [dictionaries and glossaries](#dictionaries-and-glossaries). |
|
|
323
|
+
| `terms` | One row per glossary entry, rank or office per note that owns it, with the `cultures` it reaches. See [dictionaries and glossaries](#dictionaries-and-glossaries). |
|
|
324
|
+
| `market` | The market scale as three columns — `value`, `name`, `trade` — so a table can print `village` beside the number a note wrote. |
|
|
325
|
+
| `<package>.notes` | The same relations — `notes`, `entries`, `events`, `words`, `affixes`, `fields`, `glossary`, `language_lineage`, `dictionary` and `terms` — for each package this one declares a dependency on, in a schema named for it. |
|
|
324
326
|
|
|
325
327
|
Nested frontmatter is addressed exactly as a note writes it: `name.full`, `sohl.weight`, `data.born`. Every Address is a string — the full canonical Address, as in `list_contains(data.parents, 'thalorna-note-place-xerathia')` — whichever package published it. Every note carries a computed `state` column of `full`, `draft` or `stub`. Notes tagged `gm` are absent from the relations on public surfaces.
|
|
326
328
|
|
|
@@ -417,14 +419,35 @@ ORDER BY e.whenSort, e.address
|
|
|
417
419
|
|
|
418
420
|
An event with no `where.locus` keeps its row with an empty **Where** cell.
|
|
419
421
|
|
|
422
|
+
### A language's affixes and fields
|
|
423
|
+
|
|
424
|
+
`affixes` and `fields` hold the entries of a language note's `data.affixes` and `data.fields`, so a page can set them as tables. A Suffixes table is a `layout=table` fence over `affixes`; the `tones` key is the tone pattern an affix carries:
|
|
425
|
+
|
|
426
|
+
````markdown
|
|
427
|
+
```sql
|
|
428
|
+
SELECT form AS "Suffix", attaches AS "Stem", gloss AS "Makes", array_to_string(examples, ', ') AS "Example", tones AS "Tones"
|
|
429
|
+
FROM affixes
|
|
430
|
+
WHERE language = 'thalorna-note-skill-ekpelulng' AND form LIKE '-%'
|
|
431
|
+
ORDER BY position
|
|
432
|
+
```
|
|
433
|
+
````
|
|
434
|
+
|
|
435
|
+
| Suffix | Stem | Makes | Example | Tones |
|
|
436
|
+
| ------ | ----- | -------------------------------------------- | ------- | ----- |
|
|
437
|
+
| -ndo | added | the keeper of it: one who tends and holds it | luwando | L |
|
|
438
|
+
| -ma | added | the child of it: one who is fed by it | yesoma | H |
|
|
439
|
+
|
|
440
|
+
`ORDER BY position` keeps the order the note states them in. The same shape serves `fields`, with `name` and `holds`.
|
|
441
|
+
|
|
420
442
|
### Fence attributes
|
|
421
443
|
|
|
422
|
-
A braced list may follow the language on the opening fence.
|
|
444
|
+
A braced list may follow the language on the opening fence. Four attributes are accepted, and anything else is reported by name.
|
|
423
445
|
|
|
424
446
|
| Attribute | Value | Default | What it does |
|
|
425
447
|
| --------------- | ----------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
426
448
|
| `allow-empty` | `true` or `false` | `false` | Whether a query selecting no rows is allowed. A zero-row result is a finding otherwise, because a table that silently prints nothing is the commonest way a query goes wrong unnoticed. |
|
|
427
449
|
| `section-level` | an integer from 1 through 6 | `2` | The heading level given to each group a `_section` column creates, and to each letter of a dictionary or a glossary. |
|
|
450
|
+
| `key` | `true` or `false` | `true` | Whether a dictionary or a glossary prints its key. `false` leaves it out, for a page that states the key once above several fences. |
|
|
428
451
|
| `layout` | `table`, `dictionary` or `glossary` | `table` | How the result is set: as a table, as a dictionary of words, or as a glossary of terms — see [dictionaries and glossaries](#dictionaries-and-glossaries). |
|
|
429
452
|
|
|
430
453
|
````markdown
|
|
@@ -488,9 +511,17 @@ SELECT * FROM terms WHERE list_contains(cultures, 'thalorna-note-lore-provenzian
|
|
|
488
511
|
```
|
|
489
512
|
````
|
|
490
513
|
|
|
514
|
+
A page that sets several fences, one per heading, states its key once and sets `key=false` on each fence, so the abbreviations are not repeated under every heading:
|
|
515
|
+
|
|
516
|
+
````markdown
|
|
517
|
+
```sql {layout=dictionary key=false section-level=4}
|
|
518
|
+
SELECT * FROM dictionary WHERE language = 'thalorna-note-skill-vylarilng' AND depth = 0
|
|
519
|
+
```
|
|
520
|
+
````
|
|
521
|
+
|
|
491
522
|
Either layout prints, in order:
|
|
492
523
|
|
|
493
|
-
- **a key**, once, listing only the abbreviations the result uses — a language's tag, and for words a class or a modifier — each with what it stands for, and the common mark where an entry carries it;
|
|
524
|
+
- **a key**, once, unless the fence sets `key=false`, listing only the abbreviations the result uses — a language's tag, and for words a class or a modifier — each with what it stands for, and the common mark where an entry carries it;
|
|
494
525
|
- **a heading per letter**, at `section-level`, accents taken off and apostrophes skipped, so `'ostal` files under O and `Écuyer` under E;
|
|
495
526
|
- **one paragraph per entry**: `•` for a common entry, the headword in bold, its pronunciation between slashes, its language tag in italics, the senses, and after a dash the notes it is cited from, as links.
|
|
496
527
|
|
package/docs/content-format.md
CHANGED
|
@@ -143,6 +143,6 @@ Places, affiliations, lore and beings may name their culture in `data.culture`,
|
|
|
143
143
|
|
|
144
144
|
An affiliation may name the trade, temple or order tongue it keeps in its own `data.language`. An affiliation that reaches no culture is a warning, in a tree holding a culture note. A language needs no culture and no affiliation. See [type: affiliation](reference/format-details.md#type-affiliation).
|
|
145
145
|
|
|
146
|
-
A language note (`type: skill`, `subType: language`) states its words in frontmatter: `data.words` states each word as a map of its form, class, field and gloss, with its source, the pieces it is built from and the notes it is attested in, beside `data.classModifiers`, `data.fields` and `data.affixes`. The language of a word is the note it sits on, and none of it reaches the compiled Item. A daughter language names its parent in `data.family` and reads the parent's words as its own; the `language_lineage` relation joins a language to its ancestors. Classes come from the [master word classes](reference/format-details.md#word-classes). A culture or an affiliation states its own glossary terms in `data.glossary`, and every entry resolves to a language — its own `language`, else the affiliation's, its parents' or its culture's, else the culture's — or is an error at the entry. The `words`, `glossary` and `language_lineage` relations hold them for `sql` fences, the `dictionary` relation lists each word under every language that has it, inherited words included, and the `terms` relation each glossary entry, rank and office with the cultures it reaches; a fence sets them with `layout=dictionary` or `layout=glossary`. A language note names the tag a dictionary prints for it in `data.abbreviation`. See [a language's words](reference/format-details.md#a-languages-words), [glossary terms](reference/format-details.md#glossary-terms) and [dictionaries and glossaries](reference/format-details.md#dictionaries-and-glossaries).
|
|
146
|
+
A language note (`type: skill`, `subType: language`) states its words in frontmatter: `data.words` states each word as a map of its form, class, field and gloss, with its source, the pieces it is built from and the notes it is attested in, beside `data.classModifiers`, `data.fields` and `data.affixes`. The language of a word is the note it sits on, and none of it reaches the compiled Item. A daughter language names its parent in `data.family` and reads the parent's words as its own; the `language_lineage` relation joins a language to its ancestors. Classes come from the [master word classes](reference/format-details.md#word-classes). A culture or an affiliation states its own glossary terms in `data.glossary`, and every entry resolves to a language — its own `language`, else the affiliation's, its parents' or its culture's, else the culture's — or is an error at the entry. The `words`, `affixes`, `fields`, `glossary` and `language_lineage` relations hold them for `sql` fences, the `dictionary` relation lists each word under every language that has it, inherited words included, and the `terms` relation each glossary entry, rank and office with the cultures it reaches; a fence sets them with `layout=dictionary` or `layout=glossary`. A language note names the tag a dictionary prints for it in `data.abbreviation`. See [a language's words](reference/format-details.md#a-languages-words), [glossary terms](reference/format-details.md#glossary-terms) and [dictionaries and glossaries](reference/format-details.md#dictionaries-and-glossaries).
|
|
147
147
|
|
|
148
148
|
Blockquote alerts use `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, or `[!]`, with optional attributes after the marker. They replace the former info and warn div fences; secret blocks remain supported. See [alerts](authoring/links-and-markup.md#alerts).
|
|
@@ -2827,6 +2827,16 @@ language note tagged `gm` contributes no row on a public surface. A row holds
|
|
|
2827
2827
|
the words its own note states, and nothing more: a daughter language's
|
|
2828
2828
|
inherited words are read through `language_lineage`.
|
|
2829
2829
|
|
|
2830
|
+
**`affixes`** is one row per entry of `data.affixes` on every language note,
|
|
2831
|
+
and **`fields`** one per entry of `data.fields`, in the order the note states
|
|
2832
|
+
them. Each has `language` and `position` as `words` does, then every key of the
|
|
2833
|
+
entry as its own column: `form`, `gloss`, `attaches`, `examples` and `tones` for
|
|
2834
|
+
an affix, `name` and `holds` for a field. A key an entry does not state is
|
|
2835
|
+
`NULL`, `examples` is a list, and a language note tagged `gm` contributes no
|
|
2836
|
+
row on a public surface. An affix's `tones` is the tone pattern it carries,
|
|
2837
|
+
written as a word's `tones` is; it is a key of its own and is never read out of
|
|
2838
|
+
`attaches`.
|
|
2839
|
+
|
|
2830
2840
|
**`language_lineage`** is one row per language note and each language it
|
|
2831
2841
|
inherits words from through [`data.family`](#a-languages-words): `language`,
|
|
2832
2842
|
`ancestor` and `depth`, with the language itself at depth `0`, its parent at
|
|
@@ -3143,10 +3153,11 @@ value containing spaces with double quotes and escape an internal quote as
|
|
|
3143
3153
|
`\"`. Boolean values are exactly `true` or `false`; other spellings are
|
|
3144
3154
|
errors. Duplicate and unknown attributes are errors.
|
|
3145
3155
|
|
|
3146
|
-
| Attribute | What it does
|
|
3147
|
-
| ------------------------------------------- |
|
|
3148
|
-
| `allow-empty=true` | Permits a query selecting no rows. Default `false`.
|
|
3149
|
-
| `section-level=1` through `section-level=6` | Sets the heading level for `_section`. Default `2`.
|
|
3156
|
+
| Attribute | What it does |
|
|
3157
|
+
| ------------------------------------------- | ----------------------------------------------------------------- |
|
|
3158
|
+
| `allow-empty=true` | Permits a query selecting no rows. Default `false`. |
|
|
3159
|
+
| `section-level=1` through `section-level=6` | Sets the heading level for `_section`. Default `2`. |
|
|
3160
|
+
| `key=false` | Leaves the key out of a dictionary or a glossary. Default `true`. |
|
|
3150
3161
|
|
|
3151
3162
|
##### Page lists
|
|
3152
3163
|
|
|
@@ -5234,11 +5245,12 @@ SoHL sets `system.quantity` to `1` for each compendium item.
|
|
|
5234
5245
|
| `words[].tones` | `string` | A tone pattern, as `LHL` |
|
|
5235
5246
|
| `words[].root` | `string` | A consonantal skeleton, as `d-l-k` |
|
|
5236
5247
|
| `words[].frame` | `string` | The frame a root is set in |
|
|
5237
|
-
| `affixes` | `{ form, gloss, attaches?, examples? }[]`
|
|
5248
|
+
| `affixes` | `{ form, gloss, attaches?, examples?, tones? }[]` | `language` only: the affixes a word's `built` pieces may name |
|
|
5238
5249
|
| `affixes[].form` | `string`, required | The affix as a `built` piece names it, as `-rith` |
|
|
5239
5250
|
| `affixes[].gloss` | `string`, required | What it means |
|
|
5240
5251
|
| `affixes[].attaches` | `string` | How it attaches, as `replaces a closing vowel` |
|
|
5241
5252
|
| `affixes[].examples` | `string[]` | The names it is read from |
|
|
5253
|
+
| `affixes[].tones` | `string` | The tone pattern it carries, as `L` |
|
|
5242
5254
|
|
|
5243
5255
|
#### A language's words
|
|
5244
5256
|
|
|
@@ -6743,7 +6755,7 @@ A base is one of these. A base marked `no` never prints in the glossary.
|
|
|
6743
6755
|
| `adv` | adv. | adverb | yes |
|
|
6744
6756
|
| `pron` | pron. | pronoun | yes |
|
|
6745
6757
|
| `num` | num. | numeral | yes |
|
|
6746
|
-
| `part` |
|
|
6758
|
+
| `part` | ptcl. | particle | yes |
|
|
6747
6759
|
| `art` | art. | article | yes |
|
|
6748
6760
|
| `conj` | conj. | conjunction | yes |
|
|
6749
6761
|
| `dem` | dem. | demonstrative | yes |
|
|
@@ -492,6 +492,7 @@ These fields are accepted by every note type. A field's value can still be irrel
|
|
|
492
492
|
| `data.affixes[].gloss` | a string | yes | What the affix means. |
|
|
493
493
|
| `data.affixes[].attaches` | a string | no | How it attaches, such as `replaces a closing vowel`. |
|
|
494
494
|
| `data.affixes[].examples` | a list of strings | no | The names the affix is read from. |
|
|
495
|
+
| `data.affixes[].tones` | a tone pattern, such as `L` | no | The tone pattern the affix carries, written as a word's `tones` is. |
|
|
495
496
|
|
|
496
497
|
**System mappings**
|
|
497
498
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* column is read where the query selects it. The output is Markdown, so every
|
|
22
22
|
* surface sets it as it sets a note:
|
|
23
23
|
*
|
|
24
|
-
* - **the key**, once, at the head
|
|
24
|
+
* - **the key**, once, at the head unless the fence sets `key=false`: the abbreviations this rendering uses,
|
|
25
25
|
* each with its expansion, then the common mark where an entry carries it;
|
|
26
26
|
* - **a heading per letter**, from `letter`, at the fence's `section-level`;
|
|
27
27
|
* - **one paragraph per entry**: the common mark, the bold headword, the
|
|
@@ -92,6 +92,7 @@ function sentence(text) {
|
|
|
92
92
|
* @property {object} [addressContext] - Address resolution context.
|
|
93
93
|
* @property {(address: string) => string|undefined} [labelOf] - A note's
|
|
94
94
|
* `name.full`, by its Address.
|
|
95
|
+
* @property {boolean} [showKey=true] - Whether the key is printed.
|
|
95
96
|
* @property {string} [pageLanguage] - The language of the page the fence is
|
|
96
97
|
* on, which no entry is tagged with.
|
|
97
98
|
*/
|
|
@@ -189,6 +190,7 @@ function render(
|
|
|
189
190
|
sectionLevel = 2,
|
|
190
191
|
addressContext,
|
|
191
192
|
labelOf = () => undefined,
|
|
193
|
+
showKey = true,
|
|
192
194
|
pageLanguage,
|
|
193
195
|
},
|
|
194
196
|
words,
|
|
@@ -284,7 +286,7 @@ function render(
|
|
|
284
286
|
if (usedModifiers.has(modifier.token))
|
|
285
287
|
key.push(`_${modifier.abbreviation}_ ${modifier.expansion}`);
|
|
286
288
|
if (anyCommon) key.push(`${COMMON_MARK} ${COMMON_KEY}`);
|
|
287
|
-
if (key.length) paragraphs.unshift(`**Key:** ${key.join("; ")}.`);
|
|
289
|
+
if (showKey && key.length) paragraphs.unshift(`**Key:** ${key.join("; ")}.`);
|
|
288
290
|
return paragraphs.join("\n\n");
|
|
289
291
|
}
|
|
290
292
|
|
|
@@ -53,7 +53,7 @@ import { parseAddress, renderAddress } from "./address.mjs";
|
|
|
53
53
|
import { glossaryLanguage } from "./glossary-language.mjs";
|
|
54
54
|
import { isNoteRecord } from "./index-records.mjs";
|
|
55
55
|
import { parseWordClass, WORD_CLASS_BASES } from "./word-classes.mjs";
|
|
56
|
-
import { isLanguageRecord, pieceGlosses } from "./word-rows.mjs";
|
|
56
|
+
import { isLanguageRecord, lineageChain, pieceGlosses } from "./word-rows.mjs";
|
|
57
57
|
|
|
58
58
|
/** Every column of the `dictionary` relation, in order. */
|
|
59
59
|
export const DICTIONARY_COLUMNS = Object.freeze([
|
|
@@ -224,17 +224,14 @@ export function dictionaryAndTermRows(records, all = records) {
|
|
|
224
224
|
const full = record.name?.full;
|
|
225
225
|
return typeof full === "string" && full ? full : null;
|
|
226
226
|
};
|
|
227
|
-
const lineage = (language) =>
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
return chain;
|
|
237
|
-
};
|
|
227
|
+
const lineage = (language) =>
|
|
228
|
+
lineageChain(language, {
|
|
229
|
+
has: (key) => languages.has(key),
|
|
230
|
+
parentOf: (key) => {
|
|
231
|
+
const family = languages.get(key).data?.family;
|
|
232
|
+
return typeof family === "string" && family ? family : undefined;
|
|
233
|
+
},
|
|
234
|
+
});
|
|
238
235
|
const ownWords = new Map();
|
|
239
236
|
for (const [key, record] of languages) {
|
|
240
237
|
const list = [];
|
|
@@ -470,7 +467,7 @@ export function dictionaryAndTermRows(records, all = records) {
|
|
|
470
467
|
};
|
|
471
468
|
const source = isMap(word.source) ? word.source : {};
|
|
472
469
|
const built = Array.isArray(word.built) ? word.built.map(String) : [];
|
|
473
|
-
const glossOf = pieceGlosses(languages.get(
|
|
470
|
+
const glossOf = pieceGlosses(...lineage(statedIn).map((key) => languages.get(key).data));
|
|
474
471
|
const out = {
|
|
475
472
|
...blank,
|
|
476
473
|
kind: typeof source.kind === "string" ? source.kind : null,
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
pronunciationProblem,
|
|
53
53
|
} from "./word-terms.mjs";
|
|
54
54
|
import { WORD_CLASS_MODIFIERS, parseWordClass } from "./word-classes.mjs";
|
|
55
|
-
import { pieceGlosses } from "./word-rows.mjs";
|
|
55
|
+
import { lineageChain, pieceGlosses } from "./word-rows.mjs";
|
|
56
56
|
|
|
57
57
|
const MODIFIER_TOKENS = new Set(WORD_CLASS_MODIFIERS.map((item) => item.token));
|
|
58
58
|
|
|
@@ -159,7 +159,21 @@ export function checkLanguageWords(note, { index } = {}) {
|
|
|
159
159
|
return out;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
// The language's own entries, then each ancestor's through `data.family`.
|
|
163
|
+
const ownAddress = ownKey(note, index);
|
|
164
|
+
const dataAt = (key) => {
|
|
165
|
+
const hit = index?.addressHit?.(key);
|
|
166
|
+
const target = hit?.fm ?? hit;
|
|
167
|
+
return target?.subType === LANGUAGE_SUBTYPE && isMap(target.data) ? target.data : undefined;
|
|
168
|
+
};
|
|
169
|
+
const ancestors =
|
|
170
|
+
index?.addressHit && stated("family") ?
|
|
171
|
+
lineageChain(addressKeyOf(data.family, index, "skill"), {
|
|
172
|
+
has: (key) => key !== ownAddress && dataAt(key) !== undefined,
|
|
173
|
+
parentOf: (key) => addressKeyOf(dataAt(key).family, index, "skill"),
|
|
174
|
+
}).map((key) => dataAt(key))
|
|
175
|
+
: [];
|
|
176
|
+
const glossOf = pieceGlosses(data, ...ancestors);
|
|
163
177
|
const used = new Set();
|
|
164
178
|
const forms = new Map();
|
|
165
179
|
listOf(data.words).forEach((word, i) => {
|
|
@@ -246,7 +260,7 @@ export function checkLanguageWords(note, { index } = {}) {
|
|
|
246
260
|
[...at("built"), j],
|
|
247
261
|
"warning",
|
|
248
262
|
`the piece \`${piece}\` of \`${word.form}\` matches no word or affix of this ` +
|
|
249
|
-
"language, so it prints with no gloss",
|
|
263
|
+
"language or its ancestors, so it prints with no gloss",
|
|
250
264
|
),
|
|
251
265
|
);
|
|
252
266
|
});
|
package/engine/pack-config.mjs
CHANGED
|
@@ -695,7 +695,7 @@ export function loadPackConfig() {
|
|
|
695
695
|
|
|
696
696
|
const explicit = process.env.PACKAGE_BUILD_CONFIG;
|
|
697
697
|
const found = explicit ? undefined : resolveConfigFile();
|
|
698
|
-
|
|
698
|
+
let configPath = explicit ? path.resolve(explicit) : found.path;
|
|
699
699
|
|
|
700
700
|
if (!configPath || !fs.existsSync(configPath)) {
|
|
701
701
|
throw new Error(
|
|
@@ -713,6 +713,11 @@ export function loadPackConfig() {
|
|
|
713
713
|
);
|
|
714
714
|
}
|
|
715
715
|
|
|
716
|
+
// An explicit name is followed through any symlink, so the configuration's
|
|
717
|
+
// root agrees with `process.cwd()`, which is always a real path, and
|
|
718
|
+
// diagnostic paths relative to one read relative to the other.
|
|
719
|
+
if (explicit) configPath = fs.realpathSync(configPath);
|
|
720
|
+
|
|
716
721
|
// Two different files, one of which is about to be ignored. Said out loud
|
|
717
722
|
// because the alternative is a build that reads the parent
|
|
718
723
|
// checkout's configuration, compiles the parent's tree, and reports it only
|
package/engine/sql-tables.mjs
CHANGED
|
@@ -32,11 +32,15 @@ import { encodeAddresses, flattenPublishedAddresses } from "./address-values.mjs
|
|
|
32
32
|
import { EVENT_COLUMNS, EVENT_SORT_COLUMNS, eventRows } from "./event-rows.mjs";
|
|
33
33
|
import {
|
|
34
34
|
GLOSSARY_COLUMNS,
|
|
35
|
+
AFFIX_COLUMNS,
|
|
36
|
+
FIELD_COLUMNS,
|
|
35
37
|
LINEAGE_COLUMNS,
|
|
36
38
|
ROW_INTEGER_COLUMNS,
|
|
37
39
|
WORD_COLUMNS,
|
|
38
40
|
glossaryRows,
|
|
39
41
|
lineageRows,
|
|
42
|
+
affixRows,
|
|
43
|
+
fieldRows,
|
|
40
44
|
wordRows,
|
|
41
45
|
} from "./word-rows.mjs";
|
|
42
46
|
import { DICTIONARY_COLUMNS, TERM_COLUMNS, dictionaryAndTermRows } from "./dictionary-rows.mjs";
|
|
@@ -94,6 +98,14 @@ export const SQL_FENCE_ATTRIBUTES = Object.freeze({
|
|
|
94
98
|
"The heading level given to each group a `_section` column creates, and to each " +
|
|
95
99
|
"letter of a dictionary or a glossary.",
|
|
96
100
|
}),
|
|
101
|
+
key: Object.freeze({
|
|
102
|
+
value: "`true` or `false`",
|
|
103
|
+
default: "`true`",
|
|
104
|
+
summary:
|
|
105
|
+
"Whether a dictionary or a glossary prints its key, the line of abbreviations it " +
|
|
106
|
+
"uses. `false` leaves the key out, for a page that states it once above several " +
|
|
107
|
+
"fences.",
|
|
108
|
+
}),
|
|
97
109
|
layout: Object.freeze({
|
|
98
110
|
value: "`table`, `dictionary` or `glossary`",
|
|
99
111
|
default: "`table`",
|
|
@@ -123,7 +135,7 @@ const escapeCell = (text) =>
|
|
|
123
135
|
*
|
|
124
136
|
* @param {string} markdown - The note body, frontmatter already stripped.
|
|
125
137
|
* @returns {Array<{line: number, close: number, indent: string, query: string,
|
|
126
|
-
* allowEmpty: boolean, sectionLevel: number, layout: string, block: string}>} One entry per
|
|
138
|
+
* allowEmpty: boolean, sectionLevel: number, showKey: boolean, layout: string, block: string}>} One entry per
|
|
127
139
|
* fence, in document order.
|
|
128
140
|
*/
|
|
129
141
|
export function findSqlBlocks(markdown) {
|
|
@@ -158,6 +170,14 @@ export function findSqlBlocks(markdown) {
|
|
|
158
170
|
errors.push(error.message);
|
|
159
171
|
}
|
|
160
172
|
}
|
|
173
|
+
let showKey = true;
|
|
174
|
+
if (Object.hasOwn(args, "key")) {
|
|
175
|
+
try {
|
|
176
|
+
showKey = booleanAttribute(args.key, "key");
|
|
177
|
+
} catch (error) {
|
|
178
|
+
errors.push(error.message);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
161
181
|
if (!Number.isInteger(level) || level < 1 || level > 6)
|
|
162
182
|
errors.push("section-level needs an integer from 1 through 6");
|
|
163
183
|
const layout = String(args.layout ?? "table");
|
|
@@ -170,6 +190,7 @@ export function findSqlBlocks(markdown) {
|
|
|
170
190
|
query: lines.slice(i + 1, close).join("\n"),
|
|
171
191
|
allowEmpty,
|
|
172
192
|
sectionLevel: level,
|
|
193
|
+
showKey,
|
|
173
194
|
layout,
|
|
174
195
|
problems: errors,
|
|
175
196
|
args,
|
|
@@ -310,6 +331,8 @@ export async function openNotesDatabase(
|
|
|
310
331
|
for (const relation of [
|
|
311
332
|
"events",
|
|
312
333
|
"words",
|
|
334
|
+
"affixes",
|
|
335
|
+
"fields",
|
|
313
336
|
"glossary",
|
|
314
337
|
"language_lineage",
|
|
315
338
|
"dictionary",
|
|
@@ -522,11 +545,14 @@ async function createTableFromFile(connection, name, select) {
|
|
|
522
545
|
}
|
|
523
546
|
|
|
524
547
|
/**
|
|
525
|
-
* Create the `words`, `
|
|
526
|
-
* relations: one row per language note's word, one per
|
|
548
|
+
* Create the `words`, `affixes`, `fields`, `glossary`, `language_lineage`,
|
|
549
|
+
* `dictionary` and `terms` relations: one row per language note's word, one per
|
|
550
|
+
* its affix, one per its field, one per culture's or
|
|
527
551
|
* affiliation's glossary entry as authored, one per language and ancestor, one
|
|
528
552
|
* per word per language it is listed under, and one per term per owning note,
|
|
529
553
|
* with the columns {@link module:engine/word-rows.WORD_COLUMNS},
|
|
554
|
+
* {@link module:engine/word-rows.AFFIX_COLUMNS},
|
|
555
|
+
* {@link module:engine/word-rows.FIELD_COLUMNS},
|
|
530
556
|
* {@link module:engine/word-rows.GLOSSARY_COLUMNS},
|
|
531
557
|
* {@link module:engine/word-rows.LINEAGE_COLUMNS},
|
|
532
558
|
* {@link module:engine/dictionary-rows.DICTIONARY_COLUMNS} and
|
|
@@ -550,10 +576,24 @@ async function createWordRelations(connection, stem, records, prefix, audience)
|
|
|
550
576
|
await createRowRelation(
|
|
551
577
|
connection,
|
|
552
578
|
`${stem}-words.jsonl`,
|
|
553
|
-
wordRows(visible),
|
|
579
|
+
wordRows(visible, records),
|
|
554
580
|
`${prefix}words`,
|
|
555
581
|
WORD_COLUMNS,
|
|
556
582
|
);
|
|
583
|
+
await createRowRelation(
|
|
584
|
+
connection,
|
|
585
|
+
`${stem}-affixes.jsonl`,
|
|
586
|
+
affixRows(visible),
|
|
587
|
+
`${prefix}affixes`,
|
|
588
|
+
AFFIX_COLUMNS,
|
|
589
|
+
);
|
|
590
|
+
await createRowRelation(
|
|
591
|
+
connection,
|
|
592
|
+
`${stem}-fields.jsonl`,
|
|
593
|
+
fieldRows(visible),
|
|
594
|
+
`${prefix}fields`,
|
|
595
|
+
FIELD_COLUMNS,
|
|
596
|
+
);
|
|
557
597
|
await createRowRelation(
|
|
558
598
|
connection,
|
|
559
599
|
`${stem}-glossary.jsonl`,
|
|
@@ -870,6 +910,7 @@ export async function prepareSqlTables(db, sources, { linkable, records, pageLan
|
|
|
870
910
|
setAs(result, {
|
|
871
911
|
...options,
|
|
872
912
|
labelOf,
|
|
913
|
+
showKey: block.showKey,
|
|
873
914
|
pageLanguage: languageOf(source, frontmatter),
|
|
874
915
|
})
|
|
875
916
|
: renderSqlTable(result, options),
|
package/engine/word-classes.mjs
CHANGED
|
@@ -70,7 +70,7 @@ export const WORD_CLASS_BASES = Object.freeze([
|
|
|
70
70
|
base("adv", "adv.", "adverb", true),
|
|
71
71
|
base("pron", "pron.", "pronoun", true),
|
|
72
72
|
base("num", "num.", "numeral", true),
|
|
73
|
-
base("part", "
|
|
73
|
+
base("part", "ptcl.", "particle", true),
|
|
74
74
|
base("art", "art.", "article", true),
|
|
75
75
|
base("conj", "conj.", "conjunction", true),
|
|
76
76
|
base("dem", "dem.", "demonstrative", true),
|
package/engine/word-rows.mjs
CHANGED
|
@@ -33,7 +33,13 @@
|
|
|
33
33
|
|
|
34
34
|
import { renderAddress } from "./address.mjs";
|
|
35
35
|
import { glossaryLanguage } from "./glossary-language.mjs";
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
AFFIX_KEYS,
|
|
38
|
+
FIELD_KEYS,
|
|
39
|
+
GLOSSARY_ENTRY_KEYS,
|
|
40
|
+
LANGUAGE_SUBTYPE,
|
|
41
|
+
WORD_KEYS,
|
|
42
|
+
} from "./word-terms.mjs";
|
|
37
43
|
|
|
38
44
|
/** The columns a word row carries before the word's own keys. */
|
|
39
45
|
const WORD_IDENTITY_COLUMNS = Object.freeze(["language", "position"]);
|
|
@@ -51,6 +57,12 @@ export const WORD_COLUMNS = Object.freeze([
|
|
|
51
57
|
/** Every column of the `glossary` relation, in order. */
|
|
52
58
|
export const GLOSSARY_COLUMNS = Object.freeze(["note", "position", ...GLOSSARY_ENTRY_KEYS]);
|
|
53
59
|
|
|
60
|
+
/** Every column of the `affixes` relation, in order. */
|
|
61
|
+
export const AFFIX_COLUMNS = Object.freeze([...WORD_IDENTITY_COLUMNS, ...AFFIX_KEYS]);
|
|
62
|
+
|
|
63
|
+
/** Every column of the `fields` relation, in order. */
|
|
64
|
+
export const FIELD_COLUMNS = Object.freeze([...WORD_IDENTITY_COLUMNS, ...FIELD_KEYS]);
|
|
65
|
+
|
|
54
66
|
/** Every column of the `language_lineage` relation, in order. */
|
|
55
67
|
export const LINEAGE_COLUMNS = Object.freeze(["language", "ancestor", "depth"]);
|
|
56
68
|
|
|
@@ -64,17 +76,20 @@ const isMap = (value) => Boolean(value) && typeof value === "object" && !Array.i
|
|
|
64
76
|
const bare = (form) => String(form).replace(/^-+|-+$/g, "");
|
|
65
77
|
|
|
66
78
|
/**
|
|
67
|
-
* A lookup from a built piece to its gloss, over
|
|
68
|
-
*
|
|
79
|
+
* A lookup from a built piece to its gloss, over the words (stems included)
|
|
80
|
+
* and affixes of one or more languages.
|
|
69
81
|
*
|
|
70
|
-
*
|
|
82
|
+
* Languages are given nearest first: a language's own words, then its own
|
|
83
|
+
* affixes, then the next language's, so a daughter's entry wins over its
|
|
84
|
+
* parent's. A piece matches an entry written exactly as it is, and failing
|
|
71
85
|
* that one written with the same letters and different joining hyphens, so
|
|
72
|
-
* `wulf-` finds the word `wulf`.
|
|
86
|
+
* `wulf-` finds the word `wulf`. Within a language a word's own gloss wins over
|
|
87
|
+
* an affix's.
|
|
73
88
|
*
|
|
74
|
-
* @param {object}
|
|
89
|
+
* @param {...object} datas - Each language note's `data`, nearest first.
|
|
75
90
|
* @returns {(piece: string) => string|null} The gloss, or `null` where none resolves.
|
|
76
91
|
*/
|
|
77
|
-
export function pieceGlosses(
|
|
92
|
+
export function pieceGlosses(...datas) {
|
|
78
93
|
const exact = new Map();
|
|
79
94
|
const loose = new Map();
|
|
80
95
|
const take = (entries) => {
|
|
@@ -85,11 +100,64 @@ export function pieceGlosses(data) {
|
|
|
85
100
|
if (!loose.has(bare(entry.form))) loose.set(bare(entry.form), entry.gloss);
|
|
86
101
|
}
|
|
87
102
|
};
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
for (const data of datas) {
|
|
104
|
+
take(data?.words);
|
|
105
|
+
take(data?.affixes);
|
|
106
|
+
}
|
|
90
107
|
return (piece) => exact.get(piece) ?? loose.get(bare(piece)) ?? null;
|
|
91
108
|
}
|
|
92
109
|
|
|
110
|
+
/**
|
|
111
|
+
* The chain of languages from one up through `data.family`: the language
|
|
112
|
+
* itself, then its parent, and so on. The chain stops at a parent that is no
|
|
113
|
+
* language, and before a language it has already reached, so a cycle the lint
|
|
114
|
+
* refuses still yields a finite chain. `language_lineage`, the dictionary and
|
|
115
|
+
* the resolution of built pieces all walk it.
|
|
116
|
+
*
|
|
117
|
+
* @param {string|undefined} start - The language's key.
|
|
118
|
+
* @param {object} reader
|
|
119
|
+
* @param {(key: string) => boolean} reader.has - Whether a key is a language.
|
|
120
|
+
* @param {(key: string) => string|undefined} reader.parentOf - A language's
|
|
121
|
+
* parent key, where it states one.
|
|
122
|
+
* @returns {string[]} The keys, nearest first.
|
|
123
|
+
*/
|
|
124
|
+
export function lineageChain(start, { has, parentOf }) {
|
|
125
|
+
const chain = [];
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
for (let current = start; current && has(current) && !seen.has(current);) {
|
|
128
|
+
seen.add(current);
|
|
129
|
+
chain.push(current);
|
|
130
|
+
current = parentOf(current) || undefined;
|
|
131
|
+
}
|
|
132
|
+
return chain;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A language record's `data.family` where it is a plain Address string. */
|
|
136
|
+
const familyKey = (record) => {
|
|
137
|
+
const family = record?.data?.family;
|
|
138
|
+
return typeof family === "string" && family ? family : undefined;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* A lookup of language records by Address, and the chain walk over them.
|
|
143
|
+
*
|
|
144
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
145
|
+
* @returns {{languages: Map<string, object>, chain: (key: string) => string[]}}
|
|
146
|
+
*/
|
|
147
|
+
export function languageLineages(records) {
|
|
148
|
+
const languages = new Map();
|
|
149
|
+
for (const record of records)
|
|
150
|
+
if (isLanguageRecord(record)) {
|
|
151
|
+
const key = noteAddressOf(record);
|
|
152
|
+
if (key && !languages.has(key)) languages.set(key, record);
|
|
153
|
+
}
|
|
154
|
+
const reader = {
|
|
155
|
+
has: (key) => languages.has(key),
|
|
156
|
+
parentOf: (key) => familyKey(languages.get(key)),
|
|
157
|
+
};
|
|
158
|
+
return { languages, chain: (key) => lineageChain(key, reader) };
|
|
159
|
+
}
|
|
160
|
+
|
|
93
161
|
/**
|
|
94
162
|
* Whether a record is a language note's.
|
|
95
163
|
*
|
|
@@ -124,9 +192,12 @@ function noteAddressOf(record) {
|
|
|
124
192
|
*
|
|
125
193
|
* @param {Iterable<Record<string, any>>} records - Index records, every
|
|
126
194
|
* Address a string, already narrowed to the audience being published to.
|
|
195
|
+
* @param {Iterable<Record<string, any>>} [all] - Every record of the package,
|
|
196
|
+
* for resolving a built piece through an ancestor the audience does not see.
|
|
127
197
|
* @returns {object[]} One row per word, in record then entry order.
|
|
128
198
|
*/
|
|
129
|
-
export function wordRows(records) {
|
|
199
|
+
export function wordRows(records, all = records) {
|
|
200
|
+
const { languages, chain } = languageLineages(all);
|
|
130
201
|
const rows = [];
|
|
131
202
|
for (const record of records) {
|
|
132
203
|
if (!isLanguageRecord(record)) continue;
|
|
@@ -134,7 +205,7 @@ export function wordRows(records) {
|
|
|
134
205
|
if (!Array.isArray(words) || !words.length) continue;
|
|
135
206
|
const language = noteAddressOf(record);
|
|
136
207
|
if (!language) continue;
|
|
137
|
-
const glossOf = pieceGlosses(
|
|
208
|
+
const glossOf = pieceGlosses(...chain(language).map((key) => languages.get(key).data));
|
|
138
209
|
words.forEach((entry, position) => {
|
|
139
210
|
if (!isMap(entry)) return;
|
|
140
211
|
const row = { language, position };
|
|
@@ -150,6 +221,57 @@ export function wordRows(records) {
|
|
|
150
221
|
return rows;
|
|
151
222
|
}
|
|
152
223
|
|
|
224
|
+
/**
|
|
225
|
+
* The rows of one list a language note states beside its words: one per entry,
|
|
226
|
+
* keyed by the note, with every key of the entry a column.
|
|
227
|
+
*
|
|
228
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
229
|
+
* @param {string} list - The `data` key holding the entries.
|
|
230
|
+
* @param {readonly string[]} keys - The keys an entry carries.
|
|
231
|
+
* @returns {object[]} One row per entry, in record then entry order.
|
|
232
|
+
*/
|
|
233
|
+
function languageListRows(records, list, keys) {
|
|
234
|
+
const rows = [];
|
|
235
|
+
for (const record of records) {
|
|
236
|
+
if (!isLanguageRecord(record)) continue;
|
|
237
|
+
const entries = record.data?.[list];
|
|
238
|
+
if (!Array.isArray(entries) || !entries.length) continue;
|
|
239
|
+
const language = noteAddressOf(record);
|
|
240
|
+
if (!language) continue;
|
|
241
|
+
entries.forEach((entry, position) => {
|
|
242
|
+
if (!isMap(entry)) return;
|
|
243
|
+
const row = { language, position };
|
|
244
|
+
for (const key of keys) row[key] = entry[key] ?? null;
|
|
245
|
+
rows.push(row);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return rows;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The `affixes` rows of a set of index records: one per `data.affixes` entry
|
|
253
|
+
* of a language note, which is the row's `language`.
|
|
254
|
+
*
|
|
255
|
+
* @param {Iterable<Record<string, any>>} records - Index records, every
|
|
256
|
+
* Address a string, already narrowed to the audience being published to.
|
|
257
|
+
* @returns {object[]} One row per affix, in record then entry order.
|
|
258
|
+
*/
|
|
259
|
+
export function affixRows(records) {
|
|
260
|
+
return languageListRows(records, "affixes", AFFIX_KEYS);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* The `fields` rows of a set of index records: one per `data.fields` entry of
|
|
265
|
+
* a language note, which is the row's `language`.
|
|
266
|
+
*
|
|
267
|
+
* @param {Iterable<Record<string, any>>} records - Index records, every
|
|
268
|
+
* Address a string, already narrowed to the audience being published to.
|
|
269
|
+
* @returns {object[]} One row per field, in record then entry order.
|
|
270
|
+
*/
|
|
271
|
+
export function fieldRows(records) {
|
|
272
|
+
return languageListRows(records, "fields", FIELD_KEYS);
|
|
273
|
+
}
|
|
274
|
+
|
|
153
275
|
/**
|
|
154
276
|
* The `glossary` rows of a set of index records: one per entry of a culture's
|
|
155
277
|
* or an affiliation's `data.glossary`, with `name_sort` the name and `common`
|
|
@@ -209,25 +331,13 @@ export function glossaryRows(records, all = records) {
|
|
|
209
331
|
* @returns {object[]} One row per language and ancestor, in record then depth order.
|
|
210
332
|
*/
|
|
211
333
|
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
|
-
}
|
|
334
|
+
const { chain } = languageLineages(all);
|
|
218
335
|
const rows = [];
|
|
219
336
|
for (const record of records) {
|
|
220
337
|
if (!isLanguageRecord(record)) continue;
|
|
221
338
|
const language = noteAddressOf(record);
|
|
222
339
|
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
|
-
}
|
|
340
|
+
chain(language).forEach((ancestor, depth) => rows.push({ language, ancestor, depth }));
|
|
231
341
|
}
|
|
232
342
|
return rows;
|
|
233
343
|
}
|
package/engine/word-terms.mjs
CHANGED
|
@@ -226,8 +226,20 @@ export const AFFIX_FIELDS = Object.freeze([
|
|
|
226
226
|
entries: Object.freeze({ kind: "string", shape: "a string" }),
|
|
227
227
|
describe: "The names the affix is read from.",
|
|
228
228
|
}),
|
|
229
|
+
Object.freeze({
|
|
230
|
+
name: "tones",
|
|
231
|
+
kind: "string",
|
|
232
|
+
shape: "a tone pattern, such as `L`",
|
|
233
|
+
describe: "The tone pattern the affix carries, written as a word's `tones` is.",
|
|
234
|
+
}),
|
|
229
235
|
]);
|
|
230
236
|
|
|
237
|
+
/** The keys of {@link WORD_FIELD_FIELDS}, in declared order. */
|
|
238
|
+
export const FIELD_KEYS = Object.freeze(WORD_FIELD_FIELDS.map((field) => field.name));
|
|
239
|
+
|
|
240
|
+
/** The keys of {@link AFFIX_FIELDS}, in declared order. */
|
|
241
|
+
export const AFFIX_KEYS = Object.freeze(AFFIX_FIELDS.map((field) => field.name));
|
|
242
|
+
|
|
231
243
|
/**
|
|
232
244
|
* The keys one entry of a culture's or an affiliation's `data.glossary`
|
|
233
245
|
* carries.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "23.6.
|
|
3
|
+
"version": "23.6.1",
|
|
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,7 @@
|
|
|
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} [showKey=true] - Whether the key is printed.
|
|
9
10
|
* @property {string} [pageLanguage] - The language of the page the fence is
|
|
10
11
|
* on, which no entry is tagged with.
|
|
11
12
|
*/
|
|
@@ -61,6 +62,10 @@ export type LayoutOptions = {
|
|
|
61
62
|
* `name.full`, by its Address.
|
|
62
63
|
*/
|
|
63
64
|
labelOf?: ((address: string) => string | undefined) | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* - Whether the key is printed.
|
|
67
|
+
*/
|
|
68
|
+
showKey?: boolean | undefined;
|
|
64
69
|
/**
|
|
65
70
|
* - The language of the page the fence is
|
|
66
71
|
* on, which no entry is tagged with.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @param {string} markdown - The note body, frontmatter already stripped.
|
|
9
9
|
* @returns {Array<{line: number, close: number, indent: string, query: string,
|
|
10
|
-
* allowEmpty: boolean, sectionLevel: number, layout: string, block: string}>} One entry per
|
|
10
|
+
* allowEmpty: boolean, sectionLevel: number, showKey: boolean, layout: string, block: string}>} One entry per
|
|
11
11
|
* fence, in document order.
|
|
12
12
|
*/
|
|
13
13
|
export function findSqlBlocks(markdown: string): Array<{
|
|
@@ -17,6 +17,7 @@ export function findSqlBlocks(markdown: string): Array<{
|
|
|
17
17
|
query: string;
|
|
18
18
|
allowEmpty: boolean;
|
|
19
19
|
sectionLevel: number;
|
|
20
|
+
showKey: boolean;
|
|
20
21
|
layout: string;
|
|
21
22
|
block: string;
|
|
22
23
|
}>;
|
|
@@ -1,15 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A lookup from a built piece to its gloss, over
|
|
3
|
-
*
|
|
2
|
+
* A lookup from a built piece to its gloss, over the words (stems included)
|
|
3
|
+
* and affixes of one or more languages.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Languages are given nearest first: a language's own words, then its own
|
|
6
|
+
* affixes, then the next language's, so a daughter's entry wins over its
|
|
7
|
+
* parent's. A piece matches an entry written exactly as it is, and failing
|
|
6
8
|
* that one written with the same letters and different joining hyphens, so
|
|
7
|
-
* `wulf-` finds the word `wulf`.
|
|
9
|
+
* `wulf-` finds the word `wulf`. Within a language a word's own gloss wins over
|
|
10
|
+
* an affix's.
|
|
8
11
|
*
|
|
9
|
-
* @param {object}
|
|
12
|
+
* @param {...object} datas - Each language note's `data`, nearest first.
|
|
10
13
|
* @returns {(piece: string) => string|null} The gloss, or `null` where none resolves.
|
|
11
14
|
*/
|
|
12
|
-
export function pieceGlosses(
|
|
15
|
+
export function pieceGlosses(...datas: object[]): (piece: string) => string | null;
|
|
16
|
+
/**
|
|
17
|
+
* The chain of languages from one up through `data.family`: the language
|
|
18
|
+
* itself, then its parent, and so on. The chain stops at a parent that is no
|
|
19
|
+
* language, and before a language it has already reached, so a cycle the lint
|
|
20
|
+
* refuses still yields a finite chain. `language_lineage`, the dictionary and
|
|
21
|
+
* the resolution of built pieces all walk it.
|
|
22
|
+
*
|
|
23
|
+
* @param {string|undefined} start - The language's key.
|
|
24
|
+
* @param {object} reader
|
|
25
|
+
* @param {(key: string) => boolean} reader.has - Whether a key is a language.
|
|
26
|
+
* @param {(key: string) => string|undefined} reader.parentOf - A language's
|
|
27
|
+
* parent key, where it states one.
|
|
28
|
+
* @returns {string[]} The keys, nearest first.
|
|
29
|
+
*/
|
|
30
|
+
export function lineageChain(start: string | undefined, { has, parentOf }: {
|
|
31
|
+
has: (key: string) => boolean;
|
|
32
|
+
parentOf: (key: string) => string | undefined;
|
|
33
|
+
}): string[];
|
|
34
|
+
/**
|
|
35
|
+
* A lookup of language records by Address, and the chain walk over them.
|
|
36
|
+
*
|
|
37
|
+
* @param {Iterable<Record<string, any>>} records - Index records.
|
|
38
|
+
* @returns {{languages: Map<string, object>, chain: (key: string) => string[]}}
|
|
39
|
+
*/
|
|
40
|
+
export function languageLineages(records: Iterable<Record<string, any>>): {
|
|
41
|
+
languages: Map<string, object>;
|
|
42
|
+
chain: (key: string) => string[];
|
|
43
|
+
};
|
|
13
44
|
/**
|
|
14
45
|
* Whether a record is a language note's.
|
|
15
46
|
*
|
|
@@ -24,9 +55,29 @@ export function isLanguageRecord(record: Record<string, any>): boolean;
|
|
|
24
55
|
*
|
|
25
56
|
* @param {Iterable<Record<string, any>>} records - Index records, every
|
|
26
57
|
* Address a string, already narrowed to the audience being published to.
|
|
58
|
+
* @param {Iterable<Record<string, any>>} [all] - Every record of the package,
|
|
59
|
+
* for resolving a built piece through an ancestor the audience does not see.
|
|
27
60
|
* @returns {object[]} One row per word, in record then entry order.
|
|
28
61
|
*/
|
|
29
|
-
export function wordRows(records: Iterable<Record<string, any>>): object[];
|
|
62
|
+
export function wordRows(records: Iterable<Record<string, any>>, all?: Iterable<Record<string, any>>): object[];
|
|
63
|
+
/**
|
|
64
|
+
* The `affixes` rows of a set of index records: one per `data.affixes` entry
|
|
65
|
+
* of a language note, which is the row's `language`.
|
|
66
|
+
*
|
|
67
|
+
* @param {Iterable<Record<string, any>>} records - Index records, every
|
|
68
|
+
* Address a string, already narrowed to the audience being published to.
|
|
69
|
+
* @returns {object[]} One row per affix, in record then entry order.
|
|
70
|
+
*/
|
|
71
|
+
export function affixRows(records: Iterable<Record<string, any>>): object[];
|
|
72
|
+
/**
|
|
73
|
+
* The `fields` rows of a set of index records: one per `data.fields` entry of
|
|
74
|
+
* a language note, which is the row's `language`.
|
|
75
|
+
*
|
|
76
|
+
* @param {Iterable<Record<string, any>>} records - Index records, every
|
|
77
|
+
* Address a string, already narrowed to the audience being published to.
|
|
78
|
+
* @returns {object[]} One row per field, in record then entry order.
|
|
79
|
+
*/
|
|
80
|
+
export function fieldRows(records: Iterable<Record<string, any>>): object[];
|
|
30
81
|
/**
|
|
31
82
|
* The `glossary` rows of a set of index records: one per entry of a culture's
|
|
32
83
|
* or an affiliation's `data.glossary`, with `name_sort` the name and `common`
|
|
@@ -58,6 +109,10 @@ export function lineageRows(records: Iterable<Record<string, any>>, all?: Iterab
|
|
|
58
109
|
export const WORD_COLUMNS: readonly string[];
|
|
59
110
|
/** Every column of the `glossary` relation, in order. */
|
|
60
111
|
export const GLOSSARY_COLUMNS: readonly string[];
|
|
112
|
+
/** Every column of the `affixes` relation, in order. */
|
|
113
|
+
export const AFFIX_COLUMNS: readonly string[];
|
|
114
|
+
/** Every column of the `fields` relation, in order. */
|
|
115
|
+
export const FIELD_COLUMNS: readonly string[];
|
|
61
116
|
/** Every column of the `language_lineage` relation, in order. */
|
|
62
117
|
export const LINEAGE_COLUMNS: readonly string[];
|
|
63
118
|
/** The columns of any of these relations that hold a whole number. */
|
|
@@ -57,6 +57,10 @@ export const WORD_FIELD_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[
|
|
|
57
57
|
* @type {readonly import("./data-keys.mjs").InnerKeySpec[]}
|
|
58
58
|
*/
|
|
59
59
|
export const AFFIX_FIELDS: readonly import("./data-keys.mjs").InnerKeySpec[];
|
|
60
|
+
/** The keys of {@link WORD_FIELD_FIELDS}, in declared order. */
|
|
61
|
+
export const FIELD_KEYS: readonly string[];
|
|
62
|
+
/** The keys of {@link AFFIX_FIELDS}, in declared order. */
|
|
63
|
+
export const AFFIX_KEYS: readonly string[];
|
|
60
64
|
/**
|
|
61
65
|
* The keys one entry of a culture's or an affiliation's `data.glossary`
|
|
62
66
|
* carries.
|