Files
entity-descriptions/scripts/testOutput.ts
T
2026-02-25 13:31:36 +01:00

329 lines
8.8 KiB
TypeScript

import { isNotNullish } from "@elyukai/utils/nullable"
import { assertExhaustive } from "@elyukai/utils/typeSafety"
import { deepEqual } from "@optolith/helpers/compare"
import { MessageFormat } from "messageformat"
import type { MessageValue } from "messageformat/functions"
import { findPackageJSON } from "node:module"
import { dirname, join } from "node:path"
import { argv } from "node:process"
import { styleText, type InspectColor } from "node:util"
import { schema } from "optolith-database-schema"
import {
createCache,
type IdMap as CacheIdMap,
} from "optolith-database-schema/cache"
import { TSONDB } from "tsondb"
import { fromUniformCase } from "tsondb/schema/gen"
import type { LocaleEnvironment } from "../lib/helpers/locale.js"
import {
getEntityDescription,
isSupportedEntity,
type EntityDescriptionSection,
type IdMap,
type TableEntityDescriptionSection,
} from "../lib/index.js"
const dataRootPath = join(
dirname(findPackageJSON(import.meta.url) ?? import.meta.filename),
"..",
"optolith-client",
"src",
"database",
"contents",
"data",
)
const db = await TSONDB.create({
schema,
dataRootPath,
locales: ["de-DE"],
})
const [localeId, entity, id] = argv.slice(2)
if (!entity || !db.schema.isEntityName(entity) || !isSupportedEntity(entity)) {
throw new Error("Invalid entity name")
}
if (localeId === undefined) {
throw new Error("No locale provided")
}
if (!id) {
throw new Error("No ID provided")
}
const localeInstance = db.getInstanceOfEntityById("Locale", localeId)
if (localeInstance === undefined) {
throw new Error("Locale not found")
}
const conjunctionListFormat = new Intl.ListFormat(localeId, {
type: "conjunction",
})
const disjunctionListFormat = new Intl.ListFormat(localeId, {
type: "disjunction",
})
const unitListFormat = new Intl.ListFormat(localeId, {
type: "unit",
})
const collator = new Intl.Collator(localeId, { usage: "sort" })
const localeEnv: LocaleEnvironment = {
id: localeId,
format: (text, args) =>
new MessageFormat(localeId, text, { bidiIsolation: "none" }).format(args),
compare: collator.compare.bind(collator),
translate: (key, ...rest) =>
new MessageFormat(localeId, localeInstance.translations?.[key] ?? key, {
bidiIsolation: "none",
functions: {
list: (ctx, options, input): MessageValue<"list"> => {
if (!Array.isArray(input)) {
ctx.onError(
new RangeError("Input for list function must be an array"),
)
return {
type: "list",
options,
}
} else {
switch (options.type) {
case "conjunction": {
const value = conjunctionListFormat.format(input)
return {
type: "list",
options,
toString() {
return value
},
valueOf() {
return value
},
}
}
case "disjunction": {
const value = disjunctionListFormat.format(input)
return {
type: "list",
options,
toString() {
return value
},
valueOf() {
return value
},
}
}
case "unit": {
const value = unitListFormat.format(input)
return {
type: "list",
options,
toString() {
return value
},
valueOf() {
return value
},
}
}
default:
ctx.onError(
new RangeError("Unsupported list type: ${options.type}"),
)
return {
type: "list",
options,
}
}
}
},
},
}).format(rest[0] as Record<string, unknown> | undefined),
translateMap: translations => translations?.[localeId],
measurementAdjustments: {
milesMultiplier: 1,
stepsMultiplier: 1,
halffingersMultiplier: 1,
stonesMultiplier: 1,
},
join: (list, style) => {
switch (style) {
case "conjunction":
return conjunctionListFormat.format(list)
case "disjunction":
return disjunctionListFormat.format(list)
case "unit":
return unitListFormat.format(list)
default:
return assertExhaustive(style)
}
},
}
const idMap: IdMap & CacheIdMap = {
Advantage: {
Blessed: "e5a9bb6d-9791-4d20-bf34-52a3aaf69726",
Spellcaster: "9770a700-acb7-4b60-b6ea-a329a9acb26b",
},
KarmaSpecialAbility: {
AspectKnowledge: "a1c2c1ef-9b1a-4c0b-9bc0-51e4b944269e",
},
MagicalSpecialAbility: {
PropertyKnowledge: "5d9f3ba7-0fb8-48ca-aebb-baa01c67b4ab",
},
DerivedCharacteristic: {
LifePoints: "190845e8-c2c8-40ff-8908-248f01b49f8b",
Spirit: "b6f98337-77b4-4f8e-9b6d-fda3a49d5c75",
Toughness: "1fa344af-3e53-4f25-b36a-7f53f51b90f5",
Initiative: "0b97b4ce-75b0-4573-add9-86621dcf52a6",
Movement: "0c634904-d238-47ee-9b0f-2d6a9d5ff63a",
},
ExperienceLevel: {
Experienced: "2b0a18c5-40a8-4c86-98b3-a93c85e82497",
},
}
const cache = createCache(db, idMap)
const result = getEntityDescription(
db,
localeEnv,
idMap,
(parentId, id) =>
cache.activatableSelectOptions[parentId.kind][
fromUniformCase(parentId)
]?.find(option => deepEqual(option.id, id)),
parentId =>
cache.activatableSelectOptions[parentId.kind][fromUniformCase(parentId)] ??
[],
skillId => cache.newApplicationsAndUses.newApplications[skillId] ?? [],
skillId => cache.newApplicationsAndUses.uses[skillId] ?? [],
entity,
id,
)
if (result === undefined) {
throw new Error("No description found")
}
console.log(styleText(["bold", "underline"], result.title))
if (result.subtitle) console.log(styleText("italic", result.subtitle))
const getColumnWidthForIndex = (
table: TableEntityDescriptionSection,
index: number,
): number =>
Math.max(
0,
...[
table.header[index],
...table.rows.map(row => row[index]),
table.footer?.[index],
]
.filter(isNotNullish)
.map(cell => cell.length),
)
const getColumnWiths = (table: TableEntityDescriptionSection): number[] =>
table.header.map((_, index) => getColumnWidthForIndex(table, index))
const logTableRow = (
row: string[],
columnWidths: number[],
indent: string,
format?: InspectColor | InspectColor[],
): void => {
const formatter =
format === undefined
? (text: string) => text
: (text: string) => styleText(format, text)
console.log(
indent +
row
.map(
(cell, index) =>
formatter(cell) +
" ".repeat((columnWidths[index] ?? 0) - cell.length),
)
.join(" "),
)
}
const logSection = (
section: EntityDescriptionSection,
level: number,
indent = "",
): void => {
switch (section.type) {
case "labeled":
console.log(indent + styleText("bold", section.label))
logSection(section.value, level, indent)
break
case "plain":
console.log(indent + section.text)
break
case "definitionList": {
const listIndent =
"style" in section && section.style === "hidden"
? indent.slice(0, -2)
: indent
const actualLevel =
"style" in section && section.style === "hidden" ? level - 1 : level
section.items.forEach(item => {
console.log(
listIndent +
styleText(actualLevel > 1 ? "italic" : "bold", item.label + ":"),
)
if (Array.isArray(item.value)) {
item.value.forEach((subsection, subsectionIndex) => {
if (subsectionIndex > 0) {
console.log()
}
logSection(subsection, actualLevel + 1, listIndent + " ")
})
} else {
console.log(listIndent + " " + item.value)
}
})
break
}
case "table": {
const columnWidths = getColumnWiths(section)
logTableRow(section.header, columnWidths, indent, "bold")
section.rows.forEach(row => logTableRow(row, columnWidths, indent))
if (section.footer) {
logTableRow(section.footer, columnWidths, indent, [
"italic",
"underline",
])
}
break
}
}
}
result.body.forEach(section => {
console.log()
logSection(section, 1)
})
if (result.errata) {
console.log()
result.errata.forEach(erratum => {
console.log(styleText(["bold", "yellow"], erratum.date))
console.log(" " + styleText("yellow", erratum.description))
})
}
if (result.references) {
console.log()
console.log(styleText("blue", result.references))
}