--- a/devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js
+++ b/devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js
@@ -1,40 +1,32 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
/**
* Tests if the preferences and localization objects work correctly.
*/
function test() {
initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
ok(aMonitor.panelWin.L10N,
"Should have a localization object available on the panel window.");
ok(aMonitor.panelWin.Prefs,
"Should have a preferences object available on the panel window.");
function testL10N() {
let { L10N } = aMonitor.panelWin;
-
- ok(L10N.stringBundle,
- "The localization object should have a string bundle available.");
-
- let bundleName = "chrome://devtools/locale/netmonitor.properties";
- let stringBundle = Services.strings.createBundle(bundleName);
-
- is(L10N.getStr("netmonitor.label"),
- stringBundle.GetStringFromName("netmonitor.label"),
- "The getStr() method didn't return the expected string.");
-
- is(L10N.getFormatStr("networkMenu.totalMS", "foo"),
- stringBundle.formatStringFromName("networkMenu.totalMS", ["foo"], 1),
- "The getFormatStr() method didn't return the expected string.");
+ is(typeof L10N.getStr("netmonitor.label"), "string",
+ "The getStr() method didn't return a valid string.");
+ is(typeof L10N.getFormatStr("networkMenu.totalMS", "foo"), "string",
+ "The getFormatStr() method didn't return a valid string.");
}
function testPrefs() {
let { Prefs } = aMonitor.panelWin;
is(Prefs.networkDetailsWidth,
Services.prefs.getIntPref("devtools.netmonitor.panes-network-details-width"),
"Getting a pref should work correctly.");
--- a/devtools/client/projecteditor/lib/helpers/l10n.js
+++ b/devtools/client/projecteditor/lib/helpers/l10n.js
@@ -1,25 +1,26 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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/. */
+"use strict";
+
/**
* This file contains helper functions for internationalizing projecteditor strings
*/
-const { Cu, Cc, Ci } = require("chrome");
const { LocalizationHelper } = require("devtools/client/shared/l10n");
const ITCHPAD_STRINGS_URI = "chrome://devtools/locale/projecteditor.properties";
-const L10N = new LocalizationHelper(ITCHPAD_STRINGS_URI).stringBundle;
+const L10N = new LocalizationHelper(ITCHPAD_STRINGS_URI);
function getLocalizedString(name) {
try {
- return L10N.GetStringFromName(name);
+ return L10N.getStr(name);
} catch (ex) {
console.log("Error reading '" + name + "'");
throw new Error("l10n error with " + name);
}
}
exports.getLocalizedString = getLocalizedString;
--- a/devtools/client/shared/l10n.js
+++ b/devtools/client/shared/l10n.js
@@ -1,64 +1,69 @@
/* 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/. */
"use strict";
-const Services = require("Services");
+const parsePropertiesFile = require("devtools/client/shared/vendor/node-properties");
+const { sprintf } = require("devtools/client/shared/vendor/sprintf");
/**
* Localization convenience methods.
*
* @param string stringBundleName
* The desired string bundle's name.
*/
function LocalizationHelper(stringBundleName) {
- loader.lazyGetter(this, "stringBundle", () =>
- Services.strings.createBundle(stringBundleName));
+ loader.lazyGetter(this, "properties", () => {
+ return parsePropertiesFile(require(`raw!${stringBundleName}`));
+ });
}
LocalizationHelper.prototype = {
/**
* L10N shortcut function.
*
* @param string name
* @return string
*/
getStr: function (name) {
- return this.stringBundle.GetStringFromName(name);
+ if (name in this.properties) {
+ return this.properties[name];
+ }
+
+ throw new Error("No localization found for [" + name + "]");
},
/**
* L10N shortcut function.
*
* @param string name
* @param array args
* @return string
*/
getFormatStr: function (name, ...args) {
- return this.stringBundle.formatStringFromName(name, args, args.length);
+ return sprintf(this.getStr(name), ...args);
},
/**
* L10N shortcut function for numeric arguments that need to be formatted.
* All numeric arguments will be fixed to 2 decimals and given a localized
* decimal separator. Other arguments will be left alone.
*
* @param string name
* @param array args
* @return string
*/
getFormatStrWithNumbers: function (name, ...args) {
let newArgs = args.map(x => {
return typeof x == "number" ? this.numberWithDecimals(x, 2) : x;
});
- return this.stringBundle.formatStringFromName(name,
- newArgs,
- newArgs.length);
+
+ return this.getFormatStr(name, ...newArgs);
},
/**
* Converts a number to a locale-aware string format and keeps a certain
* number of decimals.
*
* @param number number
* The number to convert.
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/vendor/NODE_PROPERTIES_UPGRADING
@@ -0,0 +1,12 @@
+NODE PROPERTIES UPGRADING
+
+Original library at https://github.com/gagle/node-properties
+The original library is intended for node and not for the browser. Most files are not
+needed here.
+
+To update
+- copy https://github.com/gagle/node-properties/blob/master/lib/parse.js
+- update the initial "module.exports" to "var parse" in parse.js
+- copy https://github.com/gagle/node-properties/blob/master/lib/read.js
+- remove the require statements at the beginning
+- merge the two files, parse.js first, read.js second
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/vendor/SPRINTF_JS_UPGRADING
@@ -0,0 +1,12 @@
+SPRINTF JS UPGRADING
+
+Original library at https://github.com/alexei/sprintf.js
+By default the library only supports string placeholders using %s (lowercase) while we use
+%S (uppercase). The library 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"
+
+The original changeset adding support for "%S" can be found on this fork:
+- https://github.com/juliandescottes/sprintf.js/commit/a60ea5d7c4cd9a006002ba9f0afc1e2689107eec
\ No newline at end of file
--- a/devtools/client/shared/vendor/moz.build
+++ b/devtools/client/shared/vendor/moz.build
@@ -1,25 +1,28 @@
# -*- 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/.
modules = []
modules += [
'immutable.js',
- 'jsol.js'
+ 'jsol.js',
+ 'node-properties.js',
]
# react-dev is used if either debug mode is enabled,
# so include it for both
if CONFIG['DEBUG_JS_MODULES'] or CONFIG['MOZ_DEBUG']:
modules += ['react-dev.js']
+
modules += [
'react-dom.js',
'react-proxy.js',
'react-redux.js',
'react.js',
'redux.js',
- 'seamless-immutable.js'
+ 'seamless-immutable.js',
+ 'sprintf.js',
]
DevToolsModules(*modules)
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/vendor/node-properties.js
@@ -0,0 +1,776 @@
+/**
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014 Gabriel Llamas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+"use strict";
+
+var hex = function (c){
+ switch (c){
+ case "0": return 0;
+ case "1": return 1;
+ case "2": return 2;
+ case "3": return 3;
+ case "4": return 4;
+ case "5": return 5;
+ case "6": return 6;
+ case "7": return 7;
+ case "8": return 8;
+ case "9": return 9;
+ case "a": case "A": return 10;
+ case "b": case "B": return 11;
+ case "c": case "C": return 12;
+ case "d": case "D": return 13;
+ case "e": case "E": return 14;
+ case "f": case "F": return 15;
+ }
+};
+
+var parse = function (data, options, handlers, control){
+ var c;
+ var code;
+ var escape;
+ var skipSpace = true;
+ var isCommentLine;
+ var isSectionLine;
+ var newLine = true;
+ var multiLine;
+ var isKey = true;
+ var key = "";
+ var value = "";
+ var section;
+ var unicode;
+ var unicodeRemaining;
+ var escapingUnicode;
+ var keySpace;
+ var sep;
+ var ignoreLine;
+
+ var line = function (){
+ if (key || value || sep){
+ handlers.line (key, value);
+ key = "";
+ value = "";
+ sep = false;
+ }
+ };
+
+ var escapeString = function (key, c, code){
+ if (escapingUnicode && unicodeRemaining){
+ unicode = (unicode << 4) + hex (c);
+ if (--unicodeRemaining) return key;
+ escape = false;
+ escapingUnicode = false;
+ return key + String.fromCharCode (unicode);
+ }
+
+ //code 117: u
+ if (code === 117){
+ unicode = 0;
+ escapingUnicode = true;
+ unicodeRemaining = 4;
+ return key;
+ }
+
+ escape = false;
+
+ //code 116: t
+ //code 114: r
+ //code 110: n
+ //code 102: f
+ if (code === 116) return key + "\t";
+ else if (code === 114) return key + "\r";
+ else if (code === 110) return key + "\n";
+ else if (code === 102) return key + "\f";
+
+ return key + c;
+ };
+
+ var isComment;
+ var isSeparator;
+
+ if (options._strict){
+ isComment = function (c, code, options){
+ return options._comments[c];
+ };
+
+ isSeparator = function (c, code, options){
+ return options._separators[c];
+ };
+ }else{
+ isComment = function (c, code, options){
+ //code 35: #
+ //code 33: !
+ return code === 35 || code === 33 || options._comments[c];
+ };
+
+ isSeparator = function (c, code, options){
+ //code 61: =
+ //code 58: :
+ return code === 61 || code === 58 || options._separators[c];
+ };
+ }
+
+ for (var i=~~control.resume; i<data.length; i++){
+ if (control.abort) return;
+ if (control.pause){
+ //The next index is always the start of a new line, it's a like a fresh
+ //start, there's no need to save the current state
+ control.resume = i;
+ return;
+ }
+
+ c = data[i];
+ code = data.charCodeAt (i);
+
+ //code 13: \r
+ if (code === 13) continue;
+
+ if (isCommentLine){
+ //code 10: \n
+ if (code === 10){
+ isCommentLine = false;
+ newLine = true;
+ skipSpace = true;
+ }
+ continue;
+ }
+
+ //code 93: ]
+ if (isSectionLine && code === 93){
+ handlers.section (section);
+ //Ignore chars after the section in the same line
+ ignoreLine = true;
+ continue;
+ }
+
+ if (skipSpace){
+ //code 32: " " (space)
+ //code 9: \t
+ //code 12: \f
+ if (code === 32 || code === 9 || code === 12){
+ continue;
+ }
+ //code 10: \n
+ if (!multiLine && code === 10){
+ //Empty line or key w/ separator and w/o value
+ isKey = true;
+ keySpace = false;
+ newLine = true;
+ line ();
+ continue;
+ }
+ skipSpace = false;
+ multiLine = false;
+ }
+
+ if (newLine){
+ newLine = false;
+ if (isComment (c, code, options)){
+ isCommentLine = true;
+ continue;
+ }
+ //code 91: [
+ if (options.sections && code === 91){
+ section = "";
+ isSectionLine = true;
+ control.skipSection = false;
+ continue;
+ }
+ }
+
+ //code 10: \n
+ if (code !== 10){
+ if (control.skipSection || ignoreLine) continue;
+
+ if (!isSectionLine){
+ if (!escape && isKey && isSeparator (c, code, options)){
+ //sep is needed to detect empty key and empty value with a
+ //non-whitespace separator
+ sep = true;
+ isKey = false;
+ keySpace = false;
+ //Skip whitespace between separator and value
+ skipSpace = true;
+ continue;
+ }
+ }
+
+ //code 92: "\" (backslash)
+ if (code === 92){
+ if (escape){
+ if (escapingUnicode) continue;
+
+ if (keySpace){
+ //Line with whitespace separator
+ keySpace = false;
+ isKey = false;
+ }
+
+ if (isSectionLine) section += "\\";
+ else if (isKey) key += "\\";
+ else value += "\\";
+ }
+ escape = !escape;
+ }else{
+ if (keySpace){
+ //Line with whitespace separator
+ keySpace = false;
+ isKey = false;
+ }
+
+ if (isSectionLine){
+ if (escape) section = escapeString (section, c, code);
+ else section += c;
+ }else if (isKey){
+ if (escape){
+ key = escapeString (key, c, code);
+ }else{
+ //code 32: " " (space)
+ //code 9: \t
+ //code 12: \f
+ if (code === 32 || code === 9 || code === 12){
+ keySpace = true;
+ //Skip whitespace between key and separator
+ skipSpace = true;
+ continue;
+ }
+ key += c;
+ }
+ }else{
+ if (escape) value = escapeString (value, c, code);
+ else value += c;
+ }
+ }
+ }else{
+ if (escape){
+ if (!escapingUnicode){
+ escape = false;
+ }
+ skipSpace = true;
+ multiLine = true;
+ }else{
+ if (isSectionLine){
+ isSectionLine = false;
+ if (!ignoreLine){
+ //The section doesn't end with ], it's a key
+ control.error = new Error ("The section line \"" + section +
+ "\" must end with \"]\"");
+ return;
+ }
+ ignoreLine = false;
+ }
+ newLine = true;
+ skipSpace = true;
+ isKey = true;
+
+ line ();
+ }
+ }
+ }
+
+ control.parsed = true;
+
+ if (isSectionLine && !ignoreLine){
+ //The section doesn't end with ], it's a key
+ control.error = new Error ("The section line \"" + section + "\" must end" +
+ "with \"]\"");
+ return;
+ }
+ line ();
+};
+
+var INCLUDE_KEY = "include";
+var INDEX_FILE = "index.properties";
+
+var cast = function (value){
+ if (value === null || value === "null") return null;
+ if (value === "undefined") return undefined;
+ if (value === "true") return true;
+ if (value === "false") return false;
+ var v = Number (value);
+ return isNaN (v) ? value : v;
+};
+
+var expand = function (o, str, options, cb){
+ if (!options.variables || !str) return cb (null, str);
+
+ var stack = [];
+ var c;
+ var cp;
+ var key = "";
+ var section = null;
+ var v;
+ var holder;
+ var t;
+ var n;
+
+ for (var i=0; i<str.length; i++){
+ c = str[i];
+
+ if (cp === "$" && c === "{"){
+ key = key.substring (0, key.length - 1);
+ stack.push ({
+ key: key,
+ section: section
+ });
+ key = "";
+ section = null;
+ continue;
+ }else if (stack.length){
+ if (options.sections && c === "|"){
+ section = key;
+ key = "";
+ continue;
+ }else if (c === "}"){
+ holder = section !== null ? searchValue (o, section, true) : o;
+ if (!holder){
+ return cb (new Error ("The section \"" + section + "\" does not " +
+ "exist"));
+ }
+
+ v = options.namespaces ? searchValue (holder, key) : holder[key];
+ if (v === undefined){
+ //Read the external vars
+ v = options.namespaces
+ ? searchValue (options._vars, key)
+ : options._vars[key]
+
+ if (v === undefined){
+ return cb (new Error ("The property \"" + key + "\" does not " +
+ "exist"));
+ }
+ }
+
+ t = stack.pop ();
+ section = t.section;
+ key = t.key + (v === null ? "" : v);
+ continue;
+ }
+ }
+
+ cp = c;
+ key += c;
+ }
+
+ if (stack.length !== 0){
+ return cb (new Error ("Malformed variable: " + str));
+ }
+
+ cb (null, key);
+};
+
+var searchValue = function (o, chain, section){
+ var n = chain.split (".");
+ var str;
+
+ for (var i=0; i<n.length-1; i++){
+ str = n[i];
+ if (o[str] === undefined) return;
+ o = o[str];
+ }
+
+ var v = o[n[n.length - 1]];
+ if (section){
+ if (typeof v !== "object") return;
+ return v;
+ }else{
+ if (typeof v === "object") return;
+ return v;
+ }
+};
+
+var namespaceKey = function (o, key, value){
+ var n = key.split (".");
+ var str;
+
+ for (var i=0; i<n.length-1; i++){
+ str = n[i];
+ if (o[str] === undefined){
+ o[str] = {};
+ }else if (typeof o[str] !== "object"){
+ throw new Error ("Invalid namespace chain in the property name '" +
+ key + "' ('" + str + "' has already a value)");
+ }
+ o = o[str];
+ }
+
+ o[n[n.length - 1]] = value;
+};
+
+var namespaceSection = function (o, section){
+ var n = section.split (".");
+ var str;
+
+ for (var i=0; i<n.length; i++){
+ str = n[i];
+ if (o[str] === undefined){
+ o[str] = {};
+ }else if (typeof o[str] !== "object"){
+ throw new Error ("Invalid namespace chain in the section name '" +
+ section + "' ('" + str + "' has already a value)");
+ }
+ o = o[str];
+ }
+
+ return o;
+};
+
+var merge = function (o1, o2){
+ for (var p in o2){
+ try{
+ if (o1[p].constructor === Object){
+ o1[p] = merge (o1[p], o2[p]);
+ }else{
+ o1[p] = o2[p];
+ }
+ }catch (e){
+ o1[p] = o2[p];
+ }
+ }
+ return o1;
+}
+
+var build = function (data, options, dirname, cb){
+ var o = {};
+
+ if (options.namespaces){
+ var n = {};
+ }
+
+ var control = {
+ abort: false,
+ skipSection: false
+ };
+
+ if (options.include){
+ var remainingIncluded = 0;
+
+ var include = function (value){
+ if (currentSection !== null){
+ return abort (new Error ("Cannot include files from inside a " +
+ "section: " + currentSection));
+ }
+
+ var p = path.resolve (dirname, value);
+ if (options._included[p]) return;
+
+ options._included[p] = true;
+ remainingIncluded++;
+ control.pause = true;
+
+ read (p, options, function (error, included){
+ if (error) return abort (error);
+
+ remainingIncluded--;
+ merge (options.namespaces ? n : o, included);
+ control.pause = false;
+
+ if (!control.parsed){
+ parse (data, options, handlers, control);
+ if (control.error) return abort (control.error);
+ }
+
+ if (!remainingIncluded) cb (null, options.namespaces ? n : o);
+ });
+ };
+ }
+
+ if (!data){
+ if (cb) return cb (null, o);
+ return o;
+ }
+
+ var currentSection = null;
+ var currentSectionStr = null;
+
+ var abort = function (error){
+ control.abort = true;
+ if (cb) return cb (error);
+ throw error;
+ };
+
+ var handlers = {};
+ var reviver = {
+ assert: function (){
+ return this.isProperty ? reviverLine.value : true;
+ }
+ };
+ var reviverLine = {};
+
+ //Line handler
+ //For speed reasons, if "namespaces" is enabled, the old object is still
+ //populated, e.g.: ${a.b} reads the "a.b" property from { "a.b": 1 }, instead
+ //of having a unique object { a: { b: 1 } } which is slower to search for
+ //the "a.b" value
+ //If "a.b" is not found, then the external vars are read. If "namespaces" is
+ //enabled, the var "a.b" is split and it searches the a.b value. If it is not
+ //enabled, then the var "a.b" searches the "a.b" value
+
+ var line;
+ var error;
+
+ if (options.reviver){
+ if (options.sections){
+ line = function (key, value){
+ if (options.include && key === INCLUDE_KEY) return include (value);
+
+ reviverLine.value = value;
+ reviver.isProperty = true;
+ reviver.isSection = false;
+
+ value = options.reviver.call (reviver, key, value, currentSectionStr);
+ if (value !== undefined){
+ if (options.namespaces){
+ try{
+ namespaceKey (currentSection === null ? n : currentSection,
+ key, value);
+ }catch (error){
+ abort (error);
+ }
+ }else{
+ if (currentSection === null) o[key] = value;
+ else currentSection[key] = value;
+ }
+ }
+ };
+ }else{
+ line = function (key, value){
+ if (options.include && key === INCLUDE_KEY) return include (value);
+
+ reviverLine.value = value;
+ reviver.isProperty = true;
+ reviver.isSection = false;
+
+ value = options.reviver.call (reviver, key, value);
+ if (value !== undefined){
+ if (options.namespaces){
+ try{
+ namespaceKey (n, key, value);
+ }catch (error){
+ abort (error);
+ }
+ }else{
+ o[key] = value;
+ }
+ }
+ };
+ }
+ }else{
+ if (options.sections){
+ line = function (key, value){
+ if (options.include && key === INCLUDE_KEY) return include (value);
+
+ if (options.namespaces){
+ try{
+ namespaceKey (currentSection === null ? n : currentSection, key,
+ value);
+ }catch (error){
+ abort (error);
+ }
+ }else{
+ if (currentSection === null) o[key] = value;
+ else currentSection[key] = value;
+ }
+ };
+ }else{
+ line = function (key, value){
+ if (options.include && key === INCLUDE_KEY) return include (value);
+
+ if (options.namespaces){
+ try{
+ namespaceKey (n, key, value);
+ }catch (error){
+ abort (error);
+ }
+ }else{
+ o[key] = value;
+ }
+ };
+ }
+ }
+
+ //Section handler
+ var section;
+ if (options.sections){
+ if (options.reviver){
+ section = function (section){
+ currentSectionStr = section;
+ reviverLine.section = section;
+ reviver.isProperty = false;
+ reviver.isSection = true;
+
+ var add = options.reviver.call (reviver, null, null, section);
+ if (add){
+ if (options.namespaces){
+ try{
+ currentSection = namespaceSection (n, section);
+ }catch (error){
+ abort (error);
+ }
+ }else{
+ currentSection = o[section] = {};
+ }
+ }else{
+ control.skipSection = true;
+ }
+ };
+ }else{
+ section = function (section){
+ currentSectionStr = section;
+ if (options.namespaces){
+ try{
+ currentSection = namespaceSection (n, section);
+ }catch (error){
+ abort (error);
+ }
+ }else{
+ currentSection = o[section] = {};
+ }
+ };
+ }
+ }
+
+ //Variables
+ if (options.variables){
+ handlers.line = function (key, value){
+ expand (options.namespaces ? n : o, key, options, function (error, key){
+ if (error) return abort (error);
+
+ expand (options.namespaces ? n : o, value, options,
+ function (error, value){
+ if (error) return abort (error);
+
+ line (key, cast (value || null));
+ });
+ });
+ };
+
+ if (options.sections){
+ handlers.section = function (s){
+ expand (options.namespaces ? n : o, s, options, function (error, s){
+ if (error) return abort (error);
+
+ section (s);
+ });
+ };
+ }
+ }else{
+ handlers.line = function (key, value){
+ line (key, cast (value || null));
+ };
+
+ if (options.sections){
+ handlers.section = section;
+ }
+ }
+
+ parse (data, options, handlers, control);
+ if (control.error) return abort (control.error);
+
+ if (control.abort || control.pause) return;
+
+ if (cb) return cb (null, options.namespaces ? n : o);
+ return options.namespaces ? n : o;
+};
+
+var read = function (f, options, cb){
+ fs.stat (f, function (error, stats){
+ if (error) return cb (error);
+
+ var dirname;
+
+ if (stats.isDirectory ()){
+ dirname = f;
+ f = path.join (f, INDEX_FILE);
+ }else{
+ dirname = path.dirname (f);
+ }
+
+ fs.readFile (f, { encoding: "utf8" }, function (error, data){
+ if (error) return cb (error);
+ build (data, options, dirname, cb);
+ });
+ });
+};
+
+module.exports = function (data, options, cb){
+ if (typeof options === "function"){
+ cb = options;
+ options = {};
+ }
+
+ options = options || {};
+ var code;
+
+ if (options.include){
+ if (!cb) throw new Error ("A callback must be passed if the 'include' " +
+ "option is enabled");
+ options._included = {};
+ }
+
+ options = options || {};
+ options._strict = options.strict && (options.comments || options.separators);
+ options._vars = options.vars || {};
+
+ var comments = options.comments || [];
+ if (!Array.isArray (comments)) comments = [comments];
+ var c = {};
+ comments.forEach (function (comment){
+ code = comment.charCodeAt (0);
+ if (comment.length > 1 || code < 33 || code > 126){
+ throw new Error ("The comment token must be a single printable ASCII " +
+ "character");
+ }
+ c[comment] = true;
+ });
+ options._comments = c;
+
+ var separators = options.separators || [];
+ if (!Array.isArray (separators)) separators = [separators];
+ var s = {};
+ separators.forEach (function (separator){
+ code = separator.charCodeAt (0);
+ if (separator.length > 1 || code < 33 || code > 126){
+ throw new Error ("The separator token must be a single printable ASCII " +
+ "character");
+ }
+ s[separator] = true;
+ });
+ options._separators = s;
+
+ if (options.path){
+ if (!cb) throw new Error ("A callback must be passed if the 'path' " +
+ "option is enabled");
+ if (options.include){
+ read (data, options, cb);
+ }else{
+ fs.readFile (data, { encoding: "utf8" }, function (error, data){
+ if (error) return cb (error);
+ build (data, options, ".", cb);
+ });
+ }
+ }else{
+ return build (data, options, ".", cb);
+ }
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/vendor/sprintf.js
@@ -0,0 +1,274 @@
+/**
+ * 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.
+ *
+ */
+
+/* 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 = get_type(parse_tree[i])
+ if (node_type === 'string') {
+ output[output.length] = parse_tree[i]
+ }
+ else if (node_type === 'array') {
+ 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++]
+ }
+
+ if (re.not_type.test(match[8]) && re.not_primitive.test(match[8]) && get_type(arg) == 'function') {
+ arg = arg()
+ }
+
+ if (re.numeric_arg.test(match[8]) && (get_type(arg) != 'number' && isNaN(arg))) {
+ throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(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 = get_type(arg)
+ 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
+ */
+ function get_type(variable) {
+ if (typeof variable === 'number') {
+ return 'number'
+ }
+ else if (typeof variable === 'string') {
+ return 'string'
+ }
+ else {
+ return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase()
+ }
+ }
+
+ 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);