@bernierllc/email-template-engine
Version:
Email template processing engine with compilation, rendering, and text generation
75 lines (67 loc) • 2.34 kB
text/typescript
/*
Copyright (c) 2025 Bernier LLC
This file is licensed to the client under a limited-use license.
The client may use and modify this code *only within the scope of the project it was delivered for*.
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
*/
/**
* Check if template has balanced braces
*/
export function checkBalancedBraces(template: string): { balanced: boolean; position?: number } {
let count = 0;
for (let i = 0; i < template.length; i++) {
if (template[i] === '{' && template[i + 1] === '{') {
count++;
i++; // Skip next brace
} else if (template[i] === '}' && template[i + 1] === '}') {
count--;
i++; // Skip next brace
if (count < 0) {
return { balanced: false, position: i };
}
}
}
return { balanced: count === 0, position: count !== 0 ? template.length : undefined };
}
/**
* Check for unclosed block helpers
*/
export function checkUnclosedBlocks(template: string): string[] {
const unclosed: string[] = [];
const blockStack: string[] = [];
const blockRegex = /\{\{#(\w+).*?\}\}|\{\{\/(\w+)\}\}/g;
let match;
while ((match = blockRegex.exec(template)) !== null) {
if (match[1]) {
// Opening block
blockStack.push(match[1]);
} else if (match[2]) {
// Closing block
const last = blockStack.pop();
if (last !== match[2]) {
unclosed.push(match[2]);
}
}
}
// Any remaining blocks are unclosed
return [...unclosed, ...blockStack];
}
/**
* Check if a variable name is a built-in Handlebars helper
*/
export function isBuiltInHelper(variable: string): boolean {
const builtIns = ['if', 'unless', 'each', 'with', 'log', 'lookup', 'else'];
const firstWord = variable.split(/[\s.]/)[0];
return builtIns.includes(firstWord);
}
/**
* Infer variable type from path
*/
export function inferVariableType(path: string): 'string' | 'number' | 'boolean' | 'object' | 'array' {
// Simple heuristic based on variable name
if (path.includes('.length') || path.includes('.count')) return 'number';
if (path.includes('.is') || path.includes('.has')) return 'boolean';
if (path.includes('.items') || path.includes('.list')) return 'array';
if (path.includes('.')) return 'object';
return 'string';
}