1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | const kindOf = require('mout/lang/kindOf');
|
9 | const repeat = require('../string/repeat');
|
10 |
|
11 | var re = {
|
12 | not_string : /[^s]/,
|
13 | number : /[diefg]/,
|
14 | json : /[j]/,
|
15 | not_json : /[^j]/,
|
16 | text : /^[^\x25]+/,
|
17 | modulo : /^\x25{2}/,
|
18 | placeholder : /^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosuxX])/,
|
19 | key : /^([a-z_][a-z_\d]*)/i,
|
20 | key_access : /^\.([a-z_][a-z_\d]*)/i,
|
21 | index_access : /^\[(\d+)\]/,
|
22 | sign : /^[+-]/
|
23 | };
|
24 |
|
25 | function sprintf() {
|
26 | var key = arguments[0];
|
27 | var cache = sprintf.cache;
|
28 | if(!(cache[key] && cache.hasOwnProperty(key)))
|
29 | cache[key] = sprintf.parse(key);
|
30 | return sprintf.format.call(null, cache[key], arguments);
|
31 | }
|
32 |
|
33 |
|
34 | sprintf.format = function(parse_tree, argv) {
|
35 | var cursor = 1;
|
36 | var tree_length = parse_tree.length;
|
37 | var node_type = "";
|
38 | var output = [];
|
39 | var is_positive = true;
|
40 | var sign = "";
|
41 |
|
42 | var arg;
|
43 | var i;
|
44 | var k;
|
45 | var match;
|
46 | var pad;
|
47 | var pad_character;
|
48 | var pad_length;
|
49 |
|
50 | for(i = 0; i < tree_length; i++) {
|
51 | node_type = kindOf(parse_tree[i]);
|
52 | if(node_type === "String") {
|
53 | output[output.length] = parse_tree[i];
|
54 | } else if(node_type === "Array") {
|
55 | match = parse_tree[i];
|
56 | if(match[2]) {
|
57 | arg = argv[cursor];
|
58 | for(k = 0; k < match[2].length; k++) {
|
59 | if(!arg.hasOwnProperty(match[2][k]))
|
60 | throw new Error(sprintf("[sprintf] property '%s' does not exist", match[2][k]));
|
61 | arg = arg[match[2][k]];
|
62 | }
|
63 | } else if(match[1]) {
|
64 | arg = argv[match[1]];
|
65 | } else {
|
66 | arg = argv[cursor++];
|
67 | }
|
68 |
|
69 | if(kindOf(arg) == "Function")
|
70 | arg = arg();
|
71 |
|
72 | if(re.not_string.test(match[8]) && re.not_json.test(match[8]) && (kindOf(arg) != "Number" && isNaN(arg)))
|
73 | throw new TypeError(sprintf("[sprintf] expecting number but found %s", kindOf(arg)));
|
74 |
|
75 | if(re.number.test(match[8]))
|
76 | is_positive = arg >= 0;
|
77 |
|
78 | switch(match[8]) {
|
79 | case "b":
|
80 | arg = arg.toString(2);
|
81 | break;
|
82 | case "c":
|
83 | arg = String.fromCharCode(arg);
|
84 | break;
|
85 | case "d":
|
86 | case "i":
|
87 | arg = parseInt(arg, 10);
|
88 | break;
|
89 | case "j":
|
90 | arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0);
|
91 | break;
|
92 | case "e":
|
93 | arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential();
|
94 | break;
|
95 | case "f":
|
96 | arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg);
|
97 | break;
|
98 | case "g":
|
99 | arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg);
|
100 | break;
|
101 | case "o":
|
102 | arg = arg.toString(8);
|
103 | break;
|
104 | case "s":
|
105 | arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg);
|
106 | break;
|
107 | case "u":
|
108 | arg = arg >>> 0;
|
109 | break;
|
110 | case "x":
|
111 | arg = arg.toString(16);
|
112 | break;
|
113 | case "X":
|
114 | arg = arg.toString(16).toUpperCase();
|
115 | break;
|
116 | }
|
117 | if(re.json.test(match[8])) {
|
118 | output[output.length] = arg;
|
119 | } else {
|
120 | if(re.number.test(match[8]) && (!is_positive || match[3])) {
|
121 | sign = is_positive ? "+" : "-";
|
122 | arg = arg.toString().replace(re.sign, "");
|
123 | } else {
|
124 | sign = "";
|
125 | }
|
126 | pad_character = match[4] ? match[4] === "0" ? "0" : match[4].charAt(1) : " ";
|
127 | pad_length = match[6] - (sign + arg).length;
|
128 | pad = match[6] ? (pad_length > 0 ? repeat(pad_character, pad_length) : "") : "";
|
129 | output[output.length] = match[5] ? sign + arg + pad : (pad_character === "0" ? sign + pad + arg : pad + sign + arg);
|
130 | }
|
131 | }
|
132 | }
|
133 | return output.join("");
|
134 | };
|
135 |
|
136 | sprintf.cache = {};
|
137 |
|
138 |
|
139 | sprintf.parse = function(fmt) {
|
140 | var _fmt = fmt;
|
141 | var match = [];
|
142 | var parse_tree = [];
|
143 | var arg_names = 0;
|
144 | while(_fmt) {
|
145 | if((match = re.text.exec(_fmt)) !== null) {
|
146 | parse_tree[parse_tree.length] = match[0];
|
147 | } else if((match = re.modulo.exec(_fmt)) !== null) {
|
148 | parse_tree[parse_tree.length] = "%";
|
149 | } else if((match = re.placeholder.exec(_fmt)) !== null) {
|
150 | if(match[2]) {
|
151 | arg_names |= 1;
|
152 | var field_list = [];
|
153 | var replacement_field = match[2];
|
154 | var field_match = [];
|
155 | if((field_match = re.key.exec(replacement_field)) !== null) {
|
156 | field_list[field_list.length] = field_match[1];
|
157 | while((replacement_field = replacement_field.substring(field_match[0].length)) !== "") {
|
158 | if((field_match = re.key_access.exec(replacement_field)) !== null)
|
159 | field_list[field_list.length] = field_match[1];
|
160 | else if((field_match = re.index_access.exec(replacement_field)) !== null)
|
161 | field_list[field_list.length] = field_match[1];
|
162 | else
|
163 | throw new SyntaxError("[sprintf] failed to parse named argument key");
|
164 | }
|
165 | } else {
|
166 | throw new SyntaxError("[sprintf] failed to parse named argument key");
|
167 | }
|
168 | match[2] = field_list;
|
169 | } else {
|
170 | arg_names |= 2;
|
171 | }
|
172 |
|
173 | if(arg_names === 3)
|
174 | throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");
|
175 |
|
176 | parse_tree[parse_tree.length] = match;
|
177 | } else {
|
178 | throw new SyntaxError("[sprintf] unexpected placeholder");
|
179 | }
|
180 | _fmt = _fmt.substring(match[0].length);
|
181 | }
|
182 | return parse_tree;
|
183 | };
|
184 |
|
185 |
|
186 | var vsprintf = function(fmt, argv, _argv) {
|
187 | _argv = (argv || []).slice(0);
|
188 | _argv.splice(0, 0, fmt);
|
189 | return sprintf.apply(null, _argv);
|
190 | };
|
191 |
|
192 | module.exports = sprintf;
|
193 | module.exports.vsprintf = vsprintf;
|