core/converters/catalog.grammar.converter.ts

core/converters/catalog.grammar.converter.ts is a file in Bane's Lab Build Scripts. 187 lines of code and 46 definitions.

import type { Address, GrammarData, GrammarKind, GrammarRow } from "#types/catalog.types";
import {
    FOLDER_PLACEHOLDER,
    INDEX_PART_MARK,
    PATH_PLACEHOLDER,
    SCHEMA_DIALECT,
    SCHEMA_KIND,
    SEARCH_KIND,
    SPANNING_PLACEHOLDERS,
} from "#configuration/constants/catalog.constants";
import { GRAMMAR_LEVELS, unknownAddress } from "#configuration/strings/catalog.strings";
import {
    closureLeaf,
    collectionIndex,
    contactIndex,
    facetCollectionIndex,
    facetFieldIndex,
    facetLeaf,
    facetsIndex,
    folderIndex,
    idsIndex,
    idsShard,
    indexPart,
    movedIndex,
    numbersIndex,
    pageIndex,
    pagesIndex,
    queryIndex,
    recordLeaf,
    recordsIndex,
    resolveLeaf,
    resolveMap,
    routeIndex,
    schemaLeaf,
    searchIndex,
    searchKindIndex,
    searchShard,
    sectionLeaf,
    siteIndex,
    slugShard,
    sourceLeaf,
    sourceText,
    sourcesIndex,
    treeIndex,
} from "#core/resolvers/catalog.resolver";

const PLACEHOLDER = "{";
const PLACEHOLDER_CLOSE = "}";
const SLASH = "/";
const PAGE = "{page}";
const TAB = "{tab}";
const COLLECTION = "{collection}";
const FIELD = "{field}";
const TREE = "{tree}";
const SECTION = "{section}";
const ID = "{id}";
const VALUE = "{value}";
const SLUG = "{slug}";
const KIND = "{kind}";
const LETTER = "{letter}";
const PART = "{part}";
const GROUP = "{group}";
const PART_KIND: GrammarKind = "part";
const PARTS_KEEP_KIND: ReadonlySet<GrammarKind> = new Set(["idsShard"]);

const TEXT_ADDRESS: Address = { json: sourceText(TREE, PATH_PLACEHOLDER), markdown: null };

export const GRAMMAR: readonly GrammarRow[] = [
    { address: siteIndex(), kind: "site" },
    { address: pagesIndex(), kind: "pages" },
    { address: pageIndex(PAGE), kind: "page" },
    { address: pageIndex(PAGE, TAB), kind: "tab" },
    { address: sectionLeaf(PAGE, TAB, SECTION), kind: "section" },
    { address: sectionLeaf(PAGE, null, SECTION), kind: "documentSection" },
    { address: indexPart(sectionLeaf(PAGE, TAB, SECTION), PART), kind: PART_KIND },
    { address: recordsIndex(), kind: "records" },
    { address: collectionIndex(COLLECTION), kind: "collection" },
    { address: indexPart(collectionIndex(COLLECTION), PART), kind: PART_KIND },
    { address: recordLeaf(COLLECTION, ID), kind: "record" },
    { address: closureLeaf(COLLECTION, ID), kind: "closure" },
    { address: facetsIndex(), kind: "facets" },
    { address: facetCollectionIndex(COLLECTION), kind: "facetCollection" },
    { address: facetFieldIndex(COLLECTION, FIELD), kind: "facetField" },
    { address: facetLeaf(COLLECTION, FIELD, VALUE), kind: "facetValue" },
    { address: resolveLeaf(SLUG), kind: "resolve" },
    { address: resolveMap(), kind: "resolveMap" },
    { address: slugShard(LETTER), kind: "slugShard" },
    { address: sourcesIndex(), kind: "sources" },
    { address: treeIndex(TREE), kind: "tree" },
    { address: folderIndex(TREE, FOLDER_PLACEHOLDER), kind: "folder" },
    { address: sourceLeaf(TREE, PATH_PLACEHOLDER), kind: "source" },
    { address: TEXT_ADDRESS, kind: "text" },
    { address: routeIndex(), kind: "route" },
    { address: numbersIndex(), kind: "numbers" },
    { address: searchIndex(), kind: SEARCH_KIND },
    { address: searchKindIndex(KIND), kind: "searchKind" },
    { address: searchShard(KIND, LETTER), kind: "searchShard" },
    { address: idsIndex(), kind: "ids" },
    { address: idsShard(GROUP), kind: "idsShard" },
    { address: movedIndex(), kind: "moved" },
    { address: queryIndex(), kind: "query" },
    { address: schemaLeaf(KIND), kind: "schema" },
    { address: contactIndex(), kind: "contact" },
];

