es-toolkit 1.25.2-dev.821 → 1.25.2-dev.824

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -201,6 +201,7 @@ export { snakeCase } from './string/snakeCase.mjs';
201
201
  export { startCase } from './string/startCase.mjs';
202
202
  export { startsWith } from './string/startsWith.mjs';
203
203
  export { upperCase } from './string/upperCase.mjs';
204
+ export { template, templateSettings } from './string/template.mjs';
204
205
  export { trim } from './string/trim.mjs';
205
206
  export { trimEnd } from './string/trimEnd.mjs';
206
207
  export { trimStart } from './string/trimStart.mjs';
@@ -201,6 +201,7 @@ export { snakeCase } from './string/snakeCase.js';
201
201
  export { startCase } from './string/startCase.js';
202
202
  export { startsWith } from './string/startsWith.js';
203
203
  export { upperCase } from './string/upperCase.js';
204
+ export { template, templateSettings } from './string/template.js';
204
205
  export { trim } from './string/trim.js';
205
206
  export { trimEnd } from './string/trimEnd.js';
206
207
  export { trimStart } from './string/trimStart.js';
@@ -2164,6 +2164,86 @@ function upperCase(str) {
2164
2164
  return upperFirst.upperCase(normalizeForCase(str));
2165
2165
  }
2166
2166
 
