UNPKG

custom-string-formatter

Version:
148 lines (147 loc) 4.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createFormatter = createFormatter; exports.hasVariables = hasVariables; exports.countVariables = countVariables; exports.enumVariables = enumVariables; const resolver_1 = require("./resolver"); const encoding_1 = require("./encoding"); const formatRegEx = /\$(?:({)|(\()|(<))\s*([\w$.]+)((\s*\|\s*[\w$]*(\s*:\s*[^{}<>()]*)*)*)\s*(?:(?=\2)(?=\3)}|(?=\1)(?=\3)\)|(?=\1)(?=\2)>)/g; /** * Creates a formatter function. * * @returns * A function that formats a string from an object-parameter, and according to the specified configurator. * * @example * import {createFormatter, IFormatter} from 'custom-string-formatter'; * * class BaseFormatter implements IFormatter { * format(value: any): string { * return (value ?? 'null').toString(); * } * } * * const format = createFormatter(new BaseFormatter()); * * format('Hello ${title} ${name}!', {title: 'Mr.', name: 'Foreman'}); * //=> Hello Mr. Foreman! * * @example * // Function createFormatter expects only an interface, * // so using a class is not really necessary: * * const format = createFormatter({ * format(value: any): string { * return (value ?? 'null').toString(); * } * }); */ function createFormatter(base) { return function (text, params) { return text.replace(formatRegEx, (...args) => { const prop = args[4]; // property name const filters = args[5]; // filters, if specified let { exists, value, ctx } = (0, resolver_1.resolveProperty)(prop, params); if (!exists) { if (typeof base.getDefaultValue !== 'function') { throw new Error(`Property ${JSON.stringify(prop)} does not exist`); } value = base.getDefaultValue(prop, params); } if (filters) { value = filters .split('|') .map(a => a.trim()) .filter(a => a) .reduce((p, c) => { const [fName, ...args] = c.split(':').map(a => a.trim()); let f = base.filters?.[fName]; if (!f && typeof base.getDefaultFilter === 'function') { f = base.getDefaultFilter(fName, args); } if (!f) { throw new Error(`Filter ${JSON.stringify(fName)} not recognized`); } const decodedArgs = typeof f.decodeArguments === 'function' ? f.decodeArguments(args) : args.map(a => (0, encoding_1.decodeFilterArg)(a)); return f.transform(p, decodedArgs, ctx); }, value); } return base.format(value); }); }; } /** * A fast check if a string has valid variables in it. * * @returns * Boolean flag, indicating if the string has valid variables in it. * * @example * import {hasVariables} from 'custom-string-formatter'; * * hasVariables('${value}'); //=> true * * hasVariables('some text'); //=> false * * @see {@link countVariables}, {@link enumVariables} */ function hasVariables(text) { return text.search(formatRegEx) >= 0; } /** * A fast count of valid variables in a string. * * @returns * Number of valid variables in the string. * * @example * import {countVariables} from 'custom-string-formatter'; * * countVariables('some text'); //=> 0 * * countVariables('${first} ${second}'); //=> 2 * * @see {@link hasVariables}, {@link enumVariables} */ function countVariables(text) { return text.match(formatRegEx)?.length ?? 0; } /** * Enumerates and parses variables from a string, for any kind of reference analysis. * * @param text * Text string with variables. * * @returns IVariable[] * An array of matched variables (as descriptors) * * @example * import {enumVariables} from 'custom-string-formatter'; * * enumVariables('${title} ${name} address: ${address | json}'); * // ==> * [ * {match: '${title}', property: 'title', filters: []}, * {match: '${name}', property: 'name', filters: []}, * { * match: '${address | json}', * property: 'address', * filters: [{name: 'json', args: []}] * } * ] * * @see {@link hasVariables}, {@link countVariables} */ function enumVariables(text) { return (text.match(formatRegEx) || []) .map(m => { const a = m.match(/.\s*([\w$.]+)((\s*\|\s*[\w$]*(\s*:\s*[^}>)]*)*)*)/); const filtersWithArgs = a[2] ? a[2].split('|').map(a => a.trim()).filter(a => a) : []; const filters = filtersWithArgs.map(a => { const [name, ...args] = a.split(':').map(b => b.trim()); return { name, args }; }); return { match: m, property: a[1], filters }; }); }