@heroiclands/package-build 23.9.0 → 23.10.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 +26 -0
- package/README.md +2 -0
- package/bin/content-commands.mjs +197 -68
- package/docs/api.md +336 -68
- package/docs/authoring/links-and-markup.md +114 -47
- package/docs/commands.md +92 -3
- package/docs/content-format.md +6 -1
- package/docs/reference/format-details.md +129 -14
- package/docs/reference/note-types.md +4 -4
- package/engine/anchors.mjs +8 -6
- package/engine/base-compiler.mjs +6 -4
- package/engine/changelog-group.mjs +58 -34
- package/engine/changelog-lint.mjs +6 -2
- package/engine/compendiums.mjs +15 -1
- package/engine/compile-corpus.mjs +14 -1
- package/engine/content-charset.mjs +13 -1
- package/engine/content-html.mjs +18 -4
- package/engine/content-icons.mjs +15 -1
- package/engine/content-images.mjs +28 -9
- package/engine/content-index.mjs +102 -23
- package/engine/content-links.mjs +27 -12
- package/engine/content-session.mjs +247 -0
- package/engine/content-sources.mjs +110 -0
- package/engine/content-spans.mjs +87 -8
- package/engine/content-tables.mjs +1 -0
- package/engine/content-tasklists.mjs +20 -4
- package/engine/diagnostics.mjs +17 -6
- package/engine/extension-attributes.mjs +28 -5
- package/engine/first-use.mjs +371 -0
- package/engine/foundry-entries.mjs +12 -9
- package/engine/generate.mjs +13 -4
- package/engine/helpers.mjs +43 -14
- package/engine/index-records.mjs +1 -0
- package/engine/index.mjs +3 -0
- package/engine/journals.mjs +9 -5
- package/engine/language-data.mjs +2 -2
- package/engine/language-rows.mjs +4 -4
- package/engine/language-terms.mjs +4 -4
- package/engine/lexical-sections.mjs +5 -5
- package/engine/markup-validation.mjs +7 -2
- package/engine/metadata-index.mjs +1 -0
- package/engine/name-markup.mjs +114 -0
- package/engine/name-rows.mjs +70 -0
- package/engine/names.mjs +460 -0
- package/engine/note-addresses.mjs +25 -4
- package/engine/path-charset.mjs +24 -1
- package/engine/pdf-render.mjs +15 -1
- package/engine/site-index.mjs +5 -1
- package/engine/sql-tables.mjs +76 -15
- package/engine/stub-lint.mjs +4 -3
- package/engine/yaml-lint.mjs +3 -5
- package/hugo-theme/static/css/style.css +4 -0
- package/package.json +1 -1
- package/types/engine/anchors.d.mts +4 -2
- package/types/engine/compendiums.d.mts +5 -1
- package/types/engine/compile-corpus.d.mts +5 -2
- package/types/engine/content-charset.d.mts +5 -1
- package/types/engine/content-html.d.mts +5 -1
- package/types/engine/content-icons.d.mts +5 -1
- package/types/engine/content-images.d.mts +5 -1
- package/types/engine/content-index.d.mts +39 -19
- package/types/engine/content-links.d.mts +22 -8
- package/types/engine/content-session.d.mts +108 -0
- package/types/engine/content-sources.d.mts +32 -0
- package/types/engine/content-spans.d.mts +3 -5
- package/types/engine/content-tasklists.d.mts +5 -1
- package/types/engine/diagnostics.d.mts +10 -0
- package/types/engine/extension-attributes.d.mts +16 -2
- package/types/engine/first-use.d.mts +31 -0
- package/types/engine/foundry-entries.d.mts +11 -4
- package/types/engine/generate.d.mts +5 -1
- package/types/engine/helpers.d.mts +30 -7
- package/types/engine/index.d.mts +1 -0
- package/types/engine/journals.d.mts +5 -2
- package/types/engine/lexical-sections.d.mts +3 -2
- package/types/engine/markup-validation.d.mts +2 -1
- package/types/engine/name-markup.d.mts +176 -0
- package/types/engine/name-rows.d.mts +35 -0
- package/types/engine/names.d.mts +356 -0
- package/types/engine/note-addresses.d.mts +3 -3
- package/types/engine/path-charset.d.mts +16 -1
- package/types/engine/site-build.d.mts +2 -2
- package/types/engine/site-index.d.mts +3 -1
- package/types/engine/sql-tables.d.mts +7 -2
- package/types/engine/stub-lint.d.mts +3 -1
|
@@ -339,8 +339,11 @@ infoboxes:
|
|
|
339
339
|
### Frontmatter has three regions
|
|
340
340
|
|
|
341
341
|
A HeroicLands note has opening YAML frontmatter with nonempty `shortcode` and
|
|
342
|
-
`type` values.
|
|
343
|
-
|
|
342
|
+
`type` values. The opening and closing `---` delimiters accept LF or CRLF line
|
|
343
|
+
endings. Parsing keeps the original source text and diagnostic positions;
|
|
344
|
+
malformed YAML is an error with either line ending. The charset check recommends
|
|
345
|
+
LF and reports carriage returns as warnings. Other Markdown files are ordinary
|
|
346
|
+
Markdown. A note's frontmatter has three regions:
|
|
344
347
|
|
|
345
348
|
| Region | Describes | Unknown keys |
|
|
346
349
|
| -------------------- | ------------------------------------------------------- | ------------ |
|
|
@@ -1805,6 +1808,86 @@ name at every citation with no link edited. `[[x|]]` is labelled; `[[x]]` is not
|
|
|
1805
1808
|
It is the label that is required, not a target: the anchor-only form above
|
|
1806
1809
|
carries its pipe like every other link.
|
|
1807
1810
|
|
|
1811
|
+
#### Marking names in prose
|
|
1812
|
+
|
|
1813
|
+
`package-build lint` reports name-marking warnings in authored body prose.
|
|
1814
|
+
The warnings leave a valid package passing. Headings, frontmatter, tables,
|
|
1815
|
+
infoboxes, code, comments and embeds are outside this check.
|
|
1816
|
+
|
|
1817
|
+
A name is explicit: a `[Name]{.name}` bracket span or an authored link label.
|
|
1818
|
+
Use `.name` for names without indexed notes and links for names with notes.
|
|
1819
|
+
Plain prose is not searched for names. Wikilinks, ordinary Markdown links and
|
|
1820
|
+
reference links all supply names; a resolved empty wikilink label uses the
|
|
1821
|
+
note's full name. Images, code, footnote markers and bare autolinks without
|
|
1822
|
+
authored labels supply none.
|
|
1823
|
+
|
|
1824
|
+
1. A `.name` span whose whole text matches an indexed `name.full` or
|
|
1825
|
+
`name.aliases` should be a link. Use `[[place-arden|Arden Vale]]` or
|
|
1826
|
+
`[[place-arden|]]` to introduce that note.
|
|
1827
|
+
2. Mark or link each name at most once per note. An indexed full name and its
|
|
1828
|
+
declared aliases share one identity, including across different heading
|
|
1829
|
+
anchors. Other labels use their whole text as their identity: different
|
|
1830
|
+
labels linking to one page are separate names, and a label is not
|
|
1831
|
+
automatically an alias of its destination.
|
|
1832
|
+
3. Introduce the full name before a short form or declared alias.
|
|
1833
|
+
Possessives and regular plurals count as uses; other indexed forms can be
|
|
1834
|
+
declared aliases.
|
|
1835
|
+
|
|
1836
|
+
Bold is emphasis. A whole bold span exactly matching an indexed full name or
|
|
1837
|
+
alias receives one advisory to mark or link the name. It supplies no name
|
|
1838
|
+
occurrence or introduction, and bold containing a marked or linked name
|
|
1839
|
+
receives no such advisory. Unknown bold phrases supply no names.
|
|
1840
|
+
|
|
1841
|
+
Inline Markdown remains active inside name spans, and name spans remain
|
|
1842
|
+
valid inside emphasis:
|
|
1843
|
+
|
|
1844
|
+
```markdown
|
|
1845
|
+
**This is a phrase with [Joe Blow]{.name} in it**
|
|
1846
|
+
```
|
|
1847
|
+
|
|
1848
|
+
The name is `Joe Blow`; the surrounding phrase is emphasis. Sites and books display
|
|
1849
|
+
`.name` text in bold.
|
|
1850
|
+
|
|
1851
|
+
Each affected occurrence produces one warning at its authored
|
|
1852
|
+
`file:line:column`, naming the full name or explicit label. A warning
|
|
1853
|
+
can describe several marking problems at that occurrence.
|
|
1854
|
+
|
|
1855
|
+
An optional `data-type` on a `.name` span states what the named thing is:
|
|
1856
|
+
|
|
1857
|
+
```markdown
|
|
1858
|
+
[Ravenmoor]{.name data-type="place/settlement"}
|
|
1859
|
+
[Aldric Venn]{.name data-type="being"}
|
|
1860
|
+
```
|
|
1861
|
+
|
|
1862
|
+
Use a declared [note type](note-types.md), optionally followed by `/` and one
|
|
1863
|
+
of that type's subtypes. An enumerated declaration requires a listed subtype;
|
|
1864
|
+
an open declaration accepts a well-formed subtype, and an absent subtype
|
|
1865
|
+
declaration admits none. An unknown or malformed supplied value is an error
|
|
1866
|
+
at the `data-type` attribute's file, line and UTF-16 column. The same attribute
|
|
1867
|
+
on a span without the exact `name` class is an ordinary attribute. An untyped
|
|
1868
|
+
`.name` is valid; its type is not inferred from a matching note.
|
|
1869
|
+
|
|
1870
|
+
The index captures actual `.name` spans throughout the authored body, including
|
|
1871
|
+
headings, tables, verse and spans within links. The prose-only marking checks
|
|
1872
|
+
above keep their narrower scope. Frontmatter, literal code, escaped syntax,
|
|
1873
|
+
unmarked link labels, bold, plain prose and generated content add no names.
|
|
1874
|
+
The rendered visible text preserves its spelling and inline content.
|
|
1875
|
+
|
|
1876
|
+
Each own-note metadata record carries `names` in authored order. Every entry
|
|
1877
|
+
has exactly `name`, `type`, `subType`, `line` and `column`; absent
|
|
1878
|
+
classifications are explicit `null`, and no occurrences gives an empty array.
|
|
1879
|
+
This is derived metadata, not frontmatter. Documentation mirrors and assets
|
|
1880
|
+
do not duplicate a note's occurrences. The [`names` relation](#content-tables)
|
|
1881
|
+
flattens these entries with their containing NOTE Address.
|
|
1882
|
+
|
|
1883
|
+
Lint groups names without an indexed full-name or alias match by supplied
|
|
1884
|
+
classification and lists all their source uses. It also groups conflicting
|
|
1885
|
+
annotations of the same visible name. Untyped uses do not conflict. A bare
|
|
1886
|
+
type and its subtype agree; different supplied types or different supplied
|
|
1887
|
+
subtypes conflict. One package advisory contains both groups and is located
|
|
1888
|
+
at the earliest reported use. Advisory findings preserve a successful lint
|
|
1889
|
+
exit status. Supplied-value errors prevent publication and compilation.
|
|
1890
|
+
|
|
1808
1891
|
### Assets are types
|
|
1809
1892
|
|
|
1810
1893
|
`icon`, `image` and `audio` are types in the type vocabulary, and an address
|
|
@@ -2911,7 +2994,7 @@ for the keys.
|
|
|
2911
2994
|
| `frames` | `data.sounds.frames` | `frame`, `shape`, `class` (a list), `makes`, `examples` (a list of `{ root, form }`) |
|
|
2912
2995
|
| `read_past` | `data.sounds.readPast` | `written`, `readAs`, `what` |
|
|
2913
2996
|
| `sound_changes` | `data.soundChanges` | One row per step: `position` (the set's), `name`, `from_language`, `to_language`, `takes`, `step_position` (the step's in its set), `step`, `change`, `where` |
|
|
2914
|
-
| `elements` | `data.elements` | `form`, `variants` (a list), `element_position` (the element's `position`), `kind`, `sense`, `examples` (a list), `word`, `
|
|
2997
|
+
| `elements` | `data.elements` | `form`, `variants` (a list), `element_position` (the element's `position`), `kind`, `sense`, `examples` (a list), `word`, `joining` |
|
|
2915
2998
|
| `roots` | `data.roots` | `root`, `sense`, `field` |
|
|
2916
2999
|
| `cognates` | `data.cognates` | One row per form of a set: `position` (the set's), `gloss`, `proto`, `form_language` (the form's language note), `form` |
|
|
2917
3000
|
| `calling_names` | `data.callingNames` | One row per closing: `form`, `doubled` (`true` or `false`), `usual`, and the language's `min_syllables` |
|
|
@@ -2922,7 +3005,7 @@ for the keys.
|
|
|
2922
3005
|
| `name_shapes` | `data.naming.shapes` | `of`, `syllables_min`, `syllables_max`, `per_vowel_min`, `per_vowel_max` |
|
|
2923
3006
|
| `name_edges` | `data.naming.edges` | `edge`, `sounds` (a list), `vowel` (`false` where unstated), `of` (`name` where unstated) |
|
|
2924
3007
|
| `name_draws` | `data.naming.draws` | `kind`, `elements` (a list) |
|
|
2925
|
-
| `held_from` | `data.naming.heldFrom` | `source`, `takes`, `clip`, `
|
|
3008
|
+
| `held_from` | `data.naming.heldFrom` | `source`, `takes`, `clip`, `joining` and `elements` (lists) |
|
|
2926
3009
|
| `near_names` | `data.naming.nearName.examples` | `near`, `given`, and the language's `min_syllables` |
|
|
2927
3010
|
|
|
2928
3011
|
A register name is carried by a note in the language's `data.scope` — any
|
|
@@ -2937,6 +3020,30 @@ office title, and by an anchor that has it as its name, which the row cites as
|
|
|
2937
3020
|
`max(form) FILTER (WHERE form_language = 'thalorna-note-skill-helonclng') AS "Helonic"`,
|
|
2938
3021
|
grouped by `position` and `gloss`.
|
|
2939
3022
|
|
|
3023
|
+
**`names`** is one row per actual `.name` span in an authored note body. Each
|
|
3024
|
+
row has these columns, including on an empty relation:
|
|
3025
|
+
|
|
3026
|
+
| Column | SQL type | Meaning |
|
|
3027
|
+
| --------- | --------- | -------------------------------------------------------------------------------------------- |
|
|
3028
|
+
| `owner` | `VARCHAR` | Containing authored page's canonical NOTE Address, or `NULL` when the record has no Address. |
|
|
3029
|
+
| `name` | `VARCHAR` | Rendered visible text, preserving its spelling. |
|
|
3030
|
+
| `type` | `VARCHAR` | Supplied note type, or `NULL`. |
|
|
3031
|
+
| `subType` | `VARCHAR` | Supplied subtype, or `NULL`. |
|
|
3032
|
+
| `line` | `BIGINT` | Original 1-based source line at the span's opening `[`. |
|
|
3033
|
+
| `column` | `BIGINT` | Original 1-based UTF-16 source column at that `[`. |
|
|
3034
|
+
|
|
3035
|
+
```sql
|
|
3036
|
+
SELECT owner, name, type, subType, line, "column"
|
|
3037
|
+
FROM names
|
|
3038
|
+
WHERE type = 'place'
|
|
3039
|
+
ORDER BY owner, line, "column"
|
|
3040
|
+
```
|
|
3041
|
+
|
|
3042
|
+
A native Item's source uses its NOTE page as `owner`. Declared dependency
|
|
3043
|
+
metadata supplies `names` in that package's SQL schema. Public queries exclude
|
|
3044
|
+
GM-only owners; all/native contexts retain them. Only authored occurrences
|
|
3045
|
+
contribute rows, so generated queries and sections add no further names.
|
|
3046
|
+
|
|
2940
3047
|
**`language_lineage`** is one row per language note and each language it
|
|
2941
3048
|
inherits words from through [`data.family`](#a-languages-words): `language`,
|
|
2942
3049
|
`ancestor` and `depth`, with the language itself at depth `0`, its parent at
|
|
@@ -3374,6 +3481,9 @@ An attribute-bearing opener allows a nested div to be distinguished from the bar
|
|
|
3374
3481
|
The watch wears [a _blue_ sash]{#watch-sash .uniform lang=en}.
|
|
3375
3482
|
```
|
|
3376
3483
|
|
|
3484
|
+
The exact `.name` class marks a name, with optional `data-type="type/subType"`;
|
|
3485
|
+
see [marking names in prose](#marking-names-in-prose) for the vocabulary and index.
|
|
3486
|
+
|
|
3377
3487
|
Inline Markdown remains active inside the span. Bracket spans have lower priority than URL links, image links, reference links, footnotes and wikilinks. A bracketed form immediately preceded by `!`, followed by `(` or another `[`, or beginning with `[[` is not a span, even when the link is unresolved. Ordinary links retain their existing syntax. Escape a bracket when it should be literal, or put examples inside a code span or fence. A span identifier addresses that point in the note; it does not start a journal page.
|
|
3378
3488
|
|
|
3379
3489
|
##### Alerts
|
|
@@ -5492,7 +5602,7 @@ SoHL sets `system.quantity` to `1` for each compendium item.
|
|
|
5492
5602
|
| `soundChanges[].steps[].step` | `string`, required | The step's label, as `A1` or `"1"`; no label stands twice in a set |
|
|
5493
5603
|
| `soundChanges[].steps[].change` | `string`, required | The change in arrow notation, as `` `ph` > `f`; `kh` > `h` ``, or the operation it names in words |
|
|
5494
5604
|
| `soundChanges[].steps[].where` | `string` | Where it holds, as `word end`; everywhere where absent |
|
|
5495
|
-
| `elements` | `{ form, variants?, position?, kind?, sense, examples?, word?,
|
|
5605
|
+
| `elements` | `{ form, variants?, position?, kind?, sense, examples?, word?, joining? }[]` | `language` only: the elements its names are built from |
|
|
5496
5606
|
| `elements[].form` | `string`, required | The element, as `arn-` |
|
|
5497
5607
|
| `elements[].variants` | `string[]` | The element's other forms, as `aldr-` beside `ald-` |
|
|
5498
5608
|
| `elements[].position` | `opening`, `closing`, `either` or `link` | Where in a name it stands |
|
|
@@ -5500,7 +5610,7 @@ SoHL sets `system.quantity` to `1` for each compendium item.
|
|
|
5500
5610
|
| `elements[].sense` | `string`, required | What it means |
|
|
5501
5611
|
| `elements[].examples` | `string[]` | The names it is read from |
|
|
5502
5612
|
| `elements[].word` | `string` | The word it is the stem of, a form of `words` of the language or an ancestor |
|
|
5503
|
-
| `elements[].
|
|
5613
|
+
| `elements[].joining` | `string` | The element's joining form, as `-borgar`, when it qualifies another word |
|
|
5504
5614
|
| `roots` | `{ root, sense, field? }[]` | `language` only: the consonantal roots its words are set from |
|
|
5505
5615
|
| `roots[].root` | `string`, required | The root, as `d-l-k` |
|
|
5506
5616
|
| `roots[].sense` | `string`, required | What it means |
|
|
@@ -5540,11 +5650,11 @@ SoHL sets `system.quantity` to `1` for each compendium item.
|
|
|
5540
5650
|
| `naming.draws` | `{ kind, elements }[]` | The element kinds each kind of name is built from |
|
|
5541
5651
|
| `naming.draws[].kind` | `byname`, `given` or `place`, required | The kind of name; once per kind |
|
|
5542
5652
|
| `naming.draws[].elements` | list of `byname`, `given` or `place`, required | The element kinds, as `elements[].kind` states them |
|
|
5543
|
-
| `naming.heldFrom` | `{ source, takes, clip?,
|
|
5653
|
+
| `naming.heldFrom` | `{ source, takes, clip?, joining?, elements? }[]` | What a place name takes first, by what the place is held from |
|
|
5544
5654
|
| `naming.heldFrom[].source` | `string`, required | What the place is held from, as `a founder` |
|
|
5545
5655
|
| `naming.heldFrom[].takes` | `stem`, `godName`, `givenName` or `element`, required | What stands first: a name-stem in any grade, a god's name, a founder's given name, or one of `elements` |
|
|
5546
5656
|
| `naming.heldFrom[].clip` | `first` | How the name is clipped: to its first element; whole where absent. A name row only |
|
|
5547
|
-
| `naming.heldFrom[].
|
|
5657
|
+
| `naming.heldFrom[].joining` | `string[]` | The joining endings, one of which stands at the seam after the name. A name row only |
|
|
5548
5658
|
| `naming.heldFrom[].elements` | `string[]` | The elements that may stand first; required on, and only on, a row that takes an `element` |
|
|
5549
5659
|
| `naming.nearName` | `{ minSyllables, examples? }` | The near name a long given name carries |
|
|
5550
5660
|
| `naming.nearName.minSyllables` | `integer`, required | The fewest syllables a given name has that carries a near name |
|
|
@@ -5856,7 +5966,7 @@ data:
|
|
|
5856
5966
|
- { kind: byname, elements: [byname, place] }
|
|
5857
5967
|
heldFrom:
|
|
5858
5968
|
- { source: a god, takes: godName, clip: first }
|
|
5859
|
-
- { source: a founder, takes: givenName,
|
|
5969
|
+
- { source: a founder, takes: givenName, joining: ["-s", "-a"] }
|
|
5860
5970
|
- { source: the ting and its law, takes: element, elements: ["ting-", "lög-"] }
|
|
5861
5971
|
nearName:
|
|
5862
5972
|
minSyllables: 4
|
|
@@ -5972,9 +6082,8 @@ form and its other forms, where in a name it stands (`opening`, `closing`,
|
|
|
5972
6082
|
`either` or `link`), its sense and the names it is read from. In a language
|
|
5973
6083
|
that keeps one set of elements for each kind of name, `kind` says which
|
|
5974
6084
|
(`byname`, `given` or `place`). `word` names the word of `words` the element is
|
|
5975
|
-
the stem of, the language's or an ancestor's, and `
|
|
5976
|
-
|
|
5977
|
-
is also a word stays a word.
|
|
6085
|
+
the stem of, the language's or an ancestor's, and `joining` gives its form
|
|
6086
|
+
when it qualifies another word. An element that is also a word stays a word.
|
|
5978
6087
|
|
|
5979
6088
|
**`roots` lists consonantal roots**, each with its sense and the field it
|
|
5980
6089
|
belongs to. **`cognates`** lists, on the eldest language of a set, the form one
|
|
@@ -6007,7 +6116,7 @@ there too and `of` where the edge holds for one class of name only; an edge
|
|
|
6007
6116
|
stands once for each class. `draws` gives the element kinds each kind of name
|
|
6008
6117
|
is built from. `heldFrom` gives what a place name takes first by what the
|
|
6009
6118
|
place is held from: a name-stem, a god's name, a founder's given name — that
|
|
6010
|
-
name `clip`ped to its `first` element, or followed by one of its `
|
|
6119
|
+
name `clip`ped to its `first` element, or followed by one of its `joining`
|
|
6011
6120
|
endings — or one of the `elements` listed. `nearName` states the fewest
|
|
6012
6121
|
syllables a given name has that carries a near name, with worked examples.
|
|
6013
6122
|
|
|
@@ -6056,7 +6165,7 @@ Each finding is located at the key at fault:
|
|
|
6056
6165
|
| A naming edge stated twice for one class of name, at the second | error |
|
|
6057
6166
|
| A kind of name whose draws are stated twice, at the second | error |
|
|
6058
6167
|
| A held-from row that takes an `element` and names no `elements`, at the row | error |
|
|
6059
|
-
| `elements` on a held-from row that takes no element, or `clip` or `
|
|
6168
|
+
| `elements` on a held-from row that takes no element, or `clip` or `joining` on one that takes no name, at the key | error |
|
|
6060
6169
|
| A `scope` Address that does not resolve, or a type that is no note type | error |
|
|
6061
6170
|
| A tongue `language` or a cognate form's language that is no language note | error |
|
|
6062
6171
|
| A register row with neither a `tongue` nor a `kind`, at the row | error |
|
|
@@ -6391,6 +6500,12 @@ word.
|
|
|
6391
6500
|
them, then its culture's `data.language`;
|
|
6392
6501
|
3. on a culture, its own `data.language`.
|
|
6393
6502
|
|
|
6503
|
+
On lore and affiliation notes, `data.language` and
|
|
6504
|
+
`data.glossary[].language` name language notes. Use `skill-provnzlng` for a
|
|
6505
|
+
language in this package, or `<pkg>-note-skill-provnzlng` for its complete
|
|
6506
|
+
NOTE Address. Any other system segment, including `sohl`, is an error at the
|
|
6507
|
+
authored value. A SOHL skill Address names a compiled Item.
|
|
6508
|
+
|
|
6394
6509
|
An affiliation's culture is the one it states or inherits, as
|
|
6395
6510
|
[Culture in the index](#culture-in-the-index) describes, and a culture's
|
|
6396
6511
|
`data.language` includes one it inherits from its
|
|
@@ -474,7 +474,7 @@ These fields are accepted by every note type. A field's value can still be irrel
|
|
|
474
474
|
| `data.scope` | a map — `{ folders?, cultures?, lore?, parents?, addresses?, paths?, types?, skip? }` | The notes the register covers. A language skill note only. |
|
|
475
475
|
| `data.sounds` | a map — `{ letters?, edges?, clusters?, stress?, finals?, syllables?, joining?, joins?, marks?, skeleton?, frames?, readPast?, mutations? }` | The letters the language writes and the rules its words keep. A language skill note only. |
|
|
476
476
|
| `data.soundChanges` | list of `{ name, from?, to?, takes?, steps }` | Named sets of ordered changes, each making one tongue's words from another's. A language skill note only. |
|
|
477
|
-
| `data.elements` | list of `{ form, variants?, position?, kind?, sense, examples?, word?,
|
|
477
|
+
| `data.elements` | list of `{ form, variants?, position?, kind?, sense, examples?, word?, joining? }` | The elements the language's names are built from. A language skill note only. |
|
|
478
478
|
| `data.roots` | list of `{ root, sense, field? }` | The consonantal roots the language's words are set from. A language skill note only. |
|
|
479
479
|
| `data.cognates` | list of `{ forms, proto?, gloss }` | Sets of forms one older word takes in several languages, stated on the eldest. A language skill note only. |
|
|
480
480
|
| `data.callingNames` | a map — `{ closings, minSyllables }` | The short name a long given name is called by. A language skill note only. |
|
|
@@ -635,7 +635,7 @@ These fields are accepted by every note type. A field's value can still be irrel
|
|
|
635
635
|
| `data.elements[].sense` | a string | yes | What the element means. |
|
|
636
636
|
| `data.elements[].examples` | a list of names | no | The names the element is read from. |
|
|
637
637
|
| `data.elements[].word` | a form of the language's words | no | The word the element is the stem of, a form of `data.words` of the language or an ancestor. |
|
|
638
|
-
| `data.elements[].
|
|
638
|
+
| `data.elements[].joining` | the element as written, such as `-borgar` | no | The element's joining form, as written when it qualifies another word. |
|
|
639
639
|
| `data.roots[].root` | a consonantal skeleton, such as `d-l-k` | yes | The root. |
|
|
640
640
|
| `data.roots[].sense` | a string | yes | What the root means. |
|
|
641
641
|
| `data.roots[].field` | a string, such as `stone` | no | The field the root belongs to. |
|
|
@@ -668,11 +668,11 @@ These fields are accepted by every note type. A field's value can still be irrel
|
|
|
668
668
|
| `data.naming.draws` | list of `{ kind, elements }` | no | The element kinds each kind of name is built from. |
|
|
669
669
|
| `data.naming.draws[].kind` | one of byname, given, place | yes | The kind of name; once per kind. |
|
|
670
670
|
| `data.naming.draws[].elements` | a list of element kinds | yes | The kinds of element, as `data.elements` states them, the name is built from. |
|
|
671
|
-
| `data.naming.heldFrom` | list of `{ source, takes, clip?,
|
|
671
|
+
| `data.naming.heldFrom` | list of `{ source, takes, clip?, joining?, elements? }` | no | What a place name takes first, by what the place is held from. |
|
|
672
672
|
| `data.naming.heldFrom[].source` | a string, such as `a founder` | yes | What the place is held from. |
|
|
673
673
|
| `data.naming.heldFrom[].takes` | one of stem, godName, givenName, element | yes | What stands first: a name-stem in any grade, a god's name, a founder's given name, or one of `elements`. |
|
|
674
674
|
| `data.naming.heldFrom[].clip` | one of first | no | How the name is clipped: `first`, to its first element; whole where absent. |
|
|
675
|
-
| `data.naming.heldFrom[].
|
|
675
|
+
| `data.naming.heldFrom[].joining` | a list of endings | no | The joining endings, one of which stands at the seam after the name. |
|
|
676
676
|
| `data.naming.heldFrom[].elements` | a list of elements | no | The elements that may stand first, for a row that takes an `element`. |
|
|
677
677
|
| `data.naming.nearName` | a map — `{ minSyllables, examples? }` | no | The near name a long given name carries. |
|
|
678
678
|
| `data.naming.nearName.minSyllables` | a whole number | yes | The fewest syllables a given name has that carries a near name. |
|
package/engine/anchors.mjs
CHANGED
|
@@ -65,10 +65,11 @@ export const ANCHOR_KINDS = Object.freeze(["prose", "event"]);
|
|
|
65
65
|
* picture's address to the role its asset declares, so a figure anchor's
|
|
66
66
|
* `name` reads `Map 1` rather than `Figure 1` where it is due — see
|
|
67
67
|
* {@link module:engine/content-figures.scanFigures}.
|
|
68
|
+
* @param {import("./content-session.mjs").ContentSyntax} [syntax] - Exact-text scanner projections.
|
|
68
69
|
* @returns {Array<{slug: string, name: string, level: number, line: number,
|
|
69
70
|
* kind: string}>} In document order.
|
|
70
71
|
*/
|
|
71
|
-
export function collectAnchors(body, bodyLine = 1, resolveRole) {
|
|
72
|
+
export function collectAnchors(body, bodyLine = 1, resolveRole, syntax) {
|
|
72
73
|
const anchors = [];
|
|
73
74
|
for (const heading of scanHeadingAttributes(body, bodyLine).headings) {
|
|
74
75
|
if (!heading.id) continue;
|
|
@@ -92,7 +93,7 @@ export function collectAnchors(body, bodyLine = 1, resolveRole) {
|
|
|
92
93
|
}
|
|
93
94
|
const blockAnchors = (source, firstLine) => {
|
|
94
95
|
const sourceLines = source.split("\n");
|
|
95
|
-
for (const block of scanBlocks(source).blocks) {
|
|
96
|
+
for (const block of (syntax ? syntax(source).blocks : scanBlocks(source)).blocks) {
|
|
96
97
|
if (block.id)
|
|
97
98
|
anchors.push({
|
|
98
99
|
slug: slugify(block.id),
|
|
@@ -108,7 +109,7 @@ export function collectAnchors(body, bodyLine = 1, resolveRole) {
|
|
|
108
109
|
}
|
|
109
110
|
};
|
|
110
111
|
blockAnchors(String(body ?? ""), bodyLine);
|
|
111
|
-
for (const alert of scanAlerts(body).blocks) {
|
|
112
|
+
for (const alert of (syntax ? syntax(body).alerts : scanAlerts(body)).blocks) {
|
|
112
113
|
if (alert.id)
|
|
113
114
|
anchors.push({
|
|
114
115
|
slug: slugify(alert.id),
|
|
@@ -128,7 +129,7 @@ export function collectAnchors(body, bodyLine = 1, resolveRole) {
|
|
|
128
129
|
kind: "prose",
|
|
129
130
|
});
|
|
130
131
|
}
|
|
131
|
-
for (const span of scanSpans(body).spans) {
|
|
132
|
+
for (const span of (syntax ? syntax(body).spans : scanSpans(body)).spans) {
|
|
132
133
|
if (span.id)
|
|
133
134
|
anchors.push({
|
|
134
135
|
slug: slugify(span.id),
|
|
@@ -144,11 +145,12 @@ export function collectAnchors(body, bodyLine = 1, resolveRole) {
|
|
|
144
145
|
/** Collisions involving a div, verse or inline-span anchor.
|
|
145
146
|
* Heading and caption collisions are diagnosed by their existing scanners.
|
|
146
147
|
* @param {string} body - Authored Markdown.
|
|
148
|
+
* @param {import("./content-session.mjs").ContentSyntax} [syntax] - Exact-text scanner projections.
|
|
147
149
|
* @returns {Array<{line: number, column: number, message: string}>} Located collisions.
|
|
148
150
|
*/
|
|
149
|
-
export function markupAnchorFindings(body) {
|
|
151
|
+
export function markupAnchorFindings(body, syntax) {
|
|
150
152
|
const groups = new Map();
|
|
151
|
-
for (const anchor of collectAnchors(body)) {
|
|
153
|
+
for (const anchor of collectAnchors(body, 1, undefined, syntax)) {
|
|
152
154
|
const slug = slugify(anchor.slug);
|
|
153
155
|
if (!groups.has(slug)) groups.set(slug, []);
|
|
154
156
|
groups.get(slug).push(anchor);
|
package/engine/base-compiler.mjs
CHANGED
|
@@ -686,11 +686,13 @@ export class BasePackCompiler {
|
|
|
686
686
|
// it made — `errors` or `findings`, never just the first — so nothing
|
|
687
687
|
// here re-scans; it only stops taking `[0]`.
|
|
688
688
|
const findings = [];
|
|
689
|
+
const syntax = this.corpus?.syntax;
|
|
690
|
+
const scanned = syntax?.(body);
|
|
689
691
|
for (const secretError of [
|
|
690
|
-
...scanBlocks(body).errors,
|
|
691
|
-
...scanAlerts(body).errors,
|
|
692
|
-
...scanSpans(body).errors,
|
|
693
|
-
...markupAnchorFindings(body),
|
|
692
|
+
...(scanned?.blocks ?? scanBlocks(body)).errors,
|
|
693
|
+
...(scanned?.alerts ?? scanAlerts(body)).errors,
|
|
694
|
+
...(scanned?.spans ?? scanSpans(body)).errors,
|
|
695
|
+
...markupAnchorFindings(body, syntax),
|
|
694
696
|
]) {
|
|
695
697
|
findings.push({
|
|
696
698
|
message: secretError.message,
|
|
@@ -46,33 +46,37 @@ import {
|
|
|
46
46
|
const CHANGES_HEADING_RE = /^### (?:Major|Minor|Patch) Changes$/gm;
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* A
|
|
49
|
+
* A label, its complete opening paragraph and its remaining items.
|
|
50
50
|
*
|
|
51
51
|
* @param {string} blockText - One block, as {@link topLevelBlocks} collects
|
|
52
52
|
* it — its first line is the label line for a labelled block.
|
|
53
|
-
* @returns {{labelLine: string,
|
|
54
|
-
*
|
|
55
|
-
* line yields `""`. `hadBlankLine` says whether the label line was
|
|
56
|
-
* followed by a blank line in `blockText` — false for a bold sentence a
|
|
57
|
-
* hard-wrapped paragraph continues directly beneath, with no blank line
|
|
58
|
-
* of its own.
|
|
53
|
+
* @returns {{labelLine: string, introduction: string, rest: string}} The
|
|
54
|
+
* introduction retains hard wrapping and stops before a bullet or code.
|
|
59
55
|
*/
|
|
60
|
-
function
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
const
|
|
56
|
+
function splitLabelBlock(blockText) {
|
|
57
|
+
const lines = blockText.replace(/\s+$/, "").split("\n");
|
|
58
|
+
const labelLine = /^\*\*[^*]+\*\*/.exec(lines[0])[0];
|
|
59
|
+
const inline = lines[0].slice(labelLine.length).trimStart();
|
|
60
|
+
const introduction = inline ? [inline] : [];
|
|
61
|
+
const codeLines = codeLineSet(blockText);
|
|
62
|
+
let cursor = 1;
|
|
63
|
+
while (
|
|
64
|
+
cursor < lines.length &&
|
|
65
|
+
lines[cursor].trim() !== "" &&
|
|
66
|
+
!/^-\s/.test(lines[cursor]) &&
|
|
67
|
+
!codeLines.has(cursor + 1)
|
|
68
|
+
)
|
|
69
|
+
introduction.push(lines[cursor++]);
|
|
64
70
|
return {
|
|
65
|
-
labelLine
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
labelLine,
|
|
72
|
+
introduction: introduction.join("\n"),
|
|
73
|
+
rest: lines.slice(cursor).join("\n").replace(/^\n+/, ""),
|
|
68
74
|
};
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
/**
|
|
72
|
-
* A block's top-level
|
|
73
|
-
*
|
|
74
|
-
* deduplicates, so a bulleted `**Compendiums**` block and a prose one merge
|
|
75
|
-
* on the same footing.
|
|
78
|
+
* A block's top-level bullets and the prose or code between them, in source
|
|
79
|
+
* order. A bullet retains its indented continuation and nested items.
|
|
76
80
|
*
|
|
77
81
|
* @param {string} text - A block's content, label line already stripped (or
|
|
78
82
|
* a lead block's whole text).
|
|
@@ -83,7 +87,26 @@ function itemsOf(text) {
|
|
|
83
87
|
if (!trimmed) return [];
|
|
84
88
|
const bullets = topLevelBullets(trimmed, codeLineSet(trimmed));
|
|
85
89
|
if (bullets.length === 0) return [trimmed];
|
|
86
|
-
|
|
90
|
+
const starts = [];
|
|
91
|
+
let offset = 0;
|
|
92
|
+
for (const line of trimmed.split("\n")) {
|
|
93
|
+
starts.push(offset);
|
|
94
|
+
offset += line.length + 1;
|
|
95
|
+
}
|
|
96
|
+
const items = [];
|
|
97
|
+
const append = (part) => {
|
|
98
|
+
const item = part.replace(/^(?:[ \t]*\n)+/, "").replace(/\s+$/, "");
|
|
99
|
+
if (item) items.push(item);
|
|
100
|
+
};
|
|
101
|
+
let cursor = 0;
|
|
102
|
+
for (const bullet of bullets) {
|
|
103
|
+
const start = starts[bullet.startLine - 1];
|
|
104
|
+
append(trimmed.slice(cursor, start));
|
|
105
|
+
append(bullet.text);
|
|
106
|
+
cursor = start + bullet.text.length;
|
|
107
|
+
}
|
|
108
|
+
append(trimmed.slice(cursor));
|
|
109
|
+
return items;
|
|
87
110
|
}
|
|
88
111
|
|
|
89
112
|
/**
|
|
@@ -124,13 +147,9 @@ function joinItems(items) {
|
|
|
124
147
|
* One label's blocks (or every unlabelled one), folded into the single
|
|
125
148
|
* block `group` writes for it.
|
|
126
149
|
*
|
|
127
|
-
* A single
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* beneath must round-trip unchanged, not gain a blank line it never had.
|
|
131
|
-
* Merging two or more blocks, or a label whose lone block carries several
|
|
132
|
-
* items (bullets), still separates the label from its body with a blank
|
|
133
|
-
* line — the convention every existing multi-item block already uses.
|
|
150
|
+
* A single block with no duplicate items retains its own spacing. Merged
|
|
151
|
+
* opening paragraphs form one hard-wrapped introduction before the items,
|
|
152
|
+
* so paragraph boundaries cannot turn labelled text into unlabelled lead.
|
|
134
153
|
*
|
|
135
154
|
* @param {Array<{label: string|null, text: string}>} blocks - Every block
|
|
136
155
|
* sharing one label, in file order. All carry the same `label`.
|
|
@@ -140,13 +159,15 @@ function mergeGroup(blocks) {
|
|
|
140
159
|
if (blocks[0].label === null) {
|
|
141
160
|
return joinItems(dedupeItems(blocks.flatMap((block) => itemsOf(block.text))));
|
|
142
161
|
}
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
162
|
+
const parts = blocks.map((block) => splitLabelBlock(block.text));
|
|
163
|
+
const introductions = dedupeItems(parts.map((part) => part.introduction).filter(Boolean));
|
|
164
|
+
const originalItems = parts.flatMap((part) => itemsOf(part.rest));
|
|
165
|
+
const items = dedupeItems(originalItems);
|
|
166
|
+
if (blocks.length === 1 && items.length === originalItems.length)
|
|
167
|
+
return blocks[0].text.replace(/\s+$/, "");
|
|
168
|
+
const heading =
|
|
169
|
+
parts[0].labelLine + (introductions.length ? ` ${introductions.join("\n")}` : "");
|
|
170
|
+
return heading + (items.length ? `\n\n${joinItems(items)}` : "");
|
|
150
171
|
}
|
|
151
172
|
|
|
152
173
|
/**
|
|
@@ -245,7 +266,10 @@ export function groupChangelogText(text, { labels = null } = {}) {
|
|
|
245
266
|
if (!range) return { text, findings: [] };
|
|
246
267
|
|
|
247
268
|
const section = text.slice(range.start, range.end);
|
|
248
|
-
const
|
|
269
|
+
const codeLines = codeLineSet(section);
|
|
270
|
+
const headings = [...section.matchAll(CHANGES_HEADING_RE)].filter(
|
|
271
|
+
(heading) => !codeLines.has(lineColOf(section, heading.index).line),
|
|
272
|
+
);
|
|
249
273
|
if (headings.length === 0) return { text, findings: [] };
|
|
250
274
|
|
|
251
275
|
let rebuilt = section.slice(0, headings[0].index);
|
|
@@ -277,7 +277,8 @@ export function topLevelBlocks(text, codeLines, scaffoldLines = new Set()) {
|
|
|
277
277
|
const raw = lines[i];
|
|
278
278
|
|
|
279
279
|
if (codeLines.has(lineNo)) {
|
|
280
|
-
|
|
280
|
+
current ??= { startLine: lineNo, label: null, parts: [] };
|
|
281
|
+
current.parts.push(raw);
|
|
281
282
|
afterBlank = raw.trim() === "";
|
|
282
283
|
continue;
|
|
283
284
|
}
|
|
@@ -800,7 +801,10 @@ export function lintReleaseText(text, { labels = null } = {}) {
|
|
|
800
801
|
* of the file.
|
|
801
802
|
*/
|
|
802
803
|
export function releaseSectionRange(text) {
|
|
803
|
-
const
|
|
804
|
+
const codeLines = codeLineSet(text);
|
|
805
|
+
const matches = [...text.matchAll(/^## /gm)].filter(
|
|
806
|
+
(heading) => !codeLines.has(lineColOf(text, heading.index).line),
|
|
807
|
+
);
|
|
804
808
|
if (matches.length === 0) return null;
|
|
805
809
|
const start = matches[0].index;
|
|
806
810
|
const end = matches.length > 1 ? matches[1].index : text.length;
|
package/engine/compendiums.mjs
CHANGED
|
@@ -70,6 +70,8 @@ import { loadPackConfig } from "./pack-config.mjs";
|
|
|
70
70
|
* @param {string} [opts.stageDest] Directory the LevelDB packs are built
|
|
71
71
|
* into, one subdirectory per pack. Defaults to the configured stage.
|
|
72
72
|
* @param {string} [opts.packName] Restrict the run to a single pack.
|
|
73
|
+
* @param {import("./content-session.mjs").ContentSession} [opts.session] - Captured context shared with other operations.
|
|
74
|
+
* @param {boolean} [opts.reportPreparation=true] - Emit shared preparation findings unless the caller already reported them.
|
|
73
75
|
* @throws {Error} If pack JSON generation reported any error. Packs compiled
|
|
74
76
|
* from incomplete or empty JSON ship blank or short compendiums, and the
|
|
75
77
|
* omission is invisible until a player looks for content that is not there
|
|
@@ -86,7 +88,14 @@ export async function compilePacks({
|
|
|
86
88
|
sourcePacks = config.packDirectories,
|
|
87
89
|
stageDest = config.paths.stage,
|
|
88
90
|
packName,
|
|
91
|
+
session,
|
|
92
|
+
reportPreparation = true,
|
|
89
93
|
} = {}) {
|
|
94
|
+
session?.assertContext({
|
|
95
|
+
config,
|
|
96
|
+
contentBase: config.paths.content,
|
|
97
|
+
skipDirectories: config.skipDirectories,
|
|
98
|
+
});
|
|
90
99
|
const packNames = sourcePacks.filter((name) => !packName || name === packName);
|
|
91
100
|
|
|
92
101
|
// A prebuilt pack's JSON is checked in rather than generated, so it is
|
|
@@ -105,7 +114,12 @@ export async function compilePacks({
|
|
|
105
114
|
// content tree, and a package that ships only prebuilt packs has none at
|
|
106
115
|
// all — so running it would fail the build over a tree nothing reads.
|
|
107
116
|
if (needsGeneration) {
|
|
108
|
-
const errors = await generatePacksJson({
|
|
117
|
+
const errors = await generatePacksJson({
|
|
118
|
+
only: packName,
|
|
119
|
+
config,
|
|
120
|
+
session,
|
|
121
|
+
reportPreparation,
|
|
122
|
+
});
|
|
109
123
|
if (errors > 0) {
|
|
110
124
|
throw new Error(
|
|
111
125
|
`Pack JSON generation reported ${errors} error(s); refusing to ` +
|
|
@@ -60,7 +60,8 @@ import { prepareTreeSqlTables } from "./sql-tables.mjs";
|
|
|
60
60
|
* created when none is passed, and returned on the corpus either way — a note
|
|
61
61
|
* the index refuses is a note the compile must still *report*, exactly as the
|
|
62
62
|
* compile loop reported it when the loop was the first to see it.
|
|
63
|
-
* @
|
|
63
|
+
* @param {import("./content-session.mjs").ContentSession} [opts.session] - Captured context shared with other operations.
|
|
64
|
+
* @returns {Promise<{records: object[], parsed: Map<string, object>, linkIndex: object, sqlTables: Map<string, object[]>|undefined, problems: object[], syntax?: import("./content-session.mjs").ContentSyntax}>} The corpus,
|
|
64
65
|
* its indexes, and the notes it could not record.
|
|
65
66
|
*/
|
|
66
67
|
export async function buildCompileCorpus({
|
|
@@ -70,8 +71,20 @@ export async function buildCompileCorpus({
|
|
|
70
71
|
router,
|
|
71
72
|
config,
|
|
72
73
|
problems,
|
|
74
|
+
session,
|
|
73
75
|
}) {
|
|
74
76
|
const resolved = config ?? loadPackConfig();
|
|
77
|
+
if (session) {
|
|
78
|
+
session.assertContext({
|
|
79
|
+
config: resolved,
|
|
80
|
+
contentBase,
|
|
81
|
+
assetsBase,
|
|
82
|
+
skipDirectories,
|
|
83
|
+
router,
|
|
84
|
+
});
|
|
85
|
+
if (problems && problems !== session.problems) problems.push(...session.problems);
|
|
86
|
+
return session.compileCorpus();
|
|
87
|
+
}
|
|
75
88
|
const collected = problems ?? [];
|
|
76
89
|
const parsed = new Map();
|
|
77
90
|
const records = indexRecordsFor({
|