UNPKG

@trap_stevo/terminal-plus

Version:

Transform your CLI into a visually striking, feature-rich command layer. Blend animated gradients, stylish badges, dynamic progress bars, sleek tables, and precise formatting utilities — all turning terminal output into a powerful visual experience.

328 lines (327 loc) 9.66 kB
"use strict"; const { generateGradientText } = require("../HUDManagers/ColorUtilityManager"); class ConsoleTable { constructor(options = {}) { this.options = { padding: 1, headerAlign: "center", cellAlign: "left", borderStyle: "solid", headerColor: null, borderColor: null, cellColor: (content, columnName, rowData) => content, columnOrder: null, columnNames: {}, title: "", tableNumber: null, gradient: { title: null, header: null, border: null, cell: null }, ...options }; this.borderChars = this.getBorderChars(this.options.borderStyle); } getBorderChars(style) { const styles = { solid: { topLeft: "┌", topRight: "┐", bottomLeft: "└", bottomRight: "┘", horizontal: "─", vertical: "│", topJoin: "┬", bottomJoin: "┴", leftJoin: "├", rightJoin: "┤", center: "┼" }, double: { topLeft: "╔", topRight: "╗", bottomLeft: "╚", bottomRight: "╝", horizontal: "═", vertical: "║", topJoin: "╦", bottomJoin: "╩", leftJoin: "╠", rightJoin: "╣", center: "╬" }, round: { topLeft: "╭", topRight: "╮", bottomLeft: "╰", bottomRight: "╯", horizontal: "─", vertical: "│", topJoin: "┬", bottomJoin: "┴", leftJoin: "├", rightJoin: "┤", center: "┼" }, bold: { topLeft: "┏", topRight: "┓", bottomLeft: "┗", bottomRight: "┛", horizontal: "━", vertical: "┃", topJoin: "┳", bottomJoin: "┻", leftJoin: "┣", rightJoin: "┫", center: "╋" } }; return styles[style] || styles["solid"]; } setData(data) { if (Array.isArray(data)) { this.data = data; } else if (typeof data === "object") { this.data = Object.values(data); } else { throw new Error("Array or an object only."); } this.extractColumns(); this.calculateColumnWidths(); } extractColumns() { if (this.options.columnOrder && Array.isArray(this.options.columnOrder)) { this.columns = this.options.columnOrder; } else { const columnsSet = new Set(); this.data.forEach(item => { Object.keys(item).forEach(key => columnsSet.add(key)); }); this.columns = Array.from(columnsSet); } } calculateColumnWidths() { const { padding } = this.options; this.columnWidths = {}; this.totalWidth = 0; this.columns.forEach(column => { const headerName = this.options.columnNames[column] || column; let maxWidth = String(headerName).length; this.data.forEach(item => { const cellContent = item[column] !== undefined ? String(item[column]) : ""; maxWidth = Math.max(maxWidth, cellContent.length); }); const columnWidth = maxWidth + padding * 2; this.columnWidths[column] = columnWidth; this.totalWidth += columnWidth; }); this.totalWidth += this.columns.length + 1; } alignText(text, width, align) { const { padding } = this.options; const textLength = text.length; let totalPadding = width - textLength; let leftPadding = " ".repeat(padding); let rightPadding = " ".repeat(padding); if (totalPadding < padding * 2) { leftPadding = ""; rightPadding = ""; } else { totalPadding -= padding * 2; if (align === "left") { rightPadding += " ".repeat(totalPadding); } else if (align === "right") { leftPadding += " ".repeat(totalPadding); } else { const half = Math.floor(totalPadding / 2); leftPadding += " ".repeat(half); rightPadding += " ".repeat(totalPadding - half); } } return leftPadding + text + rightPadding; } generateBorder(type) { const { columns, columnWidths } = this; let { horizontal, vertical, topLeft, topRight, bottomLeft, bottomRight, topJoin, bottomJoin, leftJoin, rightJoin, center } = this.borderChars; const { borderColor } = this.options; const borderGradient = this.options.gradient.border; if (borderGradient && borderGradient.length === 2) { const chars = [topLeft, topRight, bottomLeft, bottomRight, horizontal, vertical, topJoin, bottomJoin, leftJoin, rightJoin, center]; const coloredChars = {}; chars.forEach(async (char, index) => { coloredChars[char] = generateGradientText(char, borderGradient); }); bottomRight = coloredChars[bottomRight]; bottomLeft = coloredChars[bottomLeft]; topRight = coloredChars[topRight]; topLeft = coloredChars[topLeft]; horizontal = coloredChars[horizontal]; vertical = coloredChars[vertical]; bottomJoin = coloredChars[bottomJoin]; rightJoin = coloredChars[rightJoin]; leftJoin = coloredChars[leftJoin]; topJoin = coloredChars[topJoin]; center = coloredChars[center]; } else if (borderColor) { bottomRight = borderColor(bottomRight); bottomLeft = borderColor(bottomLeft); topRight = borderColor(topRight); topLeft = borderColor(topLeft); horizontal = borderColor(horizontal); vertical = borderColor(vertical); bottomJoin = borderColor(bottomJoin); rightJoin = borderColor(rightJoin); leftJoin = borderColor(leftJoin); topJoin = borderColor(topJoin); center = borderColor(center); } let border = ""; columns.forEach((column, index) => { const first = index === 0; const last = index === columns.length - 1; const width = columnWidths[column]; const line = horizontal.repeat(width); if (type === "top") { border += first ? topLeft : topJoin; } else if (type === "bottom") { border += first ? bottomLeft : bottomJoin; } else { border += first ? leftJoin : center; } border += line; if (last) { if (type === "top") { border += topRight; } else if (type === "bottom") { border += bottomRight; } else { border += rightJoin; } } }); return border; } generateRow(rowData, headerRow = false) { const { columns, columnWidths } = this; let { vertical } = this.borderChars; const { headerAlign, cellAlign, headerColor, cellColor } = this.options; const align = headerRow ? headerAlign : cellAlign; const headerGradient = this.options.gradient.header; const cellGradient = this.options.gradient.cell; const borderGradient = this.options.gradient.border; if (borderGradient && borderGradient.length === 2) { vertical = generateGradientText(vertical, borderGradient); } else if (this.options.borderColor) { vertical = this.options.borderColor(vertical); } let row = ""; columns.forEach((column, index) => { const content = rowData[column] !== undefined ? String(rowData[column]) : ""; const width = columnWidths[column]; const alignedText = this.alignText(content, width, align); let cellContent = alignedText; const first = index === 0; if (headerRow) { if (headerGradient && headerGradient.length === 2) { cellContent = generateGradientText(cellContent, headerGradient); } else if (headerColor) { cellContent = headerColor(cellContent); } } else { if (cellGradient && cellGradient.length === 2) { cellContent = generateGradientText(cellContent, cellGradient); } else if (cellColor) { cellContent = cellColor(cellContent, column, rowData); } } if (first) { row += vertical; } row += cellContent + vertical; }); return row; } setTitle(title, tableNumber = null) { this.options.title = title; if (tableNumber !== null) { this.options.tableNumber = tableNumber; } } renderTitle() { let titleText = this.options.title; if (this.options.tableNumber !== null) { titleText = titleText; } const totalWidth = this.totalWidth - 2; const titleLength = titleText.length; let leftPadding = Math.floor((totalWidth - titleLength) / 2); if (leftPadding < 0) leftPadding = 0; let titleLine = " ".repeat(leftPadding) + titleText; const titleGradient = this.options.gradient.title; if (titleGradient && titleGradient.length === 2) { titleLine = generateGradientText(titleLine.trim(), titleGradient); } return titleLine; } render() { const { data } = this; if (!data || data.length === 0) return ""; const lines = []; if (this.options.title) { const title = this.renderTitle(); lines.push(title); } lines.push(this.generateBorder("top")); const headerData = {}; this.columns.forEach(column => { headerData[column] = this.options.columnNames[column] || column; }); lines.push(this.generateRow(headerData, true)); lines.push(this.generateBorder("middle")); data.forEach(rowData => { lines.push(this.generateRow(rowData)); }); lines.push(this.generateBorder("bottom")); return lines.join("\n"); } } ; module.exports = ConsoleTable;