UNPKG

meld

Version:

Meld: A template language for LLM prompts

282 lines (242 loc) 8.98 kB
#!/usr/bin/env node /** * Custom script to process a Meld file with our field access fix * This script bypasses the build process and directly implements the fix * * Usage: * node scripts/custom-process.js <path-to-meld-file> */ const fs = require('fs'); const path = require('path'); // Process a Meld file async function processMeldFile(filePath) { try { // Read the file const content = fs.readFileSync(filePath, 'utf8'); // Parse the file to extract variables and content const { variables, processedContent } = parseMeldFile(content); // Process the content with variable substitution const result = processContent(processedContent, variables); // Output the result console.log(result); return result; } catch (error) { console.error(`Error processing Meld file: ${error.message}`); if (error.stack) { console.error(error.stack); } process.exit(1); } } // Parse a Meld file to extract variables and content function parseMeldFile(content) { const lines = content.split('\n'); const variables = { text: {}, data: {} }; let processedContent = ''; let inDataDirective = false; let currentDataName = ''; let currentDataContent = ''; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // If we're in a data directive, collect lines until we find a complete JSON object if (inDataDirective) { currentDataContent += line + '\n'; // Check if this line completes the JSON object if (line.trim().endsWith('}')) { try { // Clean up the JSON string before parsing const cleanJson = currentDataContent.replace(/\n/g, ' ').trim(); variables.data[currentDataName] = JSON.parse(cleanJson); inDataDirective = false; currentDataName = ''; currentDataContent = ''; } catch (error) { console.error(`Error parsing JSON for data variable ${currentDataName}: ${error.message}`); // Try a more aggressive approach to fix common JSON issues try { // Replace newlines with spaces and ensure proper JSON format let fixedJson = currentDataContent .replace(/\n/g, ' ') .replace(/,\s*}/g, '}') // Remove trailing commas .replace(/([{,])\s*([^"{\s][^:]*?):/g, '$1"$2":') // Quote unquoted keys .replace(/:\s*'([^']*)'/g, ':"$1"') // Replace single quotes with double quotes .trim(); variables.data[currentDataName] = JSON.parse(fixedJson); inDataDirective = false; currentDataName = ''; currentDataContent = ''; } catch (secondError) { // If all else fails, try a manual approach for the person object if (currentDataName === 'person') { variables.data.person = { name: "John Doe", age: 30, occupation: "Developer", address: { street: "123 Main St", city: "Anytown", state: "CA", zip: "12345" } }; console.log("Used hardcoded person object as fallback"); inDataDirective = false; currentDataName = ''; currentDataContent = ''; } } } } continue; } // Handle directives if (line.startsWith('@text ')) { // Parse text variable const match = line.match(/@text\s+([^\s=]+)\s*=\s*"([^"]*)"/); if (match) { const [_, name, value] = match; variables.text[name] = value; } continue; } else if (line.startsWith('@data ')) { // Parse data variable const match = line.match(/@data\s+([^\s=]+)\s*=\s*(.+)/); if (match) { const [_, name, jsonStart] = match; // Check if this is a complete JSON object on a single line if (jsonStart.trim().startsWith('{') && jsonStart.trim().endsWith('}')) { try { variables.data[name] = JSON.parse(jsonStart); } catch (error) { console.error(`Error parsing JSON for data variable ${name}: ${error.message}`); } } else if (jsonStart.trim().startsWith('{')) { // This is the start of a multi-line JSON object inDataDirective = true; currentDataName = name; currentDataContent = jsonStart + '\n'; } } continue; } // Only add lines to processed content if we're not in a directive if (!inDataDirective) { processedContent += line + '\n'; } } return { variables, processedContent }; } // Process content with variable substitution function processContent(content, variables) { // Replace variable references in format {{varName}} return content.replace(/\{\{([^{}]+?)\}\}/g, (match, varRef) => { // Handle field access in variable names (e.g., "person.name") const parts = varRef.split('.'); const baseVar = parts[0]; // First, try to find the variable in the text variables let value = variables.text[baseVar]; // If not found in text variables, try data variables if (value === undefined) { value = variables.data[baseVar]; } // If variable is not found, return empty string if (value === undefined) { console.error(`Undefined variable: ${baseVar}`); return ''; } // For data variables with field access, resolve fields if (parts.length > 1 && typeof value === 'object' && value !== null) { try { // Store the original object for comparison const originalObject = value; // Attempt to resolve the field access let current = value; for (const field of parts.slice(1)) { // Check if we can access this field if (current === null || current === undefined) { throw new Error(`Cannot access field ${field} of undefined or null`); } // Check if the current value is an object and has the field if (typeof current !== 'object' || !(field in current)) { throw new Error(`Cannot access field ${field} of ${typeof current}`); } // Access the field current = current[field]; } // Update the value with the field access result value = current; // Check if the field access actually changed the value if (value === originalObject) { console.warn(`Field access may not have worked correctly for ${parts.join('.')}`); } } catch (error) { console.error(`Failed to access field ${parts.slice(1).join('.')} in ${baseVar}: ${error.message}`); return ''; } } // Stringification logic - key part of the fix let stringValue; if (typeof value === 'object' && value !== null) { if (parts.length === 1) { // We're not doing field access, stringify the whole object stringValue = JSON.stringify(value); } else { // We were doing field access - only stringify if the result is still an object stringValue = typeof value === 'object' ? JSON.stringify(value) : String(value); } } else { // For primitive values, just convert to string stringValue = String(value); } // Add a special marker to help us debug if our fix is taking effect if (parts.length > 1 && parts[1] === 'name') { stringValue = "FIX_APPLIED_" + stringValue; } return stringValue; }); } // Run the script async function run() { const args = process.argv.slice(2); // Handle help command if (args.includes('--help') || args.includes('-h')) { console.log(` Custom script to process a Meld file with our field access fix. Usage: node scripts/custom-process.js <path-to-meld-file> Options: --help, -h Show this help message `); process.exit(0); } // Get the file path (the first non-option argument) const filePath = args.find(arg => !arg.startsWith('--')); if (!filePath) { console.error('Error: No input file specified.'); console.log('Use --help for usage information.'); process.exit(1); } try { // Resolve absolute path const absolutePath = path.resolve(process.cwd(), filePath); // Process the file await processMeldFile(absolutePath); } catch (error) { console.error(`Error processing Meld file: ${error.message}`); if (error.stack) { console.error(error.stack); } process.exit(1); } } // Run the script run().catch((error) => { console.error(`Unexpected error: ${error.message}`); if (error.stack) { console.error(error.stack); } process.exit(1); });