const segmentMatches = function segmentMatches(pattern: string, value: string): boolean {
    const open = pattern.indexOf(PLACEHOLDER);
    if (open === -1) {
        return pattern === value;
    }
    const prefix = pattern.slice(0, open);
    const suffix = pattern.slice(pattern.indexOf(PLACEHOLDER_CLOSE, open) + 1);
    return value.length > prefix.length + suffix.length && value.startsWith(prefix) && value.endsWith(suffix);
};

const spans = function spans(pattern: string): boolean {
    const open = pattern.indexOf(PLACEHOLDER);
    return open !== -1 && SPANNING_PLACEHOLDERS.has(pattern.slice(open, pattern.indexOf(PLACEHOLDER_CLOSE, open) + 1));
};

const segmentsMatch = function segmentsMatch(
    patterns: readonly string[],
    values: readonly string[],
    at: { readonly pattern: number; readonly value: number },
): boolean {
    if (at.pattern === patterns.length) {
        return at.value === values.length;
    }
    const pattern = patterns.at(at.pattern) ?? "";
    const last = spans(pattern) ? values.length : Math.min(at.value + 1, values.length);
    for (let end = at.value + 1; end <= last; end += 1) {
        const consumed = values.slice(at.value, end).join(SLASH);
        if (
            segmentMatches(pattern, consumed) &&
            segmentsMatch(patterns, values, { pattern: at.pattern + 1, value: end })
        ) {
            return true;
        }
    }
    return false;
};

export const matchesTemplate = function matchesTemplate(template: string, path: string): boolean {
    return segmentsMatch(template.split(SLASH), path.split(SLASH), { pattern: 0, value: 0 });
};

const literalLength = function literalLength(template: string): number {
    let length = 0;
    let inside = false;
    for (const char of template) {
        inside = char === PLACEHOLDER || (inside && char !== PLACEHOLDER_CLOSE);
        length += inside || char === PLACEHOLDER_CLOSE ? 0 : 1;
    }
    return length;
};

const closestKind = function closestKind(path: string): GrammarKind | null {
    const matching = GRAMMAR.filter((row) => matchesTemplate(row.address.json, path));
    const ranked = matching.toSorted(
        (left, right) => literalLength(right.address.json) - literalLength(left.address.json),
    );
    return ranked.at(0)?.kind ?? null;
};

export const kindOrNull = function kindOrNull(path: string): GrammarKind | null {
    const last = path.slice(path.lastIndexOf(SLASH) + 1);
    const base = last.startsWith(INDEX_PART_MARK) ? path.slice(0, path.lastIndexOf(SLASH)) : null;
    const kind = closestKind(base ?? path);
    if (kind === null) {
        return null;
    }
    return base === null || PARTS_KEEP_KIND.has(kind) ? kind : PART_KIND;
};

export const kindOfAddress = function kindOfAddress(path: string): GrammarKind {
    const kind = kindOrNull(path);
    if (kind === null) {
        throw new Error(unknownAddress(path));
    }
    return kind;
};

export const grammarRows = function grammarRows(published: readonly string[]): readonly GrammarData[] {
    const served = new Set(published);
    return GRAMMAR.map((row) => {
        const template = row.address.json.includes(PLACEHOLDER);
        const schema = schemaLeaf(row.kind).json;
        const described = served.has(schema) ? schema : null;
        return {
            example: template ? (published.find((path) => matchesTemplate(row.address.json, path)) ?? null) : null,
            json: row.address.json,
            kind: row.kind,
            level: GRAMMAR_LEVELS[row.kind],
            markdown: row.address.markdown,
            schema: row.kind === SCHEMA_KIND ? SCHEMA_DIALECT : described,
            template,
        };
    });
};