Bug 1895153 - Implement "Find in page..." menu functionality r=android-reviewers...
[gecko.git] / devtools / shared / sprintfjs / sprintf.js
blobfd53cd3ce988d9f398bff1cf6a704f4b48396939
1 /**
2  * Copyright (c) 2007-2016, Alexandru Marasteanu <hello [at) alexei (dot] ro>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  *   notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  *   notice, this list of conditions and the following disclaimer in the
11  *   documentation and/or other materials provided with the distribution.
12  * * Neither the name of this software nor the names of its contributors may be
13  *   used to endorse or promote products derived from this software without
14  *   specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
20  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  *
27  */
29 /* eslint-disable */
30 /* globals window, exports, define */
32 (function(window) {
33     'use strict'
35     var re = {
36         not_string: /[^s]/,
37         not_bool: /[^t]/,
38         not_type: /[^T]/,
39         not_primitive: /[^v]/,
40         number: /[diefg]/,
41         numeric_arg: /bcdiefguxX/,
42         json: /[j]/,
43         not_json: /[^j]/,
44         text: /^[^\x25]+/,
45         modulo: /^\x25{2}/,
46         placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosStTuvxX])/,
47         key: /^([a-z_][a-z_\d]*)/i,
48         key_access: /^\.([a-z_][a-z_\d]*)/i,
49         index_access: /^\[(\d+)\]/,
50         sign: /^[\+\-]/
51     }
53     function sprintf() {
54         var key = arguments[0], cache = sprintf.cache
55         if (!(cache[key] && cache.hasOwnProperty(key))) {
56             cache[key] = sprintf.parse(key)
57         }
58         return sprintf.format.call(null, cache[key], arguments)
59     }
61     sprintf.format = function(parse_tree, argv) {
62         var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = ''
63         for (i = 0; i < tree_length; i++) {
64             node_type = typeof parse_tree[i]
65             // The items of parse tree are either strings or results of a match() call.
66             if (node_type === 'string') {
67                 // this is not a placeholder, this is just a string.
68                 output[output.length] = parse_tree[i]
69             }
70             else {
71                 // this is a placeholder, need to identify its type, options and replace
72                 // it with the appropriate argument.
73                 match = parse_tree[i] // convenience purposes only
74                 if (match[2]) { // keyword argument
75                     arg = argv[cursor]
76                     for (k = 0; k < match[2].length; k++) {
77                         if (!arg.hasOwnProperty(match[2][k])) {
78                             throw new Error(sprintf('[sprintf] property "%s" does not exist', match[2][k]))
79                         }
80                         arg = arg[match[2][k]]
81                     }
82                 }
83                 else if (match[1]) { // positional argument (explicit)
84                     arg = argv[match[1]]
85                 }
86                 else { // positional argument (implicit)
87                     arg = argv[cursor++]
88                 }
90                 // The most commonly used placeholder in DevTools is the string (%S or %s).
91                 // We check it first to avoid unnecessary verifications.
92                 let hasPadding = match[6];
93                 let patternType = match[8];
94                 if (!hasPadding && (patternType === "S" || patternType === "s")) {
95                     if (typeof arg === "function") {
96                         arg = arg();
97                     }
98                     if (typeof arg !== "string") {
99                         arg = String(arg);
100                     }
101                     output[output.length] = match[7] ? arg.substring(0, match[7]) : arg;
102                     continue;
103                 }
105                 if (re.not_type.test(match[8]) && re.not_primitive.test(match[8]) && typeof arg == 'function') {
106                     arg = arg()
107                 }
109                 if (re.numeric_arg.test(match[8]) && (typeof arg != 'number' && isNaN(arg))) {
110                     throw new TypeError(sprintf("[sprintf] expecting number but found %s", typeof arg))
111                 }
113                 if (re.number.test(match[8])) {
114                     is_positive = arg >= 0
115                 }
117                 switch (match[8]) {
118                     case 'b':
119                         arg = parseInt(arg, 10).toString(2)
120                     break
121                     case 'c':
122                         arg = String.fromCharCode(parseInt(arg, 10))
123                     break
124                     case 'd':
125                     case 'i':
126                         arg = parseInt(arg, 10)
127                     break
128                     case 'j':
129                         arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0)
130                     break
131                     case 'e':
132                         arg = match[7] ? parseFloat(arg).toExponential(match[7]) : parseFloat(arg).toExponential()
133                     break
134                     case 'f':
135                         arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg)
136                     break
137                     case 'g':
138                         arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg)
139                     break
140                     case 'o':
141                         arg = arg.toString(8)
142                     break
143                     case 's':
144                     case 'S':
145                         arg = String(arg)
146                         arg = (match[7] ? arg.substring(0, match[7]) : arg)
147                     break
148                     case 't':
149                         arg = String(!!arg)
150                         arg = (match[7] ? arg.substring(0, match[7]) : arg)
151                     break
152                     case 'T':
153                         arg = Object.prototype.toString.call(arg).slice(8, -1).toLowerCase()
154                         arg = (match[7] ? arg.substring(0, match[7]) : arg)
155                     break
156                     case 'u':
157                         arg = parseInt(arg, 10) >>> 0
158                     break
159                     case 'v':
160                         arg = arg.valueOf()
161                         arg = (match[7] ? arg.substring(0, match[7]) : arg)
162                     break
163                     case 'x':
164                         arg = parseInt(arg, 10).toString(16)
165                     break
166                     case 'X':
167                         arg = parseInt(arg, 10).toString(16).toUpperCase()
168                     break
169                 }
170                 if (re.json.test(match[8])) {
171                     output[output.length] = arg
172                 }
173                 else {
174                     if (re.number.test(match[8]) && (!is_positive || match[3])) {
175                         sign = is_positive ? '+' : '-'
176                         arg = arg.toString().replace(re.sign, '')
177                     }
178                     else {
179                         sign = ''
180                     }
181                     pad_character = match[4] ? match[4] === '0' ? '0' : match[4].charAt(1) : ' '
182                     pad_length = match[6] - (sign + arg).length
183                     pad = match[6] ? (pad_length > 0 ? str_repeat(pad_character, pad_length) : '') : ''
184                     output[output.length] = match[5] ? sign + arg + pad : (pad_character === '0' ? sign + pad + arg : pad + sign + arg)
185                 }
186             }
187         }
188         return output.join('')
189     }
191     sprintf.cache = {}
193     sprintf.parse = function(fmt) {
194         var _fmt = fmt, match = [], parse_tree = [], arg_names = 0
195         while (_fmt) {
196             if ((match = re.text.exec(_fmt)) !== null) {
197                 parse_tree[parse_tree.length] = match[0]
198             }
199             else if ((match = re.modulo.exec(_fmt)) !== null) {
200                 parse_tree[parse_tree.length] = '%'
201             }
202             else if ((match = re.placeholder.exec(_fmt)) !== null) {
203                 if (match[2]) {
204                     arg_names |= 1
205                     var field_list = [], replacement_field = match[2], field_match = []
206                     if ((field_match = re.key.exec(replacement_field)) !== null) {
207                         field_list[field_list.length] = field_match[1]
208                         while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
209                             if ((field_match = re.key_access.exec(replacement_field)) !== null) {
210                                 field_list[field_list.length] = field_match[1]
211                             }
212                             else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
213                                 field_list[field_list.length] = field_match[1]
214                             }
215                             else {
216                                 throw new SyntaxError("[sprintf] failed to parse named argument key")
217                             }
218                         }
219                     }
220                     else {
221                         throw new SyntaxError("[sprintf] failed to parse named argument key")
222                     }
223                     match[2] = field_list
224                 }
225                 else {
226                     arg_names |= 2
227                 }
228                 if (arg_names === 3) {
229                     throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported")
230                 }
231                 parse_tree[parse_tree.length] = match
232             }
233             else {
234                 throw new SyntaxError("[sprintf] unexpected placeholder")
235             }
236             _fmt = _fmt.substring(match[0].length)
237         }
238         return parse_tree
239     }
241     var vsprintf = function(fmt, argv, _argv) {
242         _argv = (argv || []).slice(0)
243         _argv.splice(0, 0, fmt)
244         return sprintf.apply(null, _argv)
245     }
247     /**
248      * helpers
249      */
251     var preformattedPadding = {
252         '0': ['', '0', '00', '000', '0000', '00000', '000000', '0000000'],
253         ' ': ['', ' ', '  ', '   ', '    ', '     ', '      ', '       '],
254         '_': ['', '_', '__', '___', '____', '_____', '______', '_______'],
255     }
256     function str_repeat(input, multiplier) {
257         if (multiplier >= 0 && multiplier <= 7 && preformattedPadding[input]) {
258             return preformattedPadding[input][multiplier]
259         }
260         return Array(multiplier + 1).join(input)
261     }
263     /**
264      * export to either browser or node.js
265      */
266     if (typeof exports !== 'undefined') {
267         exports.sprintf = sprintf
268         exports.vsprintf = vsprintf
269     }
270     else {
271         window.sprintf = sprintf
272         window.vsprintf = vsprintf
274         if (typeof define === 'function' && define.amd) {
275             define(function() {
276                 return {
277                     sprintf: sprintf,
278                     vsprintf: vsprintf
279                 }
280             })
281         }
282     }
283 })(typeof window === 'undefined' ? this : window);