meld
Version:
Meld: A template language for LLM prompts
146 lines (123 loc) • 4.37 kB
text/typescript
/**
* Simple API for processing meld content directly
*/
import { createDefaultServices } from './index.js';
import { NodeFileSystem } from '@services/fs/FileSystemService/NodeFileSystem.js';
import { ProcessOptions } from '@core/types/index.js';
import { validateServicePipeline } from '@core/utils/serviceValidation.js';
import { MemoryFileSystem } from '@tests/utils/MemoryFileSystem.js';
// Export the MemoryFileSystem for users who want to use it
export { MemoryFileSystem };
/**
* Process meld content directly from a string
*
* @param content - The meld content to process
* @param options - Optional processing options
* @returns Processed content as a string
*
* @example
* ```typescript
* import { runMeld } from 'meld';
*
* const meldContent = `
* @text greeting = "Hello"
* @text name = "World"
*
* ${greeting}, ${name}!
* `;
*
* const result = await runMeld(meldContent);
* console.log(result); // "Hello, World!"
* ```
*/
export async function runMeld(
content: string,
options: Partial<ProcessOptions> = {}
): Promise<string> {
// Create a virtual file path
const virtualFilePath = '/virtual-file.mld';
// Create an in-memory file system
const memoryFS = new MemoryFileSystem();
// Store the content in the virtual file
await memoryFS.writeFile(virtualFilePath, content);
// Default options
const defaultOptions: ProcessOptions = {
format: 'markdown',
transformation: true,
fs: memoryFS
};
// Merge options
const mergedOptions: ProcessOptions = { ...defaultOptions, ...options };
// Always use the memory filesystem
mergedOptions.fs = memoryFS;
// Create services
const services = createDefaultServices(mergedOptions);
// Validate services
validateServicePipeline(services);
try {
// Read the file (from memory)
const content = await services.filesystem.readFile(virtualFilePath);
// Parse the content
const ast = await services.parser.parse(content, virtualFilePath);
// Enable transformation if requested
if (mergedOptions.transformation) {
// If transformation is a boolean, use the legacy all-or-nothing approach
// If it's an object with options, use selective transformation
services.state.enableTransformation(mergedOptions.transformation);
}
// Interpret the AST
const resultState = await services.interpreter.interpret(ast, {
filePath: virtualFilePath,
initialState: services.state,
strict: true
});
// Get nodes to process (transformed if transformation is enabled)
const nodesToProcess = resultState.isTransformationEnabled() && resultState.getTransformedNodes()
? resultState.getTransformedNodes()
: ast;
// Make sure format is properly set (normalize 'md' to 'markdown', etc.)
const outputFormat = normalizeFormat(mergedOptions.format || 'markdown');
// Convert to desired format
let converted = await services.output.convert(nodesToProcess, resultState, outputFormat);
// Post-process the output in transformation mode
if (resultState.isTransformationEnabled()) {
// Fix newlines in variable output
converted = converted
// Replace multiple newlines with a single newline
.replace(/\n{2,}/g, '\n')
// Common pattern fixes from the main function
.replace(/(\w+):\n(\w+)/g, '$1: $2')
.replace(/(\w+),\n(\w+)/g, '$1, $2')
.replace(/(\w+):\n{/g, '$1: {')
.replace(/},\n(\w+):/g, '}, $1:');
}
return converted;
} catch (error) {
// Rethrow with a clearer message for API usage
if (error instanceof Error) {
throw new Error(`Error processing meld content: ${error.message}`);
}
// For non-Error objects, convert to string
throw new Error(`Error processing meld content: ${String(error)}`);
}
}
/**
* Normalize format string to supported format
*/
function normalizeFormat(format: string): 'markdown' | 'xml' {
// Normalize format aliases
if (format === 'md') {
return 'markdown';
}
if (format === 'llmxml') {
return 'xml';
}
// Ensure 'xml' is properly handled
if (format === 'xml') {
return 'xml';
}
// Default to markdown for unsupported formats
return 'markdown';
}
// Default export for ease of use
export default runMeld;