@heroiclands/package-build 22.7.0 → 22.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The year on the canonical axis that a written year lands on.
3
+ *
4
+ * The whole of the conversion. A reckoning differs from its neighbours in
5
+ * where its year 1 sits and in nothing else, so an era states one number and
6
+ * this states what to do with it.
7
+ *
8
+ * The two branches are the year-zero hole: the authored numbering has no year
9
+ * 0, so a positive year is one place closer to the epoch than its magnitude
10
+ * suggests and a negative year is exactly its magnitude below it.
11
+ *
12
+ * @param {number} year - The year as authored, signed, within its own
13
+ * reckoning. Never 0 — the authored numbering has no such year.
14
+ * @param {number} [epoch] - Where that reckoning's year 1 sits on the axis.
15
+ * Omitted, the axis itself.
16
+ * @returns {number} The signed year on the axis — authored `-1` is `0`.
17
+ */
18
+ export function canonicalYear(year: number, epoch?: number): number;
19
+ /**
20
+ * The total-order key a date sorts on.
21
+ *
22
+ * Month and day fold into the fraction, so a mixed-precision list sorts stably
23
+ * against a single number instead of a three-key comparator. The arithmetic is
24
+ * done in ten-thousandths and divided once, because `year + 6 / 100 + 19 /
25
+ * 10000` accumulates a rounding error and `689.0619` is a value a reader
26
+ * compares against by eye.
27
+ *
28
+ * A coarser value sorts **before** the finer values inside it: `689` precedes
29
+ * `689/1`, which is the ordering a reader expects of a year whose month nobody
30
+ * wrote.
31
+ *
32
+ * @param {number} year - The year on the canonical axis.
33
+ * @param {number|null} month - The month, or `null`.
34
+ * @param {number|null} day - The day, or `null`.
35
+ * @returns {number} The sort key.
36
+ */
37
+ export function dateSortKey(year: number, month: number | null, day: number | null): number;
38
+ /**
39
+ * The bounds a package states for a month, or `null` where it states none.
40
+ *
41
+ * Separate from the check that reads it so a caller can ask what a package
42
+ * declares without phrasing a finding about it.
43
+ *
44
+ * @param {{months?: number|null, monthDays?: number|null}} [calendar] - The
45
+ * resolved `calendar:` block.
46
+ * @returns {{months: number|null, monthDays: number|null}} What it declares.
47
+ */
48
+ export function calendarStructure(calendar?: {
49
+ months?: number | null;
50
+ monthDays?: number | null;
51
+ }): {
52
+ months: number | null;
53
+ monthDays: number | null;
54
+ };
55
+ /**
56
+ * **The axis every date normalises onto, and the one function that puts a
57
+ * reckoning on it.**
58
+ *
59
+ * A setting keeps several counts of years at once, each proclaimed by a body
60
+ * that has a note — a founding, a first occasion, a reign. A date either names
61
+ * one of them or names none, in which case it sits on the **canonical axis**.
62
+ *
63
+ * ## The canonical axis is an authoring device, not a calendar
64
+ *
65
+ * The axis is the line of integers dates written by peoples who never met are
66
+ * compared on. Nobody in a setting keeps it, no body stands behind it, and no
67
+ * shortcode addresses it: a date is on the axis exactly when it names no era.
68
+ * A reckoning whose year 1 happens to sit where the axis's year 1 sits
69
+ * coincides with it numerically and is still that people's own count.
70
+ *
71
+ * ## There is no year zero, and one function converts
72
+ *
73
+ * The authored numbering skips zero — `-1` sits immediately before `1` — while
74
+ * the normalised value does not, so that arithmetic across an epoch is one
75
+ * subtraction rather than a special case somebody forgets.
76
+ *
77
+ * ```text
78
+ * authored … -3 -2 -1 1 2 3 …
79
+ * canonicalYear … -2 -1 0 1 2 3 …
80
+ * ```
81
+ *
82
+ * ```text
83
+ * canonicalYear(y, E) = E + (y > 0 ? y - 1 : y)
84
+ * ```
85
+ *
86
+ * `E` is where a reckoning's year 1 sits on the axis — the `canonicalYear` of
87
+ * an era's own `start`. The axis is the same function with `E` of
88
+ * {@link CANONICAL_EPOCH}, which holds by definition: the axis's year 1 is the
89
+ * axis's year 1.
90
+ *
91
+ * **The conversion is piecewise on the sign**, and that is the property worth
92
+ * guarding. Collapsed to a single branch it is correct on one side of an epoch
93
+ * and off by one on the other, which is invisible in any list that does not
94
+ * span it.
95
+ *
96
+ * ## The month structure is the package's, and an absent bound checks nothing
97
+ *
98
+ * An era moves where year 1 sits; it does not divide the year differently. So
99
+ * the subdivision is declared once for the package, in `calendar.months` and
100
+ * `calendar.monthDays`, and a package that declares neither is bounded below
101
+ * and not above — a toolchain that invented twelve Gregorian months would
102
+ * refuse `667/2/30`, which is an ordinary date in a year of twelve thirty-day
103
+ * months.
104
+ *
105
+ * @module
106
+ */
107
+ /**
108
+ * Where the canonical axis's own year 1 sits — on itself.
109
+ *
110
+ * It takes no configuration entry and no era row, because it is true by
111
+ * definition rather than by declaration.
112
+ *
113
+ * @type {number}
114
+ */
115
+ export const CANONICAL_EPOCH: number;
@@ -5,7 +5,8 @@
5
5
  * `body.game .app blockquote` carries a left rule and an indent — so the
