/** * Refactored by zhennann * (C) 2010 Charlie Robbins * MIT LICENCE */ import { getText } from '@cabloy/localeutil'; import { SPLAT } from './types.ts'; const formatRegExp = /%[scdjifoO%]/g; const escapedPercent = /%%/g; class Splatter { options: any; constructor(opts) { this.options = opts; } _splat(info, tokens) { const msg = info.message; const splat = info[SPLAT] || info.splat || []; const percents = msg.match(escapedPercent); const escapes = (percents && percents.length) || 0; // The expected splat is the number of tokens minus the number of escapes // e.g. // - { expectedSplat: 3 } '%d %s %j' // - { expectedSplat: 5 } '[%s] %d%% %d%% %s %j' // // Any "meta" will be arugments in addition to the expected splat size // regardless of type. e.g. // // logger.log('info', '%d%% %s %j', 100, 'wow', { such: 'js' }, { thisIsMeta: true }); // would result in splat of four (4), but only three (3) are expected. Therefore: // // extraSplat = 3 - 4 = -1 // metas = [100, 'wow', { such: 'js' }, { thisIsMeta: true }].splice(-1, -1 * -1); // splat = [100, 'wow', { such: 'js' }] const expectedSplat = tokens.length - escapes; const extraSplat = expectedSplat - splat.length; const metas = extraSplat < 0 ? splat.splice(extraSplat, -1 * extraSplat) : []; // Now that { splat } has been separated from any potential { meta }. we // can assign this to the `info` object and write it to our format stream. // If the additional metas are **NOT** objects or **LACK** enumerable properties // you are going to have a bad time. const metalen = metas.length; if (metalen) { for (let i = 0; i < metalen; i++) { Object.assign(info, metas[i]); } } info.message = getText(msg, ...splat); return info; } transform(info) { const msg = info.message; const splat = info[SPLAT] || info.splat; // No need to process anything if splat is undefined if (!splat || !splat.length) { return info; } // Extract tokens, if none available default to empty array to // ensure consistancy in expected results const tokens = msg && msg.match && msg.match(formatRegExp); // This condition will take care of inputs with info[SPLAT] // but no tokens present if (!tokens && (splat || splat.length)) { const metas = splat.length > 1 ? splat.splice(0) : splat; // Now that { splat } has been separated from any potential { meta }. we // can assign this to the `info` object and write it to our format stream. // If the additional metas are **NOT** objects or **LACK** enumerable properties // you are going to have a bad time. const metalen = metas.length; if (metalen) { for (let i = 0; i < metalen; i++) { Object.assign(info, metas[i]); } } return info; } if (tokens) { return this._splat(info, tokens); } return info; } } export function splatter(opts?) { return new Splatter(opts); }