/** ASCII Box Drawing - Minimal, dependency-free implementation */ import { fileURLToPath } from "node:url"; const SPACES = " ".repeat(128); const repeatSpace = (n) => n <= 0 ? "" : n < 128 ? SPACES.slice(0, n) : " ".repeat(n); const B = { single: { h: "─", v: "│", tl: "┌", tr: "┐", bl: "└", br: "┘", te: "┬", be: "┴", le: "├", re: "┤", c: "┼", }, double: { h: "═", v: "║", tl: "╔", tr: "╗", bl: "╚", br: "╝", te: "╦", be: "╩", le: "╠", re: "╣", c: "╬", }, bold: { h: "━", v: "┃", tl: "┏", tr: "┓", bl: "┗", br: "┛", te: "┳", be: "┻", le: "┣", re: "┫", c: "╋", }, }; function style(name) { return name === "double" ? "double" : name === "bold" ? "bold" : "single"; } function wrap(text, w) { if (w < 1) return [""]; const words = text.trim().split(/\s+/); if (!words.length || (words.length === 1 && !words[0])) return [""]; const lines = []; let cur = ""; for (const word of words) { const wl = word.length; if (wl > w) { if (cur) lines.push(cur); for (let i = 0; i < wl; i += w) lines.push(word.slice(i, i + w)); cur = ""; } else if (!cur) { cur = word; } else if (cur.length + 1 + wl <= w) { cur += " " + word; } else { lines.push(cur); cur = word; } } if (cur) lines.push(cur); return lines; } function align(line, w, a) { const t = line || ""; const e = w - t.length; if (e <= 0) return t; if (a === "left") return t + repeatSpace(e); if (a === "right") return repeatSpace(e) + t; const l = e >> 1; return repeatSpace(l) + t + repeatSpace(e - l); } export function drawBox(content, opts = {}) { const b = B[style(opts.style)]; const pt = opts.paddingTop ?? 0, pb = opts.paddingBottom ?? 0; const pl = opts.paddingLeft ?? 1, pr = opts.paddingRight ?? 1; const al = opts.alignment ?? "left"; const lines = typeof content === "string" ? opts.width > 0 ? wrap(content, opts.width) : [content] : content; let iw = opts.width ?? 0; if (iw === 0) { let m = 1; for (let i = 0; i < lines.length; i++) if (lines[i].length > m) m = lines[i].length; iw = m; } const tw = iw + pl + pr; const hf = b.h.repeat(tw); const tl = b.tl + hf + b.tr, bl = b.bl + hf + b.br; const el = b.v + repeatSpace(tw) + b.v; const out = new Array(1 + pt + lines.length + pb + 1); out[0] = tl; let idx = 1; for (let i = 0; i < pt; i++, idx++) out[idx] = el; if (al === "left") { for (let i = 0; i < lines.length; i++, idx++) { const cl = lines[i], pad = cl + repeatSpace(iw - cl.length); out[idx] = b.v + repeatSpace(pl) + pad + repeatSpace(pr) + b.v; } } else { for (let i = 0; i < lines.length; i++, idx++) { out[idx] = b.v + repeatSpace(pl) + align(lines[i], iw, al) + repeatSpace(pr) + b.v; } } for (let i = 0; i < pb; i++, idx++) out[idx] = el; out[idx] = bl; return out.join("\n"); } export function createTable(headers, rows, opts = {}) { const b = B[style(opts.style)]; const pad = opts.padding ?? 1; const nc = Math.max(headers.length, ...rows.map((r) => r.length)); const cw = new Array(nc).fill(0); for (let c = 0; c < nc; c++) { if (headers[c]) cw[c] = Math.max(cw[c], headers[c].length); for (const row of rows) if (row[c]) cw[c] = Math.max(cw[c], row[c].length); } const ps = repeatSpace(pad); const fr = (cells, l, sep, r) => { const parts = new Array(nc); for (let c = 0; c < nc; c++) parts[c] = align(cells[c] || "", cw[c], "left"); return l + ps + parts.join(ps + sep + ps) + ps + r; }; const hl = (l, m, r) => l + cw.map((w) => b.h.repeat(w + pad * 2)).join(m) + r; const lines = []; lines.push(hl(b.tl, b.te || b.h.repeat(2), b.tr)); lines.push(fr(headers, b.v, b.v, b.v)); lines.push(hl(b.le || b.tl, b.c || b.te || b.h, b.re || b.tr)); for (const row of rows) lines.push(fr(row, b.v, b.v, b.v)); lines.push(hl(b.bl, b.be || b.h.repeat(2), b.br)); return lines.join("\n"); } export function drawNested(outerText, innerText, opts = {}) { const gap = opts.gap ?? 1; const so = style(opts.styleOuter ?? opts.styleInner); const si = style(opts.styleInner ?? so); const innerBox = drawBox(innerText, { style: si, paddingTop: 0, paddingBottom: 0, paddingLeft: 1, paddingRight: 1, alignment: "left", }); const innerLines = innerBox.split("\n"); const iw = Math.max(...innerLines.map((l) => l.length)); const outerLines = wrap(outerText, iw); const centered = new Array(outerLines.length); for (let i = 0; i < outerLines.length; i++) centered[i] = align(outerLines[i], iw, "center"); const combined = new Array(gap + centered.length + gap + innerLines.length); let i = 0; for (let g = 0; g < gap; g++) combined[i++] = ""; for (let o = 0; o < centered.length; o++) combined[i++] = centered[o]; for (let g = 0; g < gap; g++) combined[i++] = ""; for (let il = 0; il < innerLines.length; il++) combined[i++] = innerLines[il]; return drawBox(combined, { style: so, paddingTop: 0, paddingBottom: 0, paddingLeft: 0, paddingRight: 0, alignment: "left", }); } function main(args) { const cmd = args[0]; if (cmd === "draw" || cmd === "box") { const textParts = []; let i = 1; while (i < args.length && !args[i].startsWith("--")) textParts.push(args[i++]); const text = textParts.join(" ") || ""; let width; let borderStyle = "single"; let alignment = "left"; while (i < args.length) { const a = args[i]; if (a === "--width" && i + 1 < args.length) { width = +args[i + 1]; i += 2; } else if (a === "--style" && i + 1 < args.length) { borderStyle = style(args[i + 1]); i += 2; } else if (a === "--align" && i + 1 < args.length) { const s = args[i + 1]; alignment = s === "center" ? "center" : s === "right" ? "right" : "left"; i += 2; } else { i++; } } console.log(drawBox(text, { width, style: borderStyle, alignment })); } else if (cmd === "table") { let headerStr = ""; const rows = []; let borderStyle = "single"; for (let i = 1; i < args.length; i++) { if (args[i] === "--header" && i + 1 < args.length) { headerStr = args[++i]; } else if (args[i] === "--rows" && i + 1 < args.length) { i++; while (i < args.length && !args[i].startsWith("--")) rows.push(args[i++].split(",").map((s) => s.trim())); i--; } else if (args[i] === "--style" && i + 1 < args.length) { borderStyle = style(args[++i]); } } console.log(createTable(headerStr.split(",").map((s) => s.trim()), rows, { style: borderStyle })); } else if (cmd === "nested") { const outer = args[1] || ""; const inner = args[2] || ""; let gap = 1; let borderStyle = "single"; for (let i = 3; i < args.length; i++) { if (args[i] === "--gap" && i + 1 < args.length) { gap = +args[++i]; } else if (args[i] === "--style" && i + 1 < args.length) { borderStyle = style(args[++i]); } } console.log(drawNested(outer, inner, { gap, styleOuter: borderStyle })); } else { console.log( `ASCII Box Drawing\n\n draw [--width N] [--style single|double|bold] [--align left|center|right]\n table --header H1,H2 --rows a,b c,d\n nested Outer Inner [--gap N]`, ); } } if (process.argv[1] === fileURLToPath(import.meta.url)) { try { main(process.argv.slice(2)); } catch (e) { console.error("Error:", e instanceof Error ? e.message : e); process.exit(1); } }