2 * extsprintf.js: extended POSIX-style sprintf
5 var mod_assert = require('assert');
6 var mod_util = require('util');
11 exports.sprintf = jsSprintf;
14 * Stripped down version of s[n]printf(3c). We make a best effort to throw an
15 * exception when given a format string we don't understand, rather than
16 * ignoring it, so that we won't break existing programs if/when we go implement
19 * This implementation currently supports specifying
20 * - field alignment ('-' flag),
21 * - zero-pad ('0' flag)
22 * - always show numeric sign ('+' flag),
24 * - conversions for strings, decimal integers, and floats (numbers).
25 * - argument size specifiers. These are all accepted but ignored, since
26 * Javascript has no notion of the physical size of an argument.
28 * Everything else is currently unsupported, most notably precision, unsigned
29 * numbers, non-decimal numbers, and characters.
31 function jsSprintf(fmt)
34 '([^%]*)', /* normal text */
35 '%', /* start of format */
36 '([\'\\-+ #0]*?)', /* flags (optional) */
37 '([1-9]\\d*)?', /* width (optional) */
38 '(\\.([1-9]\\d*))?', /* precision (optional) */
39 '[lhjztL]*?', /* length mods (ignored) */
40 '([diouxXfFeEgGaAcCsSp%jr])' /* conversion */
43 var re = new RegExp(regex);
44 var args = Array.prototype.slice.call(arguments, 1);
45 var flags, width, precision, conversion;
46 var left, pad, sign, arg, match;
50 mod_assert.equal('string', typeof (fmt));
52 while ((match = re.exec(fmt)) !== null) {
54 fmt = fmt.substring(match[0].length);
56 flags = match[2] || '';
57 width = match[3] || 0;
58 precision = match[4] || '';
59 conversion = match[6];
64 if (conversion == '%') {
69 if (args.length === 0)
70 throw (new Error('too few args to sprintf'));
75 if (flags.match(/[\' #]/))
77 'unsupported flags: ' + flags));
79 if (precision.length > 0)
81 'non-zero precision not supported'));
89 if (flags.match(/\+/))
94 if (arg === undefined || arg === null)
95 throw (new Error('argument ' + argn +
96 ': attempted to print undefined or null ' +
98 ret += doPad(pad, width, left, arg.toString());
102 arg = Math.floor(arg);
105 sign = sign && arg > 0 ? '+' : '';
106 ret += sign + doPad(pad, width, left,
110 case 'j': /* non-standard */
113 ret += mod_util.inspect(arg, false, width);
116 case 'r': /* non-standard */
117 ret += dumpException(arg);
121 throw (new Error('unsupported conversion: ' +
130 function doPad(chr, width, left, str)
134 while (ret.length < width) {
145 * This function dumps long stack traces for exceptions having a cause() method.
146 * See node-verror for an example.
148 function dumpException(ex)
152 if (!(ex instanceof Error))
153 throw (new Error(jsSprintf('invalid type for %%r: %j', ex)));
155 /* Note that V8 prepends "ex.stack" with ex.toString(). */
156 ret = 'EXCEPTION: ' + ex.constructor.name + ': ' + ex.stack;
158 if (ex.cause && typeof (ex.cause) === 'function') {
159 var cex = ex.cause();
161 ret += '\nCaused by: ' + dumpException(cex);