| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 | /* * 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(ofmt){	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);	/* variadic arguments used to fill in conversion specifiers */	var args = Array.prototype.slice.call(arguments, 1);	/* remaining format string */	var fmt = ofmt;	/* components of the current conversion specifier */	var flags, width, precision, conversion;	var left, pad, sign, arg, match;	/* return value */	var ret = '';	/* current variadic argument (1-based) */	var argn = 1;	/* 0-based position in the format string that we've read */	var posn = 0;	/* 1-based position in the format string of the current conversion */	var convposn;	/* current conversion specifier */	var curconv;	mod_assert.equal('string', typeof (fmt),	    'first argument must be a format string');	while ((match = re.exec(fmt)) !== null) {		ret += match[1];		fmt = fmt.substring(match[0].length);		/*		 * Update flags related to the current conversion specifier's		 * position so that we can report clear error messages.		 */		curconv = match[0].substring(match[1].length);		convposn = posn + match[1].length + 1;		posn += 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 (jsError(ofmt, convposn, curconv,			    'has no matching argument ' +			    '(too few arguments passed)'));		}		arg = args.shift();		argn++;		if (flags.match(/[\' #]/)) {			throw (jsError(ofmt, convposn, curconv,			    'uses unsupported flags'));		}		if (precision.length > 0) {			throw (jsError(ofmt, convposn, curconv,			    'uses 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 (jsError(ofmt, convposn, curconv,				    'attempted to print undefined or null ' +				    'as a string (argument ' + argn + ' to ' +				    'sprintf)'));			}			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 (jsError(ofmt, convposn, curconv,			    'is not supported'));		}	}	ret += fmt;	return (ret);}function jsError(fmtstr, convposn, curconv, reason) {	mod_assert.equal(typeof (fmtstr), 'string');	mod_assert.equal(typeof (curconv), 'string');	mod_assert.equal(typeof (convposn), 'number');	mod_assert.equal(typeof (reason), 'string');	return (new Error('format string "' + fmtstr +	    '": conversion specifier "' + curconv + '" at character ' +	    convposn + ' ' + reason));}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);}
 |