import { escape } from './escape.mjs'; import { attempt } from '../function/attempt.mjs'; import { defaults } from '../object/defaults.mjs'; import { toString } from '../util/toString.mjs'; const esTemplateRegExp = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; const unEscapedRegExp = /['\n\r\u2028\u2029\\]/g; const noMatchExp = /($^)/; const escapeMap = new Map([ ['\\', '\\'], ["'", "'"], ['\n', 'n'], ['\r', 'r'], ['\u2028', 'u2028'], ['\u2029', 'u2029'], ]); function escapeString(match) { return `\\${escapeMap.get(match)}`; } const templateSettings = { escape: /<%-([\s\S]+?)%>/g, evaluate: /<%([\s\S]+?)%>/g, interpolate: /<%=([\s\S]+?)%>/g, variable: '', imports: { _: { escape, template, }, }, }; function template(string, options, guard) { string = toString(string); if (guard) { options = templateSettings; } options = defaults({ ...options }, templateSettings); const delimitersRegExp = new RegExp([ options.escape?.source ?? noMatchExp.source, options.interpolate?.source ?? noMatchExp.source, options.interpolate ? esTemplateRegExp.source : noMatchExp.source, options.evaluate?.source ?? noMatchExp.source, '$', ].join('|'), 'g'); let lastIndex = 0; let isEvaluated = false; let source = `__p += ''`; for (const match of string.matchAll(delimitersRegExp)) { const [fullMatch, escapeValue, interpolateValue, esTemplateValue, evaluateValue] = match; const { index } = match; source += ` + '${string.slice(lastIndex, index).replace(unEscapedRegExp, escapeString)}'`; if (escapeValue) { source += ` + _.escape(${escapeValue})`; } if (interpolateValue) { source += ` + ((${interpolateValue}) == null ? '' : ${interpolateValue})`; } else if (esTemplateValue) { source += ` + ((${esTemplateValue}) == null ? '' : ${esTemplateValue})`; } if (evaluateValue) { source += `;\n${evaluateValue};\n __p += ''`; isEvaluated = true; } lastIndex = index + fullMatch.length; } const imports = defaults({ ...options.imports }, templateSettings.imports); const importsKeys = Object.keys(imports); const importValues = Object.values(imports); const sourceURL = `//# sourceURL=${options.sourceURL ? String(options.sourceURL).replace(/[\r\n]/g, ' ') : `es-toolkit.templateSource[${Date.now()}]`}\n`; const compiledFunction = `function(${options.variable || 'obj'}) { let __p = ''; ${options.variable ? '' : 'if (obj == null) { obj = {}; }'} ${isEvaluated ? `function print() { __p += Array.prototype.join.call(arguments, ''); }` : ''} ${options.variable ? source : `with(obj) {\n${source}\n}`} return __p; }`; const result = attempt(() => new Function(...importsKeys, `${sourceURL}return ${compiledFunction}`)(...importValues)); result.source = compiledFunction; if (result instanceof Error) { throw result; } return result; } export { template, templateSettings };