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;
};