summaryrefslogtreecommitdiffstats
path: root/devtools/shared/node-properties/node-properties.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/node-properties/node-properties.js')
-rw-r--r--devtools/shared/node-properties/node-properties.js776
1 files changed, 776 insertions, 0 deletions
diff --git a/devtools/shared/node-properties/node-properties.js b/devtools/shared/node-properties/node-properties.js
new file mode 100644
index 000000000..05feba857
--- /dev/null
+++ b/devtools/shared/node-properties/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);
+ }
+};