diff options
Diffstat (limited to 'devtools/shared/sprintfjs')
-rw-r--r-- | devtools/shared/sprintfjs/UPGRADING.md | 17 | ||||
-rw-r--r-- | devtools/shared/sprintfjs/moz.build | 9 | ||||
-rw-r--r-- | devtools/shared/sprintfjs/sprintf.js | 283 |
3 files changed, 309 insertions, 0 deletions
diff --git a/devtools/shared/sprintfjs/UPGRADING.md b/devtools/shared/sprintfjs/UPGRADING.md new file mode 100644 index 0000000000..e0db44df67 --- /dev/null +++ b/devtools/shared/sprintfjs/UPGRADING.md @@ -0,0 +1,17 @@ +SPRINTF JS UPGRADING + +Original library at https://github.com/alexei/sprintf.js + +This library should no longer be upgraded from upstream. We added performance improvements +in https://bugzilla.mozilla.org/show_bug.cgi?id=1406311. Most importantly removing the +usage of the get_type() method as well as prioritizing the %S use case. + +If for some reason, updating from upstream becomes necessary, please refer to the bug +mentioned above to reimplement the performance fixes in the new version. + +By default the library only supports string placeholders using %s (lowercase) while we use +%S (uppercase). The library also has to be manually patched in order to support it. + +- grab the unminified version at https://github.com/alexei/sprintf.js/blob/master/src/sprintf.js +- update the re.placeholder regexp to allow "S" as well as "s" +- update the switch statement in the format() method to make case "S" equivalent to case "s" diff --git a/devtools/shared/sprintfjs/moz.build b/devtools/shared/sprintfjs/moz.build new file mode 100644 index 0000000000..6f4f1f8734 --- /dev/null +++ b/devtools/shared/sprintfjs/moz.build @@ -0,0 +1,9 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DevToolsModules( + 'sprintf.js' +) diff --git a/devtools/shared/sprintfjs/sprintf.js b/devtools/shared/sprintfjs/sprintf.js new file mode 100644 index 0000000000..fd53cd3ce9 --- /dev/null +++ b/devtools/shared/sprintfjs/sprintf.js @@ -0,0 +1,283 @@ +/** + * Copyright (c) 2007-2016, Alexandru Marasteanu <hello [at) alexei (dot] ro> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of this software nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +/* eslint-disable */ +/* globals window, exports, define */ + +(function(window) { + 'use strict' + + var re = { + not_string: /[^s]/, + not_bool: /[^t]/, + not_type: /[^T]/, + not_primitive: /[^v]/, + number: /[diefg]/, + numeric_arg: /bcdiefguxX/, + json: /[j]/, + not_json: /[^j]/, + text: /^[^\x25]+/, + modulo: /^\x25{2}/, + placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosStTuvxX])/, + key: /^([a-z_][a-z_\d]*)/i, + key_access: /^\.([a-z_][a-z_\d]*)/i, + index_access: /^\[(\d+)\]/, + sign: /^[\+\-]/ + } + + function sprintf() { + var key = arguments[0], cache = sprintf.cache + if (!(cache[key] && cache.hasOwnProperty(key))) { + cache[key] = sprintf.parse(key) + } + return sprintf.format.call(null, cache[key], arguments) + } + + sprintf.format = function(parse_tree, argv) { + var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = '' + for (i = 0; i < tree_length; i++) { + node_type = typeof parse_tree[i] + // The items of parse tree are either strings or results of a match() call. + if (node_type === 'string') { + // this is not a placeholder, this is just a string. + output[output.length] = parse_tree[i] + } + else { + // this is a placeholder, need to identify its type, options and replace + // it with the appropriate argument. + match = parse_tree[i] // convenience purposes only + if (match[2]) { // keyword argument + arg = argv[cursor] + for (k = 0; k < match[2].length; k++) { + if (!arg.hasOwnProperty(match[2][k])) { + throw new Error(sprintf('[sprintf] property "%s" does not exist', match[2][k])) + } + arg = arg[match[2][k]] + } + } + else if (match[1]) { // positional argument (explicit) + arg = argv[match[1]] + } + else { // positional argument (implicit) + arg = argv[cursor++] + } + + // The most commonly used placeholder in DevTools is the string (%S or %s). + // We check it first to avoid unnecessary verifications. + let hasPadding = match[6]; + let patternType = match[8]; + if (!hasPadding && (patternType === "S" || patternType === "s")) { + if (typeof arg === "function") { + arg = arg(); + } + if (typeof arg !== "string") { + arg = String(arg); + } + output[output.length] = match[7] ? arg.substring(0, match[7]) : arg; + continue; + } + + if (re.not_type.test(match[8]) && re.not_primitive.test(match[8]) && typeof arg == 'function') { + arg = arg() + } + + if (re.numeric_arg.test(match[8]) && (typeof arg != 'number' && isNaN(arg))) { + throw new TypeError(sprintf("[sprintf] expecting number but found %s", typeof arg)) + } + + if (re.number.test(match[8])) { + is_positive = arg >= 0 + } + + switch (match[8]) { + case 'b': + arg = parseInt(arg, 10).toString(2) + break + case 'c': + arg = String.fromCharCode(parseInt(arg, 10)) + break + case 'd': + case 'i': + arg = parseInt(arg, 10) + break + case 'j': + arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0) + break + case 'e': + arg = match[7] ? parseFloat(arg).toExponential(match[7]) : parseFloat(arg).toExponential() + break + case 'f': + arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg) + break + case 'g': + arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg) + break + case 'o': + arg = arg.toString(8) + break + case 's': + case 'S': + arg = String(arg) + arg = (match[7] ? arg.substring(0, match[7]) : arg) + break + case 't': + arg = String(!!arg) + arg = (match[7] ? arg.substring(0, match[7]) : arg) + break + case 'T': + arg = Object.prototype.toString.call(arg).slice(8, -1).toLowerCase() + arg = (match[7] ? arg.substring(0, match[7]) : arg) + break + case 'u': + arg = parseInt(arg, 10) >>> 0 + break + case 'v': + arg = arg.valueOf() + arg = (match[7] ? arg.substring(0, match[7]) : arg) + break + case 'x': + arg = parseInt(arg, 10).toString(16) + break + case 'X': + arg = parseInt(arg, 10).toString(16).toUpperCase() + break + } + if (re.json.test(match[8])) { + output[output.length] = arg + } + else { + if (re.number.test(match[8]) && (!is_positive || match[3])) { + sign = is_positive ? '+' : '-' + arg = arg.toString().replace(re.sign, '') + } + else { + sign = '' + } + pad_character = match[4] ? match[4] === '0' ? '0' : match[4].charAt(1) : ' ' + pad_length = match[6] - (sign + arg).length + pad = match[6] ? (pad_length > 0 ? str_repeat(pad_character, pad_length) : '') : '' + output[output.length] = match[5] ? sign + arg + pad : (pad_character === '0' ? sign + pad + arg : pad + sign + arg) + } + } + } + return output.join('') + } + + sprintf.cache = {} + + sprintf.parse = function(fmt) { + var _fmt = fmt, match = [], parse_tree = [], arg_names = 0 + while (_fmt) { + if ((match = re.text.exec(_fmt)) !== null) { + parse_tree[parse_tree.length] = match[0] + } + else if ((match = re.modulo.exec(_fmt)) !== null) { + parse_tree[parse_tree.length] = '%' + } + else if ((match = re.placeholder.exec(_fmt)) !== null) { + if (match[2]) { + arg_names |= 1 + var field_list = [], replacement_field = match[2], field_match = [] + if ((field_match = re.key.exec(replacement_field)) !== null) { + field_list[field_list.length] = field_match[1] + while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { + if ((field_match = re.key_access.exec(replacement_field)) !== null) { + field_list[field_list.length] = field_match[1] + } + else if ((field_match = re.index_access.exec(replacement_field)) !== null) { + field_list[field_list.length] = field_match[1] + } + else { + throw new SyntaxError("[sprintf] failed to parse named argument key") + } + } + } + else { + throw new SyntaxError("[sprintf] failed to parse named argument key") + } + match[2] = field_list + } + else { + arg_names |= 2 + } + if (arg_names === 3) { + throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported") + } + parse_tree[parse_tree.length] = match + } + else { + throw new SyntaxError("[sprintf] unexpected placeholder") + } + _fmt = _fmt.substring(match[0].length) + } + return parse_tree + } + + var vsprintf = function(fmt, argv, _argv) { + _argv = (argv || []).slice(0) + _argv.splice(0, 0, fmt) + return sprintf.apply(null, _argv) + } + + /** + * helpers + */ + + var preformattedPadding = { + '0': ['', '0', '00', '000', '0000', '00000', '000000', '0000000'], + ' ': ['', ' ', ' ', ' ', ' ', ' ', ' ', ' '], + '_': ['', '_', '__', '___', '____', '_____', '______', '_______'], + } + function str_repeat(input, multiplier) { + if (multiplier >= 0 && multiplier <= 7 && preformattedPadding[input]) { + return preformattedPadding[input][multiplier] + } + return Array(multiplier + 1).join(input) + } + + /** + * export to either browser or node.js + */ + if (typeof exports !== 'undefined') { + exports.sprintf = sprintf + exports.vsprintf = vsprintf + } + else { + window.sprintf = sprintf + window.vsprintf = vsprintf + + if (typeof define === 'function' && define.amd) { + define(function() { + return { + sprintf: sprintf, + vsprintf: vsprintf + } + }) + } + } +})(typeof window === 'undefined' ? this : window); |