1 |
|
2 | var util = require('util');
|
3 |
|
4 | var tokenize = function(/*String*/ str, /*RegExp*/ re, /*Function?*/ parseDelim, /*Object?*/ instance){
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | var tokens = [];
|
13 | var match, content, lastIndex = 0;
|
14 | while(match = re.exec(str)){
|
15 | content = str.slice(lastIndex, re.lastIndex - match[0].length);
|
16 | if(content.length){
|
17 | tokens.push(content);
|
18 | }
|
19 | if(parseDelim){
|
20 | var parsed = parseDelim.apply(instance, match.slice(1).concat(tokens.length));
|
21 | if(typeof parsed != 'undefined'){
|
22 | if(parsed.specifier === '%'){
|
23 | tokens.push('%');
|
24 | }else{
|
25 | tokens.push(parsed);
|
26 | }
|
27 | }
|
28 | }
|
29 | lastIndex = re.lastIndex;
|
30 | }
|
31 | content = str.slice(lastIndex);
|
32 | if(content.length){
|
33 | tokens.push(content);
|
34 | }
|
35 | return tokens;
|
36 | }
|
37 |
|
38 | var Formatter = function(/*String*/ format){
|
39 | var tokens = [];
|
40 | this._mapped = false;
|
41 | this._format = format;
|
42 | this._tokens = tokenize(format, this._re, this._parseDelim, this);
|
43 | }
|
44 |
|
45 | Formatter.prototype._re = /\%(?:\(([\w_]+)\)|([1-9]\d*)\$)?([0 +\-\#]*)(\*|\d+)?(\.)?(\*|\d+)?[hlL]?([\%bscdeEfFgGioOuxX])/g;
|
46 | Formatter.prototype._parseDelim = function(mapping, intmapping, flags, minWidth, period, precision, specifier){
|
47 | if(mapping){
|
48 | this._mapped = true;
|
49 | }
|
50 | return {
|
51 | mapping: mapping,
|
52 | intmapping: intmapping,
|
53 | flags: flags,
|
54 | _minWidth: minWidth,
|
55 | period: period,
|
56 | _precision: precision,
|
57 | specifier: specifier
|
58 | };
|
59 | };
|
60 | Formatter.prototype._specifiers = {
|
61 | b: {
|
62 | base: 2,
|
63 | isInt: true
|
64 | },
|
65 | o: {
|
66 | base: 8,
|
67 | isInt: true
|
68 | },
|
69 | x: {
|
70 | base: 16,
|
71 | isInt: true
|
72 | },
|
73 | X: {
|
74 | extend: ['x'],
|
75 | toUpper: true
|
76 | },
|
77 | d: {
|
78 | base: 10,
|
79 | isInt: true
|
80 | },
|
81 | i: {
|
82 | extend: ['d']
|
83 | },
|
84 | u: {
|
85 | extend: ['d'],
|
86 | isUnsigned: true
|
87 | },
|
88 | c: {
|
89 | setArg: function(token){
|
90 | if(!isNaN(token.arg)){
|
91 | var num = parseInt(token.arg);
|
92 | if(num < 0 || num > 127){
|
93 | throw new Error('invalid character code passed to %c in printf');
|
94 | }
|
95 | token.arg = isNaN(num) ? '' + num : String.fromCharCode(num);
|
96 | }
|
97 | }
|
98 | },
|
99 | s: {
|
100 | setMaxWidth: function(token){
|
101 | token.maxWidth = (token.period == '.') ? token.precision : -1;
|
102 | }
|
103 | },
|
104 | e: {
|
105 | isDouble: true,
|
106 | doubleNotation: 'e'
|
107 | },
|
108 | E: {
|
109 | extend: ['e'],
|
110 | toUpper: true
|
111 | },
|
112 | f: {
|
113 | isDouble: true,
|
114 | doubleNotation: 'f'
|
115 | },
|
116 | F: {
|
117 | extend: ['f']
|
118 | },
|
119 | g: {
|
120 | isDouble: true,
|
121 | doubleNotation: 'g'
|
122 | },
|
123 | G: {
|
124 | extend: ['g'],
|
125 | toUpper: true
|
126 | },
|
127 | O: {
|
128 | isObject: true
|
129 | },
|
130 | };
|
131 | Formatter.prototype.format = function(/*mixed...*/ filler){
|
132 | if(this._mapped && typeof filler != 'object'){
|
133 | throw new Error('format requires a mapping');
|
134 | }
|
135 |
|
136 | var str = '';
|
137 | var position = 0;
|
138 | for(var i = 0, token; i < this._tokens.length; i++){
|
139 | token = this._tokens[i];
|
140 |
|
141 | if(typeof token == 'string'){
|
142 | str += token;
|
143 | }else{
|
144 | if(this._mapped){
|
145 | if(typeof filler[token.mapping] == 'undefined'){
|
146 | throw new Error('missing key ' + token.mapping);
|
147 | }
|
148 | token.arg = filler[token.mapping];
|
149 | }else{
|
150 | if(token.intmapping){
|
151 | position = parseInt(token.intmapping) - 1;
|
152 | }
|
153 | if(position >= arguments.length){
|
154 | throw new Error('got ' + arguments.length + ' printf arguments, insufficient for \'' + this._format + '\'');
|
155 | }
|
156 | token.arg = arguments[position++];
|
157 | }
|
158 |
|
159 | if(!token.compiled){
|
160 | token.compiled = true;
|
161 | token.sign = '';
|
162 | token.zeroPad = false;
|
163 | token.rightJustify = false;
|
164 | token.alternative = false;
|
165 |
|
166 | var flags = {};
|
167 | for(var fi = token.flags.length; fi--;){
|
168 | var flag = token.flags.charAt(fi);
|
169 | flags[flag] = true;
|
170 | switch(flag){
|
171 | case ' ':
|
172 | token.sign = ' ';
|
173 | break;
|
174 | case '+':
|
175 | token.sign = '+';
|
176 | break;
|
177 | case '0':
|
178 | token.zeroPad = (flags['-']) ? false : true;
|
179 | break;
|
180 | case '-':
|
181 | token.rightJustify = true;
|
182 | token.zeroPad = false;
|
183 | break;
|
184 | case '#':
|
185 | token.alternative = true;
|
186 | break;
|
187 | default:
|
188 | throw Error('bad formatting flag \'' + token.flags.charAt(fi) + '\'');
|
189 | }
|
190 | }
|
191 |
|
192 | token.minWidth = (token._minWidth) ? parseInt(token._minWidth) : 0;
|
193 | token.maxWidth = -1;
|
194 | token.toUpper = false;
|
195 | token.isUnsigned = false;
|
196 | token.isInt = false;
|
197 | token.isDouble = false;
|
198 | token.isObject = false;
|
199 | token.precision = 1;
|
200 | if(token.period == '.'){
|
201 | if(token._precision){
|
202 | token.precision = parseInt(token._precision);
|
203 | }else{
|
204 | token.precision = 0;
|
205 | }
|
206 | }
|
207 |
|
208 | var mixins = this._specifiers[token.specifier];
|
209 | if(typeof mixins == 'undefined'){
|
210 | throw new Error('unexpected specifier \'' + token.specifier + '\'');
|
211 | }
|
212 | if(mixins.extend){
|
213 | var s = this._specifiers[mixins.extend];
|
214 | for(var k in s){
|
215 | mixins[k] = s[k]
|
216 | }
|
217 | delete mixins.extend;
|
218 | }
|
219 | for(var l in mixins){
|
220 | token[l] = mixins[l];
|
221 | }
|
222 | }
|
223 |
|
224 | if(typeof token.setArg == 'function'){
|
225 | token.setArg(token);
|
226 | }
|
227 |
|
228 | if(typeof token.setMaxWidth == 'function'){
|
229 | token.setMaxWidth(token);
|
230 | }
|
231 |
|
232 | if(token._minWidth == '*'){
|
233 | if(this._mapped){
|
234 | throw new Error('* width not supported in mapped formats');
|
235 | }
|
236 | token.minWidth = parseInt(arguments[position++]);
|
237 | if(isNaN(token.minWidth)){
|
238 | throw new Error('the argument for * width at position ' + position + ' is not a number in ' + this._format);
|
239 | }
|
240 |
|
241 | if (token.minWidth < 0) {
|
242 | token.rightJustify = true;
|
243 | token.minWidth = -token.minWidth;
|
244 | }
|
245 | }
|
246 |
|
247 | if(token._precision == '*' && token.period == '.'){
|
248 | if(this._mapped){
|
249 | throw new Error('* precision not supported in mapped formats');
|
250 | }
|
251 | token.precision = parseInt(arguments[position++]);
|
252 | if(isNaN(token.precision)){
|
253 | throw Error('the argument for * precision at position ' + position + ' is not a number in ' + this._format);
|
254 | }
|
255 |
|
256 | if (token.precision < 0) {
|
257 | token.precision = 1;
|
258 | token.period = '';
|
259 | }
|
260 | }
|
261 | if(token.isInt){
|
262 |
|
263 | if(token.period == '.'){
|
264 | token.zeroPad = false;
|
265 | }
|
266 | this.formatInt(token);
|
267 | }else if(token.isDouble){
|
268 | if(token.period != '.'){
|
269 | token.precision = 6;
|
270 | }
|
271 | this.formatDouble(token);
|
272 | }else if(token.isObject){
|
273 | this.formatObject(token);
|
274 | }
|
275 | this.fitField(token);
|
276 |
|
277 | str += '' + token.arg;
|
278 | }
|
279 | }
|
280 |
|
281 | return str;
|
282 | };
|
283 | Formatter.prototype._zeros10 = '0000000000';
|
284 | Formatter.prototype._spaces10 = ' ';
|
285 | Formatter.prototype.formatInt = function(token) {
|
286 | var i = parseInt(token.arg);
|
287 | if(!isFinite(i)){
|
288 |
|
289 | if(typeof token.arg != 'number'){
|
290 | throw new Error('format argument \'' + token.arg + '\' not an integer; parseInt returned ' + i);
|
291 | }
|
292 |
|
293 | i = 0;
|
294 | }
|
295 |
|
296 |
|
297 |
|
298 | if(i < 0 && (token.isUnsigned || token.base != 10)){
|
299 | i = 0xffffffff + i + 1;
|
300 | }
|
301 |
|
302 | if(i < 0){
|
303 | token.arg = (- i).toString(token.base);
|
304 | this.zeroPad(token);
|
305 | token.arg = '-' + token.arg;
|
306 | }else{
|
307 | token.arg = i.toString(token.base);
|
308 |
|
309 | if(!i && !token.precision){
|
310 | token.arg = '';
|
311 | }else{
|
312 | this.zeroPad(token);
|
313 | }
|
314 | if(token.sign){
|
315 | token.arg = token.sign + token.arg;
|
316 | }
|
317 | }
|
318 | if(token.base == 16){
|
319 | if(token.alternative){
|
320 | token.arg = '0x' + token.arg;
|
321 | }
|
322 | token.arg = token.toUpper ? token.arg.toUpperCase() : token.arg.toLowerCase();
|
323 | }
|
324 | if(token.base == 8){
|
325 | if(token.alternative && token.arg.charAt(0) != '0'){
|
326 | token.arg = '0' + token.arg;
|
327 | }
|
328 | }
|
329 | };
|
330 | Formatter.prototype.formatDouble = function(token) {
|
331 | var f = parseFloat(token.arg);
|
332 | if(!isFinite(f)){
|
333 |
|
334 | if(typeof token.arg != 'number'){
|
335 | throw new Error('format argument \'' + token.arg + '\' not a float; parseFloat returned ' + f);
|
336 | }
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 | f = 0;
|
343 | }
|
344 |
|
345 | switch(token.doubleNotation) {
|
346 | case 'e': {
|
347 | token.arg = f.toExponential(token.precision);
|
348 | break;
|
349 | }
|
350 | case 'f': {
|
351 | token.arg = f.toFixed(token.precision);
|
352 | break;
|
353 | }
|
354 | case 'g': {
|
355 |
|
356 |
|
357 |
|
358 | if(Math.abs(f) < 0.0001){
|
359 |
|
360 | token.arg = f.toExponential(token.precision > 0 ? token.precision - 1 : token.precision);
|
361 | }else{
|
362 | token.arg = f.toPrecision(token.precision);
|
363 | }
|
364 |
|
365 |
|
366 |
|
367 | if(!token.alternative){
|
368 |
|
369 | token.arg = token.arg.replace(/(\..*[^0])0*e/, '$1e');
|
370 |
|
371 | token.arg = token.arg.replace(/\.0*e/, 'e').replace(/\.0$/,'');
|
372 | }
|
373 | break;
|
374 | }
|
375 | default: throw new Error('unexpected double notation \'' + token.doubleNotation + '\'');
|
376 | }
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 | token.arg = token.arg.replace(/e\+(\d)$/, 'e+0$1').replace(/e\-(\d)$/, 'e-0$1');
|
383 |
|
384 |
|
385 | if(token.alternative){
|
386 | token.arg = token.arg.replace(/^(\d+)$/,'$1.');
|
387 | token.arg = token.arg.replace(/^(\d+)e/,'$1.e');
|
388 | }
|
389 |
|
390 | if(f >= 0 && token.sign){
|
391 | token.arg = token.sign + token.arg;
|
392 | }
|
393 |
|
394 | token.arg = token.toUpper ? token.arg.toUpperCase() : token.arg.toLowerCase();
|
395 | };
|
396 | Formatter.prototype.formatObject = function(token) {
|
397 |
|
398 | var precision = (token.period === '.') ? token.precision : null;
|
399 | token.arg = util.inspect(token.arg, !token.alternative, precision);
|
400 | };
|
401 | Formatter.prototype.zeroPad = function(token, /*Int*/ length) {
|
402 | length = (arguments.length == 2) ? length : token.precision;
|
403 | var negative = false;
|
404 | if(typeof token.arg != "string"){
|
405 | token.arg = "" + token.arg;
|
406 | }
|
407 | if (token.arg.substr(0,1) === '-') {
|
408 | negative = true;
|
409 | token.arg = token.arg.substr(1);
|
410 | }
|
411 |
|
412 | var tenless = length - 10;
|
413 | while(token.arg.length < tenless){
|
414 | token.arg = (token.rightJustify) ? token.arg + this._zeros10 : this._zeros10 + token.arg;
|
415 | }
|
416 | var pad = length - token.arg.length;
|
417 | token.arg = (token.rightJustify) ? token.arg + this._zeros10.substring(0, pad) : this._zeros10.substring(0, pad) + token.arg;
|
418 | if (negative) token.arg = '-' + token.arg;
|
419 | };
|
420 | Formatter.prototype.fitField = function(token) {
|
421 | if(token.maxWidth >= 0 && token.arg.length > token.maxWidth){
|
422 | return token.arg.substring(0, token.maxWidth);
|
423 | }
|
424 | if(token.zeroPad){
|
425 | this.zeroPad(token, token.minWidth);
|
426 | return;
|
427 | }
|
428 | this.spacePad(token);
|
429 | };
|
430 | Formatter.prototype.spacePad = function(token, /*Int*/ length) {
|
431 | length = (arguments.length == 2) ? length : token.minWidth;
|
432 | if(typeof token.arg != 'string'){
|
433 | token.arg = '' + token.arg;
|
434 | }
|
435 | var tenless = length - 10;
|
436 | while(token.arg.length < tenless){
|
437 | token.arg = (token.rightJustify) ? token.arg + this._spaces10 : this._spaces10 + token.arg;
|
438 | }
|
439 | var pad = length - token.arg.length;
|
440 | token.arg = (token.rightJustify) ? token.arg + this._spaces10.substring(0, pad) : this._spaces10.substring(0, pad) + token.arg;
|
441 | };
|
442 |
|
443 |
|
444 | module.exports = function(){
|
445 | var args = Array.prototype.slice.call(arguments),
|
446 | stream, format;
|
447 | if(args[0] instanceof require('stream').Stream){
|
448 | stream = args.shift();
|
449 | }
|
450 | format = args.shift();
|
451 | var formatter = new Formatter(format);
|
452 | var string = formatter.format.apply(formatter, args);
|
453 | if(stream){
|
454 | stream.write(string);
|
455 | }else{
|
456 | return string;
|
457 | }
|
458 | };
|
459 |
|
460 | module.exports.Formatter = Formatter;
|
461 |
|