UNPKG

hana-cli

Version:
264 lines 8.61 kB
/** * Output formatter for hana-cli MCP server * Improves readability of command outputs */ /** * Parse ASCII table output from hana-cli */ function parseAsciiTable(output) { const lines = output.split('\n').filter(line => line.trim()); // Find header row (contains │ separators) const headerIndex = lines.findIndex(line => line.includes('│') && !line.startsWith('╭') && !line.startsWith('├')); if (headerIndex === -1) return null; const headerLine = lines[headerIndex]; // Split by │ and drop the leading/trailing empty segments from outer borders const headers = headerLine .split('│') .slice(1, -1) .map(h => h.trim()); if (headers.length === 0) return null; // Parse data rows const rows = []; for (let i = headerIndex + 1; i < lines.length; i++) { const line = lines[i]; if (line.startsWith('├') || line.startsWith('╰')) continue; if (!line.includes('│')) continue; const cells = line .split('│') .slice(1, -1) .map(c => c.trim()); if (cells.length === headers.length) { rows.push(cells); } } return { headers, rows }; } /** * Shorten schema names (UUIDs) to last 6 characters */ function shortenSchemaName(schema) { if (schema.length > 32 && schema.match(/^[A-F0-9]+$/)) { return '...' + schema.slice(-6); } return schema; } /** * Group tables by prefix */ function groupTables(tables) { const groups = new Map(); for (const table of tables) { const prefix = table.split('_')[0]; if (!groups.has(prefix)) { groups.set(prefix, []); } groups.get(prefix).push(table); } return groups; } /** * Format table data as markdown */ function formatAsMarkdown(data) { const { headers, rows } = data; // Calculate column widths const widths = headers.map((h, i) => { // Handle empty rows case: Math.max() with empty array returns -Infinity const cellLengths = rows.map(r => (r[i] || '').length); const maxCellWidth = cellLengths.length > 0 ? Math.max(...cellLengths) : 0; return Math.max(h.length, maxCellWidth); }); // Build markdown table let output = '\n| ' + headers.map((h, i) => h.padEnd(widths[i])).join(' | ') + ' |\n'; output += '| ' + widths.map(w => '-'.repeat(w)).join(' | ') + ' |\n'; for (const row of rows) { output += '| ' + row.map((cell, i) => (cell || '').padEnd(widths[i] || 0)).join(' | ') + ' |\n'; } return output; } /** * Format tables command output */ function formatTablesOutput(output) { const table = parseAsciiTable(output); if (!table) return output; const { headers, rows } = table; // Find schema and table name columns const schemaIdx = headers.findIndex(h => h.includes('SCHEMA')); const tableIdx = headers.findIndex(h => h.includes('TABLE_NAME')); if (schemaIdx === -1 || tableIdx === -1) return output; // Process rows const processedRows = rows.map(row => { const newRow = [...row]; if (schemaIdx < newRow.length) { newRow[schemaIdx] = shortenSchemaName(newRow[schemaIdx]); } return newRow; }); // Group tables const tableNames = processedRows.map(r => r[tableIdx]); const groups = groupTables(tableNames); // Build formatted output let result = '\n## Database Tables\n\n'; result += `**Total Tables:** ${rows.length}\n\n`; // Show table groups result += '### Table Groups:\n'; for (const [prefix, tables] of Array.from(groups.entries()).sort()) { result += `- **${prefix}**: ${tables.length} tables\n`; } result += '\n'; // Show formatted table result += '### Table Details:\n'; result += formatAsMarkdown({ headers, rows: processedRows }); return result; } /** * Format schemas command output */ function formatSchemasOutput(output) { const table = parseAsciiTable(output); if (!table) return output; const { headers, rows } = table; // Shorten schema names const processedRows = rows.map(row => { return row.map((cell, idx) => { if (headers[idx]?.includes('SCHEMA')) { return shortenSchemaName(cell); } return cell; }); }); let result = '\n## Database Schemas\n\n'; result += `**Total Schemas:** ${rows.length}\n\n`; result += formatAsMarkdown({ headers, rows: processedRows }); return result; } /** * Format views command output */ function formatViewsOutput(output) { const table = parseAsciiTable(output); if (!table) return output; const { headers, rows } = table; const schemaIdx = headers.findIndex(h => h.includes('SCHEMA')); const processedRows = rows.map(row => { const newRow = [...row]; if (schemaIdx >= 0 && schemaIdx < newRow.length) { newRow[schemaIdx] = shortenSchemaName(newRow[schemaIdx]); } return newRow; }); let result = '\n## Database Views\n\n'; result += `**Total Views:** ${rows.length}\n\n`; result += formatAsMarkdown({ headers, rows: processedRows }); return result; } /** * Format procedures command output */ function formatProceduresOutput(output) { const table = parseAsciiTable(output); if (!table) return output; const { headers, rows } = table; const schemaIdx = headers.findIndex(h => h.includes('SCHEMA')); const processedRows = rows.map(row => { const newRow = [...row]; if (schemaIdx >= 0 && schemaIdx < newRow.length) { newRow[schemaIdx] = shortenSchemaName(newRow[schemaIdx]); } return newRow; }); let result = '\n## Stored Procedures\n\n'; result += `**Total Procedures:** ${rows.length}\n\n`; result += formatAsMarkdown({ headers, rows: processedRows }); return result; } /** * Format functions command output */ function formatFunctionsOutput(output) { const table = parseAsciiTable(output); if (!table) return output; const { headers, rows } = table; const schemaIdx = headers.findIndex(h => h.includes('SCHEMA')); const processedRows = rows.map(row => { const newRow = [...row]; if (schemaIdx >= 0 && schemaIdx < newRow.length) { newRow[schemaIdx] = shortenSchemaName(newRow[schemaIdx]); } return newRow; }); let result = '\n## Database Functions\n\n'; result += `**Total Functions:** ${rows.length}\n\n`; result += formatAsMarkdown({ headers, rows: processedRows }); return result; } /** * Format system info output */ function formatSystemInfoOutput(output) { const table = parseAsciiTable(output); if (!table) return output; const { headers, rows } = table; let result = '\n## System Information\n\n'; // Convert to key-value format for better readability for (let i = 0; i < headers.length; i++) { result += `**${headers[i]}:** ${rows[0]?.[i] || 'N/A'}\n`; } return result; } /** * Format containers output */ function formatContainersOutput(output) { const table = parseAsciiTable(output); if (!table) return output; const { headers, rows } = table; let result = '\n## HDI Containers\n\n'; result += `**Total Containers:** ${rows.length}\n\n`; result += formatAsMarkdown({ headers, rows }); return result; } const COMMAND_FORMATTERS = { tables: formatTablesOutput, schemas: formatSchemasOutput, views: formatViewsOutput, procedures: formatProceduresOutput, functions: formatFunctionsOutput, systemInfo: formatSystemInfoOutput, sysInfo: formatSystemInfoOutput, containers: formatContainersOutput, }; /** * Main formatter function */ export function formatOutput(command, output) { const cleanOutput = output.replace(/Using Connection Configuration.*\n\n?/, ''); const formatter = COMMAND_FORMATTERS[command]; if (formatter) { return formatter(cleanOutput); } // For other commands, try generic table formatting const table = parseAsciiTable(cleanOutput); if (table && table.rows.length > 0) { let result = '\n## Command Output\n\n'; result += `**Total Rows:** ${table.rows.length}\n\n`; result += formatAsMarkdown(table); return result; } // Return original output if no formatting applied return output; } //# sourceMappingURL=output-formatter.js.map