| /* | 
|  * 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); | 
| } |