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

// Build-time ASCII rendering helpers. Pure functions, no DOM.

export const G = {
  H: "─", V: "│",
  TL: "┌", TR: "┐", BL: "└", BR: "┘",
  LJ: "├", RJ: "┤", TJ: "┬", BJ: "┴", X: "┼",
  D: "═", HD: "╌",
};

// Codepoint length, not UTF-16 units - frame math breaks on surrogate
// pairs otherwise.
export const len = (s: unknown): number => [...String(s ?? "")].length;

// The site is og ascii: printable ASCII + tab for text, box drawing +
// block elements for chrome, plus NBSP (layout) and the measured
// arrows ↑ ↓. Typographic glyphs (em dash, ellipsis, curly quotes,
// Latin-1 letters) are deliberately off-grid so the build rejects
// them - write "-", "...", and straight quotes instead.
const ON_GRID = /^[\t\x20-\x7E\u00A0\u2191\u2193\u2500-\u259F]$/u;

/** Throws unless every line of `text` fits `maxW` cells and uses only
 *  on-grid glyphs. Returns `text` so call sites can wrap in place. */
export function checkGrid(text: string, maxW: number, ctx: string): string {
  for (const line of text.split("\n")) {
    if (len(line) > maxW) {
      throw new Error(`[ascii] ${ctx}: line is ${len(line)} cells, max ${maxW}: "$
{[...line].slice(0, 40).join("")}..."`);
    }
    const bad = [...line].find((c) => !ON_GRID.test(c));
    if (bad !== undefined) {
      const cp = bad.codePointAt(0)!.toString(16).toUpperCase().padStart(4, "0");
      throw new Error(`[ascii] ${ctx}: off-grid character "${bad}" (U+${cp}). If i
t measures one cell wide in the frame font, add it to ON_GRID in ascii.ts.`);
    }
  }
  return text;
}

// For text from outside the pipeline (git history, external feeds):
// transliterate typographic glyphs to their og-ascii forms and blank
// anything else off-grid, so checkGrid never fails on foreign input.
const GRID_MAP: Record<string, string> = {
  "-": "-", "-": "-", "...": "...", "-": "-",
  "": "", """: '"', """: '"', "'": "'", "'": "'",
};

export function toGrid(s: string): string {
  return s
    .split("\n")
    .map((line) => [...line].map((c) => GRID_MAP[c] ?? (ON_GRID.test(c) ? c : "?")
).join(""))
    .join("\n");
}

export type PadSide = "start" | "end" | "center";
export function pad(s: unknown, n: number, side: PadSide = "end"): string {
  const str = String(s ?? "");
  const L = len(str);
  if (L >= n) return [...str].slice(0, n).join("");
  const fill = " ".repeat(n - L);
  if (side === "start") return fill + str;
  if (side === "center") {
    const left = Math.floor((n - L) / 2);
    const right = n - L - left;
    return " ".repeat(left) + str + " ".repeat(right);
  }
  return str + fill;
}

export const rule = (w: number) => G.H.repeat(w);
export const ruleD = (w: number) => G.D.repeat(w);

export interface BoxOpts {
  width: number;
  lines: string[];
  title?: string;
  align?: PadSide;
}
export function box({ width, lines, title, align = "start" }: BoxOpts): string {
  const inner = width - 2;
  const out: string[] = [];
  out.push(G.TL + rule(inner) + G.TR);
  if (title != null) {
    out.push(G.V + pad(" " + title, inner) + G.V);
    out.push(G.LJ + rule(inner) + G.RJ);
  }
  for (const ln of lines) out.push(G.V + pad(ln, inner, align) + G.V);
  out.push(G.BL + rule(inner) + G.BR);
  return out.join("\n");
}

export const kv = (key: string, val: string, keyW: number, totalW: number) =>
  pad(key, keyW) + pad(val, totalW - keyW);

export const center = (s: string, w: number) => pad(s, w, "center");

/** wrap(), but words wider than the width hard-split instead of
 *  overflowing - for foreign text like commit subjects. */
export function wrapHard(s: string, w: number): string {
  return wrap(s, w)
    .split("\n")
    .flatMap((line) => {
      const cps = [...line];
      if (cps.length <= w) return [line];
      const out: string[] = [];
      for (let i = 0; i < cps.length; i += w) out.push(cps.slice(i, i + w).join(""
));
      return out;
    })
    .join("\n");
}

export function row2(l: string, r: string, w: number): string {
  const gap = Math.max(1, w - len(l) - len(r));
  return l + " ".repeat(gap) + r;
}

export function row3(l: string, c: string, r: string, w: number): string {
  const out = " ".repeat(w).split("");
  const lArr = [...l], rArr = [...r], cArr = [...c];
  for (let i = 0; i < lArr.length && i < w; i++) out[i] = lArr[i];
  for (let i = 0; i < rArr.length && i < w; i++) out[w - rArr.length + i] = rArr[i
];
  const start = Math.max(lArr.length + 1, Math.floor((w - cArr.length) / 2));
  for (let i = 0; i < cArr.length && start + i < w - rArr.length - 1; i++) out[sta
rt + i] = cArr[i];
  return out.join("");
}

// Greedy word wrap.
export function wrap(text: string, w: number): string {
  const words = text.split(/\s+/);
  const out: string[] = [];
  let line = "";
  for (const word of words) {
    if (!line) { line = word; continue; }
    if (len(line) + 1 + len(word) <= w) line += " " + word;
    else { out.push(line); line = word; }
  }
  if (line) out.push(line);
  return out.join("\n");
}

// Build-time ASCII rendering helpers. Pure 
functions, no DOM.

