Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

pi/agent/skills/optional/ascii-boxes/index.mjs

Raw
/** 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 <text> [--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);
	}
}