6
6
  * treatment survives with no stylesheet of this package's own. The mark is a
7
7
  * Font Awesome element, which Foundry bundles; `aria-hidden` keeps it out of a
8
- * screen reader, where the label reads it.
8
+ * screen reader, where the label reads it. The sentence is an `em`, which every
9
+ * renderer of HTML sets in italic with no stylesheet to ask.
9
10
  *
10
11
  * @returns {string} The notice's HTML.
11
12
  */
@@ -41,9 +42,30 @@ export function withDraftNotice(fm: object | null | undefined, html: string): st
41
42
  /**
42
43
  * The `#let` the book's notice is drawn by.
43
44
  *
44
- * Emitted with the rest of the book's preamble. The mark is a circle with an
45
- * exclamation set inside it: two primitives and the face already setting the
46
- * page, so there is no font to resolve and nothing that can come out as a box.
45
+ * Emitted with the rest of the book's preamble. The mark is a disc with an
46
+ * exclamation knocked out of it: two primitives and the face already setting
47
+ * the page, so there is no font to resolve and nothing that can come out as a
48
+ * box. It is filled in the accent the book rules its headings with and the
49
+ * glyph is the colour of the paper, so the mark carries against the running
50
+ * face rather than reading as a character of it.
51
+ *
52
+ * **The mark is a column, not a word.** A two-column grid puts it beside the
53
+ * sentence rather than inside it, so a notice that takes two lines sets its
54
+ * second line against the first and never under the disc; the grid centres the
55
+ * mark's cell on the block, so it holds the middle of one line or two. The rule
56
+ * is the block's own left stroke and spans whatever height the sentence takes.
57
+ *
58
+ * **The sentence sets in the book's body face, size and ink**, because it is
59
+ * the one thing in the entry a reader has to read before trusting the rest. The
60
+ * rule and the mark set the block apart; nothing else has to, and type set
61
+ * smaller or lighter than the prose around it reads as apparatus a reader may
62
+ * skip. The mark scales with that text, so a consumer setting the book larger
63
+ * or smaller carries the disc with it.
64
+ *
65
+ * **One number sets the mark.** The glyph is a multiple of the disc's radius
66
+ * rather than a size of its own, so the exclamation keeps its share of the disc
67
+ * however large the disc is drawn and resizing the mark is one edit. Both are
68
+ * `em`, so the mark still follows the body text.
47
69
  *
48
70
  * @returns {string} The Typst definition.
49
71
  */
@@ -29,6 +29,8 @@ export * as mapGraphviz from "./map-graphviz.mjs";
29
29
  export * as mapBuild from "./map-build.mjs";
30
30
  export * as siteMaps from "./site-maps.mjs";
31
31
  export * as systems from "./systems.mjs";
32
+ export * as calendars from "./calendars.mjs";
33
+ export * as noteDates from "./note-dates.mjs";
32
34
  export * as contentSlug from "./content-slug.mjs";
33
35
  export * as contentAddress from "./content-address.mjs";
