|
@@ -0,0 +1,183 @@
|
|
|
+/*
|
|
|
+ * 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);
|
|
|
+}
|