OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
 
master @ 95 LINES
 
[ HISTORY ]  [ UP ]
 

// ```mermaid fences: linear chains render through the house chain
// layout (flow.ts); anything beyond the parsed subset - branching,
// subgraphs, RL/BT direction - shells out to mermaid-ascii from the
// dev shell. Mermaid is the one authoring syntax because Obsidian and
// GitHub preview it natively.
import { execFileSync } from "node:child_process";
import { renderChain, type FlowGraph } from "./flow";

const HEADER_RE = /^(?:graph|flowchart)\s+(LR|RL|TD|TB|BT)\s*$/;
const NODE_TOKEN = /^([A-Za-z0-9_.-]+)(?:[\[({]+([^\]})]+)[\])}]+)?/;
const ARROW_TOKEN = /^-->(?:\|([^|]*)\|)?/;

export interface ParsedMermaid {
  graph: FlowGraph;
  direction: string;
}

/** Parses the simple flowchart subset; null means "let the binary
 *  handle it" - it supports far more syntax than this. */
export function parseMermaid(source: string): ParsedMermaid | null {
  const lines = source
    .split("\n")
    .map((l) => l.trim())
    .filter((l) => l && !l.startsWith("%%"));
  if (!lines.length) return null;

  const header = lines[0].match(HEADER_RE);
  if (!header) return null;
  const direction = header[1];
  if (direction === "RL" || direction === "BT") return null;

  const labels = new Map<string, string>();
  const edges: FlowGraph["edges"] = [];
  for (const line of lines.slice(1)) {
    let rest = line;
    const first = rest.match(NODE_TOKEN);
    if (!first) return null;
    let prev = first[1];
    if (first[2]) labels.set(prev, first[2].trim());
    rest = rest.slice(first[0].length).trimStart();
    while (rest) {
      const arrow = rest.match(ARROW_TOKEN);
      if (!arrow) return null;
      rest = rest.slice(arrow[0].length).trimStart();
      const node = rest.match(NODE_TOKEN);
      if (!node || !node[1]) return null;
      if (node[2]) labels.set(node[1], node[2].trim());
      edges.push({ from: prev, to: node[1], label: (arrow[1] ?? "").trim() });
      prev = node[1];
      rest = rest.slice(node[0].length).trimStart();
    }
  }
  return edges.length ? { graph: { labels, edges }, direction } : null;
}

// Arrowhead glyphs the frame font doesn't cover, swapped for on-grid
// equivalents.
const ARROWS: Record<string, string> = {
  "?": "↓", "?": "↑", "?": ">", "?": ">", "?": "<", "?": "<",
};

export function normalizeArt(art: string): string {
  return art
    .replace(/[??????]/gu, (c) => ARROWS[c])
    .split("\n")
    .map((l) => l.replace(/\s+$/, ""))
    .join("\n")
    .replace(/^\n+|\n+$/g, "");
}

function runMermaidAscii(source: string): string {
  try {
    return execFileSync(
      "mermaid-ascii",
      ["-f", "-", "--borderPadding", "0", "--paddingX", "5", "--paddingY", "2"],
      { input: source, encoding: "utf8" },
    );
  } catch (e) {
    const err = e as NodeJS.ErrnoException & { stderr?: string };
    if (err.code === "ENOENT") {
      throw new Error("[mermaid] mermaid-ascii not found - is it in the dev shell?
");
    }
    throw new Error(`[mermaid] render failed: ${err.stderr || err.message}`);
  }
}

export function mermaidToAscii(source: string, width: number): string {
  const parsed = parseMermaid(source);
  if (parsed) {
    const art = renderChain(parsed.graph, width, parsed.direction === "LR");
    if (art) return art;
  }
  return normalizeArt(runMermaidAscii(source));
}

// ```mermaid fences: linear chains render t
hrough the house chain
// layout (flow.ts); anything beyond the par
sed subset - branching,
// subgraphs, RL/BT direction - shells out t
o mermaid-ascii from the
// dev shell. Mermaid is the one authoring s
yntax because Obsidian and
// GitHub preview it natively.
import { execFileSync } from "node:child_pro
cess";
import { renderChain, type FlowGraph } from 
"./flow";

const HEADER_RE = /^(?:graph|flowchart)\s+(L
R|RL|TD|TB|BT)\s*$/;
const NODE_TOKEN = /^([A-Za-z0-9_.-]+)(?:[\[
({]+([^\]})]+)[\])}]+)?/;
const ARROW_TOKEN = /^-->(?:\|([^|]*)\|)?/;

export interface ParsedMermaid {
  graph: FlowGraph;
  direction: string;
}

/** Parses the simple flowchart subset; null
 means "let the binary
 *  handle it" - it supports far more syntax
 than this. */
export function parseMermaid(source: string)
: ParsedMermaid | null {
  const lines = source
    .split("\n")
    .map((l) => l.trim())
    .filter((l) => l && !l.startsWith("%%"))
;
  if (!lines.length) return null;

  const header = lines[0].match(HEADER_RE);
  if (!header) return null;
  const direction = header[1];
  if (direction === "RL" || direction === "B
T") return null;

  const labels = new Map<string, string>();
  const edges: FlowGraph["edges"] = [];
  for (const line of lines.slice(1)) {
    let rest = line;
    const first = rest.match(NODE_TOKEN);
    if (!first) return null;
    let prev = first[1];
    if (first[2]) labels.set(prev, first[2].
trim());
    rest = rest.slice(first[0].length).trimS
tart();
    while (rest) {
      const arrow = rest.match(ARROW_TOKEN);
      if (!arrow) return null;
      rest = rest.slice(arrow[0].length).tri
mStart();
      const node = rest.match(NODE_TOKEN);
      if (!node || !node[1]) return null;
      if (node[2]) labels.set(node[1], node[
2].trim());
      edges.push({ from: prev, to: node[1], 
label: (arrow[1] ?? "").trim() });
      prev = node[1];
      rest = rest.slice(node[0].length).trim
Start();
    }
  }
  return edges.length ? { graph: { labels, e
dges }, direction } : null;
}

// Arrowhead glyphs the frame font doesn't c
over, swapped for on-grid
// equivalents.
const ARROWS: Record<string, string> = {
  "?": "↓", "?": "↑", "?": ">", "?": ">", "?
": "<", "?": "<",
};

export function normalizeArt(art: string): s
tring {
  return art
    .replace(/[??????]/gu, (c) => ARROWS[c])
    .split("\n")
    .map((l) => l.replace(/\s+$/, ""))
    .join("\n")
    .replace(/^\n+|\n+$/g, "");
}

function runMermaidAscii(source: string): st
ring {
  try {
    return execFileSync(
      "mermaid-ascii",
      ["-f", "-", "--borderPadding", "0", "-
-paddingX", "5", "--paddingY", "2"],
      { input: source, encoding: "utf8" },
    );
  } catch (e) {
    const err = e as NodeJS.ErrnoException &
 { stderr?: string };
    if (err.code === "ENOENT") {
      throw new Error("[mermaid] mermaid-asc
ii not found - is it in the dev shell?");
    }
    throw new Error(`[mermaid] render failed
: ${err.stderr || err.message}`);
  }
}

export function mermaidToAscii(source: strin
g, width: number): string {
  const parsed = parseMermaid(source);
  if (parsed) {
    const art = renderChain(parsed.graph, wi
dth, parsed.direction === "LR");
    if (art) return art;
  }
  return normalizeArt(runMermaidAscii(source
));
}
 
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET