extsprintf.js (4172B)
1 /* 2 * extsprintf.js: extended POSIX-style sprintf 3 */ 4 5 var mod_assert = require('assert'); 6 var mod_util = require('util'); 7 8 /* 9 * Public interface 10 */ 11 exports.sprintf = jsSprintf; 12 exports.printf = jsPrintf; 13 exports.fprintf = jsFprintf; 14 15 /* 16 * Stripped down version of s[n]printf(3c). We make a best effort to throw an 17 * exception when given a format string we don't understand, rather than 18 * ignoring it, so that we won't break existing programs if/when we go implement 19 * the rest of this. 20 * 21 * This implementation currently supports specifying 22 * - field alignment ('-' flag), 23 * - zero-pad ('0' flag) 24 * - always show numeric sign ('+' flag), 25 * - field width 26 * - conversions for strings, decimal integers, and floats (numbers). 27 * - argument size specifiers. These are all accepted but ignored, since 28 * Javascript has no notion of the physical size of an argument. 29 * 30 * Everything else is currently unsupported, most notably precision, unsigned 31 * numbers, non-decimal numbers, and characters. 32 */ 33 function jsSprintf(fmt) 34 { 35 var regex = [ 36 '([^%]*)', /* normal text */ 37 '%', /* start of format */ 38 '([\'\\-+ #0]*?)', /* flags (optional) */ 39 '([1-9]\\d*)?', /* width (optional) */ 40 '(\\.([1-9]\\d*))?', /* precision (optional) */ 41 '[lhjztL]*?', /* length mods (ignored) */ 42 '([diouxXfFeEgGaAcCsSp%jr])' /* conversion */ 43 ].join(''); 44 45 var re = new RegExp(regex); 46 var args = Array.prototype.slice.call(arguments, 1); 47 var flags, width, precision, conversion; 48 var left, pad, sign, arg, match; 49 var ret = ''; 50 var argn = 1; 51 52 mod_assert.equal('string', typeof (fmt)); 53 54 while ((match = re.exec(fmt)) !== null) { 55 ret += match[1]; 56 fmt = fmt.substring(match[0].length); 57 58 flags = match[2] || ''; 59 width = match[3] || 0; 60 precision = match[4] || ''; 61 conversion = match[6]; 62 left = false; 63 sign = false; 64 pad = ' '; 65 66 if (conversion == '%') { 67 ret += '%'; 68 continue; 69 } 70 71 if (args.length === 0) 72 throw (new Error('too few args to sprintf')); 73 74 arg = args.shift(); 75 argn++; 76 77 if (flags.match(/[\' #]/)) 78 throw (new Error( 79 'unsupported flags: ' + flags)); 80 81 if (precision.length > 0) 82 throw (new Error( 83 'non-zero precision not supported')); 84 85 if (flags.match(/-/)) 86 left = true; 87 88 if (flags.match(/0/)) 89 pad = '0'; 90 91 if (flags.match(/\+/)) 92 sign = true; 93 94 switch (conversion) { 95 case 's': 96 if (arg === undefined || arg === null) 97 throw (new Error('argument ' + argn + 98 ': attempted to print undefined or null ' + 99 'as a string')); 100 ret += doPad(pad, width, left, arg.toString()); 101 break; 102 103 case 'd': 104 arg = Math.floor(arg); 105 /*jsl:fallthru*/ 106 case 'f': 107 sign = sign && arg > 0 ? '+' : ''; 108 ret += sign + doPad(pad, width, left, 109 arg.toString()); 110 break; 111 112 case 'x': 113 ret += doPad(pad, width, left, arg.toString(16)); 114 break; 115 116 case 'j': /* non-standard */ 117 if (width === 0) 118 width = 10; 119 ret += mod_util.inspect(arg, false, width); 120 break; 121 122 case 'r': /* non-standard */ 123 ret += dumpException(arg); 124 break; 125 126 default: 127 throw (new Error('unsupported conversion: ' + 128 conversion)); 129 } 130 } 131 132 ret += fmt; 133 return (ret); 134 } 135 136 function jsPrintf() { 137 var args = Array.prototype.slice.call(arguments); 138 args.unshift(process.stdout); 139 jsFprintf.apply(null, args); 140 } 141 142 function jsFprintf(stream) { 143 var args = Array.prototype.slice.call(arguments, 1); 144 return (stream.write(jsSprintf.apply(this, args))); 145 } 146 147 function doPad(chr, width, left, str) 148 { 149 var ret = str; 150 151 while (ret.length < width) { 152 if (left) 153 ret += chr; 154 else 155 ret = chr + ret; 156 } 157 158 return (ret); 159 } 160 161 /* 162 * This function dumps long stack traces for exceptions having a cause() method. 163 * See node-verror for an example. 164 */ 165 function dumpException(ex) 166 { 167 var ret; 168 169 if (!(ex instanceof Error)) 170 throw (new Error(jsSprintf('invalid type for %%r: %j', ex))); 171 172 /* Note that V8 prepends "ex.stack" with ex.toString(). */ 173 ret = 'EXCEPTION: ' + ex.constructor.name + ': ' + ex.stack; 174 175 if (ex.cause && typeof (ex.cause) === 'function') { 176 var cex = ex.cause(); 177 if (cex) { 178 ret += '\nCaused by: ' + dumpException(cex); 179 } 180 } 181 182 return (ret); 183 }