34
36
  export * as subtypeRegistry from "./subtype-registry.mjs";
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Parse one authored date.
3
+ *
4
+ * Every field that holds a date — a birth, a death, an event's start and its
5
+ * end — parses through here, so there is one grammar to write, test, document
6
+ * and teach.
7
+ *
8
+ * Findings are returned rather than thrown, because a corpus pass wants every
9
+ * bad value in one run rather than the first. Each carries the `file` it was
10
+ * given and, where `raw` and `keyPath` locate the value, its line and column;
11
+ * a position that cannot be established honestly is dropped rather than
12
+ * defaulted, which is what keeps `file:line:column:` parseable.
13
+ *
14
+ * @param {unknown} value - The authored value. A number is what YAML hands
15
+ * back for a bare year, so it is accepted and stringified.
16
+ * @param {object} [options]
17
+ * @param {{months?: number|null, monthDays?: number|null}} [options.calendar]
18
+ * The resolved `calendar:` block, whose `months` and `monthDays` bound a
19
+ * written month and day. An absent bound is not applied.
20
+ * @param {string} [options.field] - The key that carried it, named in every
21
+ * message. Omitted, a message names the value alone.
22
+ * @param {boolean} [options.allowUnknown=true] - Whether `"unknown"` is a
23
+ * permitted value here. It is not, in a field whose absence already says the
24
+ * date is unrecorded.
25
+ * @param {string} [options.file] - The note, for the diagnostics.
26
+ * @param {string} [options.raw] - The note's full contents, to locate the value.
27
+ * @param {ReadonlyArray<string|number>} [options.keyPath] - Path to the key
28
+ * within the frontmatter, for the same.
29
+ * @returns {{date: object|null, findings: object[]}} The record, or `null` when
30
+ * the value is absent or refused, and every finding the value earned.
31
+ */
32
+ export function parseNoteDate(value: unknown, options?: {
33
+ calendar?: {
34
+ months?: number | null;
35
+ monthDays?: number | null;
36
+ } | undefined;
37
+ field?: string | undefined;
38
+ allowUnknown?: boolean | undefined;
39
+ file?: string | undefined;
40
+ raw?: string | undefined;
41
+ keyPath?: readonly (string | number)[] | undefined;
42
+ }): {
43
+ date: object | null;
44
+ findings: object[];
45
+ };
46
+ /**
47
+ * The literal that says a thing happened and the date is not recorded.
48
+ *
49
+ * @type {string}
50
+ */
51
+ export const UNKNOWN_DATE: string;
52
+ /**
53
+ * What a date's trailing token may look like.
54
+ *
55
+ * An affiliation's address followed by `.<era shortcode>`. The `{0,3}`
56
+ * repetition is the four address forms exactly — bare `shortcode`,
57
+ * `type-shortcode`, `system-type-shortcode` and
58
+ * `package-system-type-shortcode` — so the hyphen separates an address's
59
+ * segments, the dot separates the address from the era, and the split needs no
60
+ * lookahead.
61
+ *
62
+ * @type {RegExp}
63
+ */
64
+ export const ERA_QUALIFIER_PATTERN: RegExp;
65
+ /**
66
+ * The grammar, as one expression.
67
+ *
68
+ * @type {RegExp}
69
+ */
70
+ export const NOTE_DATE_PATTERN: RegExp;
@@ -292,9 +292,16 @@ export function retiredTopLevelMessage(field: {
292
292
  *
293
293
  * @param {object|null|undefined} fm - Parsed frontmatter.
294
294
  * @param {string} current - The field's current name.
295
+ * @param {object} [options] - Options.
296
+ * @param {boolean} [options.inData=false] - Search the `data:` container
297
+ * instead. A `data:` field has exactly one home, so its retired spelling has
298
+ * exactly one too, and searching the block or the top level for it would
299
+ * answer about a key the container check has already refused.
295
300
  * @returns {boolean} Whether the retired spelling is declared.
296
301
  */
297
- export function declaresRetiredAlias(fm: object | null | undefined, current: string): boolean;
302
+ export function declaresRetiredAlias(fm: object | null | undefined, current: string, { inData }?: {
303
+ inData?: boolean | undefined;
304
+ }): boolean;
298
305
  /**
299
306
  * Read a field that has a retired spelling, the current name winning.
300
307
  *
@@ -310,9 +317,16 @@ export function declaresRetiredAlias(fm: object | null | undefined, current: str
310
317
  *
311
318
  * @param {object|null|undefined} fm - Parsed frontmatter.
312
319
  * @param {string} current - The field's current name.
320
+ * @param {object} [options] - Options.
321
+ * @param {boolean} [options.inData=false] - Read the `data:` container instead,
322
+ * which is where a `data:` field and its retired spelling are both written.
323
+ * Dotted names resolve inside it, so `appearance.eye_color` reads the way it
324
+ * is authored.
313
325
  * @returns {any} The value, or `undefined` when neither spelling carries one.
314
326
  */
315
- export function readAliasedField(fm: object | null | undefined, current: string): any;
327
+ export function readAliasedField(fm: object | null | undefined, current: string, { inData }?: {
328
+ inData?: boolean | undefined;
329
+ }): any;
316
330
  /**
317
331
  * The current field name a retired spelling was renamed to.
318
332
  *
@@ -336,6 +350,13 @@ export function readAliasedField(fm: object | null | undefined, current: string)
336
350
  * read past. Only `affiliation` declares the field, so the alias is reported
337
351
  * there and the old spelling stays an ordinary unknown key everywhere else.
338
352
  *
353
+ * **`born`.** A birthday is a day that recurs; what a note records is the date
354
+ * somebody was born, once. The two read alike while every value belonged to a
355
+ * living person, and `born` is what the field has to be called beside `died`,
356
+ * which a being now also states. It is the first alias whose field lives in
357
+ * `data:` rather than in a system block, so the two readers below take
358
+ * `inData`.
359
+ *
339
360
  * @type {Readonly<Record<string, string>>}
340
361
  */
341
362
  export const RETIRED_FIELD_ALIASES: Readonly<Record<string, string>>;