nyks
Version:
nodejs exupery style
196 lines (177 loc) • 6.16 kB
JavaScript
;
/**
* Note that this module is exclude from istanbul code coverage
* TODO : complete tests (see tests/sprintf.js) for full coverage
*/
/* eslint no-prototype-builtins: "off" */
/* istanbul ignore file */
const kindOf = require('mout/lang/kindOf');
const repeat = require('../string/repeat');
var re = {
not_string : /[^s]/,
number : /[diefg]/,
json : /[j]/,
not_json : /[^j]/,
text : /^[^\x25]+/,
modulo : /^\x25{2}/,
placeholder : /^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosuxX])/,
key : /^([a-z_][a-z_\d]*)/i,
key_access : /^\.([a-z_][a-z_\d]*)/i,
index_access : /^\[(\d+)\]/,
sign : /^[+-]/
};
function sprintf() {
var key = arguments[0];
var cache = sprintf.cache;
if(!(cache[key] && cache.hasOwnProperty(key)))
cache[key] = sprintf.parse(key);
return sprintf.format.call(null, cache[key], arguments);
}
sprintf.format = function(parse_tree, argv) {
var cursor = 1;
var tree_length = parse_tree.length;
var node_type = "";
var output = [];
var is_positive = true;
var sign = "";
var arg;
var i;
var k;
var match;
var pad;
var pad_character;
var pad_length;
for(i = 0; i < tree_length; i++) {
node_type = kindOf(parse_tree[i]);
if(node_type === "String") {
output[output.length] = parse_tree[i];
} else if(node_type === "Array") {
match = parse_tree[i]; // convenience purposes only
if(match[2]) { // keyword argument
arg = argv[cursor];
for(k = 0; k < match[2].length; k++) {
if(!arg.hasOwnProperty(match[2][k]))
throw new Error(sprintf("[sprintf] property '%s' does not exist", match[2][k]));
arg = arg[match[2][k]];
}
} else if(match[1]) { // positional argument (explicit)
arg = argv[match[1]];
} else { // positional argument (implicit)
arg = argv[cursor++];
}
if(kindOf(arg) == "Function")
arg = arg();
if(re.not_string.test(match[8]) && re.not_json.test(match[8]) && (kindOf(arg) != "Number" && isNaN(arg)))
throw new TypeError(sprintf("[sprintf] expecting number but found %s", kindOf(arg)));
if(re.number.test(match[8]))
is_positive = arg >= 0;
switch(match[8]) {
case "b":
arg = arg.toString(2);
break;
case "c":
arg = String.fromCharCode(arg);
break;
case "d":
case "i":
arg = parseInt(arg, 10);
break;
case "j":
arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0);
break;
case "e":
arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential();
break;
case "f":
arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg);
break;
case "g":
arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg);
break;
case "o":
arg = arg.toString(8);
break;
case "s":
arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg);
break;
case "u":
arg = arg >>> 0;
break;
case "x":
arg = arg.toString(16);
break;
case "X":
arg = arg.toString(16).toUpperCase();
break;
}
if(re.json.test(match[8])) {
output[output.length] = arg;
} else {
if(re.number.test(match[8]) && (!is_positive || match[3])) {
sign = is_positive ? "+" : "-";
arg = arg.toString().replace(re.sign, "");
} else {
sign = "";
}
pad_character = match[4] ? match[4] === "0" ? "0" : match[4].charAt(1) : " ";
pad_length = match[6] - (sign + arg).length;
pad = match[6] ? (pad_length > 0 ? repeat(pad_character, pad_length) : "") : "";
output[output.length] = match[5] ? sign + arg + pad : (pad_character === "0" ? sign + pad + arg : pad + sign + arg);
}
}
}
return output.join("");
};
sprintf.cache = {};
/* istanbul ignore next */
sprintf.parse = function(fmt) {
var _fmt = fmt;
var match = [];
var parse_tree = [];
var arg_names = 0;
while(_fmt) {
if((match = re.text.exec(_fmt)) !== null) {
parse_tree[parse_tree.length] = match[0];
} else if((match = re.modulo.exec(_fmt)) !== null) {
parse_tree[parse_tree.length] = "%";
} else if((match = re.placeholder.exec(_fmt)) !== null) {
if(match[2]) {
arg_names |= 1;
var field_list = [];
var replacement_field = match[2];
var field_match = [];
if((field_match = re.key.exec(replacement_field)) !== null) {
field_list[field_list.length] = field_match[1];
while((replacement_field = replacement_field.substring(field_match[0].length)) !== "") {
if((field_match = re.key_access.exec(replacement_field)) !== null)
field_list[field_list.length] = field_match[1];
else if((field_match = re.index_access.exec(replacement_field)) !== null)
field_list[field_list.length] = field_match[1];
else
throw new SyntaxError("[sprintf] failed to parse named argument key");
}
} else {
throw new SyntaxError("[sprintf] failed to parse named argument key");
}
match[2] = field_list;
} else {
arg_names |= 2;
}
if(arg_names === 3)
throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");
parse_tree[parse_tree.length] = match;
} else {
throw new SyntaxError("[sprintf] unexpected placeholder");
}
_fmt = _fmt.substring(match[0].length);
}
return parse_tree;
};
/* istanbul ignore next */
var vsprintf = function(fmt, argv, _argv) {
_argv = (argv || []).slice(0);
_argv.splice(0, 0, fmt);
return sprintf.apply(null, _argv);
};
module.exports = sprintf;
module.exports.vsprintf = vsprintf;