domain/converters/ontology.converter.ts
domain/converters/ontology.converter.ts is a file in Bane's Lab Site. 180 lines of code and 33 definitions.
import {
ALGORITHMS_TAB,
ALGO_ANCHOR,
ALGO_DOMAIN_ANCHOR,
ARCH_ANCHOR,
ARCH_CATEGORY_ANCHOR,
FORCE_ANCHOR,
GRAMMAR_TAB,
KIND_ANCHOR,
LAYER_ANCHOR,
LEXICON_TAB,
LEX_ANCHOR,
LEX_CATEGORY_ANCHOR,
ONTOLOGY_PRINCIPLES_TAB,
ONTOLOGY_TAB_ORDER,
PAG_ANCHOR,
REASONING_TAB,
REASON_ANCHOR,
RELATION_ANCHOR,
SCHEMA_TAB,
STAGE_ANCHOR,
TENSION_ANCHOR,
VOCABULARY_ANCHOR,
} from "#core/ids/ontology.ids";
import {
ALGO_DOMAIN_FACE,
ALGO_FACE,
ARCH_CATEGORY_FACE,
ARCH_FACE,
FACE_SEPARATOR,
FORCE_FACE,
KIND_FACE,
LAYER_FACE,
LEX_CATEGORY_FACE,
LEX_FACE,
PAG_FACE,
REASON_FACE,
RELATION_FACE,
STAGE_FACE,
TENSION_FACE,
VOCABULARY_FACE,
} from "@govlab/constants";
import { ANCHOR_PREFIX, HYPHEN, SLUG_CHARS } from "#configuration/constants/document.constants";
import type { Block, CodeBlock, GlossaryEntry } from "#types/block.types";
import { FIELD_SEPARATOR, LABEL_SEPARATOR, LIST_SEPARATOR, NONE_LABEL } from "#configuration/strings/ontology.strings";
import { TEXT_LANGUAGE, TYPESCRIPT_LANGUAGE } from "#configuration/constants/code.constants";
import { recordPath, tabPath } from "#core/assets/link.assets";
import { CHAPTER_FACE } from "#configuration/constants/vocabulary.constants";
import type { EdgeRef } from "#types/link.types";
import type { ExemplarView } from "#types/ontology.types";
import { ONTOLOGY_PAGE } from "#core/ids/page.ids";
import { anchorMarkup } from "#core/formatters/link.formatter";
import { isPublishedRecord } from "#domain/registries/record.registry";
const TYPESCRIPT_LANG = "ts";
const COMPOSITE_MEDIUM = "composite";
const TAB_BY_FACE: ReadonlyMap<string, { readonly anchor: string; readonly tab: string }> = new Map([
[ARCH_FACE, { anchor: ARCH_ANCHOR, tab: ONTOLOGY_PRINCIPLES_TAB }],
[ARCH_CATEGORY_FACE, { anchor: ARCH_CATEGORY_ANCHOR, tab: ONTOLOGY_PRINCIPLES_TAB }],
[LEX_FACE, { anchor: LEX_ANCHOR, tab: LEXICON_TAB }],
[LEX_CATEGORY_FACE, { anchor: LEX_CATEGORY_ANCHOR, tab: LEXICON_TAB }],
[ALGO_FACE, { anchor: ALGO_ANCHOR, tab: ALGORITHMS_TAB }],
[ALGO_DOMAIN_FACE, { anchor: ALGO_DOMAIN_ANCHOR, tab: ALGORITHMS_TAB }],
[REASON_FACE, { anchor: REASON_ANCHOR, tab: REASONING_TAB }],
[STAGE_FACE, { anchor: STAGE_ANCHOR, tab: REASONING_TAB }],
[TENSION_FACE, { anchor: TENSION_ANCHOR, tab: SCHEMA_TAB }],
[LAYER_FACE, { anchor: LAYER_ANCHOR, tab: SCHEMA_TAB }],
[FORCE_FACE, { anchor: FORCE_ANCHOR, tab: SCHEMA_TAB }],
[KIND_FACE, { anchor: KIND_ANCHOR, tab: SCHEMA_TAB }],
[RELATION_FACE, { anchor: RELATION_ANCHOR, tab: SCHEMA_TAB }],
[PAG_FACE, { anchor: PAG_ANCHOR, tab: GRAMMAR_TAB }],
[VOCABULARY_FACE, { anchor: VOCABULARY_ANCHOR, tab: SCHEMA_TAB }],
]);
const ORDER = ONTOLOGY_TAB_ORDER.map((id) => ({ id }));
export const anchorOf = function anchorOf(ref: string): string | null {
const split = ref.indexOf(FACE_SEPARATOR);
const face = TAB_BY_FACE.get(ref.slice(0, split));
return face === undefined ? null : face.anchor + ref.slice(split + 1);
};
const refsOfAnchor = function refsOfAnchor(fragment: string): readonly string[] {
return [...TAB_BY_FACE]
.filter(([, { anchor }]) => fragment.startsWith(anchor))
.toSorted(([, left], [, right]) => right.anchor.length - left.anchor.length)
.map(([face, { anchor }]) => face + FACE_SEPARATOR + fragment.slice(anchor.length));
};
export const recordPageOf = function recordPageOf(anchor: string): string | null {
const ref = refsOfAnchor(anchor).find(isPublishedRecord);
if (ref === undefined) {
return null;
}
const split = ref.indexOf(FACE_SEPARATOR);
return recordPath(ref.slice(0, split), ref.slice(split + 1));
};
export const refOfAnchor = function refOfAnchor(fragment: string): string | null {
return refsOfAnchor(fragment)[0] ?? null;
};
export const anchorHrefOf = function anchorHrefOf(ref: string): string | null {
const split = ref.indexOf(FACE_SEPARATOR);
if (ref.slice(0, split) === CHAPTER_FACE) {
return ref.slice(split + 1);
}
const face = TAB_BY_FACE.get(ref.slice(0, split));
return face === undefined
? null
: tabPath(ONTOLOGY_PAGE, ORDER, face.tab) + ANCHOR_PREFIX + face.anchor + ref.slice(split + 1);
};
export const hrefOf = function hrefOf(ref: string): string | null {
const split = ref.indexOf(FACE_SEPARATOR);
const face = ref.slice(0, split);
if (face === CHAPTER_FACE) {
return ref.slice(split + 1);
}
return TAB_BY_FACE.has(face) ? recordPath(face, ref.slice(split + 1)) : null;
};
export const linkOf = function linkOf(edge: EdgeRef): string {
return anchorMarkup(edge.ref === null ? null : hrefOf(edge.ref), edge.label);
};
export const faceLink = function faceLink(face: string, id: string, label: string): string {
return linkOf({ label, ref: face + FACE_SEPARATOR + id });
};
export const linkList = function linkList(edges: readonly EdgeRef[]): string {
return edges.length === 0 ? NONE_LABEL : edges.map(linkOf).join(LIST_SEPARATOR);
};
export const plainList = function plainList(items: readonly string[]): string {
return items.length === 0 ? NONE_LABEL : items.join(LIST_SEPARATOR);
};
export const labeled = function labeled(label: string, value: string | null): string | null {
return value === null || value.length === 0 ? null : label + LABEL_SEPARATOR + value;
};
export const linked = function linked(label: string, edge: EdgeRef | null): string | null {
return edge === null ? null : labeled(label, linkOf(edge));
};
export const fields = function fields(parts: readonly (string | null)[]): string {
return parts.filter((part): part is string => part !== null && part.length > 0).join(FIELD_SEPARATOR);
};
export const row = function row(term: string, description: string): GlossaryEntry {
return { description, term };
};
export const wideRow = function wideRow(term: string, description: string): GlossaryEntry {
return { description, term, wide: true };
};
export const linkRow = function linkRow(term: string, edges: readonly EdgeRef[]): readonly GlossaryEntry[] {
return edges.length === 0 ? [] : [row(term, linkList(edges))];
};
export const glossary = function glossary(entries: readonly GlossaryEntry[]): Block {
return { entries, kind: "glossary" };
};
export const text = function text(value: string): Block {
return { kind: "text", text: value };
};
export const chips = function chips(parts: readonly (string | null)[]): Block {
return { items: parts.filter((part): part is string => part !== null && part.length > 0), kind: "list" };
};
export const codeOf = function codeOf(title: string, code: string, language: string): CodeBlock {
return { code, kind: "code", language, title };
};
export const exemplarBlocks = function exemplarBlocks(
exemplar: ExemplarView | null,
beforeTitle: string,
afterTitle: string,
): readonly Block[] {
if (exemplar === null) {
return [];
}
const code = exemplar.medium !== COMPOSITE_MEDIUM && exemplar.lang === TYPESCRIPT_LANG;
const language = code ? TYPESCRIPT_LANGUAGE : TEXT_LANGUAGE;
return [codeOf(beforeTitle, exemplar.before, language), codeOf(afterTitle, exemplar.after, language)];
};
export const slugOf = function slugOf(label: string): string {
let out = "";
let gap = false;
for (const char of label.toLowerCase()) {
if (SLUG_CHARS.includes(char)) {
out += gap && out.length > 0 ? HYPHEN + char : char;
gap = false;
} else {
gap = true;
}
}
return out;
};