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.
183 lines
4.1 KiB
183 lines
4.1 KiB
/* |
|
* 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); |
|
}
|
|
|