| 1 | import { escape } from './escape.mjs';
|
|---|
| 2 | import { attempt } from '../function/attempt.mjs';
|
|---|
| 3 | import { defaults } from '../object/defaults.mjs';
|
|---|
| 4 | import { toString } from '../util/toString.mjs';
|
|---|
| 5 |
|
|---|
| 6 | const esTemplateRegExp = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
|
|---|
| 7 | const unEscapedRegExp = /['\n\r\u2028\u2029\\]/g;
|
|---|
| 8 | const noMatchExp = /($^)/;
|
|---|
| 9 | const escapeMap = new Map([
|
|---|
| 10 | ['\\', '\\'],
|
|---|
| 11 | ["'", "'"],
|
|---|
| 12 | ['\n', 'n'],
|
|---|
| 13 | ['\r', 'r'],
|
|---|
| 14 | ['\u2028', 'u2028'],
|
|---|
| 15 | ['\u2029', 'u2029'],
|
|---|
| 16 | ]);
|
|---|
| 17 | function escapeString(match) {
|
|---|
| 18 | return `\\${escapeMap.get(match)}`;
|
|---|
| 19 | }
|
|---|
| 20 | const defaultInterpolateRegExp = /<%=([\s\S]+?)%>/g;
|
|---|
| 21 | const templateSettings = {
|
|---|
| 22 | escape: /<%-([\s\S]+?)%>/g,
|
|---|
| 23 | evaluate: /<%([\s\S]+?)%>/g,
|
|---|
| 24 | interpolate: defaultInterpolateRegExp,
|
|---|
| 25 | variable: '',
|
|---|
| 26 | imports: {
|
|---|
| 27 | _: {
|
|---|
| 28 | escape,
|
|---|
| 29 | template,
|
|---|
| 30 | },
|
|---|
| 31 | },
|
|---|
| 32 | };
|
|---|
| 33 | function template(string, options, guard) {
|
|---|
| 34 | string = toString(string);
|
|---|
| 35 | if (guard) {
|
|---|
| 36 | options = templateSettings;
|
|---|
| 37 | }
|
|---|
| 38 | options = defaults({ ...options }, templateSettings);
|
|---|
| 39 | const delimitersRegExp = new RegExp([
|
|---|
| 40 | options.escape?.source ?? noMatchExp.source,
|
|---|
| 41 | options.interpolate?.source ?? noMatchExp.source,
|
|---|
| 42 | options.interpolate === defaultInterpolateRegExp ? esTemplateRegExp.source : noMatchExp.source,
|
|---|
| 43 | options.evaluate?.source ?? noMatchExp.source,
|
|---|
| 44 | '$',
|
|---|
| 45 | ].join('|'), 'g');
|
|---|
| 46 | let lastIndex = 0;
|
|---|
| 47 | let isEvaluated = false;
|
|---|
| 48 | let source = `__p += ''`;
|
|---|
| 49 | for (const match of string.matchAll(delimitersRegExp)) {
|
|---|
| 50 | const [fullMatch, escapeValue, interpolateValue, esTemplateValue, evaluateValue] = match;
|
|---|
| 51 | const { index } = match;
|
|---|
| 52 | source += ` + '${string.slice(lastIndex, index).replace(unEscapedRegExp, escapeString)}'`;
|
|---|
| 53 | if (escapeValue) {
|
|---|
| 54 | source += ` + _.escape(${escapeValue})`;
|
|---|
| 55 | }
|
|---|
| 56 | if (interpolateValue) {
|
|---|
| 57 | source += ` + ((${interpolateValue}) == null ? '' : ${interpolateValue})`;
|
|---|
| 58 | }
|
|---|
| 59 | else if (esTemplateValue) {
|
|---|
| 60 | source += ` + ((${esTemplateValue}) == null ? '' : ${esTemplateValue})`;
|
|---|
| 61 | }
|
|---|
| 62 | if (evaluateValue) {
|
|---|
| 63 | source += `;\n${evaluateValue};\n __p += ''`;
|
|---|
| 64 | isEvaluated = true;
|
|---|
| 65 | }
|
|---|
| 66 | lastIndex = index + fullMatch.length;
|
|---|
| 67 | }
|
|---|
| 68 | const imports = defaults({ ...options.imports }, templateSettings.imports);
|
|---|
| 69 | const importsKeys = Object.keys(imports);
|
|---|
| 70 | const importValues = Object.values(imports);
|
|---|
| 71 | const sourceURL = `//# sourceURL=${options.sourceURL ? String(options.sourceURL).replace(/[\r\n]/g, ' ') : `es-toolkit.templateSource[${Date.now()}]`}\n`;
|
|---|
| 72 | const compiledFunction = `function(${options.variable || 'obj'}) {
|
|---|
| 73 | let __p = '';
|
|---|
| 74 | ${options.variable ? '' : 'if (obj == null) { obj = {}; }'}
|
|---|
| 75 | ${isEvaluated ? `function print() { __p += Array.prototype.join.call(arguments, ''); }` : ''}
|
|---|
| 76 | ${options.variable ? source : `with(obj) {\n${source}\n}`}
|
|---|
| 77 | return __p;
|
|---|
| 78 | }`;
|
|---|
| 79 | const result = attempt(() => new Function(...importsKeys, `${sourceURL}return ${compiledFunction}`)(...importValues));
|
|---|
| 80 | result.source = compiledFunction;
|
|---|
| 81 | if (result instanceof Error) {
|
|---|
| 82 | throw result;
|
|---|
| 83 | }
|
|---|
| 84 | return result;
|
|---|
| 85 | }
|
|---|
| 86 |
|
|---|
| 87 | export { template, templateSettings };
|
|---|