UNPKG

8.9 kBJavaScriptView Raw
1/**
2sprintf() for JavaScript 0.7-beta1
3http://www.diveintojavascript.com/projects/javascript-sprintf
4
5Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com>
6All rights reserved.
7
8Redistribution and use in source and binary forms, with or without
9modification, are permitted provided that the following conditions are met:
10 * Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 * Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in the
14 documentation and/or other materials provided with the distribution.
15 * Neither the name of sprintf() for JavaScript nor the
16 names of its contributors may be used to endorse or promote products
17 derived from this software without specific prior written permission.
18
19THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
23DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31Changelog:
322010.11.07 - 0.7-beta1-node
33 - converted it to a node.js compatible module
34
352010.09.06 - 0.7-beta1
36 - features: vsprintf, support for named placeholders
37 - enhancements: format cache, reduced global namespace pollution
38
392010.05.22 - 0.6:
40 - reverted to 0.4 and fixed the bug regarding the sign of the number 0
41 Note:
42 Thanks to Raphael Pigulla <raph (at] n3rd [dot) org> (http://www.n3rd.org/)
43 who warned me about a bug in 0.5, I discovered that the last update was
44 a regress. I appologize for that.
45
462010.05.09 - 0.5:
47 - bug fix: 0 is now preceeded with a + sign
48 - bug fix: the sign was not at the right position on padded results (Kamal Abdali)
49 - switched from GPL to BSD license
50
512007.10.21 - 0.4:
52 - unit test and patch (David Baird)
53
542007.09.17 - 0.3:
55 - bug fix: no longer throws exception on empty paramenters (Hans Pufal)
56
572007.09.11 - 0.2:
58 - feature: added argument swapping
59
602007.04.03 - 0.1:
61 - initial release
62**/
63
64var sprintf = (function() {
65 function get_type(variable) {
66 return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
67 }
68 function str_repeat(input, multiplier) {
69 for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
70 return output.join('');
71 }
72
73 var str_format = function() {
74 if (!str_format.cache.hasOwnProperty(arguments[0])) {
75 str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
76 }
77 return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
78 };
79
80 // convert object to simple one line string without indentation or
81 // newlines. Note that this implementation does not print array
82 // values to their actual place for sparse arrays.
83 //
84 // For example sparse array like this
85 // l = []
86 // l[4] = 1
87 // Would be printed as "[1]" instead of "[, , , , 1]"
88 //
89 // If argument 'seen' is not null and array the function will check for
90 // circular object references from argument.
91 str_format.object_stringify = function(obj, depth, maxdepth, seen) {
92 var str = '';
93 if (obj != null) {
94 switch( typeof(obj) ) {
95 case 'function':
96 return '[Function' + (obj.name ? ': '+obj.name : '') + ']';
97 break;
98 case 'object':
99 if ( obj instanceof Error) { return '[' + obj.toString() + ']' };
100 if (depth >= maxdepth) return '[Object]'
101 if (seen) {
102 // add object to seen list
103 seen = seen.slice(0)
104 seen.push(obj);
105 }
106 if (obj.length != null) { //array
107 str += '[';
108 var arr = []
109 for (var i in obj) {
110 if (seen && seen.indexOf(obj[i]) >= 0) arr.push('[Circular]');
111 else arr.push(str_format.object_stringify(obj[i], depth+1, maxdepth, seen));
112 }
113 str += arr.join(', ') + ']';
114 } else if ('getMonth' in obj) { // date
115 return 'Date(' + obj + ')';
116 } else { // object
117 str += '{';
118 var arr = []
119 for (var k in obj) {
120 if(obj.hasOwnProperty(k)) {
121 if (seen && seen.indexOf(obj[k]) >= 0) arr.push(k + ': [Circular]');
122 else arr.push(k +': ' +str_format.object_stringify(obj[k], depth+1, maxdepth, seen));
123 }
124 }
125 str += arr.join(', ') + '}';
126 }
127 return str;
128 break;
129 case 'string':
130 return '"' + obj + '"';
131 break
132 }
133 }
134 return '' + obj;
135 }
136
137 str_format.format = function(parse_tree, argv) {
138 var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
139 for (i = 0; i < tree_length; i++) {
140 node_type = get_type(parse_tree[i]);
141 if (node_type === 'string') {
142 output.push(parse_tree[i]);
143 }
144 else if (node_type === 'array') {
145 match = parse_tree[i]; // convenience purposes only
146 if (match[2]) { // keyword argument
147 arg = argv[cursor];
148 for (k = 0; k < match[2].length; k++) {
149 if (!arg.hasOwnProperty(match[2][k])) {
150 throw new Error(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
151 }
152 arg = arg[match[2][k]];
153 }
154 }
155 else if (match[1]) { // positional argument (explicit)
156 arg = argv[match[1]];
157 }
158 else { // positional argument (implicit)
159 arg = argv[cursor++];
160 }
161
162 if (/[^sO]/.test(match[8]) && (get_type(arg) != 'number')) {
163 throw new Error(sprintf('[sprintf] expecting number but found %s "' + arg + '"', get_type(arg)));
164 }
165 switch (match[8]) {
166 case 'b': arg = arg.toString(2); break;
167 case 'c': arg = String.fromCharCode(arg); break;
168 case 'd': arg = parseInt(arg, 10); break;
169 case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
170 case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
171 case 'O': arg = str_format.object_stringify(arg, 0, parseInt(match[7]) || 5); break;
172 case 'o': arg = arg.toString(8); break;
173 case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
174 case 'u': arg = Math.abs(arg); break;
175 case 'x': arg = arg.toString(16); break;
176 case 'X': arg = arg.toString(16).toUpperCase(); break;
177 }
178 arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
179 pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
180 pad_length = match[6] - String(arg).length;
181 pad = match[6] ? str_repeat(pad_character, pad_length) : '';
182 output.push(match[5] ? arg + pad : pad + arg);
183 }
184 }
185 return output.join('');
186 };
187
188 str_format.cache = {};
189
190 str_format.parse = function(fmt) {
191 var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
192 while (_fmt) {
193 if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
194 parse_tree.push(match[0]);
195 }
196 else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
197 parse_tree.push('%');
198 }
199 else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosOuxX])/.exec(_fmt)) !== null) {
200 if (match[2]) {
201 arg_names |= 1;
202 var field_list = [], replacement_field = match[2], field_match = [];
203 if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
204 field_list.push(field_match[1]);
205 while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
206 if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
207 field_list.push(field_match[1]);
208 }
209 else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
210 field_list.push(field_match[1]);
211 }
212 else {
213 throw new Error('[sprintf] ' + replacement_field);
214 }
215 }
216 }
217 else {
218 throw new Error('[sprintf] ' + replacement_field);
219 }
220 match[2] = field_list;
221 }
222 else {
223 arg_names |= 2;
224 }
225 if (arg_names === 3) {
226 throw new Error('[sprintf] mixing positional and named placeholders is not (yet) supported');
227 }
228 parse_tree.push(match);
229 }
230 else {
231 throw new Error('[sprintf] ' + _fmt);
232 }
233 _fmt = _fmt.substring(match[0].length);
234 }
235 return parse_tree;
236 };
237
238 return str_format;
239})();
240
241var vsprintf = function(fmt, argv) {
242 var argvClone = argv.slice();
243 argvClone.unshift(fmt);
244 return sprintf.apply(null, argvClone);
245};
246
247module.exports = sprintf;
248sprintf.sprintf = sprintf;
249sprintf.vsprintf = vsprintf;