1 | /* micromustache v8.0.3 */
|
2 | (function (global, factory) {
|
3 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
4 | typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
5 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.micromustache = {}));
|
6 | }(this, (function (exports) { 'use strict';
|
7 |
|
8 | /** @internal */
|
9 | /** @internal */
|
10 | // eslint-disable-next-line @typescript-eslint/unbound-method
|
11 | var numberConstructor = (0).constructor;
|
12 | /** @internal */
|
13 | // eslint-disable-next-line @typescript-eslint/unbound-method
|
14 | var isFinite = numberConstructor.isFinite;
|
15 | /** @internal */
|
16 | // eslint-disable-next-line @typescript-eslint/unbound-method
|
17 | var isInteger = numberConstructor.isInteger;
|
18 | /** @internal */
|
19 | // eslint-disable-next-line @typescript-eslint/unbound-method
|
20 | var isArray = [].constructor.isArray;
|
21 | /** @internal */
|
22 | // eslint-disable-next-line @typescript-eslint/ban-types
|
23 | function isObj(x) {
|
24 | return x !== null && typeof x === 'object';
|
25 | }
|
26 | /** @internal */
|
27 | // eslint-disable-next-line @typescript-eslint/ban-types
|
28 | function isFn(x) {
|
29 | return typeof x === 'function';
|
30 | }
|
31 | /** @internal */
|
32 | function isStr(x, minLength) {
|
33 | if (minLength === void 0) { minLength = 0; }
|
34 | return typeof x === 'string' && x.length >= minLength;
|
35 | }
|
36 | /** @internal */
|
37 | function isNum(x) {
|
38 | return isFinite(x);
|
39 | }
|
40 | /** @internal */
|
41 | function isArr(x) {
|
42 | return isArray(x);
|
43 | }
|
44 | /** @internal */
|
45 | function isProp(x, propName) {
|
46 | return isObj(x) && propName in x;
|
47 | }
|
48 |
|
49 | /**
|
50 | * @internal
|
51 | * The number of different varNames that will be cached.
|
52 | * If a varName is cached, the actual parsing algorithm will not be called
|
53 | * which significantly improves performance.
|
54 | * However, this cache is size-limited to prevent degrading the user's software
|
55 | * over a period of time.
|
56 | * If the cache is full, we start removing older varNames one at a time.
|
57 | */
|
58 | var cacheSize = 1000;
|
59 | /** @internal */
|
60 | var quoteChars = '\'"`';
|
61 | /**
|
62 | * @internal
|
63 | */
|
64 | var Cache = /** @class */ (function () {
|
65 | function Cache(size) {
|
66 | this.size = size;
|
67 | this.reset();
|
68 | }
|
69 | Cache.prototype.reset = function () {
|
70 | this.oldestIndex = 0;
|
71 | this.map = {};
|
72 | this.cachedKeys = new Array(this.size);
|
73 | };
|
74 | Cache.prototype.get = function (key) {
|
75 | return this.map[key];
|
76 | };
|
77 | Cache.prototype.set = function (key, value) {
|
78 | this.map[key] = value;
|
79 | var oldestKey = this.cachedKeys[this.oldestIndex];
|
80 | if (oldestKey !== undefined) {
|
81 | delete this.map[oldestKey];
|
82 | }
|
83 | this.cachedKeys[this.oldestIndex] = key;
|
84 | this.oldestIndex++;
|
85 | this.oldestIndex %= this.size;
|
86 | };
|
87 | return Cache;
|
88 | }());
|
89 | /** @internal */
|
90 | var cache = new Cache(cacheSize);
|
91 | /**
|
92 | * @internal
|
93 | * Removes the quotes from a string and returns it.
|
94 | * @param propName an string with quotations
|
95 | * @throws `SyntaxError` if the quotation symbols don't match or one is missing
|
96 | * @returns the input with its quotes removed
|
97 | */
|
98 | function propBetweenBrackets(propName) {
|
99 | // in our algorithms key is always a string and never only a string of spaces
|
100 | var firstChar = propName.charAt(0);
|
101 | var lastChar = propName.substr(-1);
|
102 | if (quoteChars.includes(firstChar) || quoteChars.includes(lastChar)) {
|
103 | if (propName.length < 2 || firstChar !== lastChar) {
|
104 | throw new SyntaxError("Mismatching string quotation: \"" + propName + "\"");
|
105 | }
|
106 | return propName.substring(1, propName.length - 1);
|
107 | }
|
108 | if (propName.includes('[')) {
|
109 | throw new SyntaxError("Missing ] in varName \"" + propName + "\"");
|
110 | }
|
111 | // Normalize leading plus from numerical indices
|
112 | if (firstChar === '+') {
|
113 | return propName.substr(1);
|
114 | }
|
115 | return propName;
|
116 | }
|
117 | /** @internal */
|
118 | function pushPropName(propNames, propName, preDot) {
|
119 | var pName = propName.trim();
|
120 | if (pName === '') {
|
121 | return propNames;
|
122 | }
|
123 | if (pName.startsWith('.')) {
|
124 | if (preDot) {
|
125 | pName = pName.substr(1).trim();
|
126 | if (pName === '') {
|
127 | return propNames;
|
128 | }
|
129 | }
|
130 | else {
|
131 | throw new SyntaxError("Unexpected . at the start of \"" + propName + "\"");
|
132 | }
|
133 | }
|
134 | else if (preDot) {
|
135 | throw new SyntaxError("Missing . at the start of \"" + propName + "\"");
|
136 | }
|
137 | if (pName.endsWith('.')) {
|
138 | throw new SyntaxError("Unexpected \".\" at the end of \"" + propName + "\"");
|
139 | }
|
140 | var propNameParts = pName.split('.');
|
141 | for (var _i = 0, propNameParts_1 = propNameParts; _i < propNameParts_1.length; _i++) {
|
142 | var propNamePart = propNameParts_1[_i];
|
143 | var trimmedPropName = propNamePart.trim();
|
144 | if (trimmedPropName === '') {
|
145 | throw new SyntaxError("Empty prop name when parsing \"" + propName + "\"");
|
146 | }
|
147 | propNames.push(trimmedPropName);
|
148 | }
|
149 | return propNames;
|
150 | }
|
151 | /**
|
152 | * Breaks a variable name to an array of strings that can be used to get a
|
153 | * particular value from an object
|
154 | * @param varName - the variable name as it occurs in the template.
|
155 | * For example `a["b"].c`
|
156 | * @throws `TypeError` if the varName is not a string
|
157 | * @throws `SyntaxError` if the varName syntax has a problem
|
158 | * @returns - an array of property names that can be used to get a particular
|
159 | * value.
|
160 | * For example `['a', 'b', 'c']`
|
161 | */
|
162 | function toPath(varName) {
|
163 | if (!isStr(varName)) {
|
164 | throw new TypeError("Cannot parse path. Expected string. Got a " + typeof varName);
|
165 | }
|
166 | var openBracketIndex;
|
167 | var closeBracketIndex = 0;
|
168 | var beforeBracket;
|
169 | var propName;
|
170 | var preDot = false;
|
171 | var propNames = new Array(0);
|
172 | for (var currentIndex = 0; currentIndex < varName.length; currentIndex = closeBracketIndex) {
|
173 | openBracketIndex = varName.indexOf('[', currentIndex);
|
174 | if (openBracketIndex === -1) {
|
175 | break;
|
176 | }
|
177 | closeBracketIndex = varName.indexOf(']', openBracketIndex);
|
178 | if (closeBracketIndex === -1) {
|
179 | throw new SyntaxError("Missing ] in varName \"" + varName + "\"");
|
180 | }
|
181 | propName = varName.substring(openBracketIndex + 1, closeBracketIndex).trim();
|
182 | if (propName.length === 0) {
|
183 | throw new SyntaxError('Unexpected token ]');
|
184 | }
|
185 | closeBracketIndex++;
|
186 | beforeBracket = varName.substring(currentIndex, openBracketIndex);
|
187 | pushPropName(propNames, beforeBracket, preDot);
|
188 | propNames.push(propBetweenBrackets(propName));
|
189 | preDot = true;
|
190 | }
|
191 | var rest = varName.substring(closeBracketIndex);
|
192 | return pushPropName(propNames, rest, preDot);
|
193 | }
|
194 | /**
|
195 | * This is just a faster version of `toPath()`
|
196 | */
|
197 | function toPathCached(varName) {
|
198 | var result = cache.get(varName);
|
199 | if (result === undefined) {
|
200 | result = toPath(varName);
|
201 | cache.set(varName, result);
|
202 | }
|
203 | return result;
|
204 | }
|
205 | toPath.cached = toPathCached;
|
206 |
|
207 | /**
|
208 | * A useful utility function that is used internally to lookup a variable name as a path to a
|
209 | * property in an object. It can also be used in your custom resolver functions if needed.
|
210 | *
|
211 | * This is similar to [Lodash's `_.get()`](https://lodash.com/docs/#get)
|
212 | *
|
213 | * It has a few differences with plain JavaScript syntax:
|
214 | * - No support for keys that include `[` or `]`.
|
215 | * - No support for keys that include `'` or `"` or `.`.
|
216 | * @see https://github.com/userpixel/micromustache/wiki/Known-issues
|
217 | * If it cannot find a value in the specified path, it may return undefined or throw an error
|
218 | * depending on the value of the `propsExist` param.
|
219 | * @param scope an object to resolve value from
|
220 | * @param varNameOrPropNames the variable name string or an array of property names (as returned by
|
221 | * `toPath()`)
|
222 | * @throws `SyntaxError` if the varName string cannot be parsed
|
223 | * @throws `ReferenceError` if the scope does not contain the requested key and the `propsExist` is
|
224 | * set to a truthy value
|
225 | * @returns the value or undefined. If path or scope are undefined or scope is null the result is
|
226 | * always undefined.
|
227 | */
|
228 | function get(scope, varNameOrPropNames, options) {
|
229 | if (options === void 0) { options = {}; }
|
230 | if (!isObj(options)) {
|
231 | throw new TypeError("get expects an object option. Got " + typeof options);
|
232 | }
|
233 | var _a = options.depth, depth = _a === void 0 ? 10 : _a;
|
234 | if (!isNum(depth) || depth <= 0) {
|
235 | throw new RangeError("Expected a positive number for depth. Got " + depth);
|
236 | }
|
237 | var propNames = Array.isArray(varNameOrPropNames)
|
238 | ? varNameOrPropNames
|
239 | : toPath.cached(varNameOrPropNames);
|
240 | var propNamesAsStr = function () { return propNames.join(' > '); };
|
241 | if (propNames.length > depth) {
|
242 | throw new ReferenceError("The path cannot be deeper than " + depth + " levels. Got \"" + propNamesAsStr() + "\"");
|
243 | }
|
244 | var currentScope = scope;
|
245 | for (var _i = 0, propNames_1 = propNames; _i < propNames_1.length; _i++) {
|
246 | var propName = propNames_1[_i];
|
247 | if (isProp(currentScope, propName)) {
|
248 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
249 | currentScope = currentScope[propName];
|
250 | }
|
251 | else if (options.propsExist) {
|
252 | throw new ReferenceError(propName + " is not defined in the scope at path: \"" + propNamesAsStr() + "\"");
|
253 | }
|
254 | else {
|
255 | return;
|
256 | }
|
257 | }
|
258 | return currentScope;
|
259 | }
|
260 |
|
261 | /**
|
262 | * This class does the heavy lifting of interpolation (putting the actual values
|
263 | * in the template).
|
264 | * This is created by the `.compile()` method and is used under the hood by
|
265 | * `.render()`, `renderFn()` and `renderFnAsync()` functions.
|
266 | */
|
267 | var Renderer = /** @class */ (function () {
|
268 | /**
|
269 | * Creates a new Renderer instance. This is called internally by the compiler.
|
270 | * @param tokens - the result of the `.tokenize()` function
|
271 | * @param options - some options for customizing the rendering process
|
272 | * @throws `TypeError` if the token is invalid
|
273 | */
|
274 | function Renderer(tokens, options) {
|
275 | var _this = this;
|
276 | if (options === void 0) { options = {}; }
|
277 | this.tokens = tokens;
|
278 | this.options = options;
|
279 | /**
|
280 | * Replaces every {{varName}} inside the template with values from the scope
|
281 | * parameter.
|
282 | *
|
283 | * @param template The template containing one or more {{varName}} as
|
284 | * placeholders for values from the `scope` parameter.
|
285 | * @param scope An object containing values for variable names from the the
|
286 | * template. If it's omitted, we default to an empty object.
|
287 | */
|
288 | this.render = function (scope) {
|
289 | if (scope === void 0) { scope = {}; }
|
290 | var varNames = _this.tokens.varNames;
|
291 | var length = varNames.length;
|
292 | _this.cacheParsedPaths();
|
293 | var values = new Array(length);
|
294 | for (var i = 0; i < length; i++) {
|
295 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
296 | values[i] = get(scope, _this.toPathCache[i], _this.options);
|
297 | }
|
298 | return _this.stringify(values);
|
299 | };
|
300 | /**
|
301 | * Same as [[render]] but accepts a resolver function which will be
|
302 | * responsible for returning a value for every varName.
|
303 | */
|
304 | this.renderFn = function (resolveFn, scope) {
|
305 | if (scope === void 0) { scope = {}; }
|
306 | var values = _this.resolveVarNames(resolveFn, scope);
|
307 | return _this.stringify(values);
|
308 | };
|
309 | /**
|
310 | * Same as [[render]] but accepts a resolver function which will be responsible
|
311 | * for returning promise that resolves to a value for every varName.
|
312 | */
|
313 | this.renderFnAsync = function (resolveFnAsync, scope) {
|
314 | if (scope === void 0) { scope = {}; }
|
315 | return Promise.all(_this.resolveVarNames(resolveFnAsync, scope)).then(function (values) {
|
316 | return _this.stringify(values);
|
317 | });
|
318 | };
|
319 | if (!isObj(tokens) ||
|
320 | !isArr(tokens.strings) ||
|
321 | !isArr(tokens.varNames) ||
|
322 | tokens.strings.length !== tokens.varNames.length + 1) {
|
323 | // This is most likely an internal error from tokenization algorithm
|
324 | throw new TypeError("Invalid tokens object");
|
325 | }
|
326 | if (!isObj(options)) {
|
327 | throw new TypeError("Options should be an object. Got a " + typeof options);
|
328 | }
|
329 | if (options.validateVarNames) {
|
330 | // trying to initialize toPathCache parses them which is also validation
|
331 | this.cacheParsedPaths();
|
332 | }
|
333 | }
|
334 | /**
|
335 | * This function is called internally for filling in the `toPathCache` cache.
|
336 | * If the `validateVarNames` option for the constructor is set to a truthy
|
337 | * value, this function is called immediately which leads to a validation as
|
338 | * well because it throws an error if it cannot parse variable names.
|
339 | */
|
340 | Renderer.prototype.cacheParsedPaths = function () {
|
341 | var varNames = this.tokens.varNames;
|
342 | if (this.toPathCache === undefined) {
|
343 | this.toPathCache = new Array(varNames.length);
|
344 | for (var i = 0; i < varNames.length; i++) {
|
345 | this.toPathCache[i] = toPath.cached(varNames[i]);
|
346 | }
|
347 | }
|
348 | };
|
349 | Renderer.prototype.resolveVarNames = function (resolveFn, scope) {
|
350 | if (scope === void 0) { scope = {}; }
|
351 | var varNames = this.tokens.varNames;
|
352 | if (!isFn(resolveFn)) {
|
353 | throw new TypeError("Expected a resolver function. Got " + String(resolveFn));
|
354 | }
|
355 | var length = varNames.length;
|
356 | var values = new Array(length);
|
357 | for (var i = 0; i < length; i++) {
|
358 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
359 | values[i] = resolveFn.call(null, varNames[i], scope);
|
360 | }
|
361 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
362 | return values;
|
363 | };
|
364 | /**
|
365 | * Puts the resolved `values` into the rest of the template (`strings`) and
|
366 | * returns the final result that'll be returned from `render()`, `renderFn()`
|
367 | * and `renderFnAsync()` functions.
|
368 | */
|
369 | Renderer.prototype.stringify = function (values) {
|
370 | var strings = this.tokens.strings;
|
371 | var explicit = this.options.explicit;
|
372 | var length = values.length;
|
373 | var ret = '';
|
374 | for (var i = 0; i < length; i++) {
|
375 | ret += strings[i];
|
376 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
377 | var value = values[i];
|
378 | if (explicit || (value !== null && value !== undefined)) {
|
379 | ret += value;
|
380 | }
|
381 | }
|
382 | ret += strings[length];
|
383 | return ret;
|
384 | };
|
385 | return Renderer;
|
386 | }());
|
387 |
|
388 | /**
|
389 | * Parses a template and returns the tokens in an object.
|
390 | *
|
391 | * @throws `TypeError` if there's an issue with its inputs
|
392 | * @throws `SyntaxError` if there's an issue with the template
|
393 | *
|
394 | * @param template the template
|
395 | * @param openSym the string that marks the start of a variable name
|
396 | * @param closeSym the string that marks the start of a variable name
|
397 | * @returns the resulting tokens as an object that has strings and variable names
|
398 | */
|
399 | function tokenize(template, options) {
|
400 | if (options === void 0) { options = {}; }
|
401 | if (!isStr(template)) {
|
402 | throw new TypeError("The template parameter must be a string. Got a " + typeof template);
|
403 | }
|
404 | if (!isObj(options)) {
|
405 | throw new TypeError("Options should be an object. Got a " + typeof options);
|
406 | }
|
407 | var _a = options.tags, tags = _a === void 0 ? ['{{', '}}'] : _a, _b = options.maxVarNameLength, maxVarNameLength = _b === void 0 ? 1000 : _b;
|
408 | if (!isArr(tags) || tags.length !== 2) {
|
409 | throw TypeError("tags should be an array of two elements. Got " + String(tags));
|
410 | }
|
411 | var openSym = tags[0], closeSym = tags[1];
|
412 | if (!isStr(openSym, 1) || !isStr(closeSym, 1) || openSym === closeSym) {
|
413 | throw new TypeError("The open and close symbols should be two distinct non-empty strings. Got \"" + openSym + "\" and \"" + closeSym + "\"");
|
414 | }
|
415 | if (!isNum(maxVarNameLength) || maxVarNameLength <= 0) {
|
416 | throw new Error("Expected a positive number for maxVarNameLength. Got " + maxVarNameLength);
|
417 | }
|
418 | var openSymLen = openSym.length;
|
419 | var closeSymLen = closeSym.length;
|
420 | var openIndex;
|
421 | var closeIndex = 0;
|
422 | var varName;
|
423 | var strings = [];
|
424 | var varNames = [];
|
425 | var currentIndex = 0;
|
426 | while (currentIndex < template.length) {
|
427 | openIndex = template.indexOf(openSym, currentIndex);
|
428 | if (openIndex === -1) {
|
429 | break;
|
430 | }
|
431 | var varNameStartIndex = openIndex + openSymLen;
|
432 | closeIndex = template
|
433 | .substr(varNameStartIndex, maxVarNameLength + closeSymLen)
|
434 | .indexOf(closeSym);
|
435 | if (closeIndex === -1) {
|
436 | throw new SyntaxError("Missing \"" + closeSym + "\" in the template for the \"" + openSym + "\" at position " + openIndex + " within " + maxVarNameLength + " characters");
|
437 | }
|
438 | closeIndex += varNameStartIndex;
|
439 | varName = template.substring(varNameStartIndex, closeIndex).trim();
|
440 | if (varName.length === 0) {
|
441 | throw new SyntaxError("Unexpected \"" + closeSym + "\" tag found at position " + openIndex);
|
442 | }
|
443 | if (varName.includes(openSym)) {
|
444 | throw new SyntaxError("Variable names cannot have \"" + openSym + "\". But at position " + openIndex + ". Got \"" + varName + "\"");
|
445 | }
|
446 | varNames.push(varName);
|
447 | closeIndex += closeSymLen;
|
448 | strings.push(template.substring(currentIndex, openIndex));
|
449 | currentIndex = closeIndex;
|
450 | }
|
451 | strings.push(template.substring(closeIndex));
|
452 | return { strings: strings, varNames: varNames };
|
453 | }
|
454 |
|
455 | /**
|
456 | * Compiles a template and returns an object with functions that render it.
|
457 | * Compilation makes repeated render calls more optimized by parsing the
|
458 | * template only once and reusing the results.
|
459 | * As a result, rendering gets 3-5x faster.
|
460 | * Caching is stored in the resulting object, so if you free up all the
|
461 | * references to that object, the caches will be garbage collected.
|
462 | *
|
463 | * @param template same as the template parameter to .render()
|
464 | * @param options some options for customizing the compilation
|
465 | * @throws `TypeError` if the template is not a string
|
466 | * @throws `TypeError` if the options is set but is not an object
|
467 | * @throws any error that [[tokenize]] or [[Renderer.constructor]] may throw
|
468 | * @returns a [[Renderer]] object which has render methods
|
469 | */
|
470 | function compile(template, options) {
|
471 | if (options === void 0) { options = {}; }
|
472 | var tokens = tokenize(template, options);
|
473 | return new Renderer(tokens, options);
|
474 | }
|
475 |
|
476 | /**
|
477 | * Replaces every {{varName}} inside the template with values from the scope
|
478 | * parameter.
|
479 | * @warning **When dealing with user input, always make sure to validate it.**
|
480 | * @param template The template containing one or more {{varName}} as
|
481 | * placeholders for values from the `scope` parameter.
|
482 | * @param scope An object containing values for variable names from the the
|
483 | * template. If it's omitted, we default to an empty object.
|
484 | * Since functions are objects in javascript, the `scope` can technically be a
|
485 | * function too but it won't be called. It'll be treated as an object and its
|
486 | * properties will be used for the lookup.
|
487 | * @param options same options as the [[compile]] function
|
488 | * @throws any error that [[compile]] or [[Renderer.render]] may throw
|
489 | * @returns Template where its variable names replaced with
|
490 | * corresponding values.
|
491 | */
|
492 | function render(template, scope, options) {
|
493 | var renderer = compile(template, options);
|
494 | return renderer.render(scope);
|
495 | }
|
496 | /**
|
497 | * Same as [[render]] but accepts a resolver function which will be responsible
|
498 | * for returning a value for every varName.
|
499 | * @param resolveFn a function that takes a variable name and resolves it to a value.
|
500 | * The value can be a number, string or boolean. If it is not, it'll be "stringified".
|
501 | * @throws any error that [[compile]] or [[Renderer.renderFn]] may throw
|
502 | * @returns Template where its variable names replaced with what is returned from the resolver
|
503 | * function for each varName.
|
504 | */
|
505 | function renderFn(template, resolveFn, scope, options) {
|
506 | var renderer = compile(template, options);
|
507 | return renderer.renderFn(resolveFn, scope);
|
508 | }
|
509 | /**
|
510 | * Same as [[renderFn]] but supports asynchronous resolver functions
|
511 | * (a function that returns a promise instead of the value).
|
512 | * @param resolveFn an async function that takes a variable name and resolves it to a value.
|
513 | * The value can be a number, string or boolean. If it is not, it'll be "stringified".
|
514 | * @throws any error that [[compile]] or [[Renderer.renderFnAsync]] may throw
|
515 | * @returns a promise that when resolved contains the template where its variable names replaced
|
516 | * with what is returned from the resolver function for each varName.
|
517 | */
|
518 | function renderFnAsync(template, resolveFnAsync, scope, options) {
|
519 | var renderer = compile(template, options);
|
520 | return renderer.renderFnAsync(resolveFnAsync, scope);
|
521 | }
|
522 |
|
523 | exports.Renderer = Renderer;
|
524 | exports.compile = compile;
|
525 | exports.get = get;
|
526 | exports.render = render;
|
527 | exports.renderFn = renderFn;
|
528 | exports.renderFnAsync = renderFnAsync;
|
529 | exports.tokenize = tokenize;
|
530 |
|
531 | Object.defineProperty(exports, '__esModule', { value: true });
|
532 |
|
533 | })));
|
534 | //# sourceMappingURL=micromustache.umd.js.map
|