You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					184 lines
				
				4.1 KiB
			
		
		
			
		
	
	
					184 lines
				
				4.1 KiB
			| 
											3 years ago
										 | /* | ||
|  |  * 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); | ||
|  | } |