Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | 2x 2x 2x 2x 2x 82x 4x 78x 78x 78x 22x 6x 16x 16x 16x 31x 7x 10x 16x 16x 14x 14x 16x 59x 58x 1x 16x 7x 7x 7x 1x 1x | /**
* Simple template renderer for validation reports
* Supports {{placeholder}} for escaped values and {{{placeholder}}} for raw HTML
*/
import fs from 'fs-extra';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Paths to assets
const stylesDir = path.resolve(__dirname, '../styles');
const templatesDir = path.resolve(__dirname, '../templates');
// Cache for loaded files
const cache = new Map();
/**
* Escape HTML special characters
* @param {string} text - Text to escape
* @returns {string} Escaped text
*/
export function escapeHtml(text) {
if (text === null || text === undefined) {
return '';
}
const str = String(text);
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
};
return str.replace(/[&<>"']/g, (m) => map[m]);
}
/**
* Load a file from disk (with caching)
* @param {string} filePath - Path to file
* @returns {string} File contents
*/
export function loadFile(filePath) {
if (cache.has(filePath)) {
return cache.get(filePath);
}
const content = fs.readFileSync(filePath, 'utf8');
cache.set(filePath, content);
return content;
}
/**
* Clear the file cache (useful for testing)
*/
export function clearCache() {
cache.clear();
}
/**
* Load the shared CSS styles
* @returns {string} CSS content
*/
export function loadStyles() {
return loadFile(path.join(stylesDir, 'report.css'));
}
/**
* Load a template file
* @param {string} templateName - Template filename (e.g., 'index.html')
* @returns {string} Template content
*/
export function loadTemplate(templateName) {
return loadFile(path.join(templatesDir, templateName));
}
/**
* Render a template with data
*
* Supports two placeholder syntaxes:
* - {{key}} - HTML-escaped value
* - {{{key}}} - Raw HTML (unescaped)
*
* @param {string} template - Template string
* @param {Object} data - Data object with values
* @returns {string} Rendered HTML
*/
export function render(template, data) {
let result = template;
// First, handle raw/unescaped placeholders {{{key}}}
result = result.replace(/\{\{\{(\w+)\}\}\}/g, (match, key) => {
Eif (key in data) {
return String(data[key] ?? '');
}
return match; // Keep placeholder if key not found
});
// Then, handle escaped placeholders {{key}}
result = result.replace(/\{\{(\w+)\}\}/g, (match, key) => {
if (key in data) {
return escapeHtml(data[key]);
}
return match; // Keep placeholder if key not found
});
return result;
}
/**
* Load and render a template with data
* Automatically injects styles if {{{styles}}} placeholder exists
*
* @param {string} templateName - Template filename
* @param {Object} data - Data object with values
* @returns {string} Rendered HTML
*/
export function renderTemplate(templateName, data) {
const template = loadTemplate(templateName);
// Inject styles if needed
const dataWithStyles = {
...data,
styles: data.styles ?? loadStyles(),
};
return render(template, dataWithStyles);
}
/**
* Get the templates directory path
* @returns {string} Templates directory path
*/
export function getTemplatesDir() {
return templatesDir;
}
/**
* Get the styles directory path
* @returns {string} Styles directory path
*/
export function getStylesDir() {
return stylesDir;
}
|