Skip to content

Commit

Permalink
feat: getResolver utility
Browse files Browse the repository at this point in the history
Returns resolved data in form of separate tokens
  • Loading branch information
medikoo committed Sep 26, 2018
1 parent 6867021 commit 516656c
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 101 deletions.
72 changes: 72 additions & 0 deletions 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);
};
};
91 changes: 16 additions & 75 deletions 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;
};
};
98 changes: 98 additions & 0 deletions 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();
});
31 changes: 5 additions & 26 deletions test/index.js
Expand Up @@ -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({
Expand All @@ -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"
Expand Down

0 comments on commit 516656c

Please sign in to comment.