| /* |
| * extsprintf.js: extended POSIX-style sprintf |
| */ |
| |
| var mod_assert = require('assert'); |
| var mod_util = require('util'); |
| |
| /* |
| * Public interface |
| */ |
| exports.sprintf = jsSprintf; |
| exports.printf = jsPrintf; |
| exports.fprintf = jsFprintf; |
| |
| /* |
| * Stripped down version of s[n]printf(3c). We make a best effort to throw an |
| * exception when given a format string we don't understand, rather than |
| * ignoring it, so that we won't break existing programs if/when we go implement |
| * the rest of this. |
| * |
| * This implementation currently supports specifying |
| * - field alignment ('-' flag), |
| * - zero-pad ('0' flag) |
| * - always show numeric sign ('+' flag), |
| * - field width |
| * - conversions for strings, decimal integers, and floats (numbers). |
| * - argument size specifiers. These are all accepted but ignored, since |
| * Javascript has no notion of the physical size of an argument. |
| * |
| * Everything else is currently unsupported, most notably precision, unsigned |
| * numbers, non-decimal numbers, and characters. |
| */ |
| function jsSprintf(fmt) |
| { |
| var regex = [ |
| '([^%]*)', /* normal text */ |
| '%', /* start of format */ |
| '([\'\\-+ #0]*?)', /* flags (optional) */ |
| '([1-9]\\d*)?', /* width (optional) */ |
| '(\\.([1-9]\\d*))?', /* precision (optional) */ |
| '[lhjztL]*?', /* length mods (ignored) */ |
| '([diouxXfFeEgGaAcCsSp%jr])' /* conversion */ |
| ].join(''); |
| |
| var re = new RegExp(regex); |
| var args = Array.prototype.slice.call(arguments, 1); |
| var flags, width, precision, conversion; |
| var left, pad, sign, arg, match; |
| var ret = ''; |
| var argn = 1; |
| |
| mod_assert.equal('string', typeof (fmt)); |
| |
| while ((match = re.exec(fmt)) !== null) { |
| ret += match[1]; |
| fmt = fmt.substring(match[0].length); |
| |
| flags = match[2] || ''; |
| width = match[3] || 0; |
| precision = match[4] || ''; |
| conversion = match[6]; |
| left = false; |
| sign = false; |
| pad = ' '; |
| |
| if (conversion == '%') { |
| ret += '%'; |
| continue; |
| } |
| |
| if (args.length === 0) |
| throw (new Error('too few args to sprintf')); |
| |
| arg = args.shift(); |
| argn++; |
| |
| if (flags.match(/[\' #]/)) |
| throw (new Error( |
| 'unsupported flags: ' + flags)); |
| |
| if (precision.length > 0) |
| throw (new Error( |
| 'non-zero precision not supported')); |
| |
| if (flags.match(/-/)) |
| left = true; |
| |
| if (flags.match(/0/)) |
| pad = '0'; |
| |
| if (flags.match(/\+/)) |
| sign = true; |
| |
| switch (conversion) { |
| case 's': |
| if (arg === undefined || arg === null) |
| throw (new Error('argument ' + argn + |
| ': attempted to print undefined or null ' + |
| 'as a string')); |
| ret += doPad(pad, width, left, arg.toString()); |
| break; |
| |
| case 'd': |
| arg = Math.floor(arg); |
| /*jsl:fallthru*/ |
| case 'f': |
| sign = sign && arg > 0 ? '+' : ''; |
| ret += sign + doPad(pad, width, left, |
| arg.toString()); |
| break; |
| |
| case 'x': |
| ret += doPad(pad, width, left, arg.toString(16)); |
| break; |
| |
| case 'j': /* non-standard */ |
| if (width === 0) |
| width = 10; |
| ret += mod_util.inspect(arg, false, width); |
| break; |
| |
| case 'r': /* non-standard */ |
| ret += dumpException(arg); |
| break; |
| |
| default: |
| throw (new Error('unsupported conversion: ' + |
| conversion)); |
| } |
| } |
| |
| ret += fmt; |
| return (ret); |
| } |
| |
| function jsPrintf() { |
| var args = Array.prototype.slice.call(arguments); |
| args.unshift(process.stdout); |
| jsFprintf.apply(null, args); |
| } |
| |
| function jsFprintf(stream) { |
| var args = Array.prototype.slice.call(arguments, 1); |
| return (stream.write(jsSprintf.apply(this, args))); |
| } |
| |
| function doPad(chr, width, left, str) |
| { |
| var ret = str; |
| |
| while (ret.length < width) { |
| if (left) |
| ret += chr; |
| else |
| ret = chr + ret; |
| } |
| |
| return (ret); |
| } |
| |
| /* |
| * This function dumps long stack traces for exceptions having a cause() method. |
| * See node-verror for an example. |
| */ |
| function dumpException(ex) |
| { |
| var ret; |
| |
| if (!(ex instanceof Error)) |
| throw (new Error(jsSprintf('invalid type for %%r: %j', ex))); |
| |
| /* Note that V8 prepends "ex.stack" with ex.toString(). */ |
| ret = 'EXCEPTION: ' + ex.constructor.name + ': ' + ex.stack; |
| |
| if (ex.cause && typeof (ex.cause) === 'function') { |
| var cex = ex.cause(); |
| if (cex) { |
| ret += '\nCaused by: ' + dumpException(cex); |
| } |
| } |
| |
| return (ret); |
| } |