feat: print references

This commit is contained in:
Lukas Obermann
2024-10-07 22:07:28 +02:00
parent 82878f8ddc
commit 381394fd4e
6 changed files with 406 additions and 5 deletions
+19 -5
View File
@@ -1,6 +1,8 @@
import { filterNonNullable } from "@optolith/helpers/array"
import { PublicationRefs } from "optolith-database-schema/types/source/_PublicationRef"
import { GetById } from "./helpers/getTypes.js"
import { LocaleEnvironment } from "./helpers/locale.js"
import { getReferencesTranslation } from "./references/index.js"
/**
* Creates a function that creates the JSON representation of the rules text for
@@ -15,14 +17,25 @@ export const createEntityDescriptionCreator =
return () => undefined
}
return (params) => {
const rawEntry = fn(entry, ...args)(params)
return (locale, getPublicationById) => {
const rawEntry = fn(entry, ...args)(locale, getPublicationById)
if (rawEntry === undefined) {
return undefined
}
return { ...rawEntry, body: filterNonNullable(rawEntry.body) }
return {
...rawEntry,
body: filterNonNullable(rawEntry.body),
references:
rawEntry.references === undefined
? undefined
: getReferencesTranslation(
getPublicationById,
locale,
rawEntry.references
),
}
}
}
@@ -44,7 +57,8 @@ export type EntityDescriptionCreator<
* JSON representation of the rules text for a ibrary entry.
*/
export type EntityDescriptionConfiguredCreator<R = EntityDescription> = (
locale: LocaleEnvironment
locale: LocaleEnvironment,
getPublicationById: GetById.Static.Publication
) => R | undefined
/**
@@ -55,7 +69,7 @@ export type EntityDescription = {
subtitle?: string
className: string
body: EntityDescriptionSection[]
references?: PublicationRefs
references?: string
}
/**
+107
View File
@@ -0,0 +1,107 @@
import { isNotNullish } from "@optolith/helpers/nullable"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import { PublicationRefs } from "optolith-database-schema/types/source/_PublicationRef"
import { GetById } from "../helpers/getTypes.js"
import { LocaleEnvironment } from "../helpers/locale.js"
import {
isSimpleOccurrence,
isSimpleOccurrences,
isVersionedOccurrence,
} from "./occurrence.js"
import {
fromRawPageRange,
normalizePageRanges,
numberRangeToPageRange,
printPageRange,
printPageRanges,
} from "./pageRange.js"
export const getReferencesTranslation = (
getPublicationById: GetById.Static.Publication,
locale: LocaleEnvironment,
references: PublicationRefs
) =>
references
.map((ref) => {
const publication = getPublicationById(ref.id.publication)
const publicationTranslations = locale.translateMap(
publication?.translations
)
const occurrences = locale.translateMap(ref.occurrences)
if (
publication === undefined ||
publicationTranslations === undefined ||
occurrences === undefined
) {
return undefined
}
if (isSimpleOccurrence(occurrences)) {
return `${publicationTranslations.name} ${printPageRange(
locale.translate,
numberRangeToPageRange(occurrences)
)}`
}
if (isSimpleOccurrences(occurrences)) {
const ranges = normalizePageRanges(
occurrences.map(numberRangeToPageRange)
)
return `${publicationTranslations.name} ${printPageRanges(
locale.translate,
ranges
)}`
}
if (isVersionedOccurrence(occurrences)) {
const initialPageRanges = normalizePageRanges(
occurrences.initial.pages.map(fromRawPageRange)
)
const initial =
occurrences.initial.printing === undefined
? printPageRanges(locale.translate, initialPageRanges)
: `${printPageRanges(
locale.translate,
initialPageRanges
)} (${locale.translate(
"since the {0}. printing",
occurrences.initial.printing
)})`
const revisions =
occurrences.revisions?.map((rev) => {
switch (rev.tag) {
case "Since": {
const pageRanges = normalizePageRanges(
rev.since.pages.map(fromRawPageRange)
)
return `${printPageRanges(
locale.translate,
pageRanges
)} (${locale.translate(
"since the {0}. printing",
rev.since.printing
)})`
}
case "Deprecated": {
return locale.translate(
"removed in {0}. printing",
rev.deprecated.printing
)
}
default:
return assertExhaustive(rev)
}
}) ?? []
const allPageRanges = [initial, ...revisions].join("; ")
return `${publicationTranslations.name} ${allPageRanges}`
}
return assertExhaustive(occurrences)
})
.filter(isNotNullish)
.join("; ")
+18
View File
@@ -0,0 +1,18 @@
import {
Occurrence,
SimpleOccurrence,
SimpleOccurrences,
VersionedOccurrence,
} from "optolith-database-schema/types/source/_PublicationRef"
export const isSimpleOccurrence = (
occurrence: Occurrence
): occurrence is SimpleOccurrence => Object.hasOwn(occurrence, "first_page")
export const isSimpleOccurrences = (
occurrence: Occurrence
): occurrence is SimpleOccurrences => Array.isArray(occurrence)
export const isVersionedOccurrence = (
occurrence: Occurrence
): occurrence is VersionedOccurrence => Object.hasOwn(occurrence, "initial")
+69
View File
@@ -0,0 +1,69 @@
import { Compare } from "@optolith/helpers/compare"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import { Page } from "optolith-database-schema/types/source/_PublicationRef"
import { Translate } from "../helpers/translate.js"
/**
* A comparison function for two pages.
*/
export const comparePage: Compare<Page> = (a, b) => {
switch (a.tag) {
case "InsideCoverFront":
return b.tag === "InsideCoverFront" ? 0 : -1
case "InsideCoverBack":
return b.tag === "InsideCoverBack" ? 0 : 1
case "Numbered":
return b.tag === "Numbered"
? a.numbered - b.numbered
: b.tag === "InsideCoverFront"
? 1
: -1
default:
return assertExhaustive(a)
}
}
/**
* Checks if two pages are equal.
*/
export const equalsPage = (a: Page, b: Page): boolean => comparePage(a, b) === 0
/**
* Returns the successor of a page.
*/
export const succ = (page: Page): Page => {
switch (page.tag) {
case "InsideCoverFront":
return { tag: "Numbered", numbered: 1 }
case "InsideCoverBack":
return { tag: "InsideCoverFront", inside_cover_front: {} }
case "Numbered":
return { tag: "Numbered", numbered: page.numbered + 1 }
default:
return assertExhaustive(page)
}
}
/**
* Creates a page object for a page number.
*/
export const numberToPage = (number: number): Page => ({
tag: "Numbered",
numbered: number,
})
/**
* Returns a string representation of a page.
*/
export const printPage = (translate: Translate, page: Page) => {
switch (page.tag) {
case "InsideCoverFront":
return translate("Front Cover Inside")
case "InsideCoverBack":
return translate("Back Cover Inside")
case "Numbered":
return page.numbered.toString()
default:
return assertExhaustive(page)
}
}
+102
View File
@@ -0,0 +1,102 @@
import { range } from "@optolith/helpers/array"
import {
Page,
PageRange as RawPageRange,
SimpleOccurrence,
} from "optolith-database-schema/types/source/_PublicationRef"
import { Translate } from "../helpers/translate.js"
import {
comparePage,
equalsPage,
numberToPage,
printPage,
succ,
} from "./page.js"
/**
* A range of pages, including the first and last page, if the range includes
* more than on page.
*/
export type PageRange = {
firstPage: Page
lastPage?: Page
}
/**
* Converts a numeric range to a page object range.
*/
export const numberRangeToPageRange = (
numberRange: SimpleOccurrence
): PageRange =>
numberRange.last_page === undefined
? { firstPage: numberToPage(numberRange.first_page) }
: {
firstPage: numberToPage(numberRange.first_page),
lastPage: numberToPage(numberRange.last_page),
}
/**
* Converts a page object range from the database to a local page object range.
*/
export const fromRawPageRange = (pageRange: RawPageRange): PageRange =>
pageRange.last_page === undefined
? { firstPage: pageRange.first_page }
: {
firstPage: pageRange.first_page,
lastPage: pageRange.last_page,
}
/**
* Sorts and combines page ranges while removing duplicates.
*/
export const normalizePageRanges = (ranges: PageRange[]): PageRange[] =>
ranges
.flatMap(({ firstPage, lastPage = firstPage }): Page[] => {
if (firstPage.tag === "Numbered" && lastPage.tag === "Numbered") {
return range(firstPage.numbered, lastPage.numbered).map((numbered) => ({
tag: "Numbered",
numbered,
}))
} else {
return [firstPage, lastPage]
}
})
.filter(
(page, i, pages) => pages.findIndex((p) => equalsPage(page, p)) === i
)
.sort(comparePage)
.reduce<PageRange[]>((acc, page): PageRange[] => {
const lastRange = acc[acc.length - 1]
if (lastRange === undefined) {
return [{ firstPage: page }]
}
const { firstPage, lastPage = firstPage } = lastRange
if (equalsPage(page, succ(lastPage))) {
return [...acc.slice(0, -1), { firstPage, lastPage: page }]
}
return [...acc, { firstPage: page }]
}, [])
/**
* Returns a string representation of a page range.
*/
export const printPageRange = (translate: Translate, pageRange: PageRange) =>
pageRange.lastPage === undefined
? printPage(translate, pageRange.firstPage)
: `${printPage(translate, pageRange.firstPage)}${printPage(
translate,
pageRange.lastPage
)}`
/**
* Returns a string representation of a list of page ranges.
*/
export const printPageRanges = (
translate: Translate,
pageRanges: PageRange[]
) =>
pageRanges.map((pageRange) => printPageRange(translate, pageRange)).join(", ")
+91
View File
@@ -0,0 +1,91 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"
import { comparePage } from "../../src/references/page.js"
describe("comparePage", () => {
it("returns 0 if the pages are equal", () => {
assert.equal(
comparePage(
{ tag: "InsideCoverBack", inside_cover_back: {} },
{ tag: "InsideCoverBack", inside_cover_back: {} }
),
0
)
assert.equal(
comparePage(
{ tag: "InsideCoverFront", inside_cover_front: {} },
{ tag: "InsideCoverFront", inside_cover_front: {} }
),
0
)
assert.equal(
comparePage(
{ tag: "Numbered", numbered: 42 },
{ tag: "Numbered", numbered: 42 }
),
0
)
})
it("returns a negative number if first should be sorted before the second", () => {
assert.equal(
comparePage(
{ tag: "InsideCoverFront", inside_cover_front: {} },
{ tag: "InsideCoverBack", inside_cover_back: {} }
),
-1
)
assert.equal(
comparePage(
{ tag: "InsideCoverFront", inside_cover_front: {} },
{ tag: "Numbered", numbered: 42 }
),
-1
)
assert.equal(
comparePage(
{ tag: "Numbered", numbered: 42 },
{ tag: "InsideCoverBack", inside_cover_back: {} }
),
-1
)
assert.equal(
comparePage(
{ tag: "Numbered", numbered: 24 },
{ tag: "Numbered", numbered: 42 }
),
-18
)
})
it("returns a positive number if first should be sorted after the second", () => {
assert.equal(
comparePage(
{ tag: "InsideCoverBack", inside_cover_back: {} },
{ tag: "InsideCoverFront", inside_cover_front: {} }
),
1
)
assert.equal(
comparePage(
{ tag: "Numbered", numbered: 42 },
{ tag: "InsideCoverFront", inside_cover_front: {} }
),
1
)
assert.equal(
comparePage(
{ tag: "InsideCoverBack", inside_cover_back: {} },
{ tag: "Numbered", numbered: 42 }
),
1
)
assert.equal(
comparePage(
{ tag: "Numbered", numbered: 42 },
{ tag: "Numbered", numbered: 24 }
),
18
)
})
})