sprintf.js (11917B)
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 */ 28 29 /* eslint-disable */ 30 /* globals window, exports, define */ 31 32 (function(window) { 33 'use strict' 34 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 } 52 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 } 60 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 } 89 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 } 104 105 if (re.not_type.test(match[8]) && re.not_primitive.test(match[8]) && typeof arg == 'function') { 106 arg = arg() 107 } 108 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 } 112 113 if (re.number.test(match[8])) { 114 is_positive = arg >= 0 115 } 116 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 } 190 191 sprintf.cache = {} 192 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 } 240 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 } 246 247 /** 248 * helpers 249 */ 250 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 } 262 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 273 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);