layoutz
Version:
Friendly, expressive print-layout DSL for JavaScript/TypeScript
1,176 lines (1,173 loc) • 35.4 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
AutoCentered: () => AutoCentered,
Banner: () => Banner,
Border: () => Border,
BorderStyle: () => BorderStyle,
Box: () => Box,
Center: () => Center,
Chart: () => Chart,
Columns: () => Columns,
DIMENSIONS: () => DIMENSIONS,
Empty: () => Empty,
GLYPHS: () => GLYPHS,
HorizontalRule: () => HorizontalRule,
InlineBar: () => InlineBar,
Justified: () => Justified,
KeyValue: () => KeyValue,
Layout: () => Layout,
LeftAligned: () => LeftAligned,
LineBreak: () => LineBreak,
Margin: () => Margin,
OrderedList: () => OrderedList,
Padded: () => Padded,
RightAligned: () => RightAligned,
Row: () => Row,
Section: () => Section,
StatusCard: () => StatusCard,
Table: () => Table,
Text: () => Text,
Tree: () => Tree,
Truncated: () => Truncated,
Underline: () => Underline,
UnorderedList: () => UnorderedList,
VerticalRule: () => VerticalRule,
Wrapped: () => Wrapped,
autoCenter: () => autoCenter,
banner: () => banner,
box: () => box,
br: () => br,
center: () => center,
chart: () => chart,
columns: () => columns,
dsl: () => dsl,
empty: () => empty,
getHeight: () => getHeight,
getWidth: () => getWidth,
hr: () => hr,
inlineBar: () => inlineBar,
justify: () => justify,
kv: () => kv,
layout: () => layout,
leftAlign: () => leftAlign,
margin: () => margin,
margins: () => margins,
ol: () => ol,
pad: () => pad,
rightAlign: () => rightAlign,
row: () => row,
section: () => section,
statusCard: () => statusCard,
stripAnsiCodes: () => stripAnsiCodes,
table: () => table,
text: () => text,
tree: () => tree,
truncate: () => truncate,
ul: () => ul,
underline: () => underline,
vr: () => vr,
wrap: () => wrap
});
module.exports = __toCommonJS(index_exports);
// layoutz.ts
var DIMENSIONS = {
MIN_CONTENT_PADDING: 2,
BORDER_THICKNESS: 2,
SIDE_PADDING: 2,
PROGRESS_BAR_WIDTH: 20,
TREE_INDENTATION: 4,
TREE_CONNECTOR_SPACING: 3,
DEFAULT_RULE_WIDTH: 50,
DEFAULT_CHART_WIDTH: 40,
CHART_LABEL_MAX_WIDTH: 15,
CHART_LABEL_SPACING: 15,
BOX_INNER_PADDING: 4,
BOX_BORDER_WIDTH: 2
};
var GLYPHS = {
TOP_LEFT: "\u250C",
TOP_RIGHT: "\u2510",
BOTTOM_LEFT: "\u2514",
BOTTOM_RIGHT: "\u2518",
HORIZONTAL: "\u2500",
VERTICAL: "\u2502",
CROSS: "\u253C",
TEE_DOWN: "\u252C",
TEE_UP: "\u2534",
TEE_RIGHT: "\u251C",
TEE_LEFT: "\u2524",
BULLET: "\u2022",
SPACE: " ",
BAR_FILLED: "\u2588",
BAR_EMPTY: "\u2500",
TREE_BRANCH: "\u251C\u2500\u2500",
TREE_LAST_BRANCH: "\u2514\u2500\u2500",
TREE_VERTICAL: "\u2502",
TREE_INDENT: " ".repeat(DIMENSIONS.TREE_INDENTATION)
};
var ANSI_ESCAPE_REGEX = /\u001b\[[0-9;]*m/g;
function stripAnsiCodes(text2) {
return text2.replace(ANSI_ESCAPE_REGEX, "");
}
function flattenToSingleLine(element) {
return element.render().split("\n").join(" ");
}
function getWidth(element) {
const rendered = element.render();
if (!rendered) return 0;
const lines = rendered.split("\n");
return Math.max(...lines.map((line) => stripAnsiCodes(line).length));
}
function getHeight(element) {
const rendered = element.render();
if (!rendered) return 1;
return rendered.split("\n").length;
}
var Text = class {
constructor(content) {
this.content = content;
}
render() {
return this.content;
}
};
var LineBreak = class {
render() {
return "\n";
}
};
var HorizontalRule = class {
constructor(char = "\u2500", ruleWidth) {
this.char = char;
this.ruleWidth = ruleWidth;
}
render() {
const width = this.ruleWidth ?? DIMENSIONS.DEFAULT_RULE_WIDTH;
return this.char.repeat(width);
}
};
var BorderStyle = /* @__PURE__ */ ((BorderStyle2) => {
BorderStyle2["Single"] = "single";
BorderStyle2["Double"] = "double";
BorderStyle2["Thick"] = "thick";
BorderStyle2["Round"] = "round";
return BorderStyle2;
})(BorderStyle || {});
var Border = {
Single: "single" /* Single */,
Double: "double" /* Double */,
Thick: "thick" /* Thick */,
Round: "round" /* Round */
};
function getBorderChars(style) {
switch (style) {
case "single" /* Single */:
return {
topLeft: "\u250C",
topRight: "\u2510",
bottomLeft: "\u2514",
bottomRight: "\u2518",
horizontal: "\u2500",
vertical: "\u2502"
};
case "double" /* Double */:
return {
topLeft: "\u2554",
topRight: "\u2557",
bottomLeft: "\u255A",
bottomRight: "\u255D",
horizontal: "\u2550",
vertical: "\u2551"
};
case "thick" /* Thick */:
return {
topLeft: "\u250F",
topRight: "\u2513",
bottomLeft: "\u2517",
bottomRight: "\u251B",
horizontal: "\u2501",
vertical: "\u2503"
};
case "round" /* Round */:
return {
topLeft: "\u256D",
topRight: "\u256E",
bottomLeft: "\u2570",
bottomRight: "\u256F",
horizontal: "\u2500",
vertical: "\u2502"
};
}
}
var KeyValue = class {
constructor(pairs) {
this.pairs = pairs;
}
render() {
if (this.pairs.length === 0) return "";
const maxKeyLength = Math.max(...this.pairs.map(([key]) => key.length));
const alignmentPosition = maxKeyLength + 2;
return this.pairs.map(([key, value]) => {
const keyWithColon = `${key}:`;
const spacesNeeded = alignmentPosition - keyWithColon.length;
const padding = " ".repeat(Math.max(1, spacesNeeded));
return `${keyWithColon}${padding}${value}`;
}).join("\n");
}
};
var _UnorderedList = class _UnorderedList {
constructor(items, bullet = "\u2022") {
this.items = items;
this.bullet = bullet;
}
render() {
return this.renderAtLevel(0);
}
renderAtLevel(level) {
if (this.items.length === 0) return "";
const currentBullet = this.bullet === "\u2022" ? _UnorderedList.BULLET_STYLES[level % _UnorderedList.BULLET_STYLES.length] : this.bullet;
const result = [];
for (const item of this.items) {
if (item instanceof _UnorderedList) {
const nestedContent = item.renderAtLevel(level + 1);
if (nestedContent) {
result.push(nestedContent);
}
} else {
const content = item.render();
const lines = content.split("\n");
const indent = " ".repeat(level);
if (lines.length === 1) {
result.push(`${indent}${currentBullet} ${lines[0]}`);
} else {
const firstLine = `${indent}${currentBullet} ${lines[0]}`;
const lineIndent = indent + " ".repeat(currentBullet.length + 1);
const remainingLines = lines.slice(1).map((line) => `${lineIndent}${line}`);
result.push([firstLine, ...remainingLines].join("\n"));
}
}
}
return result.join("\n");
}
};
_UnorderedList.BULLET_STYLES = ["\u2022", "\u25E6", "\u25AA"];
var UnorderedList = _UnorderedList;
var Tree = class _Tree {
constructor(label, children = []) {
this.label = label;
this.children = children;
}
render() {
return this.renderAtLevel(0, true, []);
}
renderAtLevel(level, isLast, parentPrefixes) {
if (level === 0 && this.children.length === 0) {
return this.label;
}
let result = "";
if (level === 0) {
result += this.label;
} else {
const prefix = this.buildPrefix(parentPrefixes, isLast);
const suffix = this.children.length > 0 ? "/" : "";
result += prefix + this.label + suffix;
}
if (this.children.length > 0) {
result += "\n";
const newParentPrefixes = [...parentPrefixes];
if (level > 0) {
newParentPrefixes.push(!isLast);
}
this.children.forEach((child, index) => {
const isChildLast = index === this.children.length - 1;
const childTree = new _Tree(child.label, child.children);
result += childTree.renderAtLevel(
level + 1,
isChildLast,
newParentPrefixes
);
if (!isChildLast) {
result += "\n";
}
});
}
return result;
}
buildPrefix(parentPrefixes, isLast) {
let prefix = "";
for (const hasMore of parentPrefixes) {
if (hasMore) {
prefix += "\u2502 ";
} else {
prefix += " ";
}
}
if (isLast) {
prefix += "\u2514\u2500\u2500 ";
} else {
prefix += "\u251C\u2500\u2500 ";
}
return prefix;
}
};
var OrderedList = class _OrderedList {
constructor(items) {
this.items = items;
}
render() {
return this.renderAtLevel(0);
}
renderAtLevel(level) {
if (this.items.length === 0) return "";
let itemNumber = 0;
return this.items.map((item) => {
if (item instanceof _OrderedList) {
return item.renderAtLevel(level + 1);
} else {
const number = this.getNumbering(itemNumber, level);
itemNumber++;
const content = item.render();
const lines = content.split("\n");
const indent = " ".repeat(level);
if (lines.length === 1) {
return `${indent}${number}. ${lines[0]}`;
} else {
const firstLine = `${indent}${number}. ${lines[0]}`;
const lineIndent = indent + " ".repeat(number.length + 2);
const remainingLines = lines.slice(1).map((line) => `${lineIndent}${line}`);
return [firstLine, ...remainingLines].join("\n");
}
}
}).join("\n");
}
getNumbering(index, level) {
switch (level % 3) {
case 0:
return (index + 1).toString();
case 1:
return String.fromCharCode(97 + index);
// a, b, c...
case 2:
return this.toRomanNumeral(index + 1);
default:
return (index + 1).toString();
}
}
toRomanNumeral(n) {
const mappings = [
[10, "x"],
[9, "ix"],
[5, "v"],
[4, "iv"],
[1, "i"]
];
let result = "";
let num = n;
for (const [value, symbol] of mappings) {
while (num >= value) {
result += symbol;
num -= value;
}
}
return result;
}
};
var Box = class _Box {
constructor(elements, title = "", style = "single" /* Single */) {
this.elements = elements;
this.title = title;
this.style = style;
}
/**
* Set the border style (fluent API)
*/
border(style) {
return new _Box(this.elements, this.title, style);
}
render() {
const content = this.elements.length === 1 ? this.elements[0] : new Layout(this.elements);
const contentLines = content.render().split("\n");
const contentWidth = contentLines.length === 0 ? 0 : Math.max(...contentLines.map((line) => stripAnsiCodes(line).length));
const titleWidth = this.title ? this.title.length + DIMENSIONS.MIN_CONTENT_PADDING : 0;
const innerWidth = Math.max(contentWidth, titleWidth);
const totalWidth = innerWidth + DIMENSIONS.BOX_INNER_PADDING;
const chars = getBorderChars(this.style);
const topBorder = this.title ? (() => {
const titlePadding = totalWidth - this.title.length - DIMENSIONS.BOX_BORDER_WIDTH;
const leftPad = Math.floor(titlePadding / 2);
const rightPad = titlePadding - leftPad;
return `${chars.topLeft}${chars.horizontal.repeat(leftPad)}${this.title}${chars.horizontal.repeat(rightPad)}${chars.topRight}`;
})() : `${chars.topLeft}${chars.horizontal.repeat(totalWidth - DIMENSIONS.BOX_BORDER_WIDTH)}${chars.topRight}`;
const bottomBorder = `${chars.bottomLeft}${chars.horizontal.repeat(totalWidth - DIMENSIONS.BOX_BORDER_WIDTH)}${chars.bottomRight}`;
const paddedContent = contentLines.map((line) => {
const padding = innerWidth - stripAnsiCodes(line).length;
return `${chars.vertical} ${line}${" ".repeat(padding)} ${chars.vertical}`;
});
return [topBorder, ...paddedContent, bottomBorder].join("\n");
}
};
var Section = class {
constructor(title, content, glyph = "=", flankingChars = 3) {
this.title = title;
this.content = content;
this.glyph = glyph;
this.flankingChars = flankingChars;
}
render() {
const header = `${this.glyph.repeat(this.flankingChars)} ${this.title} ${this.glyph.repeat(this.flankingChars)}`;
return `${header}
${this.content.render()}`;
}
};
var Row = class {
constructor(elements) {
this.elements = elements;
}
render() {
if (this.elements.length === 0) return "";
const renderedElements = this.elements.map((el) => el.render().split("\n"));
const maxHeight = Math.max(
...renderedElements.map((lines) => lines.length)
);
const elementWidths = this.elements.map((el) => getWidth(el));
const paddedElements = renderedElements.map((lines, i) => {
const width = elementWidths[i];
const paddedLines = [...lines];
while (paddedLines.length < maxHeight) {
paddedLines.push("");
}
return paddedLines.map((line) => line.padEnd(width, " "));
});
const result = [];
for (let row2 = 0; row2 < maxHeight; row2++) {
const rowContent = paddedElements.map((lines) => lines[row2]).join(" ");
result.push(rowContent.trimEnd());
}
return result.join("\n");
}
};
var Layout = class {
constructor(elements) {
this.elements = elements;
}
render() {
return this.elements.map((el) => el.render()).join("\n");
}
};
var InlineBar = class {
constructor(label, progress) {
this.label = label;
this.progress = progress;
}
render() {
const clampedProgress = Math.max(0, Math.min(1, this.progress));
const filledSegments = Math.floor(
clampedProgress * DIMENSIONS.PROGRESS_BAR_WIDTH
);
const emptySegments = DIMENSIONS.PROGRESS_BAR_WIDTH - filledSegments;
const bar = GLYPHS.BAR_FILLED.repeat(filledSegments) + GLYPHS.BAR_EMPTY.repeat(emptySegments);
const percentage = Math.floor(clampedProgress * 100);
return `${flattenToSingleLine(this.label)} [${bar}] ${percentage}%`;
}
};
var StatusCard = class _StatusCard {
constructor(label, content, style = "single" /* Single */) {
this.label = label;
this.content = content;
this.style = style;
}
/**
* Set the border style (fluent API)
*/
border(style) {
return new _StatusCard(this.label, this.content, style);
}
render() {
const labelRendered = this.label.render();
const contentRendered = this.content.render();
const labelLines = labelRendered.split("\n");
const contentLines = contentRendered.split("\n");
const allLines = [...labelLines, ...contentLines];
const maxTextLength = allLines.length === 0 ? 0 : Math.max(...allLines.map((line) => stripAnsiCodes(line).length));
const contentWidth = maxTextLength + DIMENSIONS.MIN_CONTENT_PADDING;
const chars = getBorderChars(this.style);
const topBorder = chars.topLeft + chars.horizontal.repeat(contentWidth + 2) + chars.topRight;
const bottomBorder = chars.bottomLeft + chars.horizontal.repeat(contentWidth + 2) + chars.bottomRight;
const createCardLines = (lines) => lines.map((line) => {
const visibleLength = stripAnsiCodes(line).length;
const padding = contentWidth - visibleLength;
return `${chars.vertical} ${line}${" ".repeat(padding)} ${chars.vertical}`;
});
const labelCardLines = createCardLines(labelLines);
const contentCardLines = createCardLines(contentLines);
return [
topBorder,
...labelCardLines,
...contentCardLines,
bottomBorder
].join("\n");
}
};
var Table = class _Table {
constructor(headers, rows, style = "single" /* Single */) {
this.headers = headers;
this.rows = rows;
this.style = style;
}
/**
* Set the border style (fluent API)
*/
border(style) {
return new _Table(this.headers, this.rows, style);
}
render() {
const headerLines = this.headers.map((h) => h.render().split("\n"));
const rowLines = this.rows.map(
(row2) => row2.map((cell) => cell.render().split("\n"))
);
const allRowLines = [headerLines, ...rowLines];
const columnWidths = this.calculateColumnWidths(allRowLines);
const chars = getBorderChars(this.style);
const borders = this.createTableBorders(columnWidths, chars);
const headerRowHeight = Math.max(
...headerLines.map((lines) => lines.length)
);
const headerRows = this.buildMultilineTableRows(
headerLines,
columnWidths,
headerRowHeight,
chars
);
const dataRows = rowLines.flatMap((row2) => {
const rowHeight = Math.max(...row2.map((cellLines) => cellLines.length));
return this.buildMultilineTableRows(row2, columnWidths, rowHeight, chars);
});
return [
borders.top,
...headerRows,
borders.separator,
...dataRows,
borders.bottom
].join("\n");
}
calculateColumnWidths(allRowLines) {
return this.headers.map((_, columnIndex) => {
let maxWidth = 0;
for (const row2 of allRowLines) {
if (columnIndex < row2.length) {
for (const line of row2[columnIndex]) {
const width = stripAnsiCodes(line).length;
if (width > maxWidth) maxWidth = width;
}
}
}
return maxWidth;
});
}
createTableBorders(widths, chars) {
const segments = widths.map((w) => chars.horizontal.repeat(w));
const junctionChars = this.getJunctionChars(chars);
return {
top: segments.join(`${chars.horizontal}${junctionChars.teeDown}${chars.horizontal}`).replace(/^/, `${chars.topLeft}${chars.horizontal}`).replace(/$/, `${chars.horizontal}${chars.topRight}`),
separator: segments.join(`${chars.horizontal}${junctionChars.cross}${chars.horizontal}`).replace(/^/, `${junctionChars.teeRight}${chars.horizontal}`).replace(/$/, `${chars.horizontal}${junctionChars.teeLeft}`),
bottom: segments.join(`${chars.horizontal}${junctionChars.teeUp}${chars.horizontal}`).replace(/^/, `${chars.bottomLeft}${chars.horizontal}`).replace(/$/, `${chars.horizontal}${chars.bottomRight}`)
};
}
getJunctionChars(chars) {
return {
teeDown: "\u252C",
teeUp: "\u2534",
teeLeft: "\u2524",
teeRight: "\u251C",
cross: "\u253C"
};
}
buildMultilineTableRows(cellLines, widths, rowHeight, chars) {
const result = [];
for (let lineIndex = 0; lineIndex < rowHeight; lineIndex++) {
const rowParts = cellLines.map((lines, colIndex) => {
const line = lineIndex < lines.length ? lines[lineIndex] : "";
const visibleLength = stripAnsiCodes(line).length;
const padding = widths[colIndex] - visibleLength;
return line + " ".repeat(Math.max(0, padding));
});
result.push(
`${chars.vertical} ${rowParts.join(` ${chars.vertical} `)} ${chars.vertical}`
);
}
return result;
}
};
function text(content) {
return new Text(content);
}
function layout(...elements) {
return new Layout(toElements(elements));
}
function section(title, glyph = "=", flankingChars = 3) {
return (content) => new Section(title, toElement(content), glyph, flankingChars);
}
function kv(...pairs) {
return new KeyValue(pairs);
}
function ul(...items) {
const elements = toElements(items);
return new UnorderedList(elements);
}
function ol(...items) {
return new OrderedList(items);
}
function box(title = "", style = "single" /* Single */) {
return (...elements) => new Box(toElements(elements), title, style);
}
function row(...elements) {
return new Row(toElements(elements));
}
function hr(char = "\u2500", width) {
return new HorizontalRule(char, width);
}
function inlineBar(label, progress) {
return new InlineBar(toElement(label), progress);
}
function statusCard(label, content, style) {
const labelElement = toElement(label);
const contentElement = toElement(content);
return new StatusCard(
labelElement,
contentElement,
style ?? "single" /* Single */
);
}
function toElement(item) {
return typeof item === "string" ? new Text(item) : item;
}
function toElements(items) {
return items.map(toElement);
}
function table(headers, rows, style) {
const headerElements = toElements(headers);
const rowElements = rows.map((row2) => toElements(row2));
return new Table(headerElements, rowElements, style ?? "single" /* Single */);
}
function br(count = 1) {
if (count === 1) {
return new LineBreak();
}
return new Layout(Array(count).fill(new LineBreak()));
}
var Center = class {
constructor(element, width) {
this.element = element;
this.width = width;
}
render() {
const content = this.element.render();
const lines = content.split("\n");
const targetWidth = this.width ?? 80;
return lines.map((line) => {
const visibleLength = stripAnsiCodes(line).length;
if (visibleLength >= targetWidth) {
return line;
}
const padding = targetWidth - visibleLength;
const leftPad = Math.floor(padding / 2);
const rightPad = padding - leftPad;
return " ".repeat(leftPad) + line + " ".repeat(rightPad);
}).join("\n");
}
};
function center(element, width) {
return new Center(toElement(element), width);
}
function underline(char = "\u2500") {
return (element) => new Underline(toElement(element), char);
}
function margin(prefix) {
return (...elements) => new Margin(prefix, toElements(elements));
}
function tree(label) {
const baseTree = new Tree(label, []);
const result = function(...children) {
return new Tree(label, children);
};
result.render = () => baseTree.render();
result.label = label;
result.children = [];
return result;
}
function chart(...data) {
const chartData = data.map(
([label, value]) => [toElement(label), value]
);
return new Chart(chartData);
}
function banner(content = "") {
return new Banner(toElement(content), "double" /* Double */);
}
function columns(spacingOrFirstElement, ...elements) {
if (typeof spacingOrFirstElement === "number") {
return new Columns(toElements(elements), spacingOrFirstElement);
} else {
const allElements = [spacingOrFirstElement, ...elements];
return new Columns(toElements(allElements), 2);
}
}
function pad(padding) {
return (element) => new Padded(toElement(element), padding);
}
function truncate(maxWidth, ellipsis = "...") {
return (element) => new Truncated(toElement(element), maxWidth, ellipsis);
}
function vr(lineCount, char = "\u2502") {
return new VerticalRule(char, lineCount);
}
function wrap(maxWidth) {
return (element) => new Wrapped(toElement(element), maxWidth);
}
function justify(targetWidth, justifyLastLine = false) {
return (element) => new Justified(toElement(element), targetWidth, justifyLastLine);
}
function leftAlign(targetWidth) {
return (element) => new LeftAligned(toElement(element), targetWidth);
}
function rightAlign(targetWidth) {
return (element) => new RightAligned(toElement(element), targetWidth);
}
function autoCenter(element) {
return new AutoCentered(toElement(element));
}
function empty() {
return new Empty();
}
var margins = {
error: (...elements) => new Margin("[\x1B[31merror\x1B[0m]", toElements(elements)),
warn: (...elements) => new Margin("[\x1B[33mwarn\x1B[0m]", toElements(elements)),
success: (...elements) => new Margin("[\x1B[32msuccess\x1B[0m]", toElements(elements)),
info: (...elements) => new Margin("[\x1B[36minfo\x1B[0m]", toElements(elements))
};
var Underline = class {
constructor(element, char = "\u2500") {
this.element = element;
this.char = char;
}
render() {
const content = this.element.render();
const lines = content.split("\n");
const maxWidth = Math.max(
...lines.map((line) => stripAnsiCodes(line).length)
);
let underlineStr = "";
while (underlineStr.length < maxWidth) {
underlineStr += this.char;
}
underlineStr = underlineStr.substring(0, maxWidth);
return content + "\n" + underlineStr;
}
};
var Margin = class {
constructor(prefix, elements) {
this.prefix = prefix;
this.elements = elements;
}
render() {
const content = this.elements.length === 1 ? this.elements[0] : new Layout(this.elements);
const lines = content.render().split("\n");
return lines.map((line) => `${this.prefix} ${line}`).join("\n");
}
};
var Chart = class {
constructor(data) {
this.data = data;
}
render() {
if (this.data.length === 0) return "";
const maxValue = Math.max(...this.data.map(([, value]) => Math.abs(value)));
if (maxValue === 0)
return this.data.map(([label]) => label.render()).join("\n");
const maxLabelWidth = Math.max(
...this.data.map(
([label]) => Math.max(
...label.render().split("\n").map((line) => stripAnsiCodes(line).length)
)
)
);
const labelWidth = Math.min(
maxLabelWidth,
DIMENSIONS.CHART_LABEL_MAX_WIDTH
);
return this.data.map(([label, value]) => {
const labelText = flattenToSingleLine(label);
const truncatedLabel = stripAnsiCodes(labelText).length > labelWidth ? stripAnsiCodes(labelText).substring(0, labelWidth - 3) + "..." : labelText;
const paddedLabel = truncatedLabel.padEnd(labelWidth, " ");
const percentage = Math.abs(value) / maxValue;
const barLength = Math.floor(
percentage * DIMENSIONS.DEFAULT_CHART_WIDTH
);
const bar = GLYPHS.BAR_FILLED.repeat(barLength);
const emptyBar = GLYPHS.BAR_EMPTY.repeat(
DIMENSIONS.DEFAULT_CHART_WIDTH - barLength
);
const valueStr = typeof value === "number" && value % 1 === 0 ? value.toString() : value.toFixed(1);
return `${paddedLabel} \u2502${bar}${emptyBar}\u2502 ${valueStr}`;
}).join("\n");
}
};
var Banner = class _Banner {
constructor(content, style = "double" /* Double */) {
this.content = content;
this.style = style;
}
/**
* Set the border style (fluent API)
*/
border(style) {
return new _Banner(this.content, style);
}
render() {
const rendered = this.content.render();
const lines = rendered.split("\n");
const maxWidth = lines.length === 0 ? 0 : Math.max(...lines.map((line) => stripAnsiCodes(line).length));
const contentWidth = maxWidth + DIMENSIONS.MIN_CONTENT_PADDING;
const chars = getBorderChars(this.style);
const topBorder = `${chars.topLeft}${chars.horizontal.repeat(contentWidth + 2)}${chars.topRight}`;
const bottomBorder = `${chars.bottomLeft}${chars.horizontal.repeat(contentWidth + 2)}${chars.bottomRight}`;
const contentLines = lines.map((line) => {
const padding = contentWidth - stripAnsiCodes(line).length;
return `${chars.vertical} ${line}${" ".repeat(padding)} ${chars.vertical}`;
});
return [topBorder, ...contentLines, bottomBorder].join("\n");
}
};
var Columns = class {
constructor(elements, spacing = 2) {
this.elements = elements;
this.spacing = spacing;
}
render() {
if (this.elements.length === 0) return "";
const renderedElements = this.elements.map((el) => el.render().split("\n"));
const maxHeight = Math.max(
...renderedElements.map((lines) => lines.length)
);
const elementWidths = this.elements.map((el) => getWidth(el));
const paddedElements = renderedElements.map((lines, i) => {
const width = elementWidths[i];
const paddedLines = [...lines];
while (paddedLines.length < maxHeight) {
paddedLines.push("");
}
return paddedLines.map((line) => line.padEnd(width, " "));
});
const spacer = " ".repeat(this.spacing);
const result = [];
for (let row2 = 0; row2 < maxHeight; row2++) {
const rowContent = paddedElements.map((lines) => lines[row2]).join(spacer);
result.push(rowContent.trimEnd());
}
return result.join("\n");
}
};
var Padded = class {
constructor(element, padding) {
this.element = element;
this.padding = padding;
}
render() {
const content = this.element.render();
const lines = content.split("\n");
const maxWidth = lines.length === 0 ? 0 : Math.max(...lines.map((line) => stripAnsiCodes(line).length));
const horizontalPad = " ".repeat(this.padding);
const verticalPad = " ".repeat(maxWidth + this.padding * 2);
const paddedLines = lines.map((line) => {
const linePadding = maxWidth - stripAnsiCodes(line).length;
return `${horizontalPad}${line}${" ".repeat(linePadding)}${horizontalPad}`;
});
const verticalLines = Array(this.padding).fill(verticalPad);
return [...verticalLines, ...paddedLines, ...verticalLines].join("\n");
}
};
var Truncated = class {
constructor(element, maxWidth, ellipsis = "...") {
this.element = element;
this.maxWidth = maxWidth;
this.ellipsis = ellipsis;
}
render() {
const content = this.element.render();
const lines = content.split("\n");
return lines.map((line) => {
const visibleLength = stripAnsiCodes(line).length;
if (visibleLength <= this.maxWidth) {
return line;
}
const truncateLength = this.maxWidth - this.ellipsis.length;
if (truncateLength <= 0) {
return this.ellipsis.substring(0, this.maxWidth);
}
const truncated = stripAnsiCodes(line).substring(0, truncateLength);
return truncated + this.ellipsis;
}).join("\n");
}
};
var VerticalRule = class {
constructor(char = "\u2502", lineCount) {
this.char = char;
this.lineCount = lineCount;
}
render() {
const count = Math.max(1, this.lineCount);
return Array(count).fill(this.char).join("\n");
}
};
var Wrapped = class {
constructor(element, maxWidth) {
this.element = element;
this.maxWidth = maxWidth;
}
render() {
const content = this.element.render();
const lines = content.split("\n");
return lines.flatMap((line) => {
const visibleLength = stripAnsiCodes(line).length;
if (visibleLength <= this.maxWidth) {
return [line];
}
const plainLine = stripAnsiCodes(line);
const words = plainLine.split(" ");
const wrappedLines = [];
let currentLine = "";
for (const word of words) {
const testLine = currentLine ? `${currentLine} ${word}` : word;
if (testLine.length <= this.maxWidth) {
currentLine = testLine;
} else {
if (currentLine) {
wrappedLines.push(currentLine);
currentLine = word;
} else {
wrappedLines.push(word.substring(0, this.maxWidth));
currentLine = word.substring(this.maxWidth);
}
}
}
if (currentLine) {
wrappedLines.push(currentLine);
}
return wrappedLines.length > 0 ? wrappedLines : [""];
}).join("\n");
}
};
var Justified = class {
constructor(element, targetWidth, justifyLastLine = false) {
this.element = element;
this.targetWidth = targetWidth;
this.justifyLastLine = justifyLastLine;
}
render() {
const content = this.element.render();
const lines = content.split("\n");
return lines.map((line, index) => {
const plainLine = stripAnsiCodes(line).trim();
const visibleLength = plainLine.length;
if (visibleLength >= this.targetWidth) {
return plainLine.substring(0, this.targetWidth);
}
const isLastLine = index === lines.length - 1;
if (isLastLine && !this.justifyLastLine) {
return line;
}
const words = plainLine.split(" ").filter((word) => word.length > 0);
if (words.length <= 1) {
return plainLine.padEnd(this.targetWidth, " ");
}
const totalWordLength = words.join("").length;
const totalSpaceNeeded = this.targetWidth - totalWordLength;
const gaps = words.length - 1;
if (gaps === 0) {
return plainLine.padEnd(this.targetWidth, " ");
}
const spacePerGap = Math.floor(totalSpaceNeeded / gaps);
const extraSpaces = totalSpaceNeeded % gaps;
let result = "";
for (let i = 0; i < words.length; i++) {
result += words[i];
if (i < words.length - 1) {
const spaces = spacePerGap + (i < extraSpaces ? 1 : 0);
result += " ".repeat(spaces);
}
}
return result;
}).join("\n");
}
};
var LeftAligned = class {
constructor(element, targetWidth) {
this.element = element;
this.targetWidth = targetWidth;
}
render() {
const content = this.element.render();
const lines = content.split("\n");
return lines.map((line) => {
const visibleLength = stripAnsiCodes(line).length;
if (visibleLength >= this.targetWidth) {
return stripAnsiCodes(line).substring(0, this.targetWidth);
}
return line + " ".repeat(this.targetWidth - visibleLength);
}).join("\n");
}
};
var RightAligned = class {
constructor(element, targetWidth) {
this.element = element;
this.targetWidth = targetWidth;
}
render() {
const content = this.element.render();
const lines = content.split("\n");
return lines.map((line) => {
const visibleLength = stripAnsiCodes(line).length;
if (visibleLength >= this.targetWidth) {
return stripAnsiCodes(line).substring(0, this.targetWidth);
}
const padding = this.targetWidth - visibleLength;
return " ".repeat(padding) + line;
}).join("\n");
}
};
var AutoCentered = class {
constructor(element) {
this.element = element;
}
render() {
return this.element.render();
}
};
var Empty = class {
render() {
return "";
}
};
var dsl = {
layout,
text,
box,
section,
banner,
row,
center,
autoCenter,
margin,
columns,
ul,
ol,
kv,
table,
tree,
chart,
statusCard,
inlineBar,
hr,
vr,
underline,
br,
pad,
truncate,
wrap,
justify,
leftAlign,
rightAlign,
empty,
Border,
BorderStyle,
margins
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AutoCentered,
Banner,
Border,
BorderStyle,
Box,
Center,
Chart,
Columns,
DIMENSIONS,
Empty,
GLYPHS,
HorizontalRule,
InlineBar,
Justified,
KeyValue,
Layout,
LeftAligned,
LineBreak,
Margin,
OrderedList,
Padded,
RightAligned,
Row,
Section,
StatusCard,
Table,
Text,
Tree,
Truncated,
Underline,
UnorderedList,
VerticalRule,
Wrapped,
autoCenter,
banner,
box,
br,
center,
chart,
columns,
dsl,
empty,
getHeight,
getWidth,
hr,
inlineBar,
justify,
kv,
layout,
leftAlign,
margin,
margins,
ol,
pad,
rightAlign,
row,
section,
statusCard,
stripAnsiCodes,
table,
text,
tree,
truncate,
ul,
underline,
vr,
wrap
});
;