• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * extsprintf.js: extended POSIX-style sprintf
3 */
4
5var mod_assert = require('assert');
6var mod_util = require('util');
7
8/*
9 * Public interface
10 */
11exports.sprintf = jsSprintf;
12exports.printf = jsPrintf;
13exports.fprintf = jsFprintf;
14
15/*
16 * Stripped down version of s[n]printf(3c).  We make a best effort to throw an
17 * exception when given a format string we don't understand, rather than
18 * ignoring it, so that we won't break existing programs if/when we go implement
19 * the rest of this.
20 *
21 * This implementation currently supports specifying
22 *	- field alignment ('-' flag),
23 * 	- zero-pad ('0' flag)
24 *	- always show numeric sign ('+' flag),
25 *	- field width
26 *	- conversions for strings, decimal integers, and floats (numbers).
27 *	- argument size specifiers.  These are all accepted but ignored, since
28 *	  Javascript has no notion of the physical size of an argument.
29 *
30 * Everything else is currently unsupported, most notably precision, unsigned
31 * numbers, non-decimal numbers, and characters.
32 */
33function jsSprintf(fmt)
34{
35	var regex = [
36	    '([^%]*)',				/* normal text */
37	    '%',				/* start of format */
38	    '([\'\\-+ #0]*?)',			/* flags (optional) */
39	    '([1-9]\\d*)?',			/* width (optional) */
40	    '(\\.([1-9]\\d*))?',		/* precision (optional) */
41	    '[lhjztL]*?',			/* length mods (ignored) */
42	    '([diouxXfFeEgGaAcCsSp%jr])'	/* conversion */
43	].join('');
44
45	var re = new RegExp(regex);
46	var args = Array.prototype.slice.call(arguments, 1);
47	var flags, width, precision, conversion;
48	var left, pad, sign, arg, match;
49	var ret = '';
50	var argn = 1;
51
52	mod_assert.equal('string', typeof (fmt));
53
54	while ((match = re.exec(fmt)) !== null) {
55		ret += match[1];
56		fmt = fmt.substring(match[0].length);
57
58		flags = match[2] || '';
59		width = match[3] || 0;
60		precision = match[4] || '';
61		conversion = match[6];
62		left = false;
63		sign = false;
64		pad = ' ';
65
66		if (conversion == '%') {
67			ret += '%';
68			continue;
69		}
70
71		if (args.length === 0)
72			throw (new Error('too few args to sprintf'));
73
74		arg = args.shift();
75		argn++;
76
77		if (flags.match(/[\' #]/))
78			throw (new Error(
79			    'unsupported flags: ' + flags));
80
81		if (precision.length > 0)
82			throw (new Error(
83			    'non-zero precision not supported'));
84
85		if (flags.match(/-/))
86			left = true;
87
88		if (flags.match(/0/))
89			pad = '0';
90
91		if (flags.match(/\+/))
92			sign = true;
93
94		switch (conversion) {
95		case 's':
96			if (arg === undefined || arg === null)
97				throw (new Error('argument ' + argn +
98				    ': attempted to print undefined or null ' +
99				    'as a string'));
100			ret += doPad(pad, width, left, arg.toString());
101			break;
102
103		case 'd':
104			arg = Math.floor(arg);
105			/*jsl:fallthru*/
106		case 'f':
107			sign = sign && arg > 0 ? '+' : '';
108			ret += sign + doPad(pad, width, left,
109			    arg.toString());
110			break;
111
112		case 'x':
113			ret += doPad(pad, width, left, arg.toString(16));
114			break;
115
116		case 'j': /* non-standard */
117			if (width === 0)
118				width = 10;
119			ret += mod_util.inspect(arg, false, width);
120			break;
121
122		case 'r': /* non-standard */
123			ret += dumpException(arg);
124			break;
125
126		default:
127			throw (new Error('unsupported conversion: ' +
128			    conversion));
129		}
130	}
131
132	ret += fmt;
133	return (ret);
134}
135
136function jsPrintf() {
137	var args = Array.prototype.slice.call(arguments);
138	args.unshift(process.stdout);
139	jsFprintf.apply(null, args);
140}
141
142function jsFprintf(stream) {
143	var args = Array.prototype.slice.call(arguments, 1);
144	return (stream.write(jsSprintf.apply(this, args)));
145}
146
147function doPad(chr, width, left, str)
148{
149	var ret = str;
150
151	while (ret.length < width) {
152		if (left)
153			ret += chr;
154		else
155			ret = chr + ret;
156	}
157
158	return (ret);
159}
160
161/*
162 * This function dumps long stack traces for exceptions having a cause() method.
163 * See node-verror for an example.
164 */
165function dumpException(ex)
166{
167	var ret;
168
169	if (!(ex instanceof Error))
170		throw (new Error(jsSprintf('invalid type for %%r: %j', ex)));
171
172	/* Note that V8 prepends "ex.stack" with ex.toString(). */
173	ret = 'EXCEPTION: ' + ex.constructor.name + ': ' + ex.stack;
174
175	if (ex.cause && typeof (ex.cause) === 'function') {
176		var cex = ex.cause();
177		if (cex) {
178			ret += '\nCaused by: ' + dumpException(cex);
179		}
180	}
181
182	return (ret);
183}
184