@heroiclands/package-build 23.0.0 → 23.1.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 +66 -0
- package/README.md +1 -1
- package/bin/content-commands.mjs +1 -1
- package/ci/content-format-reference.mjs +46 -0
- package/docs/api.md +124 -141
- package/docs/authoring/assets.md +1 -1
- package/docs/authoring/dates-and-calendars.md +12 -6
- package/docs/authoring/frontmatter.md +2 -2
- package/docs/authoring/links-and-markup.md +145 -29
- package/docs/commands.md +8 -10
- package/docs/configuration.md +13 -11
- package/docs/content-format.md +2 -2
- package/docs/guides/book.md +59 -4
- package/docs/guides/icons.md +4 -5
- package/docs/guides/packs.md +6 -0
- package/docs/guides/reference-pages.md +21 -1
- package/docs/guides/release-and-deploy.md +39 -7
- package/docs/guides/site.md +17 -18
- package/docs/reference/format-details.md +877 -276
- package/docs/reference/note-types.md +278 -91
- package/docs/reference/theme-pages.md +58 -97
- package/engine/address-values.mjs +124 -4
- package/engine/address.mjs +44 -3
- package/engine/anchors.mjs +103 -3
- package/engine/art-slots.mjs +2 -9
- package/engine/base-compiler.mjs +25 -6
- package/engine/calendar-artifacts.mjs +4 -1
- package/engine/calendar-notes.mjs +166 -102
- package/engine/content-format-check.mjs +2 -3
- package/engine/content-format.mjs +44 -1
- package/engine/content-format.yaml +79 -16
- package/engine/content-images.mjs +10 -1
- package/engine/content-index.mjs +37 -9
- package/engine/content-links.mjs +24 -5
- package/engine/content-tables.mjs +24 -3
- package/engine/data-keys.mjs +347 -0
- package/engine/derived-sections.mjs +322 -0
- package/engine/diagnostics.mjs +28 -0
- package/engine/event-fields.mjs +187 -0
- package/engine/event-rows.mjs +126 -0
- package/engine/event-views.mjs +305 -0
- package/engine/exported-scene.mjs +29 -53
- package/engine/frontmatter-lint.mjs +103 -77
- package/engine/frontmatter.mjs +7 -2
- package/engine/generate.mjs +53 -0
- package/engine/generated-sections.mjs +223 -0
- package/engine/heading-attributes.mjs +4 -2
- package/engine/helpers.mjs +12 -1
- package/engine/holdings.mjs +12 -33
- package/engine/ids.mjs +17 -11
- package/engine/index.mjs +4 -4
- package/engine/infobox-render.mjs +21 -6
- package/engine/infobox.mjs +217 -99
- package/engine/journals.mjs +104 -47
- package/engine/literature-notes.mjs +4 -1
- package/engine/literature-works.mjs +21 -13
- package/engine/map-graphviz.mjs +4 -18
- package/engine/markdown-expressions.mjs +116 -11
- package/engine/metadata-index.mjs +26 -0
- package/engine/near-miss.mjs +90 -0
- package/engine/note-addresses.mjs +75 -19
- package/engine/note-dates.mjs +5 -3
- package/engine/note-event-terms.mjs +364 -0
- package/engine/note-events.mjs +823 -0
- package/engine/note-vocabulary.mjs +184 -88
- package/engine/office-holders.mjs +20 -54
- package/engine/packagebuild-index.mjs +16 -1
- package/engine/pdf-build.mjs +97 -60
- package/engine/pdf-index.mjs +263 -0
- package/engine/pdf-render.mjs +47 -5
- package/engine/place-relation-terms.mjs +174 -0
- package/engine/place-relations.mjs +72 -160
- package/engine/rank-ladder.mjs +14 -41
- package/engine/reckoning-markers.mjs +2 -0
- package/engine/region-events.mjs +6 -12
- package/engine/scenes.mjs +73 -468
- package/engine/site-build.mjs +75 -101
- package/engine/site-index.mjs +3 -1
- package/engine/site-maps.mjs +43 -29
- package/engine/site-preview.mjs +40 -31
- package/engine/social-ties.mjs +2 -1
- package/engine/sql-tables.mjs +212 -9
- package/engine/standing-terms.mjs +145 -6
- package/engine/standings.mjs +37 -49
- package/engine/web-wikilinks.mjs +21 -1
- package/engine/wikilink-syntax.mjs +34 -4
- package/engine/wikilinks.mjs +27 -2
- package/engine/yaml-lint.mjs +7 -4
- package/hugo-theme/layouts/partials/infobox/entry.html +4 -1
- package/hugo-theme/layouts/partials/page-body.html +0 -3
- package/hugo-theme/static/css/style.css +25 -55
- package/package.json +1 -2
- package/packagebuild-metadata.jsonl +13 -13
- package/release.mjs +62 -4
- package/sohl/note-schemas.mjs +8 -73
- package/types/engine/address-values.d.mts +50 -3
- package/types/engine/address.d.mts +21 -0
- package/types/engine/anchors.d.mts +49 -2
- package/types/engine/art-slots.d.mts +1 -1
- package/types/engine/base-compiler.d.mts +13 -4
- package/types/engine/calendar-notes.d.mts +7 -0
- package/types/engine/content-format.d.mts +52 -0
- package/types/engine/content-images.d.mts +5 -1
- package/types/engine/content-tables.d.mts +9 -1
- package/types/engine/data-keys.d.mts +145 -0
- package/types/engine/derived-sections.d.mts +73 -0
- package/types/engine/diagnostics.d.mts +15 -0
- package/types/engine/event-fields.d.mts +99 -0
- package/types/engine/event-rows.d.mts +12 -0
- package/types/engine/event-views.d.mts +67 -0
- package/types/engine/exported-scene.d.mts +5 -2
- package/types/engine/frontmatter-lint.d.mts +3 -3
- package/types/engine/generated-sections.d.mts +143 -0
- package/types/engine/holdings.d.mts +6 -20
- package/types/engine/ids.d.mts +15 -12
- package/types/engine/index.d.mts +1 -1
- package/types/engine/infobox.d.mts +13 -3
- package/types/engine/journals.d.mts +24 -11
- package/types/engine/literature-works.d.mts +1 -2
- package/types/engine/map-graphviz.d.mts +2 -10
- package/types/engine/markdown-expressions.d.mts +12 -3
- package/types/engine/near-miss.d.mts +42 -0
- package/types/engine/note-addresses.d.mts +7 -1
- package/types/engine/note-event-terms.d.mts +68 -0
- package/types/engine/note-events.d.mts +71 -0
- package/types/engine/note-vocabulary.d.mts +37 -8
- package/types/engine/office-holders.d.mts +3 -0
- package/types/engine/pdf-index.d.mts +35 -0
- package/types/engine/pdf-render.d.mts +8 -2
- package/types/engine/place-relation-terms.d.mts +83 -0
- package/types/engine/place-relations.d.mts +11 -66
- package/types/engine/rank-ladder.d.mts +2 -0
- package/types/engine/region-events.d.mts +6 -12
- package/types/engine/scenes.d.mts +6 -32
- package/types/engine/site-build.d.mts +8 -22
- package/types/engine/site-maps.d.mts +15 -7
- package/types/engine/sql-tables.d.mts +77 -2
- package/types/engine/standing-terms.d.mts +37 -4
- package/types/engine/standings.d.mts +14 -9
- package/types/engine/wikilink-syntax.d.mts +20 -0
- package/types/engine/wikilinks.d.mts +3 -2
- package/types/engine/yaml-lint.d.mts +5 -4
- package/types/release.d.mts +4 -0
- package/docs/guides/government-migration.md +0 -52
- package/engine/itinerary-scenes.mjs +0 -166
- package/engine/lore-events.mjs +0 -197
- package/engine/map-notes.mjs +0 -1309
- package/engine/map-raster.mjs +0 -51
- package/hugo-theme/layouts/partials/holdings/entry.html +0 -21
- package/hugo-theme/layouts/partials/holdings/kind-groups.html +0 -71
- package/hugo-theme/layouts/partials/holdings.html +0 -61
- package/hugo-theme/layouts/partials/literature-works.html +0 -29
- package/hugo-theme/layouts/partials/place-map.html +0 -9
- package/types/engine/itinerary-scenes.d.mts +0 -29
- package/types/engine/lore-events.d.mts +0 -14
- package/types/engine/map-notes.d.mts +0 -297
- package/types/engine/map-raster.d.mts +0 -8
package/engine/base-compiler.mjs
CHANGED
|
@@ -71,6 +71,7 @@ import { scanFigures } from "./content-figures.mjs";
|
|
|
71
71
|
import { footnoteFindings } from "./content-footnotes.mjs";
|
|
72
72
|
import { checkImages } from "./content-images.mjs";
|
|
73
73
|
import { renderMarkdownExpressions } from "./markdown-expressions.mjs";
|
|
74
|
+
import { eventNoteIndex } from "./event-fields.mjs";
|
|
74
75
|
import { isGmNote } from "./note-vocabulary.mjs";
|
|
75
76
|
import { cloneAddressState } from "./address-values.mjs";
|
|
76
77
|
import fs from "fs";
|
|
@@ -91,7 +92,7 @@ import {
|
|
|
91
92
|
import { isNoteRecord, noteFile } from "./index-records.mjs";
|
|
92
93
|
import { reduceAddressFields } from "./address-fields.mjs";
|
|
93
94
|
import { artPathname, artSlot, unacceptedArtMessage, unresolvedArtMessage } from "./art-fields.mjs";
|
|
94
|
-
import { emitDiagnostic, positionOfLiteral } from "./diagnostics.mjs";
|
|
95
|
+
import { emitDiagnostic, positionOfFrontmatterPath, positionOfLiteral } from "./diagnostics.mjs";
|
|
95
96
|
import { assertNoDeclaredPackage } from "./note-package.mjs";
|
|
96
97
|
import { assertNoDeclaredFolder } from "./folder-notes.mjs";
|
|
97
98
|
import {
|
|
@@ -260,10 +261,8 @@ export class BasePackCompiler {
|
|
|
260
261
|
* here is what lets the lint tell an inert key from a live one.
|
|
261
262
|
*
|
|
262
263
|
* A pass that emits art **anywhere** in its document declares it, not only
|
|
263
|
-
* one that writes a top-level `img
|
|
264
|
-
*
|
|
265
|
-
* is no less live for it. The question this answers is whether the authored
|
|
266
|
-
* path reaches the output at all.
|
|
264
|
+
* one that writes a top-level `img`. The question this answers is whether
|
|
265
|
+
* the authored path reaches the output at all.
|
|
267
266
|
*
|
|
268
267
|
* @type {readonly string[]}
|
|
269
268
|
*/
|
|
@@ -786,6 +785,7 @@ export class BasePackCompiler {
|
|
|
786
785
|
file: absPath,
|
|
787
786
|
bodyLine,
|
|
788
787
|
figures: { get: (id) => figuresById.get(id), link: foundryRefLink },
|
|
788
|
+
events: this.eventNotes(),
|
|
789
789
|
});
|
|
790
790
|
for (const finding of expressions.findings) {
|
|
791
791
|
findings.push({ message: finding.message, line: finding.line, column: finding.column });
|
|
@@ -812,6 +812,23 @@ export class BasePackCompiler {
|
|
|
812
812
|
return markdown;
|
|
813
813
|
}
|
|
814
814
|
|
|
815
|
+
/**
|
|
816
|
+
* Every note's events by Address, for an inline reference to an event's
|
|
817
|
+
* field — built once per compile from the corpus's records and the fetched
|
|
818
|
+
* dependency indexes, the same inputs the website and the book build it
|
|
819
|
+
* from, so a reference prints the same text on all three.
|
|
820
|
+
*
|
|
821
|
+
* @returns {{note: (written: string) => object|undefined}} The lookup.
|
|
822
|
+
*/
|
|
823
|
+
eventNotes() {
|
|
824
|
+
this.eventNoteLookup ??= eventNoteIndex(this.corpus?.records ?? [], {
|
|
825
|
+
contentPackage: this.linkIndex?.contentPackage,
|
|
826
|
+
foreignIndex: this.linkIndex?.foreign,
|
|
827
|
+
dates: reckoningContext(this.linkIndex),
|
|
828
|
+
});
|
|
829
|
+
return this.eventNoteLookup;
|
|
830
|
+
}
|
|
831
|
+
|
|
815
832
|
/**
|
|
816
833
|
* The Foundry path one authored art address names, or `null`.
|
|
817
834
|
*
|
|
@@ -1368,7 +1385,9 @@ export class BasePackCompiler {
|
|
|
1368
1385
|
);
|
|
1369
1386
|
const position =
|
|
1370
1387
|
err.position ??
|
|
1371
|
-
(
|
|
1388
|
+
(err.keyPath ?
|
|
1389
|
+
positionOfFrontmatterPath(fs.readFileSync(absPath, "utf8"), err.keyPath)
|
|
1390
|
+
: repeatedAnchor ?
|
|
1372
1391
|
positionOfLiteral(
|
|
1373
1392
|
fs.readFileSync(absPath, "utf8"),
|
|
1374
1393
|
`{#${repeatedAnchor[1]}}`,
|
|
@@ -14,6 +14,7 @@ import { calendariaEnvelope, compileCalendars } from "./calendar-notes.mjs";
|
|
|
14
14
|
import { formatDiagnostic, positionOfFrontmatterPath } from "./diagnostics.mjs";
|
|
15
15
|
import { authoredFrontmatter, isNoteRecord, noteFile } from "./index-records.mjs";
|
|
16
16
|
import { metadataFileName } from "./packages.mjs";
|
|
17
|
+
import { flattenPublishedAddresses } from "./address-values.mjs";
|
|
17
18
|
|
|
18
19
|
// The import envelope requires an ISO instant. A fixed value keeps an export
|
|
19
20
|
// reproducible from its source rather than stamping the build machine's clock.
|
|
@@ -43,7 +44,9 @@ function readIndex(file) {
|
|
|
43
44
|
.filter(Boolean)
|
|
44
45
|
.map((line, index) => {
|
|
45
46
|
try {
|
|
46
|
-
|
|
47
|
+
// Each published Address is an object; the calendar compiler
|
|
48
|
+
// reads the string an author writes.
|
|
49
|
+
return flattenPublishedAddresses(JSON.parse(line));
|
|
47
50
|
} catch (cause) {
|
|
48
51
|
const error = new Error(
|
|
49
52
|
`${display}:${index + 1}: error: invalid content index record`,
|
|
@@ -112,6 +112,122 @@ export const CALENDAR_DATE_FORMAT_KEYS = Object.freeze([
|
|
|
112
112
|
/** Bare date tokens Calendaria substitutes inside authored format strings. */
|
|
113
113
|
export { CALENDAR_FORMAT_TOKENS };
|
|
114
114
|
|
|
115
|
+
/** An entry's `name`, which every calendar list states. */
|
|
116
|
+
const NAME = Object.freeze({
|
|
117
|
+
name: "name",
|
|
118
|
+
kind: "string",
|
|
119
|
+
required: true,
|
|
120
|
+
shape: "a string",
|
|
121
|
+
describe: "What it is called.",
|
|
122
|
+
});
|
|
123
|
+
/** An entry's short form, which every calendar list may state. */
|
|
124
|
+
const ABBREVIATION = Object.freeze({
|
|
125
|
+
name: "abbreviation",
|
|
126
|
+
kind: "string",
|
|
127
|
+
shape: "a string",
|
|
128
|
+
describe: "Its short form, for a compact date.",
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The keys of one entry in each of a calendar's lists, as inner-key
|
|
133
|
+
* declarations.
|
|
134
|
+
*
|
|
135
|
+
* @type {Readonly<Record<string, readonly import("./data-keys.mjs").InnerKeySpec[]>>}
|
|
136
|
+
*/
|
|
137
|
+
export const CALENDAR_ENTRY_FIELDS = Object.freeze({
|
|
138
|
+
months: Object.freeze([
|
|
139
|
+
NAME,
|
|
140
|
+
ABBREVIATION,
|
|
141
|
+
Object.freeze({
|
|
142
|
+
name: "days",
|
|
143
|
+
kind: "integer",
|
|
144
|
+
required: true,
|
|
145
|
+
shape: "a whole number of days",
|
|
146
|
+
describe: "How many days the month holds.",
|
|
147
|
+
}),
|
|
148
|
+
]),
|
|
149
|
+
weekdays: Object.freeze([NAME, ABBREVIATION]),
|
|
150
|
+
seasons: Object.freeze([
|
|
151
|
+
NAME,
|
|
152
|
+
ABBREVIATION,
|
|
153
|
+
Object.freeze({
|
|
154
|
+
name: "start",
|
|
155
|
+
kind: "integer",
|
|
156
|
+
required: true,
|
|
157
|
+
shape: "a one-based day of the year, as a whole number",
|
|
158
|
+
describe: "The day of the year the season begins.",
|
|
159
|
+
}),
|
|
160
|
+
]),
|
|
161
|
+
namedDays: Object.freeze([
|
|
162
|
+
NAME,
|
|
163
|
+
ABBREVIATION,
|
|
164
|
+
Object.freeze({
|
|
165
|
+
name: "day",
|
|
166
|
+
kind: "integer",
|
|
167
|
+
required: true,
|
|
168
|
+
shape: "a one-based day of the year, as a whole number",
|
|
169
|
+
describe: "The day of the year it names.",
|
|
170
|
+
}),
|
|
171
|
+
]),
|
|
172
|
+
eras: Object.freeze([
|
|
173
|
+
Object.freeze({
|
|
174
|
+
name: "shortcode",
|
|
175
|
+
kind: "string",
|
|
176
|
+
required: true,
|
|
177
|
+
shape:
|
|
178
|
+
"the era's own shortcode, unique within the calendar — an era is " +
|
|
179
|
+
"addressed `<calendar shortcode>.<era shortcode>`",
|
|
180
|
+
describe: "The era's own segment of `<calendar>.<era>`.",
|
|
181
|
+
}),
|
|
182
|
+
NAME,
|
|
183
|
+
Object.freeze({
|
|
184
|
+
name: "marker",
|
|
185
|
+
kind: "string",
|
|
186
|
+
shape: "uppercase letters and digits, beginning with a letter",
|
|
187
|
+
describe: "The marker an authored date names the era by, unique across the corpus.",
|
|
188
|
+
}),
|
|
189
|
+
ABBREVIATION,
|
|
190
|
+
Object.freeze({
|
|
191
|
+
name: "proclaimedBy",
|
|
192
|
+
kind: "address",
|
|
193
|
+
shape: "an Address",
|
|
194
|
+
describe: "The body that began the reckoning.",
|
|
195
|
+
}),
|
|
196
|
+
Object.freeze({
|
|
197
|
+
name: "start",
|
|
198
|
+
kind: "integer",
|
|
199
|
+
required: true,
|
|
200
|
+
nullable: true,
|
|
201
|
+
shape: "`null`, or the calendar year the era begins, as a whole number",
|
|
202
|
+
describe: "The calendar year the era begins; `null` for the era before year 1.",
|
|
203
|
+
}),
|
|
204
|
+
Object.freeze({
|
|
205
|
+
name: "label",
|
|
206
|
+
kind: "scalar-or-map",
|
|
207
|
+
shape: "a string, or an `{ after, before }` map",
|
|
208
|
+
fields: Object.freeze([
|
|
209
|
+
Object.freeze({
|
|
210
|
+
name: "after",
|
|
211
|
+
kind: "string",
|
|
212
|
+
shape: "a string with one `{date}` slot",
|
|
213
|
+
describe: "How a date in the era reads.",
|
|
214
|
+
}),
|
|
215
|
+
Object.freeze({
|
|
216
|
+
name: "before",
|
|
217
|
+
kind: "string",
|
|
218
|
+
shape: "a string with one `{date}` slot",
|
|
219
|
+
describe: "How a date before the era's start reads.",
|
|
220
|
+
}),
|
|
221
|
+
]),
|
|
222
|
+
describe: "How a date in the era reads, around one `{date}` slot.",
|
|
223
|
+
}),
|
|
224
|
+
]),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
/** One list entry of a calendar field, as a map of its declared keys. */
|
|
228
|
+
const entriesOf = (field, shape) =>
|
|
229
|
+
Object.freeze({ kind: "map", shape, fields: CALENDAR_ENTRY_FIELDS[field] });
|
|
230
|
+
|
|
115
231
|
/**
|
|
116
232
|
* The `data:` keys a calendar note may write — the closed list, in authored
|
|
117
233
|
* order.
|
|
@@ -133,6 +249,7 @@ export const CALENDAR_FIELDS = Object.freeze([
|
|
|
133
249
|
name: "months",
|
|
134
250
|
shape: "list of `{ name, abbreviation?, days }`",
|
|
135
251
|
kind: "list",
|
|
252
|
+
entries: entriesOf("months", "a map — `{ name, abbreviation?, days }`"),
|
|
136
253
|
describe:
|
|
137
254
|
"The months this calendar keeps, in order — position in the list is " +
|
|
138
255
|
"position in the year, and the day counts sum to the world's year.",
|
|
@@ -141,6 +258,7 @@ export const CALENDAR_FIELDS = Object.freeze([
|
|
|
141
258
|
name: "weekdays",
|
|
142
259
|
shape: "list of `{ name, abbreviation? }`",
|
|
143
260
|
kind: "list",
|
|
261
|
+
entries: entriesOf("weekdays", "a map — `{ name, abbreviation? }`"),
|
|
144
262
|
describe:
|
|
145
263
|
"The days of the week this calendar names, in order. A calendar with " +
|
|
146
264
|
"no week writes none.",
|
|
@@ -149,18 +267,24 @@ export const CALENDAR_FIELDS = Object.freeze([
|
|
|
149
267
|
name: "seasons",
|
|
150
268
|
shape: "list of `{ name, abbreviation?, start }`",
|
|
151
269
|
kind: "list",
|
|
270
|
+
entries: entriesOf("seasons", "a map — `{ name, abbreviation?, start }`"),
|
|
152
271
|
describe: "The seasons this calendar marks, starting on numbered days of the year.",
|
|
153
272
|
},
|
|
154
273
|
{
|
|
155
274
|
name: "namedDays",
|
|
156
275
|
shape: "list of `{ name, abbreviation?, day }`",
|
|
157
276
|
kind: "list",
|
|
277
|
+
entries: entriesOf("namedDays", "a map — `{ name, abbreviation?, day }`"),
|
|
158
278
|
describe: "Names assigned to particular days of the year.",
|
|
159
279
|
},
|
|
160
280
|
{
|
|
161
281
|
name: "eras",
|
|
162
282
|
shape: "list of `{ shortcode, name, marker?, abbreviation?, proclaimedBy?, start, label? }`",
|
|
163
283
|
kind: "list",
|
|
284
|
+
entries: entriesOf(
|
|
285
|
+
"eras",
|
|
286
|
+
"a map — `{ shortcode, name, marker?, abbreviation?, proclaimedBy?, start, label? }`",
|
|
287
|
+
),
|
|
164
288
|
describe:
|
|
165
289
|
"The year-counts kept in this calendar. A marker names one era and uses these months.",
|
|
166
290
|
},
|
|
@@ -168,6 +292,7 @@ export const CALENDAR_FIELDS = Object.freeze([
|
|
|
168
292
|
name: "formats",
|
|
169
293
|
shape: "map of named Calendaria format strings",
|
|
170
294
|
kind: "map",
|
|
295
|
+
values: Object.freeze({ kind: "string", shape: "a Calendaria format string" }),
|
|
171
296
|
describe: "Named patterns for reading and writing this calendar's dates.",
|
|
172
297
|
},
|
|
173
298
|
]);
|
|
@@ -367,6 +492,22 @@ function notesCarrying(index, key) {
|
|
|
367
492
|
return carrying.sort((a, b) => String(a.rel ?? a.file).localeCompare(String(b.rel ?? b.file)));
|
|
368
493
|
}
|
|
369
494
|
|
|
495
|
+
/**
|
|
496
|
+
* A whole number however YAML spelled it, or `undefined` — the shape the
|
|
497
|
+
* inner-key check holds a `start` or a `day` to. A value that is not one is
|
|
498
|
+
* that check's single finding, and the order of the year is not asked of it.
|
|
499
|
+
*
|
|
500
|
+
* @param {unknown} value - The authored value.
|
|
501
|
+
* @returns {number|undefined} The whole number it states.
|
|
502
|
+
*/
|
|
503
|
+
function whole(value) {
|
|
504
|
+
const read =
|
|
505
|
+
typeof value === "number" ? value
|
|
506
|
+
: typeof value === "string" && value.trim() !== "" ? Number(value)
|
|
507
|
+
: NaN;
|
|
508
|
+
return Number.isSafeInteger(read) ? read : undefined;
|
|
509
|
+
}
|
|
510
|
+
|
|
370
511
|
/** A finding positioned at a `data:` key of a note. */
|
|
371
512
|
function atData(note, keyPath, severity, message) {
|
|
372
513
|
return {
|
|
@@ -381,7 +522,9 @@ function atData(note, keyPath, severity, message) {
|
|
|
381
522
|
export function checkCalendarDateFormats(note) {
|
|
382
523
|
const formats = dataOf(note).formats;
|
|
383
524
|
if (formats === undefined) return [];
|
|
384
|
-
|
|
525
|
+
// A value that is not a map is the container's shape finding.
|
|
526
|
+
if (!formats || typeof formats !== "object" || Array.isArray(formats)) return [];
|
|
527
|
+
if (!Object.keys(formats).length)
|
|
385
528
|
return [
|
|
386
529
|
atData(note, ["formats"], "error", "data.formats needs at least one named pattern"),
|
|
387
530
|
];
|
|
@@ -398,12 +541,8 @@ export function checkCalendarDateFormats(note) {
|
|
|
398
541
|
);
|
|
399
542
|
continue;
|
|
400
543
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
atData(note, ["formats", slot], "error", `data.formats.${slot} must be a string`),
|
|
404
|
-
);
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
544
|
+
// A pattern that is not a string is the inner-key check's finding.
|
|
545
|
+
if (typeof value !== "string") continue;
|
|
407
546
|
const syntaxError = calendarFormatSyntaxError(value);
|
|
408
547
|
if (syntaxError) findings.push(atData(note, ["formats", slot], "error", syntaxError));
|
|
409
548
|
}
|
|
@@ -419,22 +558,16 @@ export function checkCalendarDateFormats(note) {
|
|
|
419
558
|
function checkCalendarDayNames(note, daysPerYear) {
|
|
420
559
|
const data = dataOf(note);
|
|
421
560
|
const findings = [];
|
|
561
|
+
// A list's shape, an entry's keys and an absent or mistyped name, start or
|
|
562
|
+
// day are the inner-key check's findings, made from
|
|
563
|
+
// `CALENDAR_ENTRY_FIELDS`. What is checked here is the order of the year.
|
|
422
564
|
const seasons = data.seasons;
|
|
423
|
-
if (seasons !== undefined && !Array.isArray(seasons))
|
|
424
|
-
findings.push(atData(note, ["seasons"], "error", "data.seasons must be a list"));
|
|
425
565
|
if (Array.isArray(seasons)) {
|
|
426
566
|
let prior = 0;
|
|
427
567
|
for (const [position, season] of seasons.entries()) {
|
|
428
|
-
const start = season?.start;
|
|
429
|
-
if (
|
|
430
|
-
|
|
431
|
-
atData(note, ["seasons", position, "name"], "error", "a season needs a name"),
|
|
432
|
-
);
|
|
433
|
-
if (
|
|
434
|
-
!Number.isSafeInteger(start) ||
|
|
435
|
-
start <= prior ||
|
|
436
|
-
(Number.isSafeInteger(daysPerYear) && start > daysPerYear)
|
|
437
|
-
)
|
|
568
|
+
const start = whole(season?.start);
|
|
569
|
+
if (start === undefined) continue;
|
|
570
|
+
if (start <= prior || (Number.isSafeInteger(daysPerYear) && start > daysPerYear))
|
|
438
571
|
findings.push(
|
|
439
572
|
atData(
|
|
440
573
|
note,
|
|
@@ -443,40 +576,16 @@ function checkCalendarDayNames(note, daysPerYear) {
|
|
|
443
576
|
"season starts must increase within the year",
|
|
444
577
|
),
|
|
445
578
|
);
|
|
446
|
-
if (
|
|
447
|
-
Object.keys(season ?? {}).some(
|
|
448
|
-
(key) => !["name", "abbreviation", "start"].includes(key),
|
|
449
|
-
)
|
|
450
|
-
)
|
|
451
|
-
findings.push(
|
|
452
|
-
atData(
|
|
453
|
-
note,
|
|
454
|
-
["seasons", position],
|
|
455
|
-
"error",
|
|
456
|
-
"a season uses only name, abbreviation, and start",
|
|
457
|
-
),
|
|
458
|
-
);
|
|
459
579
|
prior = start;
|
|
460
580
|
}
|
|
461
581
|
}
|
|
462
582
|
const namedDays = data.namedDays;
|
|
463
|
-
if (namedDays !== undefined && !Array.isArray(namedDays))
|
|
464
|
-
findings.push(atData(note, ["namedDays"], "error", "data.namedDays must be a list"));
|
|
465
583
|
if (Array.isArray(namedDays)) {
|
|
466
584
|
const seen = new Set();
|
|
467
585
|
for (const [position, namedDay] of namedDays.entries()) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
atData(
|
|
471
|
-
note,
|
|
472
|
-
["namedDays", position, "name"],
|
|
473
|
-
"error",
|
|
474
|
-
"a named day needs a name",
|
|
475
|
-
),
|
|
476
|
-
);
|
|
477
|
-
const day = namedDay?.day;
|
|
586
|
+
const day = whole(namedDay?.day);
|
|
587
|
+
if (day === undefined) continue;
|
|
478
588
|
if (
|
|
479
|
-
!Number.isSafeInteger(day) ||
|
|
480
589
|
day < 1 ||
|
|
481
590
|
(Number.isSafeInteger(daysPerYear) && day > daysPerYear) ||
|
|
482
591
|
seen.has(day)
|
|
@@ -489,19 +598,6 @@ function checkCalendarDayNames(note, daysPerYear) {
|
|
|
489
598
|
"named days need distinct days within the year",
|
|
490
599
|
),
|
|
491
600
|
);
|
|
492
|
-
if (
|
|
493
|
-
Object.keys(namedDay ?? {}).some(
|
|
494
|
-
(key) => !["name", "abbreviation", "day"].includes(key),
|
|
495
|
-
)
|
|
496
|
-
)
|
|
497
|
-
findings.push(
|
|
498
|
-
atData(
|
|
499
|
-
note,
|
|
500
|
-
["namedDays", position],
|
|
501
|
-
"error",
|
|
502
|
-
"a named day uses only name, abbreviation, and day",
|
|
503
|
-
),
|
|
504
|
-
);
|
|
505
601
|
seen.add(day);
|
|
506
602
|
}
|
|
507
603
|
}
|
|
@@ -713,20 +809,6 @@ export function checkCalendarNote(note, { index } = {}) {
|
|
|
713
809
|
findings.push(...checkMonthSum(note, index));
|
|
714
810
|
findings.push(...checkCalendarDayNames(note, worldInvariants(index).year?.days));
|
|
715
811
|
findings.push(...checkEraShortcodes(note));
|
|
716
|
-
for (const [position, day] of (Array.isArray(dataOf(note).weekdays) ?
|
|
717
|
-
dataOf(note).weekdays
|
|
718
|
-
: []
|
|
719
|
-
).entries()) {
|
|
720
|
-
if (day?.ordinal !== undefined)
|
|
721
|
-
findings.push(
|
|
722
|
-
atData(
|
|
723
|
-
note,
|
|
724
|
-
["weekdays", position, "ordinal"],
|
|
725
|
-
"error",
|
|
726
|
-
"weekday order is its array position, starting at zero; omit ordinal",
|
|
727
|
-
),
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
812
|
findings.push(
|
|
731
813
|
...reckoningContext(index).findings.filter((finding) => finding.file === note.file),
|
|
732
814
|
);
|
|
@@ -752,6 +834,9 @@ export function checkCalendarNote(note, { index } = {}) {
|
|
|
752
834
|
function checkMonthSum(note, index) {
|
|
753
835
|
const months = dataOf(note).months;
|
|
754
836
|
if (!Array.isArray(months) || months.length === 0) return [];
|
|
837
|
+
// A month whose `days` is not a whole number is the inner-key check's
|
|
838
|
+
// finding, and a sum over it would restate that finding as a second one.
|
|
839
|
+
if (months.some((month) => whole(month?.days) === undefined)) return [];
|
|
755
840
|
const days = worldInvariants(index).year?.days;
|
|
756
841
|
if (typeof days !== "number") return [];
|
|
757
842
|
const sum = daysInYear(months);
|
|
@@ -786,18 +871,8 @@ function checkEraShortcodes(note) {
|
|
|
786
871
|
const findings = [];
|
|
787
872
|
eras.forEach((era, position) => {
|
|
788
873
|
const shortcode = String(era?.shortcode ?? "");
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
atData(
|
|
792
|
-
note,
|
|
793
|
-
["eras", position],
|
|
794
|
-
"error",
|
|
795
|
-
"an era is addressed `<calendar shortcode>.<era shortcode>`, so " +
|
|
796
|
-
"every row states a `shortcode` of its own",
|
|
797
|
-
),
|
|
798
|
-
);
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
874
|
+
// Absent: the inner-key check's finding.
|
|
875
|
+
if (!shortcode) return;
|
|
801
876
|
if (seen.has(shortcode)) {
|
|
802
877
|
findings.push(
|
|
803
878
|
atData(
|
|
@@ -810,26 +885,15 @@ function checkEraShortcodes(note) {
|
|
|
810
885
|
);
|
|
811
886
|
}
|
|
812
887
|
seen.add(shortcode);
|
|
813
|
-
|
|
888
|
+
// A label's shape and keys are the inner-key check's; its `{date}`
|
|
889
|
+
// slot is this one's.
|
|
814
890
|
const label = era.label;
|
|
815
|
-
const forms =
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
note,
|
|
820
|
-
["eras", position, "label"],
|
|
821
|
-
"error",
|
|
822
|
-
"an era label is a string or an {after, before} map",
|
|
823
|
-
),
|
|
824
|
-
);
|
|
825
|
-
return;
|
|
826
|
-
}
|
|
891
|
+
const forms =
|
|
892
|
+
typeof label === "string" ? { after: label }
|
|
893
|
+
: label && typeof label === "object" && !Array.isArray(label) ? label
|
|
894
|
+
: {};
|
|
827
895
|
for (const [key, value] of Object.entries(forms)) {
|
|
828
|
-
if (
|
|
829
|
-
!["after", "before"].includes(key) ||
|
|
830
|
-
typeof value !== "string" ||
|
|
831
|
-
value.split("{date}").length !== 2
|
|
832
|
-
)
|
|
896
|
+
if (typeof value === "string" && value.split("{date}").length !== 2)
|
|
833
897
|
findings.push(
|
|
834
898
|
atData(
|
|
835
899
|
note,
|
|
@@ -106,7 +106,7 @@ import { RETIRED_FIELD_ALIASES } from "./retired-fields.mjs";
|
|
|
106
106
|
import { authoredKey } from "./system-block.mjs";
|
|
107
107
|
// The top-level vocabulary, read rather than restated — a second list of the
|
|
108
108
|
// keys a note's own identity uses is one more thing to drift.
|
|
109
|
-
import { NOTE_TOP_LEVEL_KEY_SET
|
|
109
|
+
import { NOTE_TOP_LEVEL_KEY_SET } from "./note-vocabulary.mjs";
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
112
|
* Every field path any subtype of a published schema declares.
|
|
@@ -371,8 +371,7 @@ export function measureNote(note, format, { severity = "warning" } = {}) {
|
|
|
371
371
|
[...spec.dataKeys].map((key) => RETIRED_FIELD_ALIASES[key]).filter(Boolean),
|
|
372
372
|
);
|
|
373
373
|
for (const key of Object.keys(data ?? {})) {
|
|
374
|
-
if (spec.dataKeys.has(key) || renamedDataKeys.has(key)
|
|
375
|
-
continue;
|
|
374
|
+
if (spec.dataKeys.has(key) || renamedDataKeys.has(key)) continue;
|
|
376
375
|
const guess = nearest(key, spec.dataKeys);
|
|
377
376
|
add(
|
|
378
377
|
"unknown-data-key",
|
|
@@ -52,11 +52,23 @@ export const CONTENT_FORMAT_PATH = path.join(
|
|
|
52
52
|
* property — what a note actually writes. `appearance.eye_color` is authored
|
|
53
53
|
* as `appearance`, so that is the key recorded.
|
|
54
54
|
* @property {Set<string>} dataPaths - The declared paths, whole.
|
|
55
|
+
* @property {Map<string, DataRow>} [dataRows] - Each documented path's row, as
|
|
56
|
+
* a reference document states it. Read from a Markdown specification only.
|
|
55
57
|
* @property {string[]} subTypes - The declared `subType` values,
|
|
56
58
|
* in document order — empty when it states none, which is the ordinary case
|
|
57
59
|
* for a type that has no `subType` at all.
|
|
58
60
|
*/
|
|
59
61
|
|
|
62
|
+
/**
|
|
63
|
+
* One row of a `data` property table, as a reference document states it.
|
|
64
|
+
*
|
|
65
|
+
* @typedef {object} DataRow
|
|
66
|
+
* @property {string} shape - The second cell, the value's shape in words.
|
|
67
|
+
* @property {string} text - The whole row as written, for a reader looking for
|
|
68
|
+
* a marker anywhere in it.
|
|
69
|
+
* @property {number} line - 1-based line of the row.
|
|
70
|
+
*/
|
|
71
|
+
|
|
60
72
|
/**
|
|
61
73
|
* One `system.*` target the specification names for one note type.
|
|
62
74
|
*
|
|
@@ -95,6 +107,12 @@ export const CONTENT_FORMAT_PATH = path.join(
|
|
|
95
107
|
* @property {MappingClaim[]} claims - Every `system.*` target, in document order.
|
|
96
108
|
* @property {Map<string, VocabularySpec>} vocabularies - Key → the values it
|
|
97
109
|
* admits, for every closed vocabulary the document states as a table.
|
|
110
|
+
* @property {Map<string, DataRow>} [sharedDataRows] - The rows of the shared
|
|
111
|
+
* `data` property table — the keys every type accepts — by path. Read from a
|
|
112
|
+
* Markdown specification only.
|
|
113
|
+
* @property {Map<string, DataRow>} [eventRows] - The rows of the event key
|
|
114
|
+
* tables, by path from one event entry (`names[].by`). Read from a Markdown
|
|
115
|
+
* specification only.
|
|
98
116
|
*/
|
|
99
117
|
|
|
100
118
|
/**
|
|
@@ -245,6 +263,10 @@ export function parseContentFormat(text, { file = CONTENT_FORMAT_PATH } = {}) {
|
|
|
245
263
|
const claims = [];
|
|
246
264
|
/** @type {Map<string, VocabularySpec>} */
|
|
247
265
|
const vocabularies = new Map();
|
|
266
|
+
/** @type {Map<string, DataRow>} */
|
|
267
|
+
const sharedDataRows = new Map();
|
|
268
|
+
/** @type {Map<string, DataRow>} */
|
|
269
|
+
const eventRows = new Map();
|
|
248
270
|
|
|
249
271
|
const lines = String(text ?? "").split("\n");
|
|
250
272
|
/** @type {TypeSpec|undefined} */
|
|
@@ -262,6 +284,7 @@ export function parseContentFormat(text, { file = CONTENT_FORMAT_PATH } = {}) {
|
|
|
262
284
|
line: i + 1,
|
|
263
285
|
dataKeys: new Set(),
|
|
264
286
|
dataPaths: new Set(),
|
|
287
|
+
dataRows: new Map(),
|
|
265
288
|
subTypes: [],
|
|
266
289
|
};
|
|
267
290
|
types.set(current.name, current);
|
|
@@ -299,6 +322,14 @@ export function parseContentFormat(text, { file = CONTENT_FORMAT_PATH } = {}) {
|
|
|
299
322
|
table = { kind: "data", systems: [] };
|
|
300
323
|
continue;
|
|
301
324
|
}
|
|
325
|
+
if (cells[0] === "shared `data` property") {
|
|
326
|
+
table = { kind: "shared-data", systems: [] };
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (cells[0] === "event key" || cells[0] === "nested key") {
|
|
330
|
+
table = { kind: "event", systems: [] };
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
302
333
|
if (cells[0] === "shared source") {
|
|
303
334
|
table = {
|
|
304
335
|
kind: "mapping",
|
|
@@ -343,6 +374,18 @@ export function parseContentFormat(text, { file = CONTENT_FORMAT_PATH } = {}) {
|
|
|
343
374
|
if (!declared) continue;
|
|
344
375
|
current.dataPaths.add(declared);
|
|
345
376
|
current.dataKeys.add(declared.split(".")[0]);
|
|
377
|
+
current.dataRows?.set(declared, { shape: cells[1] ?? "", text: line, line: i + 1 });
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (table.kind === "shared-data" || table.kind === "event") {
|
|
382
|
+
const declared = code(cells[0]);
|
|
383
|
+
if (declared)
|
|
384
|
+
(table.kind === "event" ? eventRows : sharedDataRows).set(declared, {
|
|
385
|
+
shape: cells[1] ?? "",
|
|
386
|
+
text: line,
|
|
387
|
+
line: i + 1,
|
|
388
|
+
});
|
|
346
389
|
continue;
|
|
347
390
|
}
|
|
348
391
|
|
|
@@ -365,7 +408,7 @@ export function parseContentFormat(text, { file = CONTENT_FORMAT_PATH } = {}) {
|
|
|
365
408
|
}
|
|
366
409
|
}
|
|
367
410
|
|
|
368
|
-
return { file, types, claims, vocabularies };
|
|
411
|
+
return { file, types, claims, vocabularies, sharedDataRows, eventRows };
|
|
369
412
|
}
|
|
370
413
|
|
|
371
414
|
/**
|