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