2167
+ const esTemplateRegExp = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
2168
+ const unEscapedRegExp = /['\n\r\u2028\u2029\\]/g;
2169
+ const noMatchExp = /($^)/;
2170
+ const escapeMap = new Map([
2171
+ ['\\', '\\'],
2172
+ ["'", "'"],
2173
+ ['\n', 'n'],
2174
+ ['\r', 'r'],
2175
+ ['\u2028', 'u2028'],
2176
+ ['\u2029', 'u2029'],
2177
+ ]);
2178
+ function escapeString(match) {
2179
+ return `\\${escapeMap.get(match)}`;
2180
+ }
2181
+ const templateSettings = {
2182
+ escape: /<%-([\s\S]+?)%>/g,
2183
+ evaluate: /<%([\s\S]+?)%>/g,
2184
+ interpolate: /<%=([\s\S]+?)%>/g,
2185
+ variable: '',
2186
+ imports: {
2187
+ _: {
2188
+ escape,
2189
+ template,
2190
+ },
2191
+ },
2192
+ };
2193
+ function template(string, options, guard) {
2194
+ string = toString(string);
2195
+ if (guard) {
2196
+ options = templateSettings;
2197
+ }
2198
+ options = defaults({ ...options }, templateSettings);
2199
+ const delimitersRegExp = new RegExp([
2200
+ options.escape?.source ?? noMatchExp.source,
2201
+ options.interpolate?.source ?? noMatchExp.source,
2202
+ options.interpolate ? esTemplateRegExp.source : noMatchExp.source,
2203
+ options.evaluate?.source ?? noMatchExp.source,
2204
+ '$',
2205
+ ].join('|'), 'g');
2206
+ let lastIndex = 0;
2207
+ let isEvaluated = false;
2208
+ let source = `__p += ''`;
2209
+ for (const match of string.matchAll(delimitersRegExp)) {
2210
+ const [fullMatch, escapeValue, interpolateValue, esTemplateValue, evaluateValue] = match;
2211
+ const { index } = match;
2212
+ source += ` + '${string.slice(lastIndex, index).replace(unEscapedRegExp, escapeString)}'`;
2213
+ if (escapeValue) {
2214
+ source += ` + _.escape(${escapeValue})`;
2215
+ }
2216
+ if (interpolateValue) {
2217
+ source += ` + ((${interpolateValue}) == null ? '' : ${interpolateValue})`;
2218
+ }
2219
+ else if (esTemplateValue) {
2220
+ source += ` + ((${esTemplateValue}) == null ? '' : ${esTemplateValue})`;
2221
+ }
2222
+ if (evaluateValue) {
2223
+ source += `;\n${evaluateValue};\n __p += ''`;
2224
+ isEvaluated = true;
2225
+ }
2226
+ lastIndex = index + fullMatch.length;
2227
+ }
2228
+ const imports = defaults({ ...options.imports }, templateSettings.imports);
2229
+ const importsKeys = Object.keys(imports);
2230
+ const importValues = Object.values(imports);
2231
+ const sourceURL = `//# sourceURL=${options.sourceURL ? String(options.sourceURL).replace(/[\r\n]/g, ' ') : `es-toolkit.templateSource[${Date.now()}]`}\n`;
2232
+ const compiledFunction = `function(${options.variable || 'obj'}) {
2233
+ let __p = '';
2234
+ ${options.variable ? '' : 'if (obj == null) { obj = {}; }'}
2235
+ ${isEvaluated ? `function print() { __p += Array.prototype.join.call(arguments, ''); }` : ''}
2236
+ ${options.variable ? source : `with(obj) {\n${source}\n}`}
2237
+ return __p;
2238
+ }`;
2239
+ const result = attempt(() => new Function(...importsKeys, `${sourceURL}return ${compiledFunction}`)(...importValues));
2240
+ result.source = compiledFunction;
2241
+ if (result instanceof Error) {
2242
+ throw result;
2243
+ }
2244
+ return result;
2245
+ }
2246
+
2167
2247
  function trim(str, chars, guard) {
2168
2248
  if (str == null) {
2169
2249
  return '';
@@ -2480,6 +2560,8 @@ exports.startsWith = startsWith;
2480
2560
  exports.tail = tail;
2481
2561
  exports.take = take;
2482
2562
  exports.takeRight = takeRight;
2563
+ exports.template = template;
2564
+ exports.templateSettings = templateSettings;
2483
2565
  exports.throttle = throttle;
2484
2566
  exports.times = times;
2485
2567
  exports.toDefaulted = toDefaulted;
@@ -202,6 +202,7 @@ export { snakeCase } from './string/snakeCase.mjs';
202
202
  export { startCase } from './string/startCase.mjs';
203
203
  export { startsWith } from './string/startsWith.mjs';
204
204
  export { upperCase } from './string/upperCase.mjs';
205
+ export { template, templateSettings } from './string/template.mjs';
205
206
  export { trim } from './string/trim.mjs';
206
207
  export { trimEnd } from './string/trimEnd.mjs';
207
208
  export { trimStart } from './string/trimStart.mjs';
@@ -0,0 +1,89 @@
1
+ import { escape } from './escape.mjs';
2
+
3
+ declare const templateSettings: {
4
+ escape: RegExp;
5
+ evaluate: RegExp;
6
+ interpolate: RegExp;
7
+ variable: string;
8
+ imports: {
9
+ _: {
10
+ escape: typeof escape;
11
+ template: typeof template;
12
+ };
13
+ };
14
+ };
15
+ interface TemplateOptions {
16
+ escape?: RegExp;
17
+ evaluate?: RegExp;
18
+ interpolate?: RegExp;
19
+ variable?: string;
20
+ imports?: Record<string, unknown>;
21
+ sourceURL?: string;
22
+ }
23
+ /**
24
+ * Compiles a template string into a function that can interpolate data properties.
25
+ *
26
+ * This function allows you to create a template with custom delimiters for escaping,
27
+ * evaluating, and interpolating values. It can also handle custom variable names and
28
+ * imported functions.
29
+ *
30
+ * @param {string} string - The template string.
31
+ * @param {TemplateOptions} [options] - The options object.
32
+ * @param {RegExp} [options.escape] - The regular expression for "escape" delimiter.
33
+ * @param {RegExp} [options.evaluate] - The regular expression for "evaluate" delimiter.
34
+ * @param {RegExp} [options.interpolate] - The regular expression for "interpolate" delimiter.
35
+ * @param {string} [options.variable] - The data object variable name.
36
+ * @param {Record<string, unknown>} [options.imports] - The object of imported functions.
37
+ * @param {string} [options.sourceURL] - The source URL of the template.
38
+ * @param {unknown} [guard] - The guard to detect if the function is called with `options`.
39
+ * @returns {(data?: object) => string} Returns the compiled template function.
40
+ *
41
+ * @example
42
+ * // Use the "escape" delimiter to escape data properties.
43
+ * const compiled = template('<%- value %>');
44
+ * compiled({ value: '<div>' }); // returns '&lt;div&gt;'
45
+ *
46
+ * @example
47
+ * // Use the "interpolate" delimiter to interpolate data properties.
48
+ * const compiled = template('<%= value %>');
49
+ * compiled({ value: 'Hello, World!' }); // returns 'Hello, World!'
50
+ *
51
+ * @example
52
+ * // Use the "evaluate" delimiter to evaluate JavaScript code.
53
+ * const compiled = template('<% if (value) { %>Yes<% } else { %>No<% } %>');
54
+ * compiled({ value: true }); // returns 'Yes'
55
+ *
56
+ * @example
57
+ * // Use the "variable" option to specify the data object variable name.
58
+ * const compiled = template('<%= data.value %>', { variable: 'data' });
59
+ * compiled({ value: 'Hello, World!' }); // returns 'Hello, World!'
60
+ *
61
+ * @example
62
+ * // Use the "imports" option to import functions.
63
+ * const compiled = template('<%= _.toUpper(value) %>', { imports: { _: { toUpper } } });
64
+ * compiled({ value: 'hello, world!' }); // returns 'HELLO, WORLD!'
65
+ *
66
+ * @example
67
+ * // Use the custom "escape" delimiter.
68
+ * const compiled = template('<@ value @>', { escape: /<@([\s\S]+?)@>/g });
69
+ * compiled({ value: '<div>' }); // returns '&lt;div&gt;'
70
+ *
71
+ * @example
72
+ * // Use the custom "evaluate" delimiter.
73
+ * const compiled = template('<# if (value) { #>Yes<# } else { #>No<# } #>', { evaluate: /<#([\s\S]+?)#>/g });
74
+ * compiled({ value: true }); // returns 'Yes'
75
+ *
76
+ * @example
77
+ * // Use the custom "interpolate" delimiter.
78
+ * const compiled = template('<$ value $>', { interpolate: /<\$([\s\S]+?)\$>/g });
79
+ * compiled({ value: 'Hello, World!' }); // returns 'Hello, World!'
80
+ *
81
+ * @example
82
+ * // Use the "sourceURL" option to specify the source URL of the template.
83
+ * const compiled = template('hello <%= user %>!', { sourceURL: 'template.js' });
84
+ */
85
+ declare function template(string: string, options?: TemplateOptions, guard?: unknown): ((data?: object) => string) & {
86
+ source: string;
87
+ };
88
+
89
+ export { template, templateSettings };
@@ -0,0 +1,89 @@
1
+ import { escape } from './escape.js';
2
+
3
+ declare const templateSettings: {
4
+ escape: RegExp;
5
+ evaluate: RegExp;
6
+ interpolate: RegExp;
7
+ variable: string;
8
+ imports: {
9
+ _: {
10
+ escape: typeof escape;
11
+ template: typeof template;
12
+ };
13
+ };
14
+ };
15
+ interface TemplateOptions {
16
+ escape?: RegExp;
17
+ evaluate?: RegExp;
18
+ interpolate?: RegExp;
19
+ variable?: string;
20
+ imports?: Record<string, unknown>;
21
+ sourceURL?: string;
22
+ }
23
+ /**
24
+ * Compiles a template string into a function that can interpolate data properties.
25
+ *
26
+ * This function allows you to create a template with custom delimiters for escaping,
27
+ * evaluating, and interpolating values. It can also handle custom variable names and
28
+ * imported functions.
29
+ *
30
+ * @param {string} string - The template string.
31
+ * @param {TemplateOptions} [options] - The options object.
32
+ * @param {RegExp} [options.escape] - The regular expression for "escape" delimiter.
33
+ * @param {RegExp} [options.evaluate] - The regular expression for "evaluate" delimiter.
34
+ * @param {RegExp} [options.interpolate] - The regular expression for "interpolate" delimiter.
35
+ * @param {string} [options.variable] - The data object variable name.
36
+ * @param {Record<string, unknown>} [options.imports] - The object of imported functions.
37
+ * @param {string} [options.sourceURL] - The source URL of the template.
38
+ * @param {unknown} [guard] - The guard to detect if the function is called with `options`.
39
+ * @returns {(data?: object) => string} Returns the compiled template function.
40
+ *
41
+ * @example
42
+ * // Use the "escape" delimiter to escape data properties.
43
+ * const compiled = template('<%- value %>');
44
+ * compiled({ value: '<div>' }); // returns '&lt;div&gt;'
45
+ *
46
+ * @example
47
+ * // Use the "interpolate" delimiter to interpolate data properties.
48
+ * const compiled = template('<%= value %>');
49
+ * compiled({ value: 'Hello, World!' }); // returns 'Hello, World!'
50
+ *
51
+ * @example
52
+ * // Use the "evaluate" delimiter to evaluate JavaScript code.
53
+ * const compiled = template('<% if (value) { %>Yes<% } else { %>No<% } %>');
54
+ * compiled({ value: true }); // returns 'Yes'
55
+ *
56
+ * @example
57
+ * // Use the "variable" option to specify the data object variable name.
58
+ * const compiled = template('<%= data.value %>', { variable: 'data' });
59
+ * compiled({ value: 'Hello, World!' }); // returns 'Hello, World!'
60
+ *
61
+ * @example
62
+ * // Use the "imports" option to import functions.
63
+ * const compiled = template('<%= _.toUpper(value) %>', { imports: { _: { toUpper } } });
64
+ * compiled({ value: 'hello, world!' }); // returns 'HELLO, WORLD!'
65
+ *
66
+ * @example
67
+ * // Use the custom "escape" delimiter.
68
+ * const compiled = template('<@ value @>', { escape: /<@([\s\S]+?)@>/g });
69
+ * compiled({ value: '<div>' }); // returns '&lt;div&gt;'
70
+ *
71
+ * @example
72
+ * // Use the custom "evaluate" delimiter.
73
+ * const compiled = template('<# if (value) { #>Yes<# } else { #>No<# } #>', { evaluate: /<#([\s\S]+?)#>/g });
74
+ * compiled({ value: true }); // returns 'Yes'
75
+ *
76
+ * @example
77
+ * // Use the custom "interpolate" delimiter.
78
+ * const compiled = template('<$ value $>', { interpolate: /<\$([\s\S]+?)\$>/g });
79
+ * compiled({ value: 'Hello, World!' }); // returns 'Hello, World!'
80
+ *
81
+ * @example
82
+ * // Use the "sourceURL" option to specify the source URL of the template.
83
+ * const compiled = template('hello <%= user %>!', { sourceURL: 'template.js' });
84
+ */
85
+ declare function template(string: string, options?: TemplateOptions, guard?: unknown): ((data?: object) => string) & {
86
+ source: string;
87
+ };
88
+
89
+ export { template, templateSettings };
@@ -0,0 +1,86 @@
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 templateSettings = {
21
+ escape: /<%-([\s\S]+?)%>/g,
22
+ evaluate: /<%([\s\S]+?)%>/g,
23
+ interpolate: /<%=([\s\S]+?)%>/g,
24
+ variable: '',
25
+ imports: {
26
+ _: {
27
+ escape,
28
+ template,
29
+ },
30
+ },
31
+ };
32
+ function template(string, options, guard) {
33
+ string = toString(string);
34
+ if (guard) {
35
+ options = templateSettings;
36
+ }
37
+ options = defaults({ ...options }, templateSettings);
38
+ const delimitersRegExp = new RegExp([
39
+ options.escape?.source ?? noMatchExp.source,
40
+ options.interpolate?.source ?? noMatchExp.source,
41
+ options.interpolate ? esTemplateRegExp.source : noMatchExp.source,
42
+ options.evaluate?.source ?? noMatchExp.source,
43
+ '$',
44
+ ].join('|'), 'g');
45
+ let lastIndex = 0;
46
+ let isEvaluated = false;
47
+ let source = `__p += ''`;
48
+ for (const match of string.matchAll(delimitersRegExp)) {
49
+ const [fullMatch, escapeValue, interpolateValue, esTemplateValue, evaluateValue] = match;
50
+ const { index } = match;
51
+ source += ` + '${string.slice(lastIndex, index).replace(unEscapedRegExp, escapeString)}'`;
52
+ if (escapeValue) {
53
+ source += ` + _.escape(${escapeValue})`;
54
+ }
55
+ if (interpolateValue) {
56
+ source += ` + ((${interpolateValue}) == null ? '' : ${interpolateValue})`;
57
+ }
58
+ else if (esTemplateValue) {
59
+ source += ` + ((${esTemplateValue}) == null ? '' : ${esTemplateValue})`;
60
+ }
61
+ if (evaluateValue) {
62
+ source += `;\n${evaluateValue};\n __p += ''`;
63
+ isEvaluated = true;
64
+ }
65
+ lastIndex = index + fullMatch.length;
66
+ }
67
+ const imports = defaults({ ...options.imports }, templateSettings.imports);
68
+ const importsKeys = Object.keys(imports);
69
+ const importValues = Object.values(imports);
70
+ const sourceURL = `//# sourceURL=${options.sourceURL ? String(options.sourceURL).replace(/[\r\n]/g, ' ') : `es-toolkit.templateSource[${Date.now()}]`}\n`;
71
+ const compiledFunction = `function(${options.variable || 'obj'}) {
72
+ let __p = '';
73
+ ${options.variable ? '' : 'if (obj == null) { obj = {}; }'}
74
+ ${isEvaluated ? `function print() { __p += Array.prototype.join.call(arguments, ''); }` : ''}
75
+ ${options.variable ? source : `with(obj) {\n${source}\n}`}
76
+ return __p;
77
+ }`;
78
+ const result = attempt(() => new Function(...importsKeys, `${sourceURL}return ${compiledFunction}`)(...importValues));
79
+ result.source = compiledFunction;
80
+ if (result instanceof Error) {
81
+ throw result;
82
+ }
83
+ return result;
84
+ }
85
+
86
+ export { template, templateSettings };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "es-toolkit",
3
3
  "description": "A state-of-the-art, high-performance JavaScript utility library with a small bundle size and strong type annotations.",
4
- "version": "1.25.2-dev.821+bd7468fc",
4
+ "version": "1.25.2-dev.824+a000c597",
5
5
  "homepage": "https://es-toolkit.slash.page",
6
6
  "bugs": "https://github.com/toss/es-toolkit/issues",
7
7
  "repository": {