Index: lib/yaml.js |
=================================================================== |
new file mode 100644 |
--- /dev/null |
+++ b/lib/yaml.js |
@@ -0,0 +1,2068 @@ |
+// Original downloaded from https://github.com/jeremyfa/yaml.js |
+/* |
+Copyright (c) 2010 Jeremy Faivre |
+ |
+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. |
+*/ |
+(function(){ |
+/** |
+ * Exception class thrown when an error occurs during parsing. |
+ * |
+ * @author Fabien Potencier <fabien@symfony.com> |
+ * |
+ * @api |
+ */ |
+ |
+/** |
+ * Constructor. |
+ * |
+ * @param {string} message The error message |
+ * @param {number} parsedLine The line where the error occurred |
+ * @param {number} snippet The snippet of code near the problem |
+ * @param {string} parsedFile The file name where the error occurred |
+ */ |
+ |
+var YamlParseException = function(message, parsedLine, snippet, parsedFile){ |
+ |
+ this.rawMessage = message; |
+ this.parsedLine = (parsedLine !== undefined) ? parsedLine : -1; |
+ this.snippet = (snippet !== undefined) ? snippet : null; |
+ this.parsedFile = (parsedFile !== undefined) ? parsedFile : null; |
+ |
+ this.updateRepr(); |
+}; |
+YamlParseException.prototype = |
+{ |
+ |
+ name: 'YamlParseException', |
+ message: null, |
+ |
+ parsedFile: null, |
+ parsedLine: -1, |
+ snippet: null, |
+ rawMessage: null, |
+ |
+ isDefined: function(input) |
+ { |
+ return input != undefined && input != null; |
+ }, |
+ |
+ /** |
+ * Gets the snippet of code near the error. |
+ * |
+ * @return string The snippet of code |
+ */ |
+ getSnippet: function() |
+ { |
+ return this.snippet; |
+ }, |
+ |
+ /** |
+ * Sets the snippet of code near the error. |
+ * |
+ * @param {string} snippet The code snippet |
+ */ |
+ setSnippet: function(snippet) |
+ { |
+ this.snippet = snippet; |
+ |
+ this.updateRepr(); |
+ }, |
+ |
+ /** |
+ * Gets the filename where the error occurred. |
+ * |
+ * This method returns null if a string is parsed. |
+ * |
+ * @return string The filename |
+ */ |
+ getParsedFile: function() |
+ { |
+ return this.parsedFile; |
+ }, |
+ |
+ /** |
+ * Sets the filename where the error occurred. |
+ * |
+ * @param {string} parsedFile The filename |
+ */ |
+ setParsedFile: function(parsedFile) |
+ { |
+ this.parsedFile = parsedFile; |
+ |
+ this.updateRepr(); |
+ }, |
+ |
+ /** |
+ * Gets the line where the error occurred. |
+ * |
+ * @return integer The file line |
+ */ |
+ getParsedLine: function() |
+ { |
+ return this.parsedLine; |
+ }, |
+ |
+ /** |
+ * Sets the line where the error occurred. |
+ * |
+ * @param {number} parsedLine The file line |
+ */ |
+ setParsedLine: function(parsedLine) |
+ { |
+ this.parsedLine = parsedLine; |
+ |
+ this.updateRepr(); |
+ }, |
+ |
+ updateRepr: function() |
+ { |
+ this.message = this.rawMessage; |
+ |
+ var dot = false; |
+ if ('.' === this.message.charAt(this.message.length - 1)) { |
+ this.message = this.message.substring(0, this.message.length - 1); |
+ dot = true; |
+ } |
+ |
+ if (null !== this.parsedFile) { |
+ this.message += ' in ' + JSON.stringify(this.parsedFile); |
+ } |
+ |
+ if (this.parsedLine >= 0) { |
+ this.message += ' at line ' + this.parsedLine; |
+ } |
+ |
+ if (this.snippet) { |
+ this.message += ' (near "' + this.snippet + '")'; |
+ } |
+ |
+ if (dot) { |
+ this.message += '.'; |
+ } |
+ } |
+}; |
+/** |
+ * Yaml offers convenience methods to parse and dump YAML. |
+ * |
+ * @author Fabien Potencier <fabien@symfony.com> |
+ * |
+ * @api |
+ */ |
+ |
+var YamlRunningUnderNode = false; |
+var Yaml = function(){}; |
+Yaml.prototype = |
+{ |
+ |
+ /** |
+ * Parses YAML into a JS representation. |
+ * |
+ * The parse method, when supplied with a YAML stream (file), |
+ * will do its best to convert YAML in a file into a JS representation. |
+ * |
+ * Usage: |
+ * <code> |
+ * obj = yaml.parseFile('config.yml'); |
+ * </code> |
+ * |
+ * @param {string} file Path of YAML file |
+ * @param {Function} [callback] |
+ * |
+ * @return array The YAML converted to a JS representation |
+ * |
+ * @throws YamlParseException If the YAML is not valid |
+ */ |
+ parseFile: function(file /* String */, callback /* Function */) |
+ { |
+ if ( callback == null ) |
+ { |
+ var input = this.getFileContents(file); |
+ var ret = null; |
+ try |
+ { |
+ ret = this.parse(input); |
+ } |
+ catch ( e ) |
+ { |
+ if ( e instanceof YamlParseException ) { |
+ e.setParsedFile(file); |
+ } |
+ throw e; |
+ } |
+ return ret; |
+ } |
+ |
+ this.getFileContents(file, function(data) |
+ { |
+ callback(new Yaml().parse(data)); |
+ }); |
+ }, |
+ |
+ /** |
+ * Parses YAML into a JS representation. |
+ * |
+ * The parse method, when supplied with a YAML stream (string), |
+ * will do its best to convert YAML into a JS representation. |
+ * |
+ * Usage: |
+ * <code> |
+ * obj = yaml.parse(...); |
+ * </code> |
+ * |
+ * @param {string} input string containing YAML |
+ * |
+ * @return array The YAML converted to a JS representation |
+ * |
+ * @throws YamlParseException If the YAML is not valid |
+ */ |
+ parse: function(input /* String */) |
+ { |
+ var yaml = new YamlParser(); |
+ |
+ return yaml.parse(input); |
+ }, |
+ |
+ /** |
+ * Dumps a JS representation to a YAML string. |
+ * |
+ * The dump method, when supplied with an array, will do its best |
+ * to convert the array into friendly YAML. |
+ * |
+ * @param {Array} array JS representation |
+ * @param {number} inline The level where you switch to inline YAML |
+ * @param {number} [spaces] |
+ * |
+ * @return string A YAML string representing the original JS representation |
+ * |
+ * @api |
+ */ |
+ dump: function(array, inline, spaces) |
+ { |
+ if ( inline == null ) inline = 2; |
+ |
+ var yaml = new YamlDumper(); |
+ if (spaces) { |
+ yaml.numSpacesForIndentation = spaces; |
+ } |
+ |
+ return yaml.dump(array, inline); |
+ }, |
+ |
+ getXHR: function() |
+ { |
+ if ( window.XMLHttpRequest ) |
+ return new XMLHttpRequest(); |
+ |
+ if ( window.ActiveXObject ) |
+ { |
+ var names = [ |
+ "Msxml2.XMLHTTP.6.0", |
+ "Msxml2.XMLHTTP.3.0", |
+ "Msxml2.XMLHTTP", |
+ "Microsoft.XMLHTTP" |
+ ]; |
+ |
+ for ( var i = 0; i < 4; i++ ) |
+ { |
+ try{ return new ActiveXObject(names[i]); } |
+ catch(e){} |
+ } |
+ } |
+ return null; |
+ }, |
+ |
+ getFileContents: function(file, callback) |
+ { |
+ if ( YamlRunningUnderNode ) |
+ { |
+ var fs = require('fs'); |
+ if ( callback == null ) |
+ { |
+ var data = fs.readFileSync(file); |
+ if (data == null) return null; |
+ return ''+data; |
+ } |
+ else |
+ { |
+ fs.readFile(file, function(err, data) |
+ { |
+ if (err) |
+ callback(null); |
+ else |
+ callback(data); |
+ }); |
+ } |
+ } |
+ else |
+ { |
+ var request = this.getXHR(); |
+ |
+ // Sync |
+ if ( callback == null ) |
+ { |
+ request.open('GET', file, false); |
+ request.send(null); |
+ |
+ if ( request.status == 200 || request.status == 0 ) |
+ return request.responseText; |
+ |
+ return null; |
+ } |
+ |
+ // Async |
+ request.onreadystatechange = function() |
+ { |
+ if ( request.readyState == 4 ) |
+ if ( request.status == 200 || request.status == 0 ) |
+ callback(request.responseText); |
+ else |
+ callback(null); |
+ }; |
+ request.open('GET', file, true); |
+ request.send(null); |
+ } |
+ } |
+}; |
+ |
+var YAML = |
+{ |
+ /* |
+ * @param {number} inline The level where you switch to inline YAML |
+ */ |
+ |
+ stringify: function(input, inline, spaces) |
+ { |
+ return new Yaml().dump(input, inline, spaces); |
+ }, |
+ |
+ parse: function(input) |
+ { |
+ return new Yaml().parse(input); |
+ }, |
+ |
+ load: function(file, callback) |
+ { |
+ return new Yaml().parseFile(file, callback); |
+ } |
+}; |
+ |
+exports.YAML = YAML; |
+exports.YamlParseException = YamlParseException; |
+ |
+// Handle browser case |
+if ( typeof(window) != "undefined" ) |
+{ |
+ window.YAML = YAML; |
+} |
+ |
+/** |
+ * YamlInline implements a YAML parser/dumper for the YAML inline syntax. |
+ */ |
+var YamlInline = function(){}; |
+YamlInline.prototype = |
+{ |
+ i: null, |
+ |
+ /** |
+ * Convert a YAML string to a JS object. |
+ * |
+ * @param {string} value A YAML string |
+ * |
+ * @return object A JS object representing the YAML string |
+ */ |
+ parse: function(value) |
+ { |
+ var result = null; |
+ value = this.trim(value); |
+ |
+ if ( 0 == value.length ) |
+ { |
+ return ''; |
+ } |
+ |
+ switch ( value.charAt(0) ) |
+ { |
+ case '[': |
+ result = this.parseSequence(value); |
+ break; |
+ case '{': |
+ result = this.parseMapping(value); |
+ break; |
+ default: |
+ result = this.parseScalar(value); |
+ } |
+ |
+ // some comment can end the scalar |
+ if ( value.substr(this.i+1).replace(/^\s*#.*$/, '') != '' ) { |
+ console.log("oups "+value.substr(this.i+1)); |
+ throw new YamlParseException('Unexpected characters near "'+value.substr(this.i)+'".'); |
+ } |
+ |
+ return result; |
+ }, |
+ |
+ /** |
+ * Dumps a given JS variable to a YAML string. |
+ * |
+ * @param value The JS variable to convert |
+ * |
+ * @return string The YAML string representing the JS object |
+ */ |
+ dump: function(value) |
+ { |
+ if ( undefined == value || null == value ) |
+ return 'null'; |
+ if ( value instanceof Date) |
+ return value.toISOString(); |
+ if ( typeof(value) == 'object') |
+ return this.dumpObject(value); |
+ if ( typeof(value) == 'boolean' ) |
+ return value ? 'true' : 'false'; |
+ if ( /^\d+$/.test(value) ) |
+ return typeof(value) == 'string' ? "'"+value+"'" : parseInt(value); |
+ if ( this.isNumeric(value) ) |
+ return typeof(value) == 'string' ? "'"+value+"'" : parseFloat(value); |
+ if ( typeof(value) == 'number' ) |
+ return value == Infinity ? '.Inf' : ( value == -Infinity ? '-.Inf' : ( isNaN(value) ? '.NAN' : value ) ); |
+ var yaml = new YamlEscaper(); |
+ if ( yaml.requiresDoubleQuoting(value) ) |
+ return yaml.escapeWithDoubleQuotes(value); |
+ if ( yaml.requiresSingleQuoting(value) ) |
+ return yaml.escapeWithSingleQuotes(value); |
+ if ( '' == value ) |
+ return ""; |
+ if ( this.getTimestampRegex().test(value) ) |
+ return "'"+value+"'"; |
+ if ( this.inArray(value.toLowerCase(), ['null','~','true','false']) ) |
+ return "'"+value+"'"; |
+ // default |
+ return value; |
+ }, |
+ |
+ /** |
+ * Dumps a JS object to a YAML string. |
+ * |
+ * @param {Object} value The JS array to dump |
+ * |
+ * @return string The YAML string representing the JS object |
+ */ |
+ dumpObject: function(value) |
+ { |
+ var keys = this.getKeys(value); |
+ var output = null; |
+ var i; |
+ var len = keys.length; |
+ |
+ // array |
+ if ( value instanceof Array ) |
+ /*( 1 == len && '0' == keys[0] ) |
+ || |
+ ( len > 1 && this.reduceArray(keys, function(v,w){return Math.floor(v+w);}, 0) == len * (len - 1) / 2) )*/ |
+ { |
+ output = []; |
+ for ( i = 0; i < len; i++ ) |
+ { |
+ output.push(this.dump(value[keys[i]])); |
+ } |
+ |
+ return '['+output.join(', ')+']'; |
+ } |
+ |
+ // mapping |
+ output = []; |
+ for ( i = 0; i < len; i++ ) |
+ { |
+ output.push(this.dump(keys[i])+': '+this.dump(value[keys[i]])); |
+ } |
+ |
+ return '{ '+output.join(', ')+' }'; |
+ }, |
+ |
+ /** |
+ * Parses a scalar to a YAML string. |
+ * |
+ * @param scalar scalar |
+ * @param {string} delimiters |
+ * @param {Object} stringDelimiters |
+ * @param {number} i |
+ * @param {boolean} evaluate |
+ * |
+ * @return string A YAML string |
+ * |
+ * @throws YamlParseException When malformed inline YAML string is parsed |
+ */ |
+ parseScalar: function(scalar, delimiters, stringDelimiters, i, evaluate) |
+ { |
+ if ( delimiters == undefined ) delimiters = null; |
+ if ( stringDelimiters == undefined ) stringDelimiters = ['"', "'"]; |
+ if ( i == undefined ) i = 0; |
+ if ( evaluate == undefined ) evaluate = true; |
+ |
+ var output = null; |
+ var pos = null; |
+ var matches = null; |
+ |
+ if ( this.inArray(scalar[i], stringDelimiters) ) |
+ { |
+ // quoted scalar |
+ output = this.parseQuotedScalar(scalar, i); |
+ i = this.i; |
+ if (null !== delimiters) { |
+ var tmp = scalar.substr(i).replace(/^\s+/, ''); |
+ if (!this.inArray(tmp.charAt(0), delimiters)) { |
+ throw new YamlParseException('Unexpected characters ('+scalar.substr(i)+').'); |
+ } |
+ } |
+ } |
+ else |
+ { |
+ // "normal" string |
+ if ( !delimiters ) |
+ { |
+ output = (scalar+'').substring(i); |
+ |
+ i += output.length; |
+ |
+ // remove comments |
+ pos = output.indexOf(' #'); |
+ if ( pos != -1 ) |
+ { |
+ output = output.substr(0, pos).replace(/\s+$/g,''); |
+ } |
+ } |
+ else if ( matches = new RegExp('^(.+?)('+delimiters.join('|')+')').exec((scalar+'').substring(i)) ) |
+ { |
+ output = matches[1]; |
+ i += output.length; |
+ } |
+ else |
+ { |
+ throw new YamlParseException('Malformed inline YAML string ('+scalar+').'); |
+ } |
+ output = evaluate ? this.evaluateScalar(output) : output; |
+ } |
+ |
+ this.i = i; |
+ |
+ return output; |
+ }, |
+ |
+ /** |
+ * Parses a quoted scalar to YAML. |
+ * |
+ * @param {string} scalar |
+ * @param {number} i |
+ * |
+ * @return string A YAML string |
+ * |
+ * @throws YamlParseException When malformed inline YAML string is parsed |
+ */ |
+ parseQuotedScalar: function(scalar, i) |
+ { |
+ var matches = null; |
+ //var item = /^(.*?)['"]\s*(?:[,:]|[}\]]\s*,)/.exec((scalar+'').substring(i))[1]; |
+ |
+ if ( !(matches = new RegExp('^'+YamlInline.REGEX_QUOTED_STRING).exec((scalar+'').substring(i))) ) |
+ { |
+ throw new YamlParseException('Malformed inline YAML string ('+(scalar+'').substring(i)+').'); |
+ } |
+ |
+ var output = matches[0].substr(1, matches[0].length - 2); |
+ |
+ var unescaper = new YamlUnescaper(); |
+ |
+ if ( '"' == (scalar+'').charAt(i) ) |
+ { |
+ output = unescaper.unescapeDoubleQuotedString(output); |
+ } |
+ else |
+ { |
+ output = unescaper.unescapeSingleQuotedString(output); |
+ } |
+ |
+ i += matches[0].length; |
+ |
+ this.i = i; |
+ return output; |
+ }, |
+ |
+ /** |
+ * Parses a sequence to a YAML string. |
+ * |
+ * @param {string} sequence |
+ * @param {number} i |
+ * |
+ * @return string A YAML string |
+ * |
+ * @throws YamlParseException When malformed inline YAML string is parsed |
+ */ |
+ parseSequence: function(sequence, i) |
+ { |
+ if ( i == undefined ) i = 0; |
+ |
+ var output = []; |
+ var len = sequence.length; |
+ i += 1; |
+ |
+ // [foo, bar, ...] |
+ while ( i < len ) |
+ { |
+ switch ( sequence.charAt(i) ) |
+ { |
+ case '[': |
+ // nested sequence |
+ output.push(this.parseSequence(sequence, i)); |
+ i = this.i; |
+ break; |
+ case '{': |
+ // nested mapping |
+ output.push(this.parseMapping(sequence, i)); |
+ i = this.i; |
+ break; |
+ case ']': |
+ this.i = i; |
+ return output; |
+ case ',': |
+ case ' ': |
+ break; |
+ default: |
+ var isQuoted = this.inArray(sequence.charAt(i), ['"', "'"]); |
+ var value = this.parseScalar(sequence, [',', ']'], ['"', "'"], i); |
+ i = this.i; |
+ |
+ if ( !isQuoted && (value+'').indexOf(': ') != -1 ) |
+ { |
+ // embedded mapping? |
+ try |
+ { |
+ value = this.parseMapping('{'+value+'}'); |
+ } |
+ catch ( e ) |
+ { |
+ if ( !(e instanceof YamlParseException ) ) throw e; |
+ // no, it's not |
+ } |
+ } |
+ |
+ output.push(value); |
+ |
+ i--; |
+ } |
+ |
+ i++; |
+ } |
+ |
+ throw new YamlParseException('Malformed inline YAML string "'+sequence+'"'); |
+ }, |
+ |
+ /** |
+ * Parses a mapping to a YAML string. |
+ * |
+ * @param {string} mapping |
+ * @param {number} i |
+ * |
+ * @return string A YAML string |
+ * |
+ * @throws YamlParseException When malformed inline YAML string is parsed |
+ */ |
+ parseMapping: function(mapping, i) |
+ { |
+ if ( i == undefined ) i = 0; |
+ var output = {}; |
+ var len = mapping.length; |
+ i += 1; |
+ var done = false; |
+ var doContinue = false; |
+ |
+ // {foo: bar, bar:foo, ...} |
+ while ( i < len ) |
+ { |
+ doContinue = false; |
+ |
+ switch ( mapping.charAt(i) ) |
+ { |
+ case ' ': |
+ case ',': |
+ i++; |
+ doContinue = true; |
+ break; |
+ case '}': |
+ this.i = i; |
+ return output; |
+ } |
+ |
+ if ( doContinue ) continue; |
+ |
+ // key |
+ var key = this.parseScalar(mapping, [':', ' '], ['"', "'"], i, false); |
+ i = this.i; |
+ |
+ // value |
+ done = false; |
+ while ( i < len ) |
+ { |
+ switch ( mapping.charAt(i) ) |
+ { |
+ case '[': |
+ // nested sequence |
+ output[key] = this.parseSequence(mapping, i); |
+ i = this.i; |
+ done = true; |
+ break; |
+ case '{': |
+ // nested mapping |
+ output[key] = this.parseMapping(mapping, i); |
+ i = this.i; |
+ done = true; |
+ break; |
+ case ':': |
+ case ' ': |
+ break; |
+ default: |
+ output[key] = this.parseScalar(mapping, [',', '}'], ['"', "'"], i); |
+ i = this.i; |
+ done = true; |
+ i--; |
+ } |
+ |
+ ++i; |
+ |
+ if ( done ) |
+ { |
+ doContinue = true; |
+ break; |
+ } |
+ } |
+ |
+ //if ( doContinue ) continue; |
+ } |
+ |
+ throw new YamlParseException('Malformed inline YAML string "'+mapping+'"'); |
+ }, |
+ |
+ /** |
+ * Evaluates scalars and replaces magic values. |
+ * |
+ * @param {string} scalar |
+ * |
+ * @return string A YAML string |
+ */ |
+ evaluateScalar: function(scalar) |
+ { |
+ scalar = this.trim(scalar); |
+ |
+ var raw = null; |
+ var cast = null; |
+ |
+ if ( ( 'null' == scalar.toLowerCase() ) || |
+ ( '' == scalar ) || |
+ ( '~' == scalar ) ) |
+ return null; |
+ if ( (scalar+'').indexOf('!str ') == 0 ) |
+ return (''+scalar).substring(5); |
+ if ( (scalar+'').indexOf('! ') == 0 ) |
+ return parseInt(this.parseScalar((scalar+'').substr(2))); |
+ if ( /^\d+$/.test(scalar) ) |
+ { |
+ raw = scalar; |
+ cast = parseInt(scalar); |
+ return '0' == scalar.charAt(0) ? this.octdec(scalar) : (( ''+raw == ''+cast ) ? cast : raw); |
+ } |
+ if ( 'true' == (scalar+'').toLowerCase() ) |
+ return true; |
+ if ( 'false' == (scalar+'').toLowerCase() ) |
+ return false; |
+ if ( this.isNumeric(scalar) ) |
+ return '0x' == (scalar+'').substr(0, 2) ? this.hexdec(scalar) : parseFloat(scalar); |
+ if ( scalar.toLowerCase() == '.inf' ) |
+ return Infinity; |
+ if ( scalar.toLowerCase() == '.nan' ) |
+ return NaN; |
+ if ( scalar.toLowerCase() == '-.inf' ) |
+ return -Infinity; |
+ if ( /^(-|\+)?[0-9,]+(\.[0-9]+)?$/.test(scalar) ) |
+ return parseFloat(scalar.split(',').join('')); |
+ if ( this.getTimestampRegex().test(scalar) ) |
+ return new Date(this.strtotime(scalar)); |
+ //else |
+ return ''+scalar; |
+ }, |
+ |
+ /** |
+ * Gets a regex that matches an unix timestamp |
+ * |
+ * @return string The regular expression |
+ */ |
+ getTimestampRegex: function() |
+ { |
+ return new RegExp('^'+ |
+ '([0-9][0-9][0-9][0-9])'+ |
+ '-([0-9][0-9]?)'+ |
+ '-([0-9][0-9]?)'+ |
+ '(?:(?:[Tt]|[ \t]+)'+ |
+ '([0-9][0-9]?)'+ |
+ ':([0-9][0-9])'+ |
+ ':([0-9][0-9])'+ |
+ '(?:\.([0-9]*))?'+ |
+ '(?:[ \t]*(Z|([-+])([0-9][0-9]?)'+ |
+ '(?::([0-9][0-9]))?))?)?'+ |
+ '$','gi'); |
+ }, |
+ |
+ trim: function(str /* String */) |
+ { |
+ return (str+'').replace(/^\s+/,'').replace(/\s+$/,''); |
+ }, |
+ |
+ isNumeric: function(input) |
+ { |
+ return (input - 0) == input && input.length > 0 && input.replace(/\s+/g,'') != ''; |
+ }, |
+ |
+ inArray: function(key, tab) |
+ { |
+ var i; |
+ var len = tab.length; |
+ for ( i = 0; i < len; i++ ) |
+ { |
+ if ( key == tab[i] ) return true; |
+ } |
+ return false; |
+ }, |
+ |
+ getKeys: function(tab) |
+ { |
+ var ret = []; |
+ |
+ for ( var name in tab ) |
+ { |
+ if ( tab.hasOwnProperty(name) ) |
+ { |
+ ret.push(name); |
+ } |
+ } |
+ |
+ return ret; |
+ }, |
+ |
+ /*reduceArray: function(tab, fun) |
+ { |
+ var len = tab.length; |
+ if (typeof fun != "function") |
+ throw new YamlParseException("fun is not a function"); |
+ |
+ // no value to return if no initial value and an empty array |
+ if (len == 0 && arguments.length == 1) |
+ throw new YamlParseException("empty array"); |
+ |
+ var i = 0; |
+ if (arguments.length >= 2) |
+ { |
+ var rv = arguments[1]; |
+ } |
+ else |
+ { |
+ do |
+ { |
+ if (i in tab) |
+ { |
+ rv = tab[i++]; |
+ break; |
+ } |
+ |
+ // if array contains no values, no initial value to return |
+ if (++i >= len) |
+ throw new YamlParseException("no initial value to return"); |
+ } |
+ while (true); |
+ } |
+ |
+ for (; i < len; i++) |
+ { |
+ if (i in tab) |
+ rv = fun.call(null, rv, tab[i], i, tab); |
+ } |
+ |
+ return rv; |
+ },*/ |
+ |
+ octdec: function(input) |
+ { |
+ return parseInt((input+'').replace(/[^0-7]/gi, ''), 8); |
+ }, |
+ |
+ hexdec: function(input) |
+ { |
+ input = this.trim(input); |
+ if ( (input+'').substr(0, 2) == '0x' ) input = (input+'').substring(2); |
+ return parseInt((input+'').replace(/[^a-f0-9]/gi, ''), 16); |
+ }, |
+ |
+ /** |
+ * @see http://phpjs.org/functions/strtotime |
+ * @note we need timestamp with msecs so /1000 removed |
+ * @note original contained binary | 0 (wtf?!) everywhere, which messes everything up |
+ */ |
+ strtotime: function (h,b){var f,c,g,k,d="";h=(h+"").replace(/\s{2,}|^\s|\s$/g," ").replace(/[\t\r\n]/g,"");if(h==="now"){return b===null||isNaN(b)?new Date().getTime()||0:b||0}else{if(!isNaN(d=Date.parse(h))){return d||0}else{if(b){b=new Date(b)}else{b=new Date()}}}h=h.toLowerCase();var e={day:{sun:0,mon:1,tue:2,wed:3,thu:4,fri:5,sat:6},mon:["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"]};var a=function(i){var o=(i[2]&&i[2]==="ago");var n=(i[0]==="last"?-1:1)*(o?-1:1);switch(i[0]){case"last":case"next":switch(i[1].substring(0,3)){case"yea":b.setFullYear(b.getFullYear()+n);break;case"wee":b.setDate(b.getDate()+(n*7));break;case"day":b.setDate(b.getDate()+n);break;case"hou":b.setHours(b.getHours()+n);break;case"min":b.setMinutes(b.getMinutes()+n);break;case"sec":b.setSeconds(b.getSeconds()+n);break;case"mon":if(i[1]==="month"){b.setMonth(b.getMonth()+n);}break;default:var l=e.day[i[1].substring(0,3)];if(typeof l!=="undefined"){var p=l-b.getDay();if(p===0){p=7*n}else{if(p>0){if(i[0]==="last"){p-=7}}else{if(i[0]==="next"){p+=7}}}b.setDate(b.getDate()+p);b.setHours(0,0,0,0)}}break;default:if(/\d+/.test(i[0])){n*=parseInt(i[0],10);switch(i[1].substring(0,3)){case"yea":b.setFullYear(b.getFullYear()+n);break;case"mon":b.setMonth(b.getMonth()+n);break;case"wee":b.setDate(b.getDate()+(n*7));break;case"day":b.setDate(b.getDate()+n);break;case"hou":b.setHours(b.getHours()+n);break;case"min":b.setMinutes(b.getMinutes()+n);break;case"sec":b.setSeconds(b.getSeconds()+n);break}}else{return false}break}return true};g=h.match(/^(\d{2,4}-\d{2}-\d{2})(?:\s(\d{1,2}:\d{2}(:\d{2})?)?(?:\.(\d+))?)?$/);if(g!==null){if(!g[2]){g[2]="00:00:00"}else{if(!g[3]){g[2]+=":00"}}k=g[1].split(/-/g);k[1]=e.mon[k[1]-1]||k[1];k[0]=+k[0];k[0]=(k[0]>=0&&k[0]<=69)?"20"+(k[0]<10?"0"+k[0]:k[0]+""):(k[0]>=70&&k[0]<=99)?"19"+k[0]:k[0]+"";return parseInt(this.strtotime(k[2]+" "+k[1]+" "+k[0]+" "+g[2])+(g[4]?g[4]:""),10)}var j="([+-]?\\d+\\s(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday)|(last|next)\\s(years?|months?|weeks?|days?|hours?|min|minutes?|sec|seconds?|sun\\.?|sunday|mon\\.?|monday|tue\\.?|tuesday|wed\\.?|wednesday|thu\\.?|thursday|fri\\.?|friday|sat\\.?|saturday))(\\sago)?";g=h.match(new RegExp(j,"gi"));if(g===null){return false}for(f=0,c=g.length;f<c;f++){if(!a(g[f].split(" "))){return false}}return b.getTime()||0} |
+ |
+}; |
+ |
+/* |
+ * @note uses only non-capturing sub-patterns (unlike PHP original) |
+ */ |
+YamlInline.REGEX_QUOTED_STRING = '(?:"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\']*(?:\'\'[^\']*)*)\')'; |
+ |
+ |
+/** |
+ * YamlParser parses YAML strings to convert them to JS objects |
+ * (port of Yaml Symfony Component) |
+ */ |
+var YamlParser = function(offset /* Integer */) |
+{ |
+ this.offset = (offset !== undefined) ? offset : 0; |
+}; |
+YamlParser.prototype = |
+{ |
+ offset: 0, |
+ lines: [], |
+ currentLineNb: -1, |
+ currentLine: '', |
+ refs: {}, |
+ |
+ /** |
+ * Parses a YAML string to a JS value. |
+ * |
+ * @param {string} value A YAML string |
+ * |
+ * @return mixed A JS value |
+ */ |
+ parse: function(value /* String */) |
+ { |
+ this.currentLineNb = -1; |
+ this.currentLine = ''; |
+ this.lines = this.cleanup(value).split("\n"); |
+ |
+ var data = null; |
+ var context = null; |
+ |
+ while ( this.moveToNextLine() ) |
+ { |
+ if ( this.isCurrentLineEmpty() ) |
+ { |
+ continue; |
+ } |
+ |
+ // tab? |
+ if ( this.currentLine.charAt(0) == '\t' ) |
+ { |
+ throw new YamlParseException('A YAML file cannot contain tabs as indentation.', this.getRealCurrentLineNb() + 1, this.currentLine); |
+ } |
+ |
+ var isRef = false; |
+ var isInPlace = false; |
+ var isProcessed = false; |
+ var values = null; |
+ var matches = null; |
+ var c = null; |
+ var parser = null; |
+ var block = null; |
+ var key = null; |
+ var parsed = null; |
+ var len = null; |
+ var reverse = null; |
+ |
+ if ( values = /^\-((\s+)(.+?))?\s*$/.exec(this.currentLine) ) |
+ { |
+ |
+ if (context && 'mapping' == context) { |
+ throw new YamlParseException('You cannot define a sequence item when in a mapping', this.getRealCurrentLineNb() + 1, this.currentLine); |
+ } |
+ context = 'sequence'; |
+ |
+ if ( !this.isDefined(data) ) data = []; |
+ //if ( !(data instanceof Array) ) throw new YamlParseException("Non array entry", this.getRealCurrentLineNb() + 1, this.currentLine); |
+ |
+ values = {leadspaces: values[2], value: values[3]}; |
+ |
+ if ( this.isDefined(values.value) && ( matches = /^&([^ ]+) *(.*)/.exec(values.value) ) ) |
+ { |
+ matches = {ref: matches[1], value: matches[2]}; |
+ isRef = matches.ref; |
+ values.value = matches.value; |
+ } |
+ |
+ // array |
+ if ( !this.isDefined(values.value) || '' == this.trim(values.value) || values.value.replace(/^ +/,'').charAt(0) == '#' ) |
+ { |
+ c = this.getRealCurrentLineNb() + 1; |
+ parser = new YamlParser(c); |
+ parser.refs = this.refs; |
+ data.push(parser.parse(this.getNextEmbedBlock())); |
+ this.refs = parser.refs; |
+ } |
+ else |
+ { |
+ if ( this.isDefined(values.leadspaces) && |
+ ' ' == values.leadspaces && |
+ ( matches = new RegExp('^('+YamlInline.REGEX_QUOTED_STRING+'|[^ \'"\{\[].*?) *\:(\\s+(.+?))?\\s*$').exec(values.value) ) |
+ ) { |
+ matches = {key: matches[1], value: matches[3]}; |
+ // this is a compact notation element, add to next block and parse |
+ c = this.getRealCurrentLineNb(); |
+ parser = new YamlParser(c); |
+ parser.refs = this.refs; |
+ block = values.value; |
+ |
+ if ( !this.isNextLineIndented() ) |
+ { |
+ block += "\n"+this.getNextEmbedBlock(this.getCurrentLineIndentation() + 2); |
+ } |
+ |
+ data.push(parser.parse(block)); |
+ this.refs = parser.refs; |
+ } |
+ else |
+ { |
+ data.push(this.parseValue(values.value)); |
+ } |
+ } |
+ } |
+ else if ( values = new RegExp('^('+YamlInline.REGEX_QUOTED_STRING+'|[^ \'"\[\{].*?) *\:(\\s+(.+?))?\\s*$').exec(this.currentLine) ) |
+ { |
+ if ( !this.isDefined(data) ) data = {}; |
+ if (context && 'sequence' == context) { |
+ throw new YamlParseException('You cannot define a mapping item when in a sequence', this.getRealCurrentLineNb() + 1, this.currentLine); |
+ } |
+ context = 'mapping'; |
+ //if ( data instanceof Array ) throw new YamlParseException("Non mapped entry", this.getRealCurrentLineNb() + 1, this.currentLine); |
+ |
+ values = {key: values[1], value: values[3]}; |
+ |
+ try { |
+ key = new YamlInline().parseScalar(values.key); |
+ } catch (e) { |
+ if ( e instanceof YamlParseException ) { |
+ e.setParsedLine(this.getRealCurrentLineNb() + 1); |
+ e.setSnippet(this.currentLine); |
+ } |
+ throw e; |
+ } |
+ |
+ |
+ if ( '<<' == key ) |
+ { |
+ if ( this.isDefined(values.value) && '*' == (values.value+'').charAt(0) ) |
+ { |
+ isInPlace = values.value.substr(1); |
+ if ( this.refs[isInPlace] == undefined ) |
+ { |
+ throw new YamlParseException('Reference "'+value+'" does not exist', this.getRealCurrentLineNb() + 1, this.currentLine); |
+ } |
+ } |
+ else |
+ { |
+ if ( this.isDefined(values.value) && values.value != '' ) |
+ { |
+ value = values.value; |
+ } |
+ else |
+ { |
+ value = this.getNextEmbedBlock(); |
+ } |
+ |
+ c = this.getRealCurrentLineNb() + 1; |
+ parser = new YamlParser(c); |
+ parser.refs = this.refs; |
+ parsed = parser.parse(value); |
+ this.refs = parser.refs; |
+ |
+ var merged = []; |
+ if ( !this.isObject(parsed) ) |
+ { |
+ throw new YamlParseException("YAML merge keys used with a scalar value instead of an array", this.getRealCurrentLineNb() + 1, this.currentLine); |
+ } |
+ else if ( this.isDefined(parsed[0]) ) |
+ { |
+ // Numeric array, merge individual elements |
+ reverse = this.reverseArray(parsed); |
+ len = reverse.length; |
+ for ( var i = 0; i < len; i++ ) |
+ { |
+ var parsedItem = reverse[i]; |
+ if ( !this.isObject(reverse[i]) ) |
+ { |
+ throw new YamlParseException("Merge items must be arrays", this.getRealCurrentLineNb() + 1, this.currentLine); |
+ } |
+ merged = this.mergeObject(reverse[i], merged); |
+ } |
+ } |
+ else |
+ { |
+ // Associative array, merge |
+ merged = this.mergeObject(merged, parsed); |
+ } |
+ |
+ isProcessed = merged; |
+ } |
+ } |
+ else if ( this.isDefined(values.value) && (matches = /^&([^ ]+) *(.*)/.exec(values.value) ) ) |
+ { |
+ matches = {ref: matches[1], value: matches[2]}; |
+ isRef = matches.ref; |
+ values.value = matches.value; |
+ } |
+ |
+ if ( isProcessed ) |
+ { |
+ // Merge keys |
+ data = isProcessed; |
+ } |
+ // hash |
+ else if ( !this.isDefined(values.value) || '' == this.trim(values.value) || this.trim(values.value).charAt(0) == '#' ) |
+ { |
+ // if next line is less indented or equal, then it means that the current value is null |
+ if ( this.isNextLineIndented() && !this.isNextLineUnIndentedCollection() ) |
+ { |
+ data[key] = null; |
+ } |
+ else |
+ { |
+ c = this.getRealCurrentLineNb() + 1; |
+ parser = new YamlParser(c); |
+ parser.refs = this.refs; |
+ data[key] = parser.parse(this.getNextEmbedBlock()); |
+ this.refs = parser.refs; |
+ } |
+ } |
+ else |
+ { |
+ if ( isInPlace ) |
+ { |
+ data = this.refs[isInPlace]; |
+ } |
+ else |
+ { |
+ data[key] = this.parseValue(values.value); |
+ } |
+ } |
+ } |
+ else |
+ { |
+ // 1-liner followed by newline |
+ if ( 2 == this.lines.length && this.isEmpty(this.lines[1]) ) |
+ { |
+ try { |
+ value = new YamlInline().parse(this.lines[0]); |
+ } catch (e) { |
+ if ( e instanceof YamlParseException ) { |
+ e.setParsedLine(this.getRealCurrentLineNb() + 1); |
+ e.setSnippet(this.currentLine); |
+ } |
+ throw e; |
+ } |
+ |
+ if ( this.isObject(value) ) |
+ { |
+ var first = value[0]; |
+ if ( typeof(value) == 'string' && '*' == first.charAt(0) ) |
+ { |
+ data = []; |
+ len = value.length; |
+ for ( var i = 0; i < len; i++ ) |
+ { |
+ data.push(this.refs[value[i].substr(1)]); |
+ } |
+ value = data; |
+ } |
+ } |
+ |
+ return value; |
+ } |
+ |
+ throw new YamlParseException('Unable to parse.', this.getRealCurrentLineNb() + 1, this.currentLine); |
+ } |
+ |
+ if ( isRef ) |
+ { |
+ if ( data instanceof Array ) |
+ this.refs[isRef] = data[data.length-1]; |
+ else |
+ { |
+ var lastKey = null; |
+ for ( var k in data ) |
+ { |
+ if ( data.hasOwnProperty(k) ) lastKey = k; |
+ } |
+ this.refs[isRef] = data[k]; |
+ } |
+ } |
+ } |
+ |
+ return this.isEmpty(data) ? null : data; |
+ }, |
+ |
+ /** |
+ * Returns the current line number (takes the offset into account). |
+ * |
+ * @return integer The current line number |
+ */ |
+ getRealCurrentLineNb: function() |
+ { |
+ return this.currentLineNb + this.offset; |
+ }, |
+ |
+ /** |
+ * Returns the current line indentation. |
+ * |
+ * @return integer The current line indentation |
+ */ |
+ getCurrentLineIndentation: function() |
+ { |
+ return this.currentLine.length - this.currentLine.replace(/^ +/g, '').length; |
+ }, |
+ |
+ /** |
+ * Returns the next embed block of YAML. |
+ * |
+ * @param {number} indentation The indent level at which the block is to be read, or null for default |
+ * |
+ * @return string A YAML string |
+ * |
+ * @throws YamlParseException When indentation problem are detected |
+ */ |
+ getNextEmbedBlock: function(indentation) |
+ { |
+ this.moveToNextLine(); |
+ var newIndent = null; |
+ var indent = null; |
+ |
+ if ( !this.isDefined(indentation) ) |
+ { |
+ newIndent = this.getCurrentLineIndentation(); |
+ |
+ var unindentedEmbedBlock = this.isStringUnIndentedCollectionItem(this.currentLine); |
+ |
+ if ( !this.isCurrentLineEmpty() && 0 == newIndent && !unindentedEmbedBlock ) |
+ { |
+ throw new YamlParseException('Indentation problem A', this.getRealCurrentLineNb() + 1, this.currentLine); |
+ } |
+ } |
+ else |
+ { |
+ newIndent = indentation; |
+ } |
+ |
+ var data = [this.currentLine.substr(newIndent)]; |
+ |
+ var isUnindentedCollection = this.isStringUnIndentedCollectionItem(this.currentLine); |
+ |
+ var continuationIndent = -1; |
+ if (isUnindentedCollection === true) { |
+ continuationIndent = 1 + /^\-((\s+)(.+?))?\s*$/.exec(this.currentLine)[2].length; |
+ } |
+ |
+ while ( this.moveToNextLine() ) |
+ { |
+ |
+ if (isUnindentedCollection && !this.isStringUnIndentedCollectionItem(this.currentLine) && this.getCurrentLineIndentation() != continuationIndent) { |
+ this.moveToPreviousLine(); |
+ break; |
+ } |
+ |
+ if ( this.isCurrentLineEmpty() ) |
+ { |
+ if ( this.isCurrentLineBlank() ) |
+ { |
+ data.push(this.currentLine.substr(newIndent)); |
+ } |
+ |
+ continue; |
+ } |
+ |
+ indent = this.getCurrentLineIndentation(); |
+ var matches; |
+ if ( matches = /^( *)$/.exec(this.currentLine) ) |
+ { |
+ // empty line |
+ data.push(matches[1]); |
+ } |
+ else if ( indent >= newIndent ) |
+ { |
+ data.push(this.currentLine.substr(newIndent)); |
+ } |
+ else if ( 0 == indent ) |
+ { |
+ this.moveToPreviousLine(); |
+ |
+ break; |
+ } |
+ else |
+ { |
+ throw new YamlParseException('Indentation problem B', this.getRealCurrentLineNb() + 1, this.currentLine); |
+ } |
+ } |
+ |
+ return data.join("\n"); |
+ }, |
+ |
+ /** |
+ * Moves the parser to the next line. |
+ * |
+ * @return Boolean |
+ */ |
+ moveToNextLine: function() |
+ { |
+ if ( this.currentLineNb >= this.lines.length - 1 ) |
+ { |
+ return false; |
+ } |
+ |
+ this.currentLineNb++; |
+ this.currentLine = this.lines[this.currentLineNb]; |
+ |
+ return true; |
+ }, |
+ |
+ /** |
+ * Moves the parser to the previous line. |
+ */ |
+ moveToPreviousLine: function() |
+ { |
+ this.currentLineNb--; |
+ this.currentLine = this.lines[this.currentLineNb]; |
+ }, |
+ |
+ /** |
+ * Parses a YAML value. |
+ * |
+ * @param {string} value A YAML value |
+ * |
+ * @return mixed A JS value |
+ * |
+ * @throws YamlParseException When reference does not exist |
+ */ |
+ parseValue: function(value) |
+ { |
+ if ( '*' == (value+'').charAt(0) ) |
+ { |
+ if ( this.trim(value).charAt(0) == '#' ) |
+ { |
+ value = (value+'').substr(1, value.indexOf('#') - 2); |
+ } |
+ else |
+ { |
+ value = (value+'').substr(1); |
+ } |
+ |
+ if ( this.refs[value] == undefined ) |
+ { |
+ throw new YamlParseException('Reference "'+value+'" does not exist', this.getRealCurrentLineNb() + 1, this.currentLine); |
+ } |
+ return this.refs[value]; |
+ } |
+ |
+ var matches = null; |
+ if ( matches = /^(\||>)(\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?( +#.*)?$/.exec(value) ) |
+ { |
+ matches = {separator: matches[1], modifiers: matches[2], comments: matches[3]}; |
+ var modifiers = this.isDefined(matches.modifiers) ? matches.modifiers : ''; |
+ |
+ return this.parseFoldedScalar(matches.separator, modifiers.replace(/\d+/g, ''), Math.abs(parseInt(modifiers))); |
+ } |
+ try { |
+ return new YamlInline().parse(value); |
+ } catch (e) { |
+ if ( e instanceof YamlParseException ) { |
+ e.setParsedLine(this.getRealCurrentLineNb() + 1); |
+ e.setSnippet(this.currentLine); |
+ } |
+ throw e; |
+ } |
+ }, |
+ |
+ /** |
+ * Parses a folded scalar. |
+ * |
+ * @param {string} separator The separator that was used to begin this folded scalar (| or >) |
+ * @param {string} indicator The indicator that was used to begin this folded scalar (+ or -) |
+ * @param {number} indentation The indentation that was used to begin this folded scalar |
+ * |
+ * @return string The text value |
+ */ |
+ parseFoldedScalar: function(separator, indicator, indentation) |
+ { |
+ if ( indicator == undefined ) indicator = ''; |
+ if ( indentation == undefined ) indentation = 0; |
+ |
+ separator = '|' == separator ? "\n" : ' '; |
+ var text = ''; |
+ var diff = null; |
+ |
+ var notEOF = this.moveToNextLine(); |
+ |
+ while ( notEOF && this.isCurrentLineBlank() ) |
+ { |
+ text += "\n"; |
+ |
+ notEOF = this.moveToNextLine(); |
+ } |
+ |
+ if ( !notEOF ) |
+ { |
+ return ''; |
+ } |
+ |
+ var matches = null; |
+ if ( !(matches = new RegExp('^('+(indentation ? this.strRepeat(' ', indentation) : ' +')+')(.*)$').exec(this.currentLine)) ) |
+ { |
+ this.moveToPreviousLine(); |
+ |
+ return ''; |
+ } |
+ |
+ matches = {indent: matches[1], text: matches[2]}; |
+ |
+ var textIndent = matches.indent; |
+ var previousIndent = 0; |
+ |
+ text += matches.text + separator; |
+ while ( this.currentLineNb + 1 < this.lines.length ) |
+ { |
+ this.moveToNextLine(); |
+ |
+ if ( matches = new RegExp('^( {'+textIndent.length+',})(.+)$').exec(this.currentLine) ) |
+ { |
+ matches = {indent: matches[1], text: matches[2]}; |
+ |
+ if ( ' ' == separator && previousIndent != matches.indent ) |
+ { |
+ text = text.substr(0, text.length - 1)+"\n"; |
+ } |
+ |
+ previousIndent = matches.indent; |
+ |
+ diff = matches.indent.length - textIndent.length; |
+ text += this.strRepeat(' ', diff) + matches.text + (diff != 0 ? "\n" : separator); |
+ } |
+ else if ( matches = /^( *)$/.exec(this.currentLine) ) |
+ { |
+ text += matches[1].replace(new RegExp('^ {1,'+textIndent.length+'}','g'), '')+"\n"; |
+ } |
+ else |
+ { |
+ this.moveToPreviousLine(); |
+ |
+ break; |
+ } |
+ } |
+ |
+ if ( ' ' == separator ) |
+ { |
+ // replace last separator by a newline |
+ text = text.replace(/ (\n*)$/g, "\n$1"); |
+ } |
+ |
+ switch ( indicator ) |
+ { |
+ case '': |
+ text = text.replace(/\n+$/g, "\n"); |
+ break; |
+ case '+': |
+ break; |
+ case '-': |
+ text = text.replace(/\n+$/g, ''); |
+ break; |
+ } |
+ |
+ return text; |
+ }, |
+ |
+ /** |
+ * Returns true if the next line is indented. |
+ * |
+ * @return Boolean Returns true if the next line is indented, false otherwise |
+ */ |
+ isNextLineIndented: function() |
+ { |
+ var currentIndentation = this.getCurrentLineIndentation(); |
+ var notEOF = this.moveToNextLine(); |
+ |
+ while ( notEOF && this.isCurrentLineEmpty() ) |
+ { |
+ notEOF = this.moveToNextLine(); |
+ } |
+ |
+ if ( false == notEOF ) |
+ { |
+ return false; |
+ } |
+ |
+ var ret = false; |
+ if ( this.getCurrentLineIndentation() <= currentIndentation ) |
+ { |
+ ret = true; |
+ } |
+ |
+ this.moveToPreviousLine(); |
+ |
+ return ret; |
+ }, |
+ |
+ /** |
+ * Returns true if the current line is blank or if it is a comment line. |
+ * |
+ * @return Boolean Returns true if the current line is empty or if it is a comment line, false otherwise |
+ */ |
+ isCurrentLineEmpty: function() |
+ { |
+ return this.isCurrentLineBlank() || this.isCurrentLineComment(); |
+ }, |
+ |
+ /** |
+ * Returns true if the current line is blank. |
+ * |
+ * @return Boolean Returns true if the current line is blank, false otherwise |
+ */ |
+ isCurrentLineBlank: function() |
+ { |
+ return '' == this.trim(this.currentLine); |
+ }, |
+ |
+ /** |
+ * Returns true if the current line is a comment line. |
+ * |
+ * @return Boolean Returns true if the current line is a comment line, false otherwise |
+ */ |
+ isCurrentLineComment: function() |
+ { |
+ //checking explicitly the first char of the trim is faster than loops or strpos |
+ var ltrimmedLine = this.currentLine.replace(/^ +/g, ''); |
+ return ltrimmedLine.charAt(0) == '#'; |
+ }, |
+ |
+ /** |
+ * Cleanups a YAML string to be parsed. |
+ * |
+ * @param {string} value The input YAML string |
+ * |
+ * @return string A cleaned up YAML string |
+ */ |
+ cleanup: function(value) |
+ { |
+ value = value.split("\r\n").join("\n").split("\r").join("\n"); |
+ |
+ if ( !/\n$/.test(value) ) |
+ { |
+ value += "\n"; |
+ } |
+ |
+ // strip YAML header |
+ var count = 0; |
+ var regex = /^\%YAML[: ][\d\.]+.*\n/; |
+ while ( regex.test(value) ) |
+ { |
+ value = value.replace(regex, ''); |
+ count++; |
+ } |
+ this.offset += count; |
+ |
+ // remove leading comments |
+ regex = /^(#.*?\n)+/; |
+ if ( regex.test(value) ) |
+ { |
+ var trimmedValue = value.replace(regex, ''); |
+ |
+ // items have been removed, update the offset |
+ this.offset += this.subStrCount(value, "\n") - this.subStrCount(trimmedValue, "\n"); |
+ value = trimmedValue; |
+ } |
+ |
+ // remove start of the document marker (---) |
+ regex = /^\-\-\-.*?\n/; |
+ if ( regex.test(value) ) |
+ { |
+ trimmedValue = value.replace(regex, ''); |
+ |
+ // items have been removed, update the offset |
+ this.offset += this.subStrCount(value, "\n") - this.subStrCount(trimmedValue, "\n"); |
+ value = trimmedValue; |
+ |
+ // remove end of the document marker (...) |
+ value = value.replace(/\.\.\.\s*$/g, ''); |
+ } |
+ |
+ return value; |
+ }, |
+ |
+ /** |
+ * Returns true if the next line starts unindented collection |
+ * |
+ * @return Boolean Returns true if the next line starts unindented collection, false otherwise |
+ */ |
+ isNextLineUnIndentedCollection: function() |
+ { |
+ var currentIndentation = this.getCurrentLineIndentation(); |
+ var notEOF = this.moveToNextLine(); |
+ |
+ while (notEOF && this.isCurrentLineEmpty()) { |
+ notEOF = this.moveToNextLine(); |
+ } |
+ |
+ if (false === notEOF) { |
+ return false; |
+ } |
+ |
+ var ret = false; |
+ if ( |
+ this.getCurrentLineIndentation() == currentIndentation |
+ && |
+ this.isStringUnIndentedCollectionItem(this.currentLine) |
+ ) { |
+ ret = true; |
+ } |
+ |
+ this.moveToPreviousLine(); |
+ |
+ return ret; |
+ }, |
+ |
+ /** |
+ * Returns true if the string is unindented collection item |
+ * |
+ * @return Boolean Returns true if the string is unindented collection item, false otherwise |
+ */ |
+ isStringUnIndentedCollectionItem: function(string) |
+ { |
+ return (0 === this.currentLine.indexOf('- ')); |
+ }, |
+ |
+ isObject: function(input) |
+ { |
+ return typeof(input) == 'object' && this.isDefined(input); |
+ }, |
+ |
+ isEmpty: function(input) |
+ { |
+ return input == undefined || input == null || input == '' || input == 0 || input == "0" || input == false; |
+ }, |
+ |
+ isDefined: function(input) |
+ { |
+ return input != undefined && input != null; |
+ }, |
+ |
+ reverseArray: function(input /* Array */) |
+ { |
+ var result = []; |
+ var len = input.length; |
+ for ( var i = len-1; i >= 0; i-- ) |
+ { |
+ result.push(input[i]); |
+ } |
+ |
+ return result; |
+ }, |
+ |
+ merge: function(a /* Object */, b /* Object */) |
+ { |
+ var c = {}; |
+ var i; |
+ |
+ for ( i in a ) |
+ { |
+ if ( a.hasOwnProperty(i) ) |
+ if ( /^\d+$/.test(i) ) c.push(a); |
+ else c[i] = a[i]; |
+ } |
+ for ( i in b ) |
+ { |
+ if ( b.hasOwnProperty(i) ) |
+ if ( /^\d+$/.test(i) ) c.push(b); |
+ else c[i] = b[i]; |
+ } |
+ |
+ return c; |
+ }, |
+ |
+ strRepeat: function(str /* String */, count /* Integer */) |
+ { |
+ var i; |
+ var result = ''; |
+ for ( i = 0; i < count; i++ ) result += str; |
+ return result; |
+ }, |
+ |
+ subStrCount: function(string, subString, start, length) |
+ { |
+ var c = 0; |
+ |
+ string = '' + string; |
+ subString = '' + subString; |
+ |
+ if ( start != undefined ) string = string.substr(start); |
+ if ( length != undefined ) string = string.substr(0, length); |
+ |
+ var len = string.length; |
+ var sublen = subString.length; |
+ for ( var i = 0; i < len; i++ ) |
+ { |
+ if ( subString == string.substr(i, sublen) ) |
+ c++; |
+ i += sublen - 1; |
+ } |
+ |
+ return c; |
+ }, |
+ |
+ trim: function(str /* String */) |
+ { |
+ return (str+'').replace(/^ +/,'').replace(/ +$/,''); |
+ } |
+}; |
+/** |
+ * YamlEscaper encapsulates escaping rules for single and double-quoted |
+ * YAML strings. |
+ * |
+ * @author Matthew Lewinski <matthew@lewinski.org> |
+ */ |
+var YamlEscaper = function(){}; |
+YamlEscaper.prototype = |
+{ |
+ /** |
+ * Determines if a JS value would require double quoting in YAML. |
+ * |
+ * @param {string} value A JS value |
+ * |
+ * @return Boolean True if the value would require double quotes. |
+ */ |
+ requiresDoubleQuoting: function(value) |
+ { |
+ return new RegExp(YamlEscaper.REGEX_CHARACTER_TO_ESCAPE).test(value); |
+ }, |
+ |
+ /** |
+ * Escapes and surrounds a JS value with double quotes. |
+ * |
+ * @param {string} value A JS value |
+ * |
+ * @return string The quoted, escaped string |
+ */ |
+ escapeWithDoubleQuotes: function(value) |
+ { |
+ value = value + ''; |
+ var len = YamlEscaper.escapees.length; |
+ var maxlen = YamlEscaper.escaped.length; |
+ var esc = YamlEscaper.escaped; |
+ for (var i = 0; i < len; ++i) |
+ if ( i >= maxlen ) esc.push(''); |
+ |
+ var ret = ''; |
+ ret = value.replace(new RegExp(YamlEscaper.escapees.join('|'),'g'), function(str){ |
+ for(var i = 0; i < len; ++i){ |
+ if( str == YamlEscaper.escapees[i] ) |
+ return esc[i]; |
+ } |
+ }); |
+ return '"' + ret + '"'; |
+ }, |
+ |
+ /** |
+ * Determines if a JS value would require single quoting in YAML. |
+ * |
+ * @param {string} value A JS value |
+ * |
+ * @return Boolean True if the value would require single quotes. |
+ */ |
+ requiresSingleQuoting: function(value) |
+ { |
+ return /[\s'":{}[\],&*#?]|^[-?|<>=!%@`]/.test(value); |
+ }, |
+ |
+ /** |
+ * Escapes and surrounds a JS value with single quotes. |
+ * |
+ * @param {string} value A JS value |
+ * |
+ * @return string The quoted, escaped string |
+ */ |
+ escapeWithSingleQuotes : function(value) |
+ { |
+ return "'" + value.replace(/'/g, "''") + "'"; |
+ } |
+}; |
+ |
+// Characters that would cause a dumped string to require double quoting. |
+YamlEscaper.REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; |
+ |
+// Mapping arrays for escaping a double quoted string. The backslash is |
+// first to ensure proper escaping. |
+YamlEscaper.escapees = ['\\\\', '\\"', '"', |
+ "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", |
+ "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", |
+ "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", |
+ "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", |
+ "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9"]; |
+YamlEscaper.escaped = ['\\"', '\\\\', '\\"', |
+ "\\0", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a", |
+ "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f", |
+ "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17", |
+ "\\x18", "\\x19", "\\x1a", "\\e", "\\x1c", "\\x1d", "\\x1e", "\\x1f", |
+ "\\N", "\\_", "\\L", "\\P"]; |
+/** |
+ * YamlUnescaper encapsulates unescaping rules for single and double-quoted |
+ * YAML strings. |
+ * |
+ * @author Matthew Lewinski <matthew@lewinski.org> |
+ */ |
+var YamlUnescaper = function(){}; |
+YamlUnescaper.prototype = |
+{ |
+ /** |
+ * Unescapes a single quoted string. |
+ * |
+ * @param {string} value A single quoted string. |
+ * |
+ * @return string The unescaped string. |
+ */ |
+ unescapeSingleQuotedString: function(value) |
+ { |
+ return value.replace(/''/g, "'"); |
+ }, |
+ |
+ /** |
+ * Unescapes a double quoted string. |
+ * |
+ * @param {string} value A double quoted string. |
+ * |
+ * @return string The unescaped string. |
+ */ |
+ unescapeDoubleQuotedString: function(value) |
+ { |
+ var callback = function(m) { |
+ return new YamlUnescaper().unescapeCharacter(m); |
+ }; |
+ |
+ // evaluate the string |
+ return value.replace(new RegExp(YamlUnescaper.REGEX_ESCAPED_CHARACTER, 'g'), callback); |
+ }, |
+ |
+ /** |
+ * Unescapes a character that was found in a double-quoted string |
+ * |
+ * @param {string} value An escaped character |
+ * |
+ * @return string The unescaped character |
+ */ |
+ unescapeCharacter: function(value) |
+ { |
+ switch (value.charAt(1)) { |
+ case '0': |
+ return String.fromCharCode(0); |
+ case 'a': |
+ return String.fromCharCode(7); |
+ case 'b': |
+ return String.fromCharCode(8); |
+ case 't': |
+ return "\t"; |
+ case "\t": |
+ return "\t"; |
+ case 'n': |
+ return "\n"; |
+ case 'v': |
+ return String.fromCharCode(11); |
+ case 'f': |
+ return String.fromCharCode(12); |
+ case 'r': |
+ return String.fromCharCode(13); |
+ case 'e': |
+ return "\x1b"; |
+ case ' ': |
+ return ' '; |
+ case '"': |
+ return '"'; |
+ case '/': |
+ return '/'; |
+ case '\\': |
+ return '\\'; |
+ case 'N': |
+ // U+0085 NEXT LINE |
+ return "\x00\x85"; |
+ case '_': |
+ // U+00A0 NO-BREAK SPACE |
+ return "\x00\xA0"; |
+ case 'L': |
+ // U+2028 LINE SEPARATOR |
+ return "\x20\x28"; |
+ case 'P': |
+ // U+2029 PARAGRAPH SEPARATOR |
+ return "\x20\x29"; |
+ case 'x': |
+ return this.pack('n', new YamlInline().hexdec(value.substr(2, 2))); |
+ case 'u': |
+ return this.pack('n', new YamlInline().hexdec(value.substr(2, 4))); |
+ case 'U': |
+ return this.pack('N', new YamlInline().hexdec(value.substr(2, 8))); |
+ } |
+ throw new Error( "unknown escape character '" + value.charAt(1) + "'"); |
+ }, |
+ |
+ /** |
+ * @see http://phpjs.org/functions/pack |
+ * @warning only modes used above copied |
+ */ |
+ pack: function(B){var g=0,o=1,m="",l="",z=0,p=[],E,s,C,I,h,c;var d,b,x,H,u,e,A,q,D,t,w,a,G,F,y,v,f;while(g<B.length){E=B.charAt(g);s="";g++;while((g<B.length)&&(B.charAt(g).match(/[\d\*]/)!==null)){s+=B.charAt(g);g++}if(s===""){s="1"}switch(E){case"n":if(s==="*"){s=arguments.length-o}if(s>(arguments.length-o)){throw new Error("Warning: pack() Type "+E+": too few arguments")}for(z=0;z<s;z++){m+=String.fromCharCode(arguments[o]>>8&255);m+=String.fromCharCode(arguments[o]&255);o++}break;case"N":if(s==="*"){s=arguments.length-o}if(s>(arguments.length-o)){throw new Error("Warning: pack() Type "+E+": too few arguments")}for(z=0;z<s;z++){m+=String.fromCharCode(arguments[o]>>24&255);m+=String.fromCharCode(arguments[o]>>16&255);m+=String.fromCharCode(arguments[o]>>8&255);m+=String.fromCharCode(arguments[o]&255);o++}break;default:throw new Error("Warning: pack() Type "+E+": unknown format code")}}if(o<arguments.length){throw new Error("Warning: pack(): "+(arguments.length-o)+" arguments unused")}return m} |
+}; |
+ |
+// Regex fragment that matches an escaped character in a double quoted |
+// string. |
+// why escape quotes, ffs! |
+YamlUnescaper.REGEX_ESCAPED_CHARACTER = '\\\\([0abt\tnvfre "\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})'; |
+ |
+/** |
+ * YamlDumper dumps JS variables to YAML strings. |
+ * |
+ * @author Fabien Potencier <fabien@symfony.com> |
+ */ |
+var YamlDumper = function(){}; |
+YamlDumper.prototype = |
+{ |
+ /** |
+ * Dumps a JS value to YAML. |
+ * |
+ * @param input The JS value |
+ * @param {number} inline The level where you switch to inline YAML |
+ * @param {number} indent The level o indentation indentation (used internally) |
+ * |
+ * @return string The YAML representation of the JS value |
+ */ |
+ dump: function(input, inline, indent) |
+ { |
+ if ( inline == null ) inline = 0; |
+ if ( indent == null ) indent = 0; |
+ var output = ''; |
+ var prefix = indent ? this.strRepeat(' ', indent) : ''; |
+ var yaml; |
+ if (!this.numSpacesForIndentation) this.numSpacesForIndentation = 2; |
+ |
+ if ( inline <= 0 || !this.isObject(input) || this.isEmpty(input) ) |
+ { |
+ yaml = new YamlInline(); |
+ output += prefix + yaml.dump(input); |
+ } |
+ else |
+ { |
+ var isAHash = !this.arrayEquals(this.getKeys(input), this.range(0,input.length - 1)); |
+ var willBeInlined; |
+ |
+ for ( var key in input ) |
+ { |
+ if ( input.hasOwnProperty(key) ) |
+ { |
+ willBeInlined = inline - 1 <= 0 || !this.isObject(input[key]) || this.isEmpty(input[key]); |
+ |
+ if ( isAHash ) yaml = new YamlInline(); |
+ |
+ output += |
+ prefix + '' + |
+ (isAHash ? yaml.dump(key)+':' : '-') + '' + |
+ (willBeInlined ? ' ' : "\n") + '' + |
+ this.dump(input[key], inline - 1, (willBeInlined ? 0 : indent + this.numSpacesForIndentation)) + '' + |
+ (willBeInlined ? "\n" : ''); |
+ } |
+ } |
+ } |
+ |
+ return output; |
+ }, |
+ |
+ strRepeat: function(str /* String */, count /* Integer */) |
+ { |
+ var i; |
+ var result = ''; |
+ for ( i = 0; i < count; i++ ) result += str; |
+ return result; |
+ }, |
+ |
+ isObject: function(input) |
+ { |
+ return this.isDefined(input) && typeof(input) == 'object'; |
+ }, |
+ |
+ isEmpty: function(input) |
+ { |
+ var ret = input == undefined || input == null || input == '' || input == 0 || input == "0" || input == false; |
+ if ( !ret && typeof(input) == "object" && !(input instanceof Array)){ |
+ var propCount = 0; |
+ for ( var key in input ) |
+ if ( input.hasOwnProperty(key) ) propCount++; |
+ ret = !propCount; |
+ } |
+ return ret; |
+ }, |
+ |
+ isDefined: function(input) |
+ { |
+ return input != undefined && input != null; |
+ }, |
+ |
+ getKeys: function(tab) |
+ { |
+ var ret = []; |
+ |
+ for ( var name in tab ) |
+ { |
+ if ( tab.hasOwnProperty(name) ) |
+ { |
+ ret.push(name); |
+ } |
+ } |
+ |
+ return ret; |
+ }, |
+ |
+ range: function(start, end) |
+ { |
+ if ( start > end ) return []; |
+ |
+ var ret = []; |
+ |
+ for ( var i = start; i <= end; i++ ) |
+ { |
+ ret.push(i); |
+ } |
+ |
+ return ret; |
+ }, |
+ |
+ arrayEquals: function(a,b) |
+ { |
+ if ( a.length != b.length ) return false; |
+ |
+ var len = a.length; |
+ |
+ for ( var i = 0; i < len; i++ ) |
+ { |
+ if ( a[i] != b[i] ) return false; |
+ } |
+ |
+ return true; |
+ } |
+}; |
+})(); |