Files
documentation/node_modules/eta/dist/eta.module.mjs
2024-03-22 03:47:51 +05:30

890 lines
28 KiB
JavaScript

import { existsSync, readFileSync } from 'fs';
import * as path from 'path';
function setPrototypeOf(obj, proto) {
// eslint-disable-line @typescript-eslint/no-explicit-any
if (Object.setPrototypeOf) {
Object.setPrototypeOf(obj, proto);
} else {
obj.__proto__ = proto;
}
}
// This is pretty much the only way to get nice, extended Errors
// without using ES6
/**
* This returns a new Error with a custom prototype. Note that it's _not_ a constructor
*
* @param message Error message
*
* **Example**
*
* ```js
* throw EtaErr("template not found")
* ```
*/
function EtaErr(message) {
const err = new Error(message);
setPrototypeOf(err, EtaErr.prototype);
return err;
}
EtaErr.prototype = Object.create(Error.prototype, {
name: {
value: "Eta Error",
enumerable: false
}
});
/**
* Throws an EtaErr with a nicely formatted error and message showing where in the template the error occurred.
*/
function ParseErr(message, str, indx) {
const whitespace = str.slice(0, indx).split(/\n/);
const lineNo = whitespace.length;
const colNo = whitespace[lineNo - 1].length + 1;
message += " at line " + lineNo + " col " + colNo + ":\n\n" + " " + str.split(/\n/)[lineNo - 1] + "\n" + " " + Array(colNo).join(" ") + "^";
throw EtaErr(message);
}
/**
* @returns The global Promise function
*/
const promiseImpl = new Function("return this")().Promise;
/**
* @returns A new AsyncFunction constuctor
*/
function getAsyncFunctionConstructor() {
try {
return new Function("return (async function(){}).constructor")();
} catch (e) {
if (e instanceof SyntaxError) {
throw EtaErr("This environment doesn't support async/await");
} else {
throw e;
}
}
}
/**
* str.trimLeft polyfill
*
* @param str - Input string
* @returns The string with left whitespace removed
*
*/
function trimLeft(str) {
// eslint-disable-next-line no-extra-boolean-cast
if (!!String.prototype.trimLeft) {
return str.trimLeft();
} else {
return str.replace(/^\s+/, "");
}
}
/**
* str.trimRight polyfill
*
* @param str - Input string
* @returns The string with right whitespace removed
*
*/
function trimRight(str) {
// eslint-disable-next-line no-extra-boolean-cast
if (!!String.prototype.trimRight) {
return str.trimRight();
} else {
return str.replace(/\s+$/, ""); // TODO: do we really need to replace BOM's?
}
}
// TODO: allow '-' to trim up until newline. Use [^\S\n\r] instead of \s
/* END TYPES */
function hasOwnProp(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
function copyProps(toObj, fromObj) {
for (const key in fromObj) {
if (hasOwnProp(fromObj, key)) {
toObj[key] = fromObj[key];
}
}
return toObj;
}
/**
* Takes a string within a template and trims it, based on the preceding tag's whitespace control and `config.autoTrim`
*/
function trimWS(str, config, wsLeft, wsRight) {
let leftTrim;
let rightTrim;
if (Array.isArray(config.autoTrim)) {
// kinda confusing
// but _}} will trim the left side of the following string
leftTrim = config.autoTrim[1];
rightTrim = config.autoTrim[0];
} else {
leftTrim = rightTrim = config.autoTrim;
}
if (wsLeft || wsLeft === false) {
leftTrim = wsLeft;
}
if (wsRight || wsRight === false) {
rightTrim = wsRight;
}
if (!rightTrim && !leftTrim) {
return str;
}
if (leftTrim === "slurp" && rightTrim === "slurp") {
return str.trim();
}
if (leftTrim === "_" || leftTrim === "slurp") {
// console.log('trimming left' + leftTrim)
// full slurp
str = trimLeft(str);
} else if (leftTrim === "-" || leftTrim === "nl") {
// nl trim
str = str.replace(/^(?:\r\n|\n|\r)/, "");
}
if (rightTrim === "_" || rightTrim === "slurp") {
// full slurp
str = trimRight(str);
} else if (rightTrim === "-" || rightTrim === "nl") {
// nl trim
str = str.replace(/(?:\r\n|\n|\r)$/, ""); // TODO: make sure this gets \r\n
}
return str;
}
/**
* A map of special HTML characters to their XML-escaped equivalents
*/
const escMap = {
"&": "&",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;"
};
function replaceChar(s) {
return escMap[s];
}
/**
* XML-escapes an input value after converting it to a string
*
* @param str - Input value (usually a string)
* @returns XML-escaped string
*/
function XMLEscape(str) {
// eslint-disable-line @typescript-eslint/no-explicit-any
// To deal with XSS. Based on Escape implementations of Mustache.JS and Marko, then customized.
const newStr = String(str);
if (/[&<>"']/.test(newStr)) {
return newStr.replace(/[&<>"']/g, replaceChar);
} else {
return newStr;
}
}
/* END TYPES */
const templateLitReg = /`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})*}|(?!\${)[^\\`])*`/g;
const singleQuoteReg = /'(?:\\[\s\w"'\\`]|[^\n\r'\\])*?'/g;
const doubleQuoteReg = /"(?:\\[\s\w"'\\`]|[^\n\r"\\])*?"/g;
/** Escape special regular expression characters inside a string */
function escapeRegExp(string) {
// From MDN
return string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}
function parse(str, config) {
let buffer = [];
let trimLeftOfNextStr = false;
let lastIndex = 0;
const parseOptions = config.parse;
if (config.plugins) {
for (let i = 0; i < config.plugins.length; i++) {
const plugin = config.plugins[i];
if (plugin.processTemplate) {
str = plugin.processTemplate(str, config);
}
}
}
/* Adding for EJS compatibility */
if (config.rmWhitespace) {
// Code taken directly from EJS
// Have to use two separate replaces here as `^` and `$` operators don't
// work well with `\r` and empty lines don't work well with the `m` flag.
// Essentially, this replaces the whitespace at the beginning and end of
// each line and removes multiple newlines.
str = str.replace(/[\r\n]+/g, "\n").replace(/^\s+|\s+$/gm, "");
}
/* End rmWhitespace option */
templateLitReg.lastIndex = 0;
singleQuoteReg.lastIndex = 0;
doubleQuoteReg.lastIndex = 0;
function pushString(strng, shouldTrimRightOfString) {
if (strng) {
// if string is truthy it must be of type 'string'
strng = trimWS(strng, config, trimLeftOfNextStr,
// this will only be false on the first str, the next ones will be null or undefined
shouldTrimRightOfString);
if (strng) {
// replace \ with \\, ' with \'
// we're going to convert all CRLF to LF so it doesn't take more than one replace
strng = strng.replace(/\\|'/g, "\\$&").replace(/\r\n|\n|\r/g, "\\n");
buffer.push(strng);
}
}
}
const prefixes = [parseOptions.exec, parseOptions.interpolate, parseOptions.raw].reduce(function (accumulator, prefix) {
if (accumulator && prefix) {
return accumulator + "|" + escapeRegExp(prefix);
} else if (prefix) {
// accumulator is falsy
return escapeRegExp(prefix);
} else {
// prefix and accumulator are both falsy
return accumulator;
}
}, "");
const parseOpenReg = new RegExp(escapeRegExp(config.tags[0]) + "(-|_)?\\s*(" + prefixes + ")?\\s*", "g");
const parseCloseReg = new RegExp("'|\"|`|\\/\\*|(\\s*(-|_)?" + escapeRegExp(config.tags[1]) + ")", "g");
// TODO: benchmark having the \s* on either side vs using str.trim()
let m;
while (m = parseOpenReg.exec(str)) {
const precedingString = str.slice(lastIndex, m.index);
lastIndex = m[0].length + m.index;
const wsLeft = m[1];
const prefix = m[2] || ""; // by default either ~, =, or empty
pushString(precedingString, wsLeft);
parseCloseReg.lastIndex = lastIndex;
let closeTag;
let currentObj = false;
while (closeTag = parseCloseReg.exec(str)) {
if (closeTag[1]) {
const content = str.slice(lastIndex, closeTag.index);
parseOpenReg.lastIndex = lastIndex = parseCloseReg.lastIndex;
trimLeftOfNextStr = closeTag[2];
const currentType = prefix === parseOptions.exec ? "e" : prefix === parseOptions.raw ? "r" : prefix === parseOptions.interpolate ? "i" : "";
currentObj = {
t: currentType,
val: content
};
break;
} else {
const char = closeTag[0];
if (char === "/*") {
const commentCloseInd = str.indexOf("*/", parseCloseReg.lastIndex);
if (commentCloseInd === -1) {
ParseErr("unclosed comment", str, closeTag.index);
}
parseCloseReg.lastIndex = commentCloseInd;
} else if (char === "'") {
singleQuoteReg.lastIndex = closeTag.index;
const singleQuoteMatch = singleQuoteReg.exec(str);
if (singleQuoteMatch) {
parseCloseReg.lastIndex = singleQuoteReg.lastIndex;
} else {
ParseErr("unclosed string", str, closeTag.index);
}
} else if (char === '"') {
doubleQuoteReg.lastIndex = closeTag.index;
const doubleQuoteMatch = doubleQuoteReg.exec(str);
if (doubleQuoteMatch) {
parseCloseReg.lastIndex = doubleQuoteReg.lastIndex;
} else {
ParseErr("unclosed string", str, closeTag.index);
}
} else if (char === "`") {
templateLitReg.lastIndex = closeTag.index;
const templateLitMatch = templateLitReg.exec(str);
if (templateLitMatch) {
parseCloseReg.lastIndex = templateLitReg.lastIndex;
} else {
ParseErr("unclosed string", str, closeTag.index);
}
}
}
}
if (currentObj) {
buffer.push(currentObj);
} else {
ParseErr("unclosed tag", str, m.index + precedingString.length);
}
}
pushString(str.slice(lastIndex, str.length), false);
if (config.plugins) {
for (let i = 0; i < config.plugins.length; i++) {
const plugin = config.plugins[i];
if (plugin.processAST) {
buffer = plugin.processAST(buffer, config);
}
}
}
return buffer;
}
/* END TYPES */
/**
* Compiles a template string to a function string. Most often users just use `compile()`, which calls `compileToString` and creates a new function using the result
*
* **Example**
*
* ```js
* compileToString("Hi <%= it.user %>", eta.config)
* // "var tR='',include=E.include.bind(E),includeFile=E.includeFile.bind(E);tR+='Hi ';tR+=E.e(it.user);if(cb){cb(null,tR)} return tR"
* ```
*/
function compileToString(str, config) {
const buffer = parse(str, config);
let res = "var tR='',__l,__lP" + (config.include ? ",include=E.include.bind(E)" : "") + (config.includeFile ? ",includeFile=E.includeFile.bind(E)" : "") + "\nfunction layout(p,d){__l=p;__lP=d}\n" + (config.useWith ? "with(" + config.varName + "||{}){" : "") + compileScope(buffer, config) + (config.includeFile ? "if(__l)tR=" + (config.async ? "await " : "") + `includeFile(__l,Object.assign(${config.varName},{body:tR},__lP))\n` : config.include ? "if(__l)tR=" + (config.async ? "await " : "") + `include(__l,Object.assign(${config.varName},{body:tR},__lP))\n` : "") + "if(cb){cb(null,tR)} return tR" + (config.useWith ? "}" : "");
if (config.plugins) {
for (let i = 0; i < config.plugins.length; i++) {
const plugin = config.plugins[i];
if (plugin.processFnString) {
res = plugin.processFnString(res, config);
}
}
}
return res;
}
/**
* Loops through the AST generated by `parse` and transform each item into JS calls
*
* **Example**
*
* ```js
* // AST version of 'Hi <%= it.user %>'
* let templateAST = ['Hi ', { val: 'it.user', t: 'i' }]
* compileScope(templateAST, eta.config)
* // "tR+='Hi ';tR+=E.e(it.user);"
* ```
*/
function compileScope(buff, config) {
let i = 0;
const buffLength = buff.length;
let returnStr = "";
for (i; i < buffLength; i++) {
const currentBlock = buff[i];
if (typeof currentBlock === "string") {
const str = currentBlock;
// we know string exists
returnStr += "tR+='" + str + "'\n";
} else {
const type = currentBlock.t; // ~, s, !, ?, r
let content = currentBlock.val || "";
if (type === "r") {
// raw
if (config.filter) {
content = "E.filter(" + content + ")";
}
returnStr += "tR+=" + content + "\n";
} else if (type === "i") {
// interpolate
if (config.filter) {
content = "E.filter(" + content + ")";
}
if (config.autoEscape) {
content = "E.e(" + content + ")";
}
returnStr += "tR+=" + content + "\n";
// reference
} else if (type === "e") {
// execute
returnStr += content + "\n"; // you need a \n in case you have <% } %>
}
}
}
return returnStr;
}
/**
* Handles storage and accessing of values
*
* In this case, we use it to store compiled template functions
* Indexed by their `name` or `filename`
*/
class Cacher {
constructor(cache) {
this.cache = void 0;
this.cache = cache;
}
define(key, val) {
this.cache[key] = val;
}
get(key) {
// string | array.
// TODO: allow array of keys to look down
// TODO: create plugin to allow referencing helpers, filters with dot notation
return this.cache[key];
}
remove(key) {
delete this.cache[key];
}
reset() {
this.cache = {};
}
load(cacheObj) {
copyProps(this.cache, cacheObj);
}
}
/* END TYPES */
/**
* Eta's template storage
*
* Stores partials and cached templates
*/
const templates = new Cacher({});
/* END TYPES */
/**
* Include a template based on its name (or filepath, if it's already been cached).
*
* Called like `include(templateNameOrPath, data)`
*/
function includeHelper(templateNameOrPath, data) {
const template = this.templates.get(templateNameOrPath);
if (!template) {
throw EtaErr('Could not fetch template "' + templateNameOrPath + '"');
}
return template(data, this);
}
/** Eta's base (global) configuration */
const config = {
async: false,
autoEscape: true,
autoTrim: [false, "nl"],
cache: false,
e: XMLEscape,
include: includeHelper,
parse: {
exec: "",
interpolate: "=",
raw: "~"
},
plugins: [],
rmWhitespace: false,
tags: ["<%", "%>"],
templates: templates,
useWith: false,
varName: "it"
};
/**
* Takes one or two partial (not necessarily complete) configuration objects, merges them 1 layer deep into eta.config, and returns the result
*
* @param override Partial configuration object
* @param baseConfig Partial configuration object to merge before `override`
*
* **Example**
*
* ```js
* let customConfig = getConfig({tags: ['!#', '#!']})
* ```
*/
function getConfig(override, baseConfig) {
// TODO: run more tests on this
const res = {}; // Linked
copyProps(res, config); // Creates deep clone of eta.config, 1 layer deep
if (baseConfig) {
copyProps(res, baseConfig);
}
if (override) {
copyProps(res, override);
}
return res;
}
/** Update Eta's base config */
function configure(options) {
return copyProps(config, options);
}
/* END TYPES */
/**
* Takes a template string and returns a template function that can be called with (data, config, [cb])
*
* @param str - The template string
* @param config - A custom configuration object (optional)
*
* **Example**
*
* ```js
* let compiledFn = eta.compile("Hi <%= it.user %>")
* // function anonymous()
* let compiledFnStr = compiledFn.toString()
* // "function anonymous(it,E,cb\n) {\nvar tR='',include=E.include.bind(E),includeFile=E.includeFile.bind(E);tR+='Hi ';tR+=E.e(it.user);if(cb){cb(null,tR)} return tR\n}"
* ```
*/
function compile(str, config) {
const options = getConfig(config || {});
/* ASYNC HANDLING */
// The below code is modified from mde/ejs. All credit should go to them.
const ctor = options.async ? getAsyncFunctionConstructor() : Function;
/* END ASYNC HANDLING */
try {
return new ctor(options.varName, "E",
// EtaConfig
"cb",
// optional callback
compileToString(str, options)); // eslint-disable-line no-new-func
} catch (e) {
if (e instanceof SyntaxError) {
throw EtaErr("Bad template syntax\n\n" + e.message + "\n" + Array(e.message.length + 1).join("=") + "\n" + compileToString(str, options) + "\n" // This will put an extra newline before the callstack for extra readability
);
} else {
throw e;
}
}
}
const _BOM = /^\uFEFF/;
/* END TYPES */
/**
* Get the path to the included file from the parent file path and the
* specified path.
*
* If `name` does not have an extension, it will default to `.eta`
*
* @param name specified path
* @param parentfile parent file path
* @param isDirectory whether parentfile is a directory
* @return absolute path to template
*/
function getWholeFilePath(name, parentfile, isDirectory) {
const includePath = path.resolve(isDirectory ? parentfile : path.dirname(parentfile),
// returns directory the parent file is in
name // file
) + (path.extname(name) ? "" : ".eta");
return includePath;
}
/**
* Get the absolute path to an included template
*
* If this is called with an absolute path (for example, starting with '/' or 'C:\')
* then Eta will attempt to resolve the absolute path within options.views. If it cannot,
* Eta will fallback to options.root or '/'
*
* If this is called with a relative path, Eta will:
* - Look relative to the current template (if the current template has the `filename` property)
* - Look inside each directory in options.views
*
* Note: if Eta is unable to find a template using path and options, it will throw an error.
*
* @param path specified path
* @param options compilation options
* @return absolute path to template
*/
function getPath(path, options) {
let includePath = false;
const views = options.views;
const searchedPaths = [];
// If these four values are the same,
// getPath() will return the same result every time.
// We can cache the result to avoid expensive
// file operations.
const pathOptions = JSON.stringify({
filename: options.filename,
path: path,
root: options.root,
views: options.views
});
if (options.cache && options.filepathCache && options.filepathCache[pathOptions]) {
// Use the cached filepath
return options.filepathCache[pathOptions];
}
/** Add a filepath to the list of paths we've checked for a template */
function addPathToSearched(pathSearched) {
if (!searchedPaths.includes(pathSearched)) {
searchedPaths.push(pathSearched);
}
}
/**
* Take a filepath (like 'partials/mypartial.eta'). Attempt to find the template file inside `views`;
* return the resulting template file path, or `false` to indicate that the template was not found.
*
* @param views the filepath that holds templates, or an array of filepaths that hold templates
* @param path the path to the template
*/
function searchViews(views, path) {
let filePath;
// If views is an array, then loop through each directory
// And attempt to find the template
if (Array.isArray(views) && views.some(function (v) {
filePath = getWholeFilePath(path, v, true);
addPathToSearched(filePath);
return existsSync(filePath);
})) {
// If the above returned true, we know that the filePath was just set to a path
// That exists (Array.some() returns as soon as it finds a valid element)
return filePath;
} else if (typeof views === "string") {
// Search for the file if views is a single directory
filePath = getWholeFilePath(path, views, true);
addPathToSearched(filePath);
if (existsSync(filePath)) {
return filePath;
}
}
// Unable to find a file
return false;
}
// Path starts with '/', 'C:\', etc.
const match = /^[A-Za-z]+:\\|^\//.exec(path);
// Absolute path, like /partials/partial.eta
if (match && match.length) {
// We have to trim the beginning '/' off the path, or else
// path.resolve(dir, path) will always resolve to just path
const formattedPath = path.replace(/^\/*/, "");
// First, try to resolve the path within options.views
includePath = searchViews(views, formattedPath);
if (!includePath) {
// If that fails, searchViews will return false. Try to find the path
// inside options.root (by default '/', the base of the filesystem)
const pathFromRoot = getWholeFilePath(formattedPath, options.root || "/", true);
addPathToSearched(pathFromRoot);
includePath = pathFromRoot;
}
} else {
// Relative paths
// Look relative to a passed filename first
if (options.filename) {
const filePath = getWholeFilePath(path, options.filename);
addPathToSearched(filePath);
if (existsSync(filePath)) {
includePath = filePath;
}
}
// Then look for the template in options.views
if (!includePath) {
includePath = searchViews(views, path);
}
if (!includePath) {
throw EtaErr('Could not find the template "' + path + '". Paths tried: ' + searchedPaths);
}
}
// If caching and filepathCache are enabled,
// cache the input & output of this function.
if (options.cache && options.filepathCache) {
options.filepathCache[pathOptions] = includePath;
}
return includePath;
}
/**
* Reads a file synchronously
*/
function readFile(filePath) {
try {
return readFileSync(filePath).toString().replace(_BOM, ""); // TODO: is replacing BOM's necessary?
} catch {
throw EtaErr("Failed to read template at '" + filePath + "'");
}
}
// express is set like: app.engine('html', require('eta').renderFile)
/* END TYPES */
/**
* Reads a template, compiles it into a function, caches it if caching isn't disabled, returns the function
*
* @param filePath Absolute path to template file
* @param options Eta configuration overrides
* @param noCache Optionally, make Eta not cache the template
*/
function loadFile(filePath, options, noCache) {
const config = getConfig(options);
const template = readFile(filePath);
try {
const compiledTemplate = compile(template, config);
if (!noCache) {
config.templates.define(config.filename, compiledTemplate);
}
return compiledTemplate;
} catch (e) {
throw EtaErr("Loading file: " + filePath + " failed:\n\n" + e.message);
}
}
/**
* Get the template from a string or a file, either compiled on-the-fly or
* read from cache (if enabled), and cache the template if needed.
*
* If `options.cache` is true, this function reads the file from
* `options.filename` so it must be set prior to calling this function.
*
* @param options compilation options
* @return Eta template function
*/
function handleCache$1(options) {
const filename = options.filename;
if (options.cache) {
const func = options.templates.get(filename);
if (func) {
return func;
}
return loadFile(filename, options);
}
// Caching is disabled, so pass noCache = true
return loadFile(filename, options, true);
}
/**
* Try calling handleCache with the given options and data and call the
* callback with the result. If an error occurs, call the callback with
* the error. Used by renderFile().
*
* @param data template data
* @param options compilation options
* @param cb callback
*/
function tryHandleCache(data, options, cb) {
if (cb) {
try {
// Note: if there is an error while rendering the template,
// It will bubble up and be caught here
const templateFn = handleCache$1(options);
templateFn(data, options, cb);
} catch (err) {
return cb(err);
}
} else {
// No callback, try returning a promise
if (typeof promiseImpl === "function") {
return new promiseImpl(function (resolve, reject) {
try {
const templateFn = handleCache$1(options);
const result = templateFn(data, options);
resolve(result);
} catch (err) {
reject(err);
}
});
} else {
throw EtaErr("Please provide a callback function, this env doesn't support Promises");
}
}
}
/**
* Get the template function.
*
* If `options.cache` is `true`, then the template is cached.
*
* This returns a template function and the config object with which that template function should be called.
*
* @remarks
*
* It's important that this returns a config object with `filename` set.
* Otherwise, the included file would not be able to use relative paths
*
* @param path path for the specified file (if relative, specify `views` on `options`)
* @param options compilation options
* @return [Eta template function, new config object]
*/
function includeFile(path, options) {
// the below creates a new options object, using the parent filepath of the old options object and the path
const newFileOptions = getConfig({
filename: getPath(path, options)
}, options);
// TODO: make sure properties are currectly copied over
return [handleCache$1(newFileOptions), newFileOptions];
}
function renderFile(filename, data, config, cb) {
/*
Here we have some function overloading.
Essentially, the first 2 arguments to renderFile should always be the filename and data
Express will call renderFile with (filename, data, cb)
We also want to make (filename, data, options, cb) available
*/
let renderConfig;
let callback;
data = data || {};
// First, assign our callback function to `callback`
// We can leave it undefined if neither parameter is a function;
// Callbacks are optional
if (typeof cb === "function") {
// The 4th argument is the callback
callback = cb;
} else if (typeof config === "function") {
// The 3rd arg is the callback
callback = config;
}
// If there is a config object passed in explicitly, use it
if (typeof config === "object") {
renderConfig = getConfig(config || {});
} else {
// Otherwise, get the default config
renderConfig = getConfig({});
}
// Set the filename option on the template
// This will first try to resolve the file path (see getPath for details)
renderConfig.filename = getPath(filename, renderConfig);
return tryHandleCache(data, renderConfig, callback);
}
function renderFileAsync(filename, data, config, cb) {
return renderFile(filename, typeof config === "function" ? {
...data,
async: true
} : data, typeof config === "object" ? {
...config,
async: true
} : config, cb);
}
/* END TYPES */
/**
* Called with `includeFile(path, data)`
*/
function includeFileHelper(path, data) {
const templateAndConfig = includeFile(path, this);
return templateAndConfig[0](data, templateAndConfig[1]);
}
/* END TYPES */
function handleCache(template, options) {
if (options.cache && options.name && options.templates.get(options.name)) {
return options.templates.get(options.name);
}
const templateFunc = typeof template === "function" ? template : compile(template, options);
// Note that we don't have to check if it already exists in the cache;
// it would have returned earlier if it had
if (options.cache && options.name) {
options.templates.define(options.name, templateFunc);
}
return templateFunc;
}
function render(template, data, config, cb) {
const options = getConfig(config || {});
if (options.async) {
if (cb) {
// If user passes callback
try {
// Note: if there is an error while rendering the template,
// It will bubble up and be caught here
const templateFn = handleCache(template, options);
templateFn(data, options, cb);
} catch (err) {
return cb(err);
}
} else {
// No callback, try returning a promise
if (typeof promiseImpl === "function") {
return new promiseImpl(function (resolve, reject) {
try {
resolve(handleCache(template, options)(data, options));
} catch (err) {
reject(err);
}
});
} else {
throw EtaErr("Please provide a callback function, this env doesn't support Promises");
}
}
} else {
return handleCache(template, options)(data, options);
}
}
function renderAsync(template, data, config, cb) {
// Using Object.assign to lower bundle size, using spread operator makes it larger because of typescript injected polyfills
return render(template, data, Object.assign({}, config, {
async: true
}), cb);
}
// @denoify-ignore
config.includeFile = includeFileHelper;
config.filepathCache = {};
export { renderFile as __express, compile, compileToString, config, configure, config as defaultConfig, getConfig, loadFile, parse, render, renderAsync, renderFile, renderFileAsync, templates };
//# sourceMappingURL=eta.module.mjs.map