@ideascol/cli-maker
Version:
A simple library to help create CLIs
137 lines (136 loc) • 6.27 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.renderMarkdown = renderMarkdown;
const colors_1 = require("../colors");
/**
* Render a markdown string to terminal-formatted output.
* Supports: headers, bold, italic, inline code, fenced code blocks, lists, and horizontal rules.
*/
function renderMarkdown(md) {
const lines = md.split('\n');
const output = [];
let inCodeBlock = false;
let codeBlockLang = '';
let codeLines = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Fenced code block toggle
if (line.trimStart().startsWith('```')) {
if (!inCodeBlock) {
inCodeBlock = true;
codeBlockLang = line.trimStart().slice(3).trim();
codeLines = [];
continue;
}
else {
// End of code block — render it
const header = codeBlockLang
? `${colors_1.Colors.FgGray}┌─ ${codeBlockLang} ${'─'.repeat(Math.max(0, 40 - codeBlockLang.length))}┐${colors_1.Colors.Reset}`
: `${colors_1.Colors.FgGray}┌${'─'.repeat(44)}┐${colors_1.Colors.Reset}`;
output.push(header);
for (const cl of codeLines) {
output.push(`${colors_1.Colors.FgGray}│${colors_1.Colors.Reset} ${colors_1.Colors.FgCyan}${cl}${colors_1.Colors.Reset}`);
}
const footer = `${colors_1.Colors.FgGray}└${'─'.repeat(44)}┘${colors_1.Colors.Reset}`;
output.push(footer);
inCodeBlock = false;
codeBlockLang = '';
codeLines = [];
continue;
}
}
if (inCodeBlock) {
codeLines.push(line);
continue;
}
// Horizontal rule
if (/^(\s*[-*_]\s*){3,}$/.test(line)) {
output.push(`${colors_1.Colors.FgGray}${'─'.repeat(46)}${colors_1.Colors.Reset}`);
continue;
}
// Headers
const headerMatch = line.match(/^(#{1,6})\s+(.*)$/);
if (headerMatch) {
const level = headerMatch[1].length;
const text = formatInline(headerMatch[2]);
if (level === 1) {
output.push(`\n${colors_1.Colors.Bright}${colors_1.Colors.FgCyan}${text}${colors_1.Colors.Reset}`);
output.push(`${colors_1.Colors.FgGray}${'═'.repeat(stripAnsi(text).length)}${colors_1.Colors.Reset}`);
}
else if (level === 2) {
output.push(`\n${colors_1.Colors.Bright}${colors_1.Colors.FgGreen}${text}${colors_1.Colors.Reset}`);
output.push(`${colors_1.Colors.FgGray}${'─'.repeat(stripAnsi(text).length)}${colors_1.Colors.Reset}`);
}
else {
output.push(`\n${colors_1.Colors.Bright}${colors_1.Colors.FgYellow}${text}${colors_1.Colors.Reset}`);
}
continue;
}
// Unordered list
const ulMatch = line.match(/^(\s*)([-*+])\s+(.*)$/);
if (ulMatch) {
const indent = ulMatch[1];
const text = formatInline(ulMatch[3]);
output.push(`${indent}${colors_1.Colors.FgCyan}•${colors_1.Colors.Reset} ${text}`);
continue;
}
// Ordered list
const olMatch = line.match(/^(\s*)(\d+)\.\s+(.*)$/);
if (olMatch) {
const indent = olMatch[1];
const num = olMatch[2];
const text = formatInline(olMatch[3]);
output.push(`${indent}${colors_1.Colors.FgCyan}${num}.${colors_1.Colors.Reset} ${text}`);
continue;
}
// Blockquote
const bqMatch = line.match(/^>\s?(.*)$/);
if (bqMatch) {
const text = formatInline(bqMatch[1]);
output.push(`${colors_1.Colors.FgGray}│${colors_1.Colors.Reset} ${colors_1.Colors.Dim}${text}${colors_1.Colors.Reset}`);
continue;
}
// Regular paragraph line
output.push(formatInline(line));
}
// If we ended while still inside a code block, flush it
if (inCodeBlock && codeLines.length > 0) {
const header = codeBlockLang
? `${colors_1.Colors.FgGray}┌─ ${codeBlockLang} ${'─'.repeat(Math.max(0, 40 - codeBlockLang.length))}┐${colors_1.Colors.Reset}`
: `${colors_1.Colors.FgGray}┌${'─'.repeat(44)}┐${colors_1.Colors.Reset}`;
output.push(header);
for (const cl of codeLines) {
output.push(`${colors_1.Colors.FgGray}│${colors_1.Colors.Reset} ${colors_1.Colors.FgCyan}${cl}${colors_1.Colors.Reset}`);
}
const footer = `${colors_1.Colors.FgGray}└${'─'.repeat(44)}┘${colors_1.Colors.Reset}`;
output.push(footer);
}
return output.join('\n');
}
/**
* Format inline markdown: bold, italic, inline code, strikethrough, links.
*/
function formatInline(text) {
// Inline code (must come before bold/italic to avoid conflicts)
text = text.replace(/`([^`]+)`/g, `${colors_1.Colors.FgCyan}$1${colors_1.Colors.Reset}`);
// Bold + italic
text = text.replace(/\*\*\*(.+?)\*\*\*/g, `${colors_1.Colors.Bright}${colors_1.Colors.Dim}$1${colors_1.Colors.Reset}`);
// Bold
text = text.replace(/\*\*(.+?)\*\*/g, `${colors_1.Colors.Bright}$1${colors_1.Colors.Reset}`);
text = text.replace(/__(.+?)__/g, `${colors_1.Colors.Bright}$1${colors_1.Colors.Reset}`);
// Italic
text = text.replace(/\*(.+?)\*/g, `${colors_1.Colors.Dim}$1${colors_1.Colors.Reset}`);
text = text.replace(/_(.+?)_/g, `${colors_1.Colors.Dim}$1${colors_1.Colors.Reset}`);
// Strikethrough
text = text.replace(/~~(.+?)~~/g, `${colors_1.Colors.FgGray}$1${colors_1.Colors.Reset}`);
// Links [text](url)
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, `${colors_1.Colors.FgBlue}${colors_1.Colors.Underscore}$1${colors_1.Colors.Reset} ${colors_1.Colors.FgGray}($2)${colors_1.Colors.Reset}`);
return text;
}
/**
* Strip ANSI escape codes from a string (for length calculations).
*/
function stripAnsi(str) {
// biome-ignore lint: safe regex for ANSI codes
return str.replace(/\u001b\[[0-9;]*[mG]/g, '');
}