mlld
Version:
mlld: a modular prompt scripting language
1 lines • 20.4 kB
Source Map (JSON)
{"version":3,"sources":["../interpreter/utils/markdown-formatter.ts","../interpreter/utils/blank-line-normalizer.ts","../interpreter/output/formatter.ts"],"names":["hasWarnedAboutHanging","formatMarkdown","content","TRAILING_SPACE_MARKER","JSON_BLOCK_MARKER","JSON_BLOCK_END","jsonBlocks","protectedContent","multilineJsonRegex","replace","match","includes","index","length","push","lines","split","trailingSpaceInfo","map","line","markedContent","join","formatted","format","parser","proseWrap","printWidth","tabWidth","useTabs","trailingComma","singleQuote","endOfLine","embeddedLanguageFormatting","restoredContent","forEach","block","marker","formattedLines","restoredLines","originalIndex","Math","min","spaceCount","RegExp","repeat","process","env","MLLD_CLI_MODE","error","console","warn","normalizeOutputBlankLines","formatOutput","nodes","options","structuredMarkdown","buildStructuredMarkdown","llmxmlInstance","toXML","formatMarkdownNodes","parts","node","type","fence","lang","language","result","useFormatter","useMarkdownFormatter","shouldNormalize","normalizeBlankLines","trim","variables","size","name","variable","value","getVariableValue","isTextLike","isObject","isArray","JSON","stringify","isPath","isExecutable","definition","isImported","source","Error"],"mappings":"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","file":"chunk-S3VE3NJA.mjs","sourcesContent":["import * as prettier from 'prettier';\n\n// Track if we've warned about prettier hanging issue\nlet hasWarnedAboutHanging = false;\n\n/**\n * Format markdown content using Prettier\n * This ensures consistent formatting of the output\n */\nexport async function formatMarkdown(content: string): Promise<string> {\n try {\n // First check if the content has intentional trailing spaces\n // We'll preserve them by replacing with a placeholder\n const TRAILING_SPACE_MARKER = '⟪MLLD_TRAILING_SPACE⟫';\n const JSON_BLOCK_MARKER = '⟪MLLD_JSON_BLOCK_';\n const JSON_BLOCK_END = '⟫';\n \n // Find and protect multiline JSON blocks\n const jsonBlocks: string[] = [];\n let protectedContent = content;\n \n // Match JSON that spans multiple lines (has newlines inside)\n // Look for opening brace followed by content with newlines, then closing brace\n const multilineJsonRegex = /(\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\})/g;\n protectedContent = protectedContent.replace(multilineJsonRegex, (match) => {\n // Only protect if it looks like pretty-printed JSON (has consistent indentation)\n if (match.includes('\\n ')) {\n const index = jsonBlocks.length;\n jsonBlocks.push(match);\n return `${JSON_BLOCK_MARKER}${index}${JSON_BLOCK_END}`;\n }\n return match;\n });\n \n const lines = protectedContent.split('\\n');\n const trailingSpaceInfo = lines.map(line => {\n const match = line.match(/(\\s+)$/);\n return match ? match[1].length : 0;\n });\n \n // Replace trailing spaces with marker\n const markedContent = lines.map(line => {\n return line.replace(/\\s+$/, TRAILING_SPACE_MARKER);\n }).join('\\n');\n \n // Format with prettier using markdown parser\n const formatted = await prettier.format(markedContent, {\n parser: 'markdown',\n // Preserve existing line breaks where possible\n proseWrap: 'preserve',\n // Use 80 character line length for wrapping\n printWidth: 80,\n // Ensure consistent spacing\n tabWidth: 2,\n useTabs: false,\n // Don't add trailing commas in markdown code blocks\n trailingComma: 'none',\n // Single quotes in code blocks\n singleQuote: true,\n // Preserve empty lines\n endOfLine: 'lf',\n // Try to preserve formatting in certain contexts\n embeddedLanguageFormatting: 'auto'\n });\n \n // Restore JSON blocks\n let restoredContent = formatted;\n jsonBlocks.forEach((block, index) => {\n const marker = `${JSON_BLOCK_MARKER}${index}${JSON_BLOCK_END}`;\n restoredContent = restoredContent.replace(marker, block);\n });\n \n // Restore trailing spaces\n const formattedLines = restoredContent.split('\\n');\n const restoredLines = formattedLines.map((line, index) => {\n if (line.includes(TRAILING_SPACE_MARKER)) {\n // Find the original line index (may have changed due to formatting)\n // For now, just replace all markers with a single space if they had trailing space\n const originalIndex = Math.min(index, trailingSpaceInfo.length - 1);\n const spaceCount = trailingSpaceInfo[originalIndex] || 0;\n if (spaceCount > 0) {\n return line.replace(new RegExp(TRAILING_SPACE_MARKER, 'g'), ' '.repeat(spaceCount));\n }\n }\n return line.replace(new RegExp(TRAILING_SPACE_MARKER, 'g'), '');\n });\n \n // Add a small note about the hanging issue if in CLI mode\n if (!hasWarnedAboutHanging && process.env.MLLD_CLI_MODE === 'true') {\n hasWarnedAboutHanging = true;\n // This is a known issue with Prettier v3 - it can prevent Node.js from exiting\n // The CLI works around this with process.exit()\n }\n \n return restoredLines.join('\\n');\n } catch (error) {\n // If prettier fails, return the original content\n console.warn('Prettier formatting failed:', error);\n return content;\n }\n}\n\n/**\n * Options for markdown formatting\n */\nexport interface MarkdownFormatOptions {\n // Whether to format the output (default: true)\n enabled?: boolean;\n // Prettier options to override defaults\n prettierOptions?: prettier.Options;\n}\n\n/**\n * Format markdown with custom options\n */\nexport async function formatMarkdownWithOptions(\n content: string, \n options: MarkdownFormatOptions = {}\n): Promise<string> {\n if (options.enabled === false) {\n return content;\n }\n \n try {\n // First check if the content has intentional trailing spaces\n const TRAILING_SPACE_MARKER = '⟪MLLD_TRAILING_SPACE⟫';\n const JSON_BLOCK_MARKER = '⟪MLLD_JSON_BLOCK_';\n const JSON_BLOCK_END = '⟫';\n \n // Find and protect multiline JSON blocks\n const jsonBlocks: string[] = [];\n let protectedContent = content;\n \n // Match JSON that spans multiple lines (has newlines inside)\n // Look for opening brace followed by content with newlines, then closing brace\n const multilineJsonRegex = /(\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\})/g;\n protectedContent = protectedContent.replace(multilineJsonRegex, (match) => {\n // Only protect if it looks like pretty-printed JSON (has consistent indentation)\n if (match.includes('\\n ')) {\n const index = jsonBlocks.length;\n jsonBlocks.push(match);\n return `${JSON_BLOCK_MARKER}${index}${JSON_BLOCK_END}`;\n }\n return match;\n });\n \n const lines = protectedContent.split('\\n');\n const trailingSpaceInfo = lines.map(line => {\n const match = line.match(/(\\s+)$/);\n return match ? match[1].length : 0;\n });\n \n // Replace trailing spaces with marker\n const markedContent = lines.map(line => {\n return line.replace(/\\s+$/, TRAILING_SPACE_MARKER);\n }).join('\\n');\n \n const prettierOptions: prettier.Options = {\n parser: 'markdown',\n proseWrap: 'preserve',\n printWidth: 80,\n tabWidth: 2,\n useTabs: false,\n trailingComma: 'none',\n singleQuote: true,\n endOfLine: 'lf',\n ...options.prettierOptions\n };\n \n const formatted = await prettier.format(markedContent, prettierOptions);\n \n // Restore JSON blocks\n let restoredContent = formatted;\n jsonBlocks.forEach((block, index) => {\n const marker = `${JSON_BLOCK_MARKER}${index}${JSON_BLOCK_END}`;\n restoredContent = restoredContent.replace(marker, block);\n });\n \n // Restore trailing spaces\n const formattedLines = restoredContent.split('\\n');\n const restoredLines = formattedLines.map((line, index) => {\n if (line.includes(TRAILING_SPACE_MARKER)) {\n // Find the original line index (may have changed due to formatting)\n // For now, just replace all markers with a single space if they had trailing space\n const originalIndex = Math.min(index, trailingSpaceInfo.length - 1);\n const spaceCount = trailingSpaceInfo[originalIndex] || 0;\n if (spaceCount > 0) {\n return line.replace(new RegExp(TRAILING_SPACE_MARKER, 'g'), ' '.repeat(spaceCount));\n }\n }\n return line.replace(new RegExp(TRAILING_SPACE_MARKER, 'g'), '');\n });\n \n return restoredLines.join('\\n');\n } catch (error) {\n console.warn('Prettier formatting failed:', error);\n return content;\n }\n}","/**\n * Utilities for normalizing blank lines in mlld output\n */\n\n/**\n * Normalize blank lines between output elements.\n * \n * Rule: No more than one blank line between any output elements\n * (consecutive newlines > 2 are reduced to 2)\n * \n * @param content The content to normalize\n * @returns Normalized content\n */\nexport function normalizeOutputBlankLines(content: string): string {\n // Replace 3 or more consecutive newlines with exactly 2\n // This ensures max 1 blank line between elements\n return content.replace(/\\n{3,}/g, '\\n\\n');\n}\n\n/**\n * Normalize final output ensuring it ends with exactly one newline\n * \n * @param content The content to normalize\n * @returns Normalized content\n */\nexport function normalizeFinalOutput(content: string): string {\n // First normalize blank lines between elements\n let normalized = normalizeOutputBlankLines(content);\n \n // Ensure exactly one trailing newline if there's content\n if (normalized.length > 0) {\n normalized = normalized.replace(/\\n*$/, '\\n');\n }\n \n return normalized;\n}","import type { MlldNode } from '@core/types';\nimport type { Variable } from '@core/types/variable';\nimport { llmxmlInstance } from '../utils/llmxml-instance';\nimport { isTextLike, isObject, isArray, isPath, isExecutable, isImported } from '@core/types/variable';\nimport { formatMarkdown } from '../utils/markdown-formatter';\nimport { normalizeOutputBlankLines } from '../utils/blank-line-normalizer';\n\n/**\n * Output formatting options\n */\nexport interface FormatOptions {\n format: 'markdown' | 'xml';\n variables?: Map<string, Variable>;\n useMarkdownFormatter?: boolean; // Default: true - use prettier for formatting\n normalizeBlankLines?: boolean; // Default: true - normalize blank lines\n}\n\n/**\n * Format nodes into final output.\n * This is a simplified version - we can reuse the existing OutputService later.\n */\nexport async function formatOutput(nodes: MlldNode[], options: FormatOptions): Promise<string> {\n if (options.format === 'xml') {\n // Build structured markdown for llmxml\n const structuredMarkdown = await buildStructuredMarkdown(nodes, options);\n \n // Use llmxml to convert markdown to XML\n return await llmxmlInstance.toXML(structuredMarkdown);\n }\n \n // Default to plain markdown (just the content)\n return formatMarkdownNodes(nodes, options);\n}\n\n/**\n * Format as markdown (process all node types)\n */\nasync function formatMarkdownNodes(nodes: MlldNode[], options: FormatOptions): Promise<string> {\n const parts: string[] = [];\n \n for (const node of nodes) {\n switch (node.type) {\n case 'Text':\n // eslint-disable-next-line mlld/no-ast-string-manipulation\n parts.push(node.content);\n break;\n case 'Newline':\n parts.push('\\n');\n break;\n case 'CodeFence':\n // Reconstruct code fence with proper formatting\n const fence = '```';\n const lang = (node as any).language || '';\n const content = (node as any).content || '';\n parts.push(`${fence}${lang}\\n${content}\\n${fence}`);\n break;\n // Other node types can be added as needed\n }\n }\n \n let result = parts.join('');\n \n // Use prettier for formatting if enabled (default: true)\n const useFormatter = options.useMarkdownFormatter !== false;\n if (useFormatter) {\n result = await formatMarkdown(result);\n } else {\n // Apply the original normalization when not using prettier\n // Apply final output normalization if enabled (default: true)\n const shouldNormalize = options.normalizeBlankLines !== false;\n if (shouldNormalize) {\n // Trim leading and trailing whitespace\n result = result.trim();\n // Normalize multiple blank lines to max 2 newlines (1 blank line)\n result = normalizeOutputBlankLines(result);\n // Ensure single trailing newline if there's content\n if (result.length > 0) {\n result += '\\n';\n }\n }\n }\n \n return result;\n}\n\n/**\n * Build structured markdown for XML conversion\n */\nasync function buildStructuredMarkdown(nodes: MlldNode[], options: FormatOptions): Promise<string> {\n const parts: string[] = [];\n \n // Add document header\n parts.push('# Mlld Output');\n parts.push('');\n \n // Add variables section if we have any\n if (options.variables && options.variables.size > 0) {\n parts.push('## Variables');\n parts.push('');\n \n for (const [name, variable] of options.variables) {\n const value = getVariableValue(variable);\n parts.push(`### ${name}`);\n parts.push(`- **Type**: ${variable.type}`);\n parts.push(`- **Value**: ${value}`);\n parts.push('');\n }\n }\n \n // Add content section\n const content = await formatMarkdownNodes(nodes, options);\n if (content.trim()) {\n parts.push('## Content');\n parts.push('');\n parts.push(content);\n }\n \n return parts.join('\\n');\n}\n\n/**\n * Get string value from variable using type-safe approach\n */\nfunction getVariableValue(variable: Variable): string {\n if (isTextLike(variable)) {\n return variable.value;\n } else if (isObject(variable) || isArray(variable)) {\n return JSON.stringify(variable.value, null, 2);\n } else if (isPath(variable)) {\n return variable.value;\n } else if (isExecutable(variable)) {\n return JSON.stringify(variable.definition);\n } else if (isImported(variable)) {\n return `Imported from ${variable.source}`;\n } else {\n throw new Error(`Unknown variable type in formatter: ${(variable as any).type}`);\n }\n}\n\n"]}