export const G = {
  H: "─", V: "│",
  TL: "┌", TR: "┐", BL: "└", BR: "┘",
  LJ: "├", RJ: "┤", TJ: "┬", BJ: "┴", X: "┼"
,
  D: "═", HD: "╌",
};

// Codepoint length, not UTF-16 units - fram
e math breaks on surrogate
// pairs otherwise.
export const len = (s: unknown): number => [
...String(s ?? "")].length;

// The site is og ascii: printable ASCII + t
ab for text, box drawing +
// block elements for chrome, plus NBSP (lay
out) and the measured
// arrows ↑ ↓. Typographic glyphs (em dash, 
ellipsis, curly quotes,
// Latin-1 letters) are deliberately off-gri
d so the build rejects
// them - write "-", "...", and straight quo
tes instead.
const ON_GRID = /^[\t\x20-\x7E\u00A0\u2191\u
2193\u2500-\u259F]$/u;

/** Throws unless every line of `text` fits 
`maxW` cells and uses only
 *  on-grid glyphs. Returns `text` so call s
ites can wrap in place. */
export function checkGrid(text: string, maxW
: number, ctx: string): string {
  for (const line of text.split("\n")) {
    if (len(line) > maxW) {
      throw new Error(`[ascii] ${ctx}: line 
is ${len(line)} cells, max ${maxW}: "${[...l
ine].slice(0, 40).join("")}..."`);
    }
    const bad = [...line].find((c) => !ON_GR
ID.test(c));
    if (bad !== undefined) {
      const cp = bad.codePointAt(0)!.toStrin
g(16).toUpperCase().padStart(4, "0");
      throw new Error(`[ascii] ${ctx}: off-g
rid character "${bad}" (U+${cp}). If it meas
ures one cell wide in the frame font, add it
 to ON_GRID in ascii.ts.`);
    }
  }
  return text;
}

// For text from outside the pipeline (git h
istory, external feeds):
// transliterate typographic glyphs to their
 og-ascii forms and blank
// anything else off-grid, so checkGrid neve
r fails on foreign input.
const GRID_MAP: Record<string, string> = {
  "-": "-", "-": "-", "...": "...", "-": "-"
,
  "": "", """: '"', """: '"', "'": "'", "'":
 "'",
};

export function toGrid(s: string): string {
  return s
    .split("\n")
    .map((line) => [...line].map((c) => GRID
_MAP[c] ?? (ON_GRID.test(c) ? c : "?")).join
(""))
    .join("\n");
}

export type PadSide = "start" | "end" | "cen
ter";
export function pad(s: unknown, n: number, s
ide: PadSide = "end"): string {
  const str = String(s ?? "");
  const L = len(str);
  if (L >= n) return [...str].slice(0, n).jo
in("");
  const fill = " ".repeat(n - L);
  if (side === "start") return fill + str;
  if (side === "center") {
    const left = Math.floor((n - L) / 2);
    const right = n - L - left;
    return " ".repeat(left) + str + " ".repe
at(right);
  }
  return str + fill;
}

export const rule = (w: number) => G.H.repea
t(w);
export const ruleD = (w: number) => G.D.repe
at(w);

export interface BoxOpts {
  width: number;
  lines: string[];
  title?: string;
  align?: PadSide;
}
export function box({ width, lines, title, a
lign = "start" }: BoxOpts): string {
  const inner = width - 2;
  const out: string[] = [];
  out.push(G.TL + rule(inner) + G.TR);
  if (title != null) {
    out.push(G.V + pad(" " + title, inner) +
 G.V);
    out.push(G.LJ + rule(inner) + G.RJ);
  }
  for (const ln of lines) out.push(G.V + pad
(ln, inner, align) + G.V);
  out.push(G.BL + rule(inner) + G.BR);
  return out.join("\n");
}

export const kv = (key: string, val: string,
 keyW: number, totalW: number) =>
  pad(key, keyW) + pad(val, totalW - keyW);

export const center = (s: string, w: number)
 => pad(s, w, "center");

/** wrap(), but words wider than the width h
ard-split instead of
 *  overflowing - for foreign text like comm
it subjects. */
export function wrapHard(s: string, w: numbe
r): string {
  return wrap(s, w)
    .split("\n")
    .flatMap((line) => {
      const cps = [...line];
      if (cps.length <= w) return [line];
      const out: string[] = [];
      for (let i = 0; i < cps.length; i += w
) out.push(cps.slice(i, i + w).join(""));
      return out;
    })
    .join("\n");
}

export function row2(l: string, r: string, w
: number): string {
  const gap = Math.max(1, w - len(l) - len(r
));
  return l + " ".repeat(gap) + r;
}

export function row3(l: string, c: string, r
: string, w: number): string {
  const out = " ".repeat(w).split("");
  const lArr = [...l], rArr = [...r], cArr =
 [...c];
  for (let i = 0; i < lArr.length && i < w; 
i++) out[i] = lArr[i];
  for (let i = 0; i < rArr.length && i < w; 
i++) out[w - rArr.length + i] = rArr[i];
  const start = Math.max(lArr.length + 1, Ma
th.floor((w - cArr.length) / 2));
  for (let i = 0; i < cArr.length && start +
 i < w - rArr.length - 1; i++) out[start + i
] = cArr[i];
  return out.join("");
}

// Greedy word wrap.
export function wrap(text: string, w: number
): string {
  const words = text.split(/\s+/);
  const out: string[] = [];
  let line = "";
  for (const word of words) {
    if (!line) { line = word; continue; }
    if (len(line) + 1 + len(word) <= w) line
 += " " + word;
    else { out.push(line); line = word; }
  }
  if (line) out.push(line);
  return out.join("\n");
}
 
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET