core/converters/inventory.converter.ts

core/converters/inventory.converter.ts is a file in Bane's Lab Content. 143 lines of code and 35 definitions.

import {
    ALWAYS_WORD,
    ARROW,
    BACKTICK,
    BOLD_MARK,
    COLON,
    GATE_TAIL,
    HEADING_MARK,
    LEAD_IN_SEPARATOR,
    LINE_END,
    LIST_MARKER,
    LOCKED_MARKERS,
    NEVER_WORD,
    PAREN_CLOSE,
    PAREN_OPEN,
    RULE_PREFIX,
} from "#configuration/constants/inventory.constants";
import type { InventoryRecord } from "#types/inventory.types";

const NOT_FOUND = -1;

const headingText = function headingText(line: string): string | null {
    if (!line.startsWith(HEADING_MARK)) {
        return null;
    }
    let at = 0;
    while (at < line.length && line.charAt(at) === HEADING_MARK) {
        at += 1;
    }
    return line.slice(at).trim();
};

const slugPrefixAccepted = function slugPrefixAccepted(prefix: string): boolean {
    if (prefix.length === 0 || prefix === LIST_MARKER) {
        return true;
    }
    return prefix.startsWith(BOLD_MARK) && prefix.endsWith(BOLD_MARK + LEAD_IN_SEPARATOR);
};

interface SlugSpan {
    readonly slug: string;
    readonly tail: string;
}

const slugOf = function slugOf(line: string): SlugSpan | null {
    const open = line.indexOf(BACKTICK);
    if (open === NOT_FOUND || !slugPrefixAccepted(line.slice(0, open))) {
        return null;
    }
    const close = line.indexOf(BACKTICK, open + 1);
    if (close === NOT_FOUND || close === open + 1) {
        return null;
    }
    return { slug: line.slice(open + 1, close), tail: line.slice(close + 1) };
};

interface LockedSpan {
    readonly locked: boolean;
    readonly rest: string;
}

const lockedOf = function lockedOf(tail: string): LockedSpan {
    const trimmed = tail.trimStart();
    for (const marker of LOCKED_MARKERS) {
        if (trimmed.startsWith(marker)) {
            return { locked: true, rest: trimmed.slice(marker.length).trimStart() };
        }
    }
    return { locked: false, rest: trimmed };
};

interface GateSpan {
    readonly directive: string;
    readonly gate: string | null;
}

const gateOf = function gateOf(directive: string): GateSpan {
    const at = directive.indexOf(GATE_TAIL);
    if (at === NOT_FOUND) {
        return { directive: directive.trim(), gate: null };
    }
    return { directive: directive.slice(0, at).trim(), gate: directive.slice(at + GATE_TAIL.length).trim() };
};

const ruleOf = function ruleOf(line: string, source: string, tier: string): InventoryRecord | null {
    const span = slugOf(line);
    if (span === null) {
        return null;
    }
    const { locked, rest } = lockedOf(span.tail);
    if (!rest.startsWith(COLON)) {
        return null;
    }
    const { directive, gate } = gateOf(rest.slice(COLON.length));
    return { always: null, directive, gate, kind: "rule", locked, never: null, slug: span.slug, source, tier };
};

const arrowOutsideParens = function arrowOutsideParens(text: string): number {
    let depth = 0;
    for (let at = 0; at < text.length; at += 1) {
        const char = text.charAt(at);
        if (char === ARROW && depth === 0) {
            return at;
        }
        depth += char === PAREN_OPEN ? 1 : 0;
        depth -= char === PAREN_CLOSE ? 1 : 0;
    }
    return NOT_FOUND;
};

const stripLead = function stripLead(text: string, lead: string): string | null {
    const trimmed = text.trim();
    return trimmed.startsWith(lead) ? trimmed.slice(lead.length).trim() : null;
};

const avoidanceOf = function avoidanceOf(line: string, source: string, tier: string): InventoryRecord | null {
    if (!line.startsWith(RULE_PREFIX)) {
        return null;
    }
    const colon = line.indexOf(COLON);
    if (colon === NOT_FOUND) {
        return null;
    }
    const slug = line.slice(RULE_PREFIX.length, colon).trim();
    const directive = line.slice(colon + COLON.length).trim();
    const arrow = arrowOutsideParens(directive);
    if (arrow === NOT_FOUND) {
        return null;
    }
    const never = stripLead(directive.slice(0, arrow), NEVER_WORD);
    const always = stripLead(directive.slice(arrow + ARROW.length), ALWAYS_WORD);
    if (never === null || always === null) {
        return null;
    }
    return { always, directive, gate: null, kind: "avoidance", locked: false, never, slug, source, tier };
};

export const scanRules = function scanRules(text: string, source: string): InventoryRecord[] {
    const records: InventoryRecord[] = [];
    let tier = "";
    for (const raw of text.split(LINE_END)) {
        const line = raw.trim();
        const heading = headingText(line);
        if (heading !== null) {
            tier = heading;
            continue;
        }
        const record = ruleOf(line, source, tier) ?? avoidanceOf(line, source, tier);
        if (record !== null) {
            records.push(record);
        }
    }
    return records;
};

export const lineCount = function lineCount(text: string): number {
    return text.split(LINE_END).length;
};