@heroiclands/package-build 23.12.5 → 23.12.7
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 +22 -0
- package/assets/styles/content-base.css +25 -0
- package/bin/content-commands.mjs +264 -3
- package/bin/package-build.mjs +25 -0
- package/content-config.mjs +98 -0
- package/docs/api.md +18 -14
- package/docs/authoring/dates-and-calendars.md +29 -1
- package/docs/authoring/links-and-markup.md +442 -50
- package/docs/commands.md +127 -0
- package/docs/configuration.md +90 -3
- package/docs/content-format.md +3 -1
- package/docs/guides/book.md +11 -0
- package/docs/guides/module-project.md +17 -1
- package/docs/guides/site.md +19 -0
- package/docs/reference/format-details.md +434 -23
- package/docs/reference/note-types.md +45 -21
- package/engine/base-compiler.mjs +2 -0
- package/engine/bundles.mjs +7 -2
- package/engine/compile-corpus.mjs +3 -1
- package/engine/content-charset.mjs +21 -2
- package/engine/content-format.yaml +1 -2
- package/engine/content-index.mjs +33 -2
- package/engine/content-links.mjs +3 -0
- package/engine/content-poetry.mjs +22 -4
- package/engine/content-scripts.mjs +1219 -0
- package/engine/content-session.mjs +2 -0
- package/engine/data-keys.mjs +5 -1
- package/engine/dictionary-layout.mjs +18 -12
- package/engine/dictionary-rows.mjs +11 -2
- package/engine/event-rows.mjs +63 -18
- package/engine/event-views.mjs +28 -15
- package/engine/helpers.mjs +8 -2
- package/engine/index-records.mjs +2 -0
- package/engine/infobox.mjs +4 -8
- package/engine/journals.mjs +14 -3
- package/engine/language-data.mjs +137 -1
- package/engine/language-tag.mjs +103 -0
- package/engine/language-terms.mjs +16 -4
- package/engine/map-lexicon.mjs +762 -0
- package/engine/markdown-expressions.mjs +72 -17
- package/engine/markup-validation.mjs +17 -3
- package/engine/names.mjs +1 -1
- package/engine/note-calendars.mjs +314 -0
- package/engine/note-vocabulary.mjs +124 -16
- package/engine/pdf-build.mjs +28 -10
- package/engine/pdf-render.mjs +116 -11
- package/engine/reckoning-markers.mjs +63 -3
- package/engine/run-rows.mjs +119 -0
- package/engine/run-words.mjs +266 -0
- package/engine/scenes.mjs +1 -0
- package/engine/script-font-delivery.mjs +321 -0
- package/engine/script-fonts.mjs +172 -0
- package/engine/site-build.mjs +10 -3
- package/engine/site-config.mjs +5 -0
- package/engine/site-root.mjs +19 -5
- package/engine/sql-tables.mjs +120 -4
- package/engine/text-maps.mjs +607 -0
- package/engine/verb-pattern.mjs +99 -0
- package/engine/word-pattern.mjs +27 -0
- package/engine/word-rows.mjs +8 -2
- package/engine/word-terms.mjs +44 -0
- package/hugo-theme/layouts/_default/baseof.html +3 -0
- package/hugo-theme/static/css/style.css +10 -0
- package/manifest.mjs +9 -1
- package/package.json +1 -1
- package/types/content-config.d.mts +38 -0
- package/types/engine/compile-corpus.d.mts +2 -1
- package/types/engine/content-index.d.mts +17 -3
- package/types/engine/content-poetry.d.mts +7 -0
- package/types/engine/content-scripts.d.mts +226 -0
- package/types/engine/data-keys.d.mts +2 -2
- package/types/engine/dictionary-rows.d.mts +15 -0
- package/types/engine/event-rows.d.mts +16 -1
- package/types/engine/event-views.d.mts +2 -1
- package/types/engine/helpers.d.mts +5 -1
- package/types/engine/journals.d.mts +7 -2
- package/types/engine/language-data.d.mts +2 -0
- package/types/engine/language-tag.d.mts +8 -0
- package/types/engine/language-terms.d.mts +2 -1
- package/types/engine/map-lexicon.d.mts +274 -0
- package/types/engine/markup-validation.d.mts +7 -2
- package/types/engine/note-calendars.d.mts +60 -0
- package/types/engine/pdf-build.d.mts +26 -3
- package/types/engine/pdf-render.d.mts +17 -2
- package/types/engine/reckoning-markers.d.mts +32 -1
- package/types/engine/run-rows.d.mts +89 -0
- package/types/engine/run-words.d.mts +86 -0
- package/types/engine/script-font-delivery.d.mts +134 -0
- package/types/engine/script-fonts.d.mts +168 -0
- package/types/engine/site-build.d.mts +2 -1
- package/types/engine/site-root.d.mts +11 -3
- package/types/engine/text-maps.d.mts +175 -0
- package/types/engine/verb-pattern.d.mts +65 -0
- package/types/engine/word-pattern.d.mts +13 -0
- package/types/engine/word-terms.d.mts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 23.12.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
**Writing in the setting's languages and scripts**
|
|
8
|
+
|
|
9
|
+
- Text can be marked with its language and set in one of the HeroicLands fonts, or in plain serif, sans or monospace, on the site, in Foundry and in books.
|
|
10
|
+
- Roman spelling converts into a script through a mapping note, which takes a word from the language's dictionary when the dictionary spells it differently.
|
|
11
|
+
- `package-build transliterate` and `package-build lexicon` show a passage or a word exactly as a build sets it.
|
|
12
|
+
- Converted text is checked against its language, and an unknown word or unregistered name is reported where it is written.
|
|
13
|
+
- A language's present endings can replace its infinitive ending, as Vylari's do.
|
|
14
|
+
|
|
15
|
+
## 23.12.6
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
A package can name a default calendar with `defaultCalendar` in its configuration, and it replaces the world note's calendar as the one dates fall back to.
|
|
20
|
+
|
|
21
|
+
A note's dates print in its own `data.calendar`, which any note may now set, else its culture's, else for a place its parents', else the default. A place whose parents keep different calendars is a build error until it names its own.
|
|
22
|
+
|
|
23
|
+
Chronologies and the other event sections print every date in the calendar of the page showing them, and `dateformat` with no calendar named uses the note's.
|
|
24
|
+
|
|
3
25
|
## 23.12.5
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
|
@@ -164,6 +164,31 @@
|
|
|
164
164
|
--poetry-indent: 10em;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/* Text set in one of the faces. The font family and features come from the
|
|
168
|
+
* face's own class; a right-to-left block or verse reads from the right. */
|
|
169
|
+
.script-block {
|
|
170
|
+
margin: 1rem 0;
|
|
171
|
+
line-height: 1.7;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.script-block[dir="rtl"],
|
|
175
|
+
.poetry[dir="rtl"] {
|
|
176
|
+
text-align: start;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* Text set in the reader's generic families, `font=serif`, `sans` and `mono`. */
|
|
180
|
+
.font-serif {
|
|
181
|
+
font-family: serif;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.font-sans {
|
|
185
|
+
font-family: sans-serif;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.font-mono {
|
|
189
|
+
font-family: monospace;
|
|
190
|
+
}
|
|
191
|
+
|
|
167
192
|
/*
|
|
168
193
|
* Historical info/warn section classes remain available for styled prose.
|
|
169
194
|
* New alerts use the separate alert classes below.
|
package/bin/content-commands.mjs
CHANGED
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
* npx package-build site [--out <dir>]
|
|
31
31
|
* npx package-build map [--tree] [--from <shortcode>] [--chart <shortcode>] [--travel] [--out <dir>]
|
|
32
32
|
* npx package-build reachability <dir> [file] [--index <shortcode>]
|
|
33
|
+
* npx package-build transliterate <map> [--lang <language>] [--font <slug>] [--hex] [text..]
|
|
34
|
+
* npx package-build lexicon <language> --map <map> [--font <slug>] [--json]
|
|
33
35
|
* npx package-build addresses diff --from <zip|dir> [--strict]
|
|
34
36
|
* npx package-build npc new (--spec <json> | --specfile <path>) [--seed <text>] [--out <path>]
|
|
35
37
|
*
|
|
@@ -62,7 +64,19 @@ import {
|
|
|
62
64
|
unaddressableForeignPackages,
|
|
63
65
|
formatUnaddressableFinding,
|
|
64
66
|
} from "../engine/metadata-index.mjs";
|
|
65
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
fetchNavigation,
|
|
69
|
+
generateHugoConfig,
|
|
70
|
+
HUGO_SOURCE,
|
|
71
|
+
writeHugoConfig,
|
|
72
|
+
} from "../engine/site-config.mjs";
|
|
73
|
+
import {
|
|
74
|
+
checkScriptFonts,
|
|
75
|
+
copyScriptFonts,
|
|
76
|
+
scriptFontFiles,
|
|
77
|
+
SITE_FONTS_DIR,
|
|
78
|
+
siteFontsDestination,
|
|
79
|
+
} from "../engine/script-font-delivery.mjs";
|
|
66
80
|
import { renderItemFieldReference, renderItemFieldsPage } from "../engine/field-reference.mjs";
|
|
67
81
|
import { checkDocLinks, checkDocIndex } from "../engine/docs-checks.mjs";
|
|
68
82
|
import { checkArtSlotSizes } from "../engine/art-fields.mjs";
|
|
@@ -102,6 +116,14 @@ import { ENGINE_NOTE_SCHEMAS } from "../engine/note-schemas.mjs";
|
|
|
102
116
|
import { schemaSubtypeOf } from "../engine/subtype-registry.mjs";
|
|
103
117
|
import { NOTE_VOCABULARY } from "../engine/note-vocabulary.mjs";
|
|
104
118
|
import { markupFindings } from "../engine/markup-validation.mjs";
|
|
119
|
+
import { runContext } from "../engine/content-scripts.mjs";
|
|
120
|
+
import {
|
|
121
|
+
checkLexiconNatives,
|
|
122
|
+
checkMapFonts,
|
|
123
|
+
languageNoteFindings,
|
|
124
|
+
lexiconEntries,
|
|
125
|
+
transliterate,
|
|
126
|
+
} from "../engine/map-lexicon.mjs";
|
|
105
127
|
import { checkFormatting, checkPrettierConventions, lintMarkdown } from "../engine/prose-lint.mjs";
|
|
106
128
|
import { lintProse, scoreProseTree } from "../engine/readability-lint.mjs";
|
|
107
129
|
import { loadPackageBuildConfig } from "../config.mjs";
|
|
@@ -300,6 +322,8 @@ export function registerContentCommands(cli) {
|
|
|
300
322
|
)
|
|
301
323
|
.command(withIndexPreflight(mapCommand()))
|
|
302
324
|
.command(withIndexPreflight(reachabilityCommand()))
|
|
325
|
+
.command(transliterateCommand())
|
|
326
|
+
.command(lexiconCommand())
|
|
303
327
|
.command(addressesCommand())
|
|
304
328
|
.command(withIndexPreflight(npcCommand()));
|
|
305
329
|
}
|
|
@@ -461,7 +485,7 @@ function withBodyPreflight(
|
|
|
461
485
|
: undefined;
|
|
462
486
|
const records = session?.records ?? indexRecordsFor({ config, contentBase });
|
|
463
487
|
if (session?.problems.length) {
|
|
464
|
-
|
|
488
|
+
for (const problem of session.problems) emitDiagnostic(problem);
|
|
465
489
|
process.exitCode = 1;
|
|
466
490
|
return;
|
|
467
491
|
}
|
|
@@ -1257,10 +1281,19 @@ function lintCommand() {
|
|
|
1257
1281
|
assetsBase: config.paths.assets,
|
|
1258
1282
|
});
|
|
1259
1283
|
|
|
1284
|
+
// Text marked with a language, a font or a map, checked against
|
|
1285
|
+
// the languages this package declares, the faces it installed
|
|
1286
|
+
// and the maps its notes declare.
|
|
1287
|
+
const runs = runContext(config, records);
|
|
1260
1288
|
const findings = [
|
|
1261
1289
|
...index.notes.flatMap((note) =>
|
|
1262
|
-
markupFindings(note.raw, note.file, session.syntax),
|
|
1290
|
+
markupFindings(note.raw, note.file, session.syntax, runs),
|
|
1263
1291
|
),
|
|
1292
|
+
// Each mapping note's faces, each language's native forms
|
|
1293
|
+
// and each language's `note`, against the faces and maps.
|
|
1294
|
+
...index.notes.flatMap((note) => checkMapFonts(note, runs)),
|
|
1295
|
+
...index.notes.flatMap((note) => checkLexiconNatives(note, runs)),
|
|
1296
|
+
...languageNoteFindings({ ...config, configFile: packConfigPath() }, records),
|
|
1264
1297
|
...index.notes.flatMap(firstUseChecker),
|
|
1265
1298
|
...(names ? [names] : []),
|
|
1266
1299
|
...addresses.findings,
|
|
@@ -1891,6 +1924,38 @@ function pdfCommand() {
|
|
|
1891
1924
|
};
|
|
1892
1925
|
}
|
|
1893
1926
|
|
|
1927
|
+
/**
|
|
1928
|
+
* Copy the installed script fonts into the Hugo static tree, under a path that
|
|
1929
|
+
* names their version, and drop any other version's copy.
|
|
1930
|
+
*
|
|
1931
|
+
* A package that has not installed `@heroiclands/fonts`, or sets no text in
|
|
1932
|
+
* one of its faces, gets no copy; a package missing a file its fonts depend on
|
|
1933
|
+
* gets a located error and a non-zero exit.
|
|
1934
|
+
*
|
|
1935
|
+
* @param {object} config - The resolved content configuration.
|
|
1936
|
+
* @returns {void}
|
|
1937
|
+
*/
|
|
1938
|
+
function stageSiteFonts(config) {
|
|
1939
|
+
const root = path.join(config.rootDir, HUGO_SOURCE, "static", SITE_FONTS_DIR);
|
|
1940
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
1941
|
+
const found = scriptFontFiles(config, ["woff2"]);
|
|
1942
|
+
if (!found) return;
|
|
1943
|
+
const findings = checkScriptFonts(config);
|
|
1944
|
+
for (const finding of findings) emitDiagnostic(finding);
|
|
1945
|
+
if (findings.length) {
|
|
1946
|
+
process.exitCode = 1;
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
const dest = path.join(
|
|
1950
|
+
config.rootDir,
|
|
1951
|
+
HUGO_SOURCE,
|
|
1952
|
+
"static",
|
|
1953
|
+
siteFontsDestination(found.version),
|
|
1954
|
+
);
|
|
1955
|
+
const count = copyScriptFonts(config, dest, ["woff2"]);
|
|
1956
|
+
log.info(`wrote ${count} script font file(s) to ${path.relative(process.cwd(), dest)}`);
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1894
1959
|
/**
|
|
1895
1960
|
* `package-build site` — publish the content tree as a website.
|
|
1896
1961
|
*
|
|
@@ -2077,6 +2142,7 @@ function siteCommand() {
|
|
|
2077
2142
|
);
|
|
2078
2143
|
const { file } = writeHugoConfig(config, hugo);
|
|
2079
2144
|
log.info(`wrote ${path.relative(process.cwd(), file)}`);
|
|
2145
|
+
stageSiteFonts(config);
|
|
2080
2146
|
} catch (err) {
|
|
2081
2147
|
reportFailure(err);
|
|
2082
2148
|
process.exitCode = 1;
|
|
@@ -2241,6 +2307,201 @@ function mapCommand() {
|
|
|
2241
2307
|
};
|
|
2242
2308
|
}
|
|
2243
2309
|
|
|
2310
|
+
/**
|
|
2311
|
+
* The run context a mapping command reads: the content index's maps and
|
|
2312
|
+
* lexicons, and the configured languages and installed faces, exactly as the
|
|
2313
|
+
* build has them.
|
|
2314
|
+
*
|
|
2315
|
+
* @returns {{config: object, ctx: object}|undefined} The context, or
|
|
2316
|
+
* `undefined` when the content tree could not be read whole, which is
|
|
2317
|
+
* reported.
|
|
2318
|
+
*/
|
|
2319
|
+
function mappingContext() {
|
|
2320
|
+
const config = loadPackConfig();
|
|
2321
|
+
const problems = [];
|
|
2322
|
+
const records = indexRecordsFor({
|
|
2323
|
+
contentBase: config.paths.content,
|
|
2324
|
+
config,
|
|
2325
|
+
skipDirectories: config.skipDirectories,
|
|
2326
|
+
problems,
|
|
2327
|
+
});
|
|
2328
|
+
for (const problem of problems) emitDiagnostic(problem);
|
|
2329
|
+
if (problems.length) return undefined;
|
|
2330
|
+
return { config, ctx: runContext(config, records) };
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
/**
|
|
2334
|
+
* Report what a mapping command was given that names nothing: an unknown
|
|
2335
|
+
* language at the configuration's `languages`, and an unknown map or face at
|
|
2336
|
+
* the content tree, where mapping notes are. A word the rows cannot
|
|
2337
|
+
* capitalise is in the passage given, so it has no file.
|
|
2338
|
+
*
|
|
2339
|
+
* @param {object} config - The resolved configuration.
|
|
2340
|
+
* @param {{problem: string, about: string}} found - What is wrong.
|
|
2341
|
+
* @param {string} [lang] - The language asked for.
|
|
2342
|
+
*/
|
|
2343
|
+
function reportMapping(config, found) {
|
|
2344
|
+
if (found.about === "lang") {
|
|
2345
|
+
const file = packConfigPath();
|
|
2346
|
+
let text;
|
|
2347
|
+
try {
|
|
2348
|
+
text = fs.readFileSync(file, "utf8");
|
|
2349
|
+
} catch {
|
|
2350
|
+
text = undefined;
|
|
2351
|
+
}
|
|
2352
|
+
emitDiagnostic({
|
|
2353
|
+
file,
|
|
2354
|
+
...(text ? positionOfYamlPath(text, ["languages"], { key: true }) : {}),
|
|
2355
|
+
severity: "error",
|
|
2356
|
+
message: found.problem,
|
|
2357
|
+
});
|
|
2358
|
+
} else if (found.about === "word")
|
|
2359
|
+
// A word of the passage given, which sits in no file.
|
|
2360
|
+
emitDiagnostic({ severity: "error", message: found.problem });
|
|
2361
|
+
else emitDiagnostic({ file: config.paths.content, severity: "error", message: found.problem });
|
|
2362
|
+
process.exitCode = 1;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
/**
|
|
2366
|
+
* `package-build transliterate <map> [text..]` — map a passage exactly as the
|
|
2367
|
+
* build maps a run marked with the same `map=`, `lang=` and `font=`.
|
|
2368
|
+
*
|
|
2369
|
+
* @returns {object} The yargs command module.
|
|
2370
|
+
*/
|
|
2371
|
+
// eslint-disable-next-line
|
|
2372
|
+
function transliterateCommand() {
|
|
2373
|
+
return {
|
|
2374
|
+
command: "transliterate <map> [text..]",
|
|
2375
|
+
describe: "Map a passage into a script exactly as the build maps a marked run",
|
|
2376
|
+
builder: (yargs) =>
|
|
2377
|
+
yargs
|
|
2378
|
+
.positional("map", {
|
|
2379
|
+
type: "string",
|
|
2380
|
+
describe: "The `data.map.name` of a mapping note",
|
|
2381
|
+
})
|
|
2382
|
+
.positional("text", {
|
|
2383
|
+
type: "string",
|
|
2384
|
+
array: true,
|
|
2385
|
+
describe: "The passage; `-` or nothing reads it from stdin",
|
|
2386
|
+
})
|
|
2387
|
+
.option("lang", {
|
|
2388
|
+
type: "string",
|
|
2389
|
+
describe: "A declared language, whose lexicon is read first",
|
|
2390
|
+
})
|
|
2391
|
+
.option("font", {
|
|
2392
|
+
type: "string",
|
|
2393
|
+
describe: "The face; required when the map lists more than one",
|
|
2394
|
+
})
|
|
2395
|
+
.option("hex", {
|
|
2396
|
+
type: "boolean",
|
|
2397
|
+
default: false,
|
|
2398
|
+
describe: "Print the code points, as `U+XXXX` separated by spaces",
|
|
2399
|
+
}),
|
|
2400
|
+
handler: (argv) => {
|
|
2401
|
+
try {
|
|
2402
|
+
const read = mappingContext();
|
|
2403
|
+
if (!read) {
|
|
2404
|
+
process.exitCode = 1;
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
const words = (argv.text ?? []).map(String);
|
|
2408
|
+
const text =
|
|
2409
|
+
!words.length || (words.length === 1 && words[0] === "-") ?
|
|
2410
|
+
fs.readFileSync(0, "utf8").replace(/\r?\n$/, "")
|
|
2411
|
+
: words.join(" ");
|
|
2412
|
+
const mapped = transliterate(
|
|
2413
|
+
read.ctx,
|
|
2414
|
+
{ map: String(argv.map), lang: argv.lang, font: argv.font },
|
|
2415
|
+
text,
|
|
2416
|
+
);
|
|
2417
|
+
if ("problem" in mapped) {
|
|
2418
|
+
reportMapping(read.config, mapped);
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
console.log(
|
|
2422
|
+
argv.hex ?
|
|
2423
|
+
Array.from(
|
|
2424
|
+
mapped.text,
|
|
2425
|
+
(char) =>
|
|
2426
|
+
`U+${char.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")}`,
|
|
2427
|
+
).join(" ")
|
|
2428
|
+
: mapped.text,
|
|
2429
|
+
);
|
|
2430
|
+
} catch (err) {
|
|
2431
|
+
reportFailure(err);
|
|
2432
|
+
process.exitCode = 1;
|
|
2433
|
+
}
|
|
2434
|
+
},
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
/**
|
|
2439
|
+
* `package-build lexicon <language> --map <map>` — every word of a language's
|
|
2440
|
+
* lexicon with its native form under a map, as the build writes it.
|
|
2441
|
+
*
|
|
2442
|
+
* @returns {object} The yargs command module.
|
|
2443
|
+
*/
|
|
2444
|
+
// eslint-disable-next-line
|
|
2445
|
+
function lexiconCommand() {
|
|
2446
|
+
return {
|
|
2447
|
+
command: "lexicon <language>",
|
|
2448
|
+
describe: "List a language's words with their native forms under a map",
|
|
2449
|
+
builder: (yargs) =>
|
|
2450
|
+
yargs
|
|
2451
|
+
.positional("language", {
|
|
2452
|
+
type: "string",
|
|
2453
|
+
describe: "A declared language whose `note` names its language note",
|
|
2454
|
+
})
|
|
2455
|
+
.option("map", {
|
|
2456
|
+
type: "string",
|
|
2457
|
+
demandOption: true,
|
|
2458
|
+
describe: "The `data.map.name` of a mapping note",
|
|
2459
|
+
})
|
|
2460
|
+
.option("font", {
|
|
2461
|
+
type: "string",
|
|
2462
|
+
describe: "The face; required when the map lists more than one",
|
|
2463
|
+
})
|
|
2464
|
+
.option("json", {
|
|
2465
|
+
type: "boolean",
|
|
2466
|
+
default: false,
|
|
2467
|
+
describe: "Print a JSON array rather than tab-separated lines",
|
|
2468
|
+
}),
|
|
2469
|
+
handler: (argv) => {
|
|
2470
|
+
try {
|
|
2471
|
+
const read = mappingContext();
|
|
2472
|
+
if (!read) {
|
|
2473
|
+
process.exitCode = 1;
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
const listed = lexiconEntries(read.ctx, {
|
|
2477
|
+
lang: String(argv.language),
|
|
2478
|
+
map: String(argv.map),
|
|
2479
|
+
font: argv.font,
|
|
2480
|
+
});
|
|
2481
|
+
if ("problem" in listed) {
|
|
2482
|
+
reportMapping(read.config, listed);
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
if (argv.json) console.log(JSON.stringify(listed.entries, null, 2));
|
|
2486
|
+
else
|
|
2487
|
+
for (const entry of listed.entries)
|
|
2488
|
+
console.log(
|
|
2489
|
+
[
|
|
2490
|
+
entry.form,
|
|
2491
|
+
entry.native,
|
|
2492
|
+
entry.exception ? "exception" : "rows",
|
|
2493
|
+
entry.class ?? "",
|
|
2494
|
+
entry.gloss ?? "",
|
|
2495
|
+
].join("\t"),
|
|
2496
|
+
);
|
|
2497
|
+
} catch (err) {
|
|
2498
|
+
reportFailure(err);
|
|
2499
|
+
process.exitCode = 1;
|
|
2500
|
+
}
|
|
2501
|
+
},
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2244
2505
|
/**
|
|
2245
2506
|
* `package-build reachability <dir> [file]` — check that a corpus reads through.
|
|
2246
2507
|
*
|
package/bin/package-build.mjs
CHANGED
|
@@ -96,6 +96,11 @@ import { loadPackConfig, packConfigPath, resolveConfigFile } from "../engine/pac
|
|
|
96
96
|
import { cleanBuildArtifacts, resetStage, stageAssets } from "../stage.mjs";
|
|
97
97
|
import { rowsStagingPictureRoots, stagePackageAssets } from "../engine/asset-shipping.mjs";
|
|
98
98
|
import { baseStyleStageEntry } from "../engine/base-styles.mjs";
|
|
99
|
+
import {
|
|
100
|
+
checkScriptFonts,
|
|
101
|
+
copyScriptFonts,
|
|
102
|
+
STAGED_FONTS_DIR,
|
|
103
|
+
} from "../engine/script-font-delivery.mjs";
|
|
99
104
|
import {
|
|
100
105
|
CDN_ENV,
|
|
101
106
|
cdnCredentials,
|
|
@@ -576,6 +581,26 @@ function assetsCommand() {
|
|
|
576
581
|
count += staged.entries;
|
|
577
582
|
files += staged.files;
|
|
578
583
|
}
|
|
584
|
+
|
|
585
|
+
// The installed fonts, when the package has installed the fonts
|
|
586
|
+
// package and a note sets text in one of its faces: the
|
|
587
|
+
// stylesheet, the WOFF2 files and the licence, which the
|
|
588
|
+
// manifest's `styles` names.
|
|
589
|
+
const fontFindings = checkScriptFonts(packConfig);
|
|
590
|
+
if (fontFindings.length) {
|
|
591
|
+
const error = new Error(fontFindings.map(formatDiagnostic).join("\n"));
|
|
592
|
+
error.located = true;
|
|
593
|
+
die(error);
|
|
594
|
+
}
|
|
595
|
+
const fonts = copyScriptFonts(
|
|
596
|
+
packConfig,
|
|
597
|
+
path.resolve(config.rootDir, config.stageDir, STAGED_FONTS_DIR),
|
|
598
|
+
["woff2"],
|
|
599
|
+
);
|
|
600
|
+
if (fonts) {
|
|
601
|
+
count += 1;
|
|
602
|
+
files += fonts;
|
|
603
|
+
}
|
|
579
604
|
console.log(`✅ Static assets staged (${count} entries, ${files} files).`);
|
|
580
605
|
}),
|
|
581
606
|
};
|
package/content-config.mjs
CHANGED
|
@@ -70,6 +70,7 @@ import { normalizeCurrentDate } from "./engine/current-date.mjs";
|
|
|
70
70
|
// Leaves with no local imports of their own, so naming them here cannot close
|
|
71
71
|
// a cycle around a consumer's config file (see `engine/pack-config.mjs`).
|
|
72
72
|
import { ADDRESS_SEGMENT_PATTERN, isAddressSegment } from "./engine/address-charset.mjs";
|
|
73
|
+
import { isLanguageTag } from "./engine/language-tag.mjs";
|
|
73
74
|
import { ASSET_TYPE_NAMES, shippingLicensesProblem } from "./engine/asset-types.mjs";
|
|
74
75
|
import { isReservedPackage } from "./engine/packages.mjs";
|
|
75
76
|
import { EMPTY_ICON_REGISTRY, checkIconRegistry } from "./engine/content-icons.mjs";
|
|
@@ -571,6 +572,10 @@ export function publishesContentPages(config) {
|
|
|
571
572
|
* resolved against it, so the build never
|
|
572
573
|
* depends on the working directory.
|
|
573
574
|
* @property {string} [currentDate] Setting-wide neutral current date; defaults to "720.1".
|
|
575
|
+
* @property {string} [defaultCalendar] Shortcode of the `lore` note with
|
|
576
|
+
* `subType: calendar` that prints the
|
|
577
|
+
* dates of a note no other rule gives a
|
|
578
|
+
* calendar.
|
|
574
579
|
* @property {string} contentPackage Content package name — the address
|
|
575
580
|
* namespace every note in this
|
|
576
581
|
* repository is published under.
|
|
@@ -610,6 +615,9 @@ export function publishesContentPages(config) {
|
|
|
610
615
|
* `{ system, builders }` registries
|
|
611
616
|
* instead, and the accepted type
|
|
612
617
|
* vocabulary is their union.
|
|
618
|
+
* @property {Record<string, LanguageSpec>} [languages] The languages a
|
|
619
|
+
* note may tag text with, by short
|
|
620
|
+
* name. Default `{}`.
|
|
613
621
|
* @property {PackSpec[]} packs Packs to compile. More than one entry
|
|
614
622
|
* may share a `type`: a note then names
|
|
615
623
|
* the pack it belongs in with its
|
|
@@ -646,6 +654,8 @@ export function publishesContentPages(config) {
|
|
|
646
654
|
*
|
|
647
655
|
* @typedef {object} ContentBuildConfig
|
|
648
656
|
* @property {string} currentDate Setting-wide neutral current date.
|
|
657
|
+
* @property {string|null} defaultCalendar The calendar of last resort's
|
|
658
|
+
* shortcode, `null` when none is named.
|
|
649
659
|
* @property {string} rootDir
|
|
650
660
|
* @property {string} contentPackage
|
|
651
661
|
* @property {string|null} foundryPackage `null` for a `documentation`
|
|
@@ -713,6 +723,8 @@ export function publishesContentPages(config) {
|
|
|
713
723
|
* fonts this package ships and the names it
|
|
714
724
|
* draws from them; empty when it declares
|
|
715
725
|
* none.
|
|
726
|
+
* @property {Readonly<Record<string, Readonly<LanguageSpec>>>} languages
|
|
727
|
+
* Frozen; `{}` when absent.
|
|
716
728
|
* @property {readonly Readonly<ResolvedPackSpec>[]} packs
|
|
717
729
|
* @property {readonly string[]} packDirectories Derived: every pack directory
|
|
718
730
|
* the build produces, in compile order —
|
|
@@ -730,6 +742,7 @@ const CONFIG_KEYS = [
|
|
|
730
742
|
"rootDir",
|
|
731
743
|
"contentPackage",
|
|
732
744
|
"currentDate",
|
|
745
|
+
"defaultCalendar",
|
|
733
746
|
"foundryPackage",
|
|
734
747
|
"homepage",
|
|
735
748
|
"authors",
|
|
@@ -740,6 +753,7 @@ const CONFIG_KEYS = [
|
|
|
740
753
|
"skipDirectories",
|
|
741
754
|
"forbidGeneratedArt",
|
|
742
755
|
"icons",
|
|
756
|
+
"languages",
|
|
743
757
|
"packs",
|
|
744
758
|
"docs",
|
|
745
759
|
"site",
|
|
@@ -912,6 +926,88 @@ function fail(field, problem) {
|
|
|
912
926
|
throw Object.assign(new TypeError(`package-build config: \`${field}\` ${problem}.`), { field });
|
|
913
927
|
}
|
|
914
928
|
|
|
929
|
+
/**
|
|
930
|
+
* The configured `defaultCalendar`: a calendar note's shortcode, or `null`.
|
|
931
|
+
* Whether it names a calendar note is checked against the content tree when
|
|
932
|
+
* the content index is built.
|
|
933
|
+
*
|
|
934
|
+
* @param {unknown} value - The configured value.
|
|
935
|
+
* @returns {string|null} The shortcode.
|
|
936
|
+
*/
|
|
937
|
+
function normalizeDefaultCalendar(value) {
|
|
938
|
+
if (value === undefined || value === null) return null;
|
|
939
|
+
if (typeof value !== "string" || !isAddressSegment(value))
|
|
940
|
+
fail(
|
|
941
|
+
"defaultCalendar",
|
|
942
|
+
"must be the shortcode of a calendar note — lowercase letters and digits",
|
|
943
|
+
);
|
|
944
|
+
return /** @type {string} */ (value);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* One entry of `languages`.
|
|
949
|
+
*
|
|
950
|
+
* @typedef {object} LanguageSpec
|
|
951
|
+
* @property {string} tag Its BCP 47 tag. A constructed language takes
|
|
952
|
+
* `art-x-<name>`.
|
|
953
|
+
* @property {string} [note] The Address of its language note, whose
|
|
954
|
+
* `data.words` is the lexicon a mapped run in the
|
|
955
|
+
* language reads first.
|
|
956
|
+
*/
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* What a language name may be: lowercase letters, digits and single hyphens,
|
|
960
|
+
* starting with a letter. Lowercase keeps two names from differing only in
|
|
961
|
+
* case, which an index keyed by them cannot tell apart.
|
|
962
|
+
*/
|
|
963
|
+
const LANGUAGE_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Check `languages` and freeze it.
|
|
967
|
+
*
|
|
968
|
+
* @param {unknown} value - The configured mapping.
|
|
969
|
+
* @returns {Readonly<Record<string, Readonly<LanguageSpec>>>} The frozen
|
|
970
|
+
* mapping; `{}` when absent.
|
|
971
|
+
*/
|
|
972
|
+
function normalizeLanguages(value) {
|
|
973
|
+
if (value === undefined || value === null) return Object.freeze({});
|
|
974
|
+
if (!isPlainObject(value)) fail("languages", "must be a mapping of language name to `{tag}`");
|
|
975
|
+
const out = {};
|
|
976
|
+
for (const [name, entry] of Object.entries(/** @type {object} */ (value))) {
|
|
977
|
+
const where = `languages.${name}`;
|
|
978
|
+
if (!LANGUAGE_NAME.test(name))
|
|
979
|
+
fail(
|
|
980
|
+
where,
|
|
981
|
+
"is not a name: a name is lowercase letters, digits and single hyphens, " +
|
|
982
|
+
"starting with a letter",
|
|
983
|
+
);
|
|
984
|
+
if (!isPlainObject(entry)) fail(where, "must be a mapping with `tag`");
|
|
985
|
+
rejectUnknownKeys(entry, ["tag", "note"], `${where}.`);
|
|
986
|
+
const { tag, note } = /** @type {Record<string, unknown>} */ (entry);
|
|
987
|
+
if (tag === undefined || tag === null || tag === "")
|
|
988
|
+
fail(
|
|
989
|
+
`${where}.tag`,
|
|
990
|
+
"must be a BCP 47 language tag, such as `art-x-vyl` for a constructed language",
|
|
991
|
+
);
|
|
992
|
+
if (!isLanguageTag(tag))
|
|
993
|
+
fail(
|
|
994
|
+
`${where}.tag`,
|
|
995
|
+
`${String(tag)} is not a valid BCP 47 language tag; a constructed ` +
|
|
996
|
+
"language takes `art-x-<name>`",
|
|
997
|
+
);
|
|
998
|
+
if (note !== undefined && (typeof note !== "string" || note.trim() === ""))
|
|
999
|
+
fail(
|
|
1000
|
+
`${where}.note`,
|
|
1001
|
+
"must be the Address of a language note, such as `skill-vylarilng`",
|
|
1002
|
+
);
|
|
1003
|
+
out[name] = Object.freeze({
|
|
1004
|
+
tag: /** @type {string} */ (tag),
|
|
1005
|
+
...(note === undefined ? {} : { note: /** @type {string} */ (note) }),
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
return Object.freeze(out);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
915
1011
|
/**
|
|
916
1012
|
* @param {object} object
|
|
917
1013
|
* @param {readonly string[]} allowed
|
|
@@ -2973,6 +3069,7 @@ export function defineConfig(config) {
|
|
|
2973
3069
|
fail("currentDate", error.message);
|
|
2974
3070
|
}
|
|
2975
3071
|
})(),
|
|
3072
|
+
defaultCalendar: normalizeDefaultCalendar(input.defaultCalendar),
|
|
2976
3073
|
contentPackage: requireContentPackage(input.contentPackage, docEntryTypes, {
|
|
2977
3074
|
rootDir,
|
|
2978
3075
|
packageKind,
|
|
@@ -3049,6 +3146,7 @@ export function defineConfig(config) {
|
|
|
3049
3146
|
skipDirectories: Object.freeze(skipDirectories),
|
|
3050
3147
|
forbidGeneratedArt,
|
|
3051
3148
|
icons: normalizeIcons(input.icons, rootDir),
|
|
3149
|
+
languages: normalizeLanguages(input.languages),
|
|
3052
3150
|
packs: Object.freeze(packs),
|
|
3053
3151
|
packDirectories: Object.freeze(packDirectories),
|
|
3054
3152
|
docs: normalizeDocs(input.docs),
|
package/docs/api.md
CHANGED
|
@@ -723,7 +723,7 @@ resolved value: the culture a place or affiliation inherits through
|
|
|
723
723
|
|
|
724
724
|
### `engine.markdownExpressions`
|
|
725
725
|
|
|
726
|
-
`renderMarkdownExpressions(body, { fm, dates, sqlResults, figures, file, bodyLine })` expands scalar frontmatter references and registered Handlebars helpers outside Markdown code. The `dateformat` helper uses `formatDateInCalendar
|
|
726
|
+
`renderMarkdownExpressions(body, { fm, dates, sqlResults, figures, file, bodyLine })` expands scalar frontmatter references and registered Handlebars helpers outside Markdown code. The `dateformat` helper uses `formatDateInCalendar`, in the calendar it names or else the note's resolved calendar, which it reads from `dates.noteCalendars` — the map a reckoning context built over content-index records carries; `gt`, `gte`, `lt`, `lte`, `eq`, `and`, `or`, and `not` evaluate explicit comparisons. The `sql` helper reads a prepared scalar result, which must contain one row and one column. The `ref` helper links to a captioned item by its anchor — `"#thorn"` on the same note — in `number` (the default, and admitted explicitly), `full` or `title` form; `figures` is a `Map` of this note's own figures, by id, to `{label, caption, hasCaption}`, and an address naming another note is reported as unresolved rather than looked up. `sqlQueriesInMarkdown` discovers SQL calls, including nested calls; `prepareInlineSqlExpressions` evaluates them through the same DuckDB connection as SQL tables. `EXPRESSION_HELPERS` is the one list of helpers: the engine registers from it and the authoring document is checked against it, so each name there carries the parameters it takes and a sentence on what it does. The renderer returns `{ markdown, findings }`, with source positions for invalid expressions. Site, Foundry and PDF passes invoke it after generated tables expand and before links resolve. Hugo shortcodes remain literal for the site renderer.
|
|
727
727
|
|
|
728
728
|
| Export | Signature | Returns | Use it when |
|
|
729
729
|
| --------------------------- | ------------------------------------------ | ---------------------- | ------------------------------------------------ |
|
|
@@ -736,10 +736,13 @@ resolved value: the culture a place or affiliation inherits through
|
|
|
736
736
|
|
|
737
737
|
The corpus registry anchors each calendar's year 1, day 1 to its canonical `data.epoch` and maps every declared era to that calendar. Exactly one `start: null` era covers earlier years and counts backwards; exactly one `start: 1` era begins the forward count. Other starts are distinct positive calendar years. Numeric order determines bounds, irrespective of array order. Duplicate starts and markers, missing required eras, and invalid epochs are findings. Short qualifiers are ambiguous when they name eras in several packages; a full Address selects one.
|
|
738
738
|
|
|
739
|
-
| Export | Signature | Returns
|
|
740
|
-
| ------------------------- | --------------------------------------------- |
|
|
741
|
-
| `resolveReckoningMarkers` | `resolveReckoningMarkers(index, daysPerYear)` | `{eras, markers, findings}`
|
|
742
|
-
| `reckoningContext` | `reckoningContext(index)` | `{eras, markers, daysPerYear, findings}` | sharing one resolved date context across checks on a corpus
|
|
739
|
+
| Export | Signature | Returns | Use it when |
|
|
740
|
+
| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
741
|
+
| `resolveReckoningMarkers` | `resolveReckoningMarkers(index, daysPerYear)` | `{eras, markers, findings}` | resolving authored eras, their markers, and their bounds |
|
|
742
|
+
| `reckoningContext` | `reckoningContext(index)` | `{eras, markers, daysPerYear, findings, noteCalendars}` | sharing one resolved date context across checks on a corpus |
|
|
743
|
+
| `noteCalendarMap` | `noteCalendarMap(notes)` | `Map<string, string>` | reading each record's `resolvedCalendar` by note, as `reckoningContext` carries it in `noteCalendars` |
|
|
744
|
+
| `noteCalendarKey` | `noteCalendarKey(fm)` | `string \| undefined` | finding a note's entry in `noteCalendars` by its type and shortcode |
|
|
745
|
+
| `calendarAddress` | `calendarAddress(value)` | `string \| undefined` | reading a record's `resolvedCalendar`, tuple or string, as its canonical Address |
|
|
743
746
|
|
|
744
747
|
### `engine.calendarNotes`
|
|
745
748
|
|
|
@@ -2532,15 +2535,16 @@ The wildcard engine subpath exposes
|
|
|
2532
2535
|
`@heroiclands/package-build/engine/dictionary-rows` for deriving the
|
|
2533
2536
|
`dictionary` and `terms` SQL relations from content-index records:
|
|
2534
2537
|
|
|
2535
|
-
| Export | Signature | Returns | Use it when
|
|
2536
|
-
| ----------------------- | ----------------------------------------------- | ------------------------------------------- |
|
|
2537
|
-
| `DICTIONARY_COLUMNS` | `const DICTIONARY_COLUMNS` | A readonly list of column names | Declaring the schema of a language's dictionary.
|
|
2538
|
-
| `TERM_COLUMNS` | `const TERM_COLUMNS` | A readonly list of column names | Declaring the schema of owner-specific glossary terms.
|
|
2539
|
-
| `ETYMOLOGY_KEYS` | `const ETYMOLOGY_KEYS` | A readonly list of keys | Reading the structure of a dictionary word's etymology.
|
|
2540
|
-
| `dictionaryLetter` | `dictionaryLetter(text)` | `string` | Deriving a heading letter from a sort key.
|
|
2541
|
-
| `dictionaryEntryAnchor` | `dictionaryEntryAnchor(language, name)` | `string` | Deriving the stable entry anchor for a word in a language's dictionary.
|
|
2542
|
-
| `dictionaryAndTermRows` | `dictionaryAndTermRows(records, all = records)` | `{ dictionary: object[], terms: object[] }` | Deriving dictionary words and glossary terms for the selected records.
|
|
2543
|
-
| `serializeRows` | `serializeRows(rows)` | `string` | Writing deterministic JSON Lines with keys sorted at every depth.
|
|
2538
|
+
| Export | Signature | Returns | Use it when |
|
|
2539
|
+
| ----------------------- | ----------------------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
2540
|
+
| `DICTIONARY_COLUMNS` | `const DICTIONARY_COLUMNS` | A readonly list of column names | Declaring the schema of a language's dictionary. |
|
|
2541
|
+
| `TERM_COLUMNS` | `const TERM_COLUMNS` | A readonly list of column names | Declaring the schema of owner-specific glossary terms. |
|
|
2542
|
+
| `ETYMOLOGY_KEYS` | `const ETYMOLOGY_KEYS` | A readonly list of keys | Reading the structure of a dictionary word's etymology. |
|
|
2543
|
+
| `dictionaryLetter` | `dictionaryLetter(text)` | `string` | Deriving a heading letter from a sort key. |
|
|
2544
|
+
| `dictionaryEntryAnchor` | `dictionaryEntryAnchor(language, name)` | `string` | Deriving the stable entry anchor for a word in a language's dictionary. |
|
|
2545
|
+
| `dictionaryAndTermRows` | `dictionaryAndTermRows(records, all = records)` | `{ dictionary: object[], terms: object[] }` | Deriving dictionary words and glossary terms for the selected records. |
|
|
2546
|
+
| `serializeRows` | `serializeRows(rows)` | `string` | Writing deterministic JSON Lines with keys sorted at every depth. |
|
|
2547
|
+
| `pluralOf` | `pluralOf(word, grammar)` | `string \| null` | Deriving a noun's plural: its own `plural`, else the first `grammar.plural` row whose ending fits. |
|
|
2544
2548
|
|
|
2545
2549
|
`dictionaryEntryAnchor` takes the language's canonical Address and the
|
|
2546
2550
|
word's form. Its `word-` anchor is derived from the full SHA-256 digest of
|