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);
- }
|