twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

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 }