From 516656cf9d9a14cdfb0d1ee01ee7b12504521fd5 Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 26 Sep 2018 15:07:51 +0200 Subject: [PATCH] feat: getResolver utility Returns resolved data in form of separate tokens --- get-resolver.js | 72 ++++++++++++++++++++++++++++++++ index.js | 91 ++++++++-------------------------------- test/get-resolver.js | 98 ++++++++++++++++++++++++++++++++++++++++++++ test/index.js | 31 +++----------- 4 files changed, 191 insertions(+), 101 deletions(-) create mode 100644 get-resolver.js create mode 100644 test/get-resolver.js diff --git a/get-resolver.js b/get-resolver.js new file mode 100644 index 0000000..b48a342 --- /dev/null +++ b/get-resolver.js @@ -0,0 +1,72 @@ +"use strict"; + +var ensureObject = require("es5-ext/object/valid-object") + , objForEach = require("es5-ext/object/for-each") + , ensurePlainFunction = require("es5-ext/object/ensure-plain-function") + , primitiveSet = require("es5-ext/object/primitive-set") + , typeChars = require("./lib/type-chars") + , parse = require("./parse"); + +var slice = Array.prototype.slice; + +var customTypes = primitiveSet("literal", "rest"); + +var validateModifiers = function (modifiers) { + objForEach(ensureObject(modifiers), function (value, type) { + if (!hasOwnProperty.call(typeChars, type) && !customTypes[type]) { + throw new TypeError("Invalid modifier type: " + type); + } + ensurePlainFunction(value); + }); +}; + +var resolveRest = function (modifier, data, args, placeholderArgsLength) { + if (!modifier || args.length <= placeholderArgsLength) return null; + return modifier(slice.call(args, placeholderArgsLength), data); +}; + +var resolveResultWithPlaceholders = function (modifiers, data, args, result) { + var extraPlaceholderArgsLength = 0; + var isParameterIndexingValid = data.isParameterIndexingValid; + result.substitutions = data.placeholders.map(function (placeholder, index) { + var parameter = index + 1; + if (placeholder.width === "*") ++extraPlaceholderArgsLength; + if (placeholder.precision === "*") ++extraPlaceholderArgsLength; + if (!isParameterIndexingValid) return "[invalid placeholder parameters]"; + if (!modifiers[placeholder.type]) return placeholder.content; + if (placeholder.parameter) parameter = placeholder.parameter; + if (parameter > args.length) return placeholder.content; + var placeholderIndex = parameter + extraPlaceholderArgsLength - 1; + var arg = args[placeholderIndex]; + return modifiers[placeholder.type](arg, placeholder, placeholderIndex, args); + }); + result.rest = resolveRest( + modifiers.rest, data, args, data.placeholders.length + extraPlaceholderArgsLength + ); + return result; +}; + +module.exports = function (modifiers) { + validateModifiers(modifiers); + + return function (format/*, ...params*/) { + var result; + if (typeof format !== "string") { + return { + literals: [], + substitutions: [], + rest: resolveRest(modifiers.rest, null, arguments, 0) + }; + } + var data = parse(format); + var args = slice.call(arguments, 1); + + result = { literals: data.literals }; + if (data.literals.length <= 1) { + result.substitutions = []; + result.rest = resolveRest(modifiers.rest, data, args, 0); + return result; + } + return resolveResultWithPlaceholders(modifiers, data, args, result); + }; +}; diff --git a/index.js b/index.js index 1315a3b..2e580fd 100644 --- a/index.js +++ b/index.js @@ -1,82 +1,23 @@ "use strict"; -var aFrom = require("es5-ext/array/from") - , identity = require("es5-ext/function/identity") - , ensureObject = require("es5-ext/object/valid-object") - , objForEach = require("es5-ext/object/for-each") - , ensurePlainFunction = require("es5-ext/object/ensure-plain-function") - , primitiveSet = require("es5-ext/object/primitive-set") - , typeChars = require("./lib/type-chars") - , parse = require("./parse"); - -var slice = Array.prototype.slice; - -var customTypes = primitiveSet("literal", "rest"); - -var validateModifiers = function (modifiers) { - objForEach(ensureObject(modifiers), function (value, type) { - if (!hasOwnProperty.call(typeChars, type) && !customTypes[type]) { - throw new TypeError("Invalid modifier type: " + type); - } - ensurePlainFunction(value); - }); -}; - -var resolveRest = function (modifier, data, args, placeholderArgsLength) { - if (!modifier || args.length <= placeholderArgsLength) return ""; - return modifier(args.slice(placeholderArgsLength), data); -}; - -var resolveStringWithPlaceholders = function (modifiers, data, args) { - var extraPlaceholderArgsLength = 0; - var isParameterIndexingValid = data.isParameterIndexingValid; - var resolvedString = data.literals.reduce(function (result, literal, index) { - if (index === 1 && modifiers.literal) result = modifiers.literal(result, args); - var placeholder = data.placeholders[index - 1]; - var resolvedPlaceholder; - if (placeholder.width === "*") ++extraPlaceholderArgsLength; - if (placeholder.precision === "*") ++extraPlaceholderArgsLength; - if (!isParameterIndexingValid) { - resolvedPlaceholder = "[invalid placeholder parameters]"; - } else if (modifiers[placeholder.type]) { - if (placeholder.parameter) index = placeholder.parameter; - if (index > args.length) { - resolvedPlaceholder = placeholder.content; - } else { - index += extraPlaceholderArgsLength - 1; - var arg = args[index]; - resolvedPlaceholder = modifiers[placeholder.type](arg, placeholder, index, args); - } - } else { - resolvedPlaceholder = placeholder.content; - } - return result + resolvedPlaceholder + (modifiers.literal || identity)(literal, args); - }); - return ( - resolvedString + - resolveRest( - modifiers.rest, data, args, data.placeholders.length + extraPlaceholderArgsLength - ) - ); -}; - -var resolveStringWithoutPlaceholders = function (modifiers, data, args) { - var resolvedString = data.literals[0]; - if (modifiers.literal) resolvedString = modifiers.literal(resolvedString); - return resolvedString + resolveRest(modifiers.rest, data, args, data.placeholders.length); -}; +var getResolver = require("./get-resolver"); module.exports = function (modifiers) { - validateModifiers(modifiers); - - return function (format/*, ...params*/) { - if (typeof format !== "string") { - return modifiers.rest ? modifiers.rest(aFrom(arguments)) : ""; + var resolve = getResolver(modifiers); + + return function (formatIgnored/*, ...params*/) { + var data = resolve.apply(null, arguments); + var literals = data.literals; + var substitutions = data.substitutions; + if (modifiers.literal) { + literals = literals.map(function (literal) { return modifiers.literal(literal); }); } - var data = parse(format); - var args = slice.call(arguments, 1); - - if (data.literals.length > 1) return resolveStringWithPlaceholders(modifiers, data, args); - return resolveStringWithoutPlaceholders(modifiers, data, args); + var resolvedString = literals.length + ? literals.reduce(function (resolved, literal, index) { + return resolved + substitutions[index - 1] + literal; + }) + : ""; + if (data.rest) resolvedString += data.rest; + return resolvedString; }; }; diff --git a/test/get-resolver.js b/test/get-resolver.js new file mode 100644 index 0000000..225e63d --- /dev/null +++ b/test/get-resolver.js @@ -0,0 +1,98 @@ +"use strict"; + +var test = require("tape") + , modifierD = require("../modifiers/d") + , modifierS = require("../modifiers/s") + , getResolver = require("../get-resolver"); + +test("getResolver", function (t) { + t.test("Should resolve", function (t) { + // eslint-disable-next-line id-length + var resolve = getResolver({ d: modifierD, s: modifierS }); + t.deepEqual( + resolve("foo raz", "marko"), { literals: ["foo raz"], substitutions: [], rest: null }, + "No placeholders" + ); + t.deepEqual( + resolve("foo %s", "marko"), + { literals: ["foo ", ""], substitutions: ["marko"], rest: null }, "Single placeholder" + ); + t.deepEqual( + resolve("foo %s %d", "marko", 12), + { literals: ["foo ", " ", ""], substitutions: ["marko", "12"], rest: null }, + "Two placeholders" + ); + t.deepEqual( + resolve("foo %s %d", "marko", 12, "elo"), + { literals: ["foo ", " ", ""], substitutions: ["marko", "12"], rest: null }, + "Two placeholders with arguments overflow and no rest handling defined" + ); + t.deepEqual( + resolve("foo %s %d", "marko"), + { literals: ["foo ", " ", ""], substitutions: ["marko", "%d"], rest: null }, + "foo marko %d", "Two placeholders with argument missing" + ); + t.deepEqual( + resolve("foo %2$s %1$d", 12, "bar"), + { literals: ["foo ", " ", ""], substitutions: ["bar", "12"], rest: null }, + "Parameters swap" + ); + t.deepEqual( + resolve("foo %*d", 10, 12), + { literals: ["foo ", ""], substitutions: ["12"], rest: null }, "Dynamic width" + ); + t.deepEqual( + resolve("foo %.*d", 10, 12), + { literals: ["foo ", ""], substitutions: ["12"], rest: null }, "Dynamic precision" + ); + t.deepEqual( + resolve("foo %2$s %2$d", 12, "bar"), + { + literals: ["foo ", " ", ""], + substitutions: [ + "[invalid placeholder parameters]", "[invalid placeholder parameters]" + ], + rest: null + }, + + "Invalid parameters setup" + ); + t.deepEqual( + resolve(12, 13), { literals: [], substitutions: [], rest: null }, + "Non-string first argument without rest" + ); + + resolve = getResolver({ + d: modifierD, + // eslint-disable-next-line id-length + s: modifierS, + rest: function (args, data) { return (data ? " " : "") + args.join("-"); } + }); + + t.deepEqual( + resolve("foo %s", "marko", 12, "elo"), + { literals: ["foo ", ""], substitutions: ["marko"], rest: " 12-elo" }, + "Arguments overflow with rest handling" + ); + t.deepEqual( + resolve("foo %*s", 10, "marko", 12, "elo"), + { literals: ["foo ", ""], substitutions: ["marko"], rest: " 12-elo" }, + "Arguments overflow with rest handling and width shift" + ); + t.deepEqual( + resolve("foo %x", "elo"), { literals: ["foo ", ""], substitutions: ["%x"], rest: null }, + "Placeholder content on unknown type" + ); + t.deepEqual( + resolve(12, 13), { literals: [], substitutions: [], rest: "12-13" }, + "Non-string first argument with rest" + ); + t.end(); + }); + + t.throws( + function () { getResolver({ foo: modifierD }); }, TypeError, "Reject invalid modifiers map" + ); + + t.end(); +}); diff --git a/test/index.js b/test/index.js index bece17f..bc1fc66 100644 --- a/test/index.js +++ b/test/index.js @@ -5,28 +5,12 @@ var test = require("tape") , modifierS = require("../modifiers/s") , getResolver = require("../"); -test("Should", function (t) { - t.test("Resolve", function (t) { +test("(main)", function (t) { + t.test("Should resolve", function (t) { // eslint-disable-next-line id-length var resolve = getResolver({ d: modifierD, s: modifierS }); t.equal(resolve("foo raz", "marko"), "foo raz", "No placeholders"); - t.equal(resolve("foo %s", "marko"), "foo marko", "Single placeholder"); - t.equal(resolve("foo %s %d", "marko", 12), "foo marko 12", "Two placeholders"); - t.equal( - resolve("foo %s %d", "marko", 12, "elo"), "foo marko 12", - "Two placeholders with arguments overflow and no rest handling defined" - ); - t.equal( - resolve("foo %s %d", "marko"), "foo marko %d", "Two placeholders with argument missing" - ); - t.equal(resolve("foo %2$s %1$d", 12, "bar"), "foo bar 12", "Parameters swap"); - t.equal(resolve("foo %*d", 10, 12), "foo 12", "Dynamic width"); - t.equal(resolve("foo %.*d", 10, 12), "foo 12", "Dynamic precision"); - t.equal( - resolve("foo %2$s %2$d", 12, "bar"), - "foo [invalid placeholder parameters] [invalid placeholder parameters]", - "Invalid parameters setup" - ); + t.equal(resolve("foo %s %d", "marko", 12), "foo marko 12", "Placeholders"); t.equal(resolve(12, 13), "", "Non-string first argument without rest"); resolve = getResolver({ @@ -40,17 +24,12 @@ test("Should", function (t) { resolve("foo %s", "marko", 12, "elo"), "foo marko 12-elo", "Arguments overflow with rest handling" ); - t.equal( - resolve("foo %*s", 10, "marko", 12, "elo"), "foo marko 12-elo", - "Arguments overflow with rest handling and width shift" - ); - t.equal(resolve("foo %x", "elo"), "foo %x", "Placeholder content on unknown type"); t.equal(resolve(12, 13), "12-13", "Non-string first argument with rest"); t.end(); }); var resolve = getResolver({ d: modifierD, literal: function (str) { return str + "foo"; } }); - t.equal(resolve("mar %d ko", 12), "mar foo12 kofoo", "Support 'literal' modifier"); - t.equal(resolve("marlo"), "marlofoo", "Support 'literal' modifier with no placeholder"); + t.equal(resolve("mar %d ko", 12), "mar foo12 kofoo", "Shouold support 'literal' modifier"); + t.equal(resolve("marlo"), "marlofoo", "Shoud support 'literal' modifier with no placeholder"); t.throws( function () { getResolver({ foo: modifierD }); }, TypeError, "Reject invalid modifiers map"