UNPKG

simple-task-master

Version:
370 lines 12.7 kB
"use strict"; /** * Output formatting utilities for Simple Task Master */ Object.defineProperty(exports, "__esModule", { value: true }); exports.formatAsNDJSON = formatAsNDJSON; exports.formatAsJSON = formatAsJSON; exports.formatAsYAML = formatAsYAML; exports.formatTaskAsYAML = formatTaskAsYAML; exports.formatTaskAsMarkdown = formatTaskAsMarkdown; exports.formatAsTable = formatAsTable; exports.formatAsCSV = formatAsCSV; exports.formatTasks = formatTasks; exports.formatTask = formatTask; exports.printOutput = printOutput; exports.printError = printError; exports.printSuccess = printSuccess; exports.printWarning = printWarning; /** * Formats tasks as newline-delimited JSON (default format) */ function formatAsNDJSON(tasks) { if (tasks.length === 0) { return ''; } return tasks.map((task) => JSON.stringify(task)).join('\n'); } /** * Formats tasks as pretty JSON */ function formatAsJSON(tasks) { return JSON.stringify(tasks, null, 2); } /** * Formats tasks as YAML */ function formatAsYAML(tasks) { if (tasks.length === 0) { return ''; } // For multiple tasks, format as YAML array const yamlTasks = tasks.map((task) => { const lines = []; lines.push(`- id: ${task.id}`); lines.push(` title: ${JSON.stringify(task.title)}`); lines.push(` status: ${task.status}`); lines.push(` created: ${JSON.stringify(task.created)}`); lines.push(` updated: ${JSON.stringify(task.updated)}`); if (task.tags.length > 0) { lines.push(' tags:'); task.tags.forEach((tag) => lines.push(` - ${JSON.stringify(tag)}`)); } else { lines.push(' tags: []'); } if (task.dependencies.length > 0) { lines.push(' dependencies:'); task.dependencies.forEach((dep) => lines.push(` - ${dep}`)); } else { lines.push(' dependencies: []'); } if (task.content) { lines.push(` content: ${JSON.stringify(task.content)}`); } return lines.join('\n'); }); return yamlTasks.join('\n'); } /** * Formats a single task as YAML front-matter with markdown content */ function formatTaskAsYAML(task) { const { content, ...frontMatter } = task; const yamlContent = Object.entries(frontMatter) .map(([key, value]) => { if (Array.isArray(value)) { if (value.length === 0) { return `${key}: []`; } return `${key}:\n${value.map((v) => ` - ${JSON.stringify(v)}`).join('\n')}`; } return `${key}: ${JSON.stringify(value)}`; }) .join('\n'); return `---\n${yamlContent}\n---\n\n${content || ''}`; } /** * Formats a single task as markdown */ function formatTaskAsMarkdown(task) { const lines = []; // Title lines.push(`# ${task.title}`); lines.push(''); // Metadata lines.push('## Task Details'); lines.push(''); lines.push(`- **ID**: ${task.id}`); lines.push(`- **Status**: ${task.status}`); lines.push(`- **Created**: ${new Date(task.created).toLocaleString()}`); lines.push(`- **Updated**: ${new Date(task.updated).toLocaleString()}`); if (task.tags && task.tags.length > 0) { lines.push(`- **Tags**: ${task.tags.map((tag) => `\`${tag}\``).join(', ')}`); } if (task.dependencies && task.dependencies.length > 0) { lines.push(`- **Dependencies**: ${task.dependencies.join(', ')}`); } // Content if (task.content && task.content.trim()) { lines.push(''); lines.push('## Description'); lines.push(''); lines.push(task.content); } return lines.join('\n'); } /** * Formats tasks as a table */ function formatAsTable(tasks, options = {}) { if (tasks.length === 0) { return ''; } // Default to terminal width, fallback to 120 if not available const defaultWidth = (process.stdout.isTTY ? process.stdout.columns : undefined) || 120; const { headers = true, borders = true, padding = 1, maxWidth = defaultWidth } = options; // Define columns with flexible widths const columns = [ { key: 'id', title: 'ID', width: 4, minWidth: 4, flexible: false }, { key: 'title', title: 'Title', width: 40, minWidth: 20, flexible: true }, { key: 'status', title: 'Status', width: 12, minWidth: 10, flexible: false }, { key: 'tags', title: 'Tags', width: 25, minWidth: 15, flexible: true }, { key: 'updated', title: 'Updated', width: 12, minWidth: 10, flexible: false } ]; // Adjust column widths based on maxWidth const totalBorderWidth = borders ? (columns.length + 1) * 3 : 0; const totalPadding = columns.length * (padding * 2); const availableWidth = maxWidth - totalBorderWidth - totalPadding; if (availableWidth > 0) { const totalPreferredWidth = columns.reduce((sum, col) => sum + col.width, 0); if (totalPreferredWidth <= availableWidth) { // If preferred widths fit, distribute extra space to flexible columns const flexibleColumns = columns.filter((col) => col.flexible); const extraSpace = availableWidth - totalPreferredWidth; const extraPerFlexible = Math.floor(extraSpace / flexibleColumns.length); flexibleColumns.forEach((col) => { col.width += extraPerFlexible; }); } else { // If preferred widths don't fit, scale down proportionally but respect minimums const totalMinWidth = columns.reduce((sum, col) => sum + (col.minWidth || col.width), 0); if (totalMinWidth <= availableWidth) { // Scale down to minimums first, then distribute remaining space columns.forEach((col) => { col.width = col.minWidth || col.width; }); const remainingSpace = availableWidth - totalMinWidth; const flexibleColumns = columns.filter((col) => col.flexible); const extraPerFlexible = Math.floor(remainingSpace / flexibleColumns.length); flexibleColumns.forEach((col) => { col.width += extraPerFlexible; }); } else { // Even minimums don't fit, scale everything down proportionally const ratio = availableWidth / totalMinWidth; columns.forEach((col) => { col.width = Math.max(3, Math.floor((col.minWidth || col.width) * ratio)); }); } } } // Helper function to truncate and pad text const formatCell = (text, width) => { const truncated = text.length > width ? text.substring(0, width - 3) + '...' : text; return truncated.padEnd(width); }; // Build table rows const rows = []; // Header row if (headers) { const headerRow = columns .map((col) => formatCell(col.title, col.width)) .join(borders ? ' │ ' : ' '); rows.push(borders ? `│ ${headerRow} │` : headerRow); // Separator if (borders) { const separator = columns.map((col) => '─'.repeat(col.width)).join('─┼─'); rows.push(`├─${separator}─┤`); } } // Data rows tasks.forEach((task) => { const row = columns .map((col) => { let value; switch (col.key) { case 'id': value = task.id.toString(); break; case 'title': value = task.title; break; case 'status': value = task.status; break; case 'tags': value = task.tags?.join(', ') || ''; break; case 'updated': { // More compact date format const date = new Date(task.updated); const now = new Date(); const diffDays = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24)); if (diffDays === 0) { value = 'Today'; } else if (diffDays === 1) { value = 'Yesterday'; } else if (diffDays < 7) { value = `${diffDays}d ago`; } else { value = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); } break; } default: value = ''; } return formatCell(value, col.width); }) .join(borders ? ' │ ' : ' '); rows.push(borders ? `│ ${row} │` : row); }); // Top and bottom borders if (borders && rows.length > 0) { const topBorder = '┌─' + columns.map((col) => '─'.repeat(col.width)).join('─┬─') + '─┐'; const bottomBorder = '└─' + columns.map((col) => '─'.repeat(col.width)).join('─┴─') + '─┘'; rows.unshift(topBorder); rows.push(bottomBorder); } return rows.join('\n'); } /** * Formats tasks as CSV with proper escaping */ function formatAsCSV(tasks) { if (tasks.length === 0) { return ''; } // CSV headers const headers = [ 'id', 'title', 'status', 'created', 'updated', 'tags', 'dependencies', 'content' ]; // Escape CSV values according to RFC 4180 const escapeCsvValue = (value) => { const str = String(value || ''); // Always quote if contains: comma, quote, newline, or carriage return if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')) { // Escape internal quotes by doubling them return `"${str.replace(/"/g, '""')}"`; } // Quote if starts or ends with whitespace if (str !== str.trim()) { return `"${str}"`; } return str; }; // Build CSV rows const rows = [headers.join(',')]; tasks.forEach((task) => { const row = [ task.id, task.title, task.status, task.created, task.updated, task.tags?.join(';') || '', task.dependencies?.join(';') || '', task.content || '' ].map(escapeCsvValue); rows.push(row.join(',')); }); return rows.join('\n'); } /** * Main formatting function that delegates to specific formatters */ function formatTasks(tasks, format, options = {}) { switch (format) { case 'ndjson': return formatAsNDJSON(tasks); case 'json': return formatAsJSON(tasks); case 'table': return formatAsTable(tasks, options); case 'csv': return formatAsCSV(tasks); case 'yaml': return formatAsYAML(tasks); default: throw new Error(`Unknown output format: ${format}`); } } /** * Formats a single task */ function formatTask(task, format) { switch (format) { case 'yaml': return formatTaskAsYAML(task); case 'markdown': return formatTaskAsMarkdown(task); case 'json': return JSON.stringify(task, null, 2); case 'ndjson': return JSON.stringify(task); case 'table': return formatAsTable([task]); case 'csv': return formatAsCSV([task]); default: throw new Error(`Unknown output format: ${format}`); } } /** * Prints output to stdout or stderr */ function printOutput(content, toStderr = false) { if (content.length === 0) { return; // Don't print anything for empty content } if (toStderr) { process.stderr.write(content + '\n'); } else { process.stdout.write(content + '\n'); } } /** * Prints an error message to stderr */ function printError(error) { const message = typeof error === 'string' ? error : error.message; printOutput(`Error: ${message}`, true); } /** * Prints a success message to stderr */ function printSuccess(message) { printOutput(`✓ ${message}`, true); } /** * Prints a warning message to stderr */ function printWarning(message) { printOutput(`⚠ ${message}`, true); } //# sourceMappingURL=output.js.map