UNPKG

@kansnpms/console-log-pipe-cli

Version:

Global CLI tool for Console Log Pipe - Real-time log streaming server and management

287 lines (237 loc) 7.09 kB
/** * TableFormatter - Formats data as tables for CLI display */ const chalk = require('chalk'); class TableFormatter { /** * Display data as a formatted table */ static display(headers, rows, options = {}) { const { title, color = true, maxWidth = process.stdout.columns || 120, } = options; if (title) { console.log(); console.log(color ? chalk.cyan.bold(title) : title); console.log(); } if (rows.length === 0) { console.log( color ? chalk.gray('No data to display') : 'No data to display' ); return; } // Calculate column widths const columnWidths = this.calculateColumnWidths(headers, rows, maxWidth); // Display table this.displayHeader(headers, columnWidths, color); this.displaySeparator(columnWidths, color); rows.forEach(row => { this.displayRow(row, columnWidths, color); }); console.log(); } /** * Calculate optimal column widths */ static calculateColumnWidths(headers, rows, maxWidth) { const columnCount = headers.length; const minWidth = 8; const padding = 3; // Space between columns const availableWidth = maxWidth - padding * (columnCount - 1); // Calculate content widths const contentWidths = headers.map((header, index) => { const headerWidth = header.length; const maxRowWidth = Math.max( ...rows.map(row => String(row[index] || '').length) ); return Math.max(headerWidth, maxRowWidth, minWidth); }); // If total width fits, use content widths const totalContentWidth = contentWidths.reduce( (sum, width) => sum + width, 0 ); if (totalContentWidth <= availableWidth) { return contentWidths; } // Otherwise, distribute available width proportionally const totalRatio = contentWidths.reduce((sum, width) => sum + width, 0); return contentWidths.map(width => Math.max(minWidth, Math.floor((width / totalRatio) * availableWidth)) ); } /** * Display table header */ static displayHeader(headers, columnWidths, color) { const headerRow = headers .map((header, index) => this.padText(header, columnWidths[index])) .join(' '); console.log(color ? chalk.bold(headerRow) : headerRow); } /** * Display separator line */ static displaySeparator(columnWidths, color) { const separator = columnWidths.map(width => '─'.repeat(width)).join(' '); console.log(color ? chalk.gray(separator) : separator); } /** * Display table row */ static displayRow(row, columnWidths, _color) { const formattedRow = row .map((cell, index) => { const cellText = String(cell || ''); const truncated = this.truncateText(cellText, columnWidths[index]); return this.padText(truncated, columnWidths[index]); }) .join(' '); console.log(formattedRow); } /** * Pad text to specified width */ static padText(text, width) { if (text.length >= width) { return text; } return text + ' '.repeat(width - text.length); } /** * Truncate text to fit in column */ static truncateText(text, maxWidth) { if (text.length <= maxWidth) { return text; } if (maxWidth <= 3) { return '...'.substring(0, maxWidth); } return `${text.substring(0, maxWidth - 3)}...`; } /** * Create a simple key-value table */ static displayKeyValue(data, options = {}) { const { title, color = true } = options; if (title) { console.log(); console.log(color ? chalk.cyan.bold(title) : title); console.log(); } const entries = Object.entries(data); if (entries.length === 0) { console.log( color ? chalk.gray('No data to display') : 'No data to display' ); return; } const maxKeyWidth = Math.max(...entries.map(([key]) => key.length)); entries.forEach(([key, value]) => { const paddedKey = this.padText(key, maxKeyWidth); const keyFormatted = color ? chalk.cyan(paddedKey) : paddedKey; const valueFormatted = String(value || ''); console.log(`${keyFormatted} ${valueFormatted}`); }); console.log(); } /** * Display a list with bullets */ static displayList(items, options = {}) { const { title, color = true, bullet = '•' } = options; if (title) { console.log(); console.log(color ? chalk.cyan.bold(title) : title); console.log(); } if (items.length === 0) { console.log( color ? chalk.gray('No items to display') : 'No items to display' ); return; } items.forEach(item => { const bulletFormatted = color ? chalk.gray(bullet) : bullet; console.log(` ${bulletFormatted} ${item}`); }); console.log(); } /** * Display a tree structure */ static displayTree(tree, options = {}) { const { title, color = true, indent = 0 } = options; if (title && indent === 0) { console.log(); console.log(color ? chalk.cyan.bold(title) : title); console.log(); } const prefix = ' '.repeat(indent); if (typeof tree === 'string') { console.log(`${prefix}${tree}`); return; } Object.entries(tree).forEach(([key, value]) => { const keyFormatted = color ? chalk.white.bold(key) : key; console.log(`${prefix}${keyFormatted}`); if (typeof value === 'object' && value !== null) { this.displayTree(value, { ...options, title: null, indent: indent + 1, }); } else { const valuePrefix = ' '.repeat(indent + 1); console.log(`${valuePrefix}${value}`); } }); if (indent === 0) { console.log(); } } /** * Display statistics in a grid format */ static displayStats(stats, options = {}) { const { title, color = true, columns = 3 } = options; if (title) { console.log(); console.log(color ? chalk.cyan.bold(title) : title); console.log(); } const entries = Object.entries(stats); if (entries.length === 0) { console.log( color ? chalk.gray('No statistics to display') : 'No statistics to display' ); return; } // Group entries into rows const rows = []; for (let i = 0; i < entries.length; i += columns) { rows.push(entries.slice(i, i + columns)); } // Calculate column width const maxWidth = Math.floor((process.stdout.columns || 120) / columns) - 4; rows.forEach(row => { const formattedCells = row.map(([key, value]) => { const keyFormatted = color ? chalk.cyan(key) : key; const valueFormatted = color ? chalk.white.bold(String(value)) : String(value); const cell = `${keyFormatted}: ${valueFormatted}`; return this.padText(cell, maxWidth); }); console.log(formattedCells.join(' ')); }); console.log(); } } module.exports = TableFormatter;