custom-string-formatter
Version:
Customizable String Formatter
148 lines (147 loc) • 4.89 kB
JavaScript
;
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 };
});
}