/bower_components/yaml.js/src/Inline.coffee |
@@ -0,0 +1,487 @@ |
|
Pattern = require './Pattern' |
Unescaper = require './Unescaper' |
Escaper = require './Escaper' |
Utils = require './Utils' |
ParseException = require './Exception/ParseException' |
DumpException = require './Exception/DumpException' |
|
# Inline YAML parsing and dumping |
class Inline |
|
# Quoted string regular expression |
@REGEX_QUOTED_STRING: '(?:"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\']*(?:\'\'[^\']*)*)\')' |
|
# Pre-compiled patterns |
# |
@PATTERN_TRAILING_COMMENTS: new Pattern '^\\s*#.*$' |
@PATTERN_QUOTED_SCALAR: new Pattern '^'+@REGEX_QUOTED_STRING |
@PATTERN_THOUSAND_NUMERIC_SCALAR: new Pattern '^(-|\\+)?[0-9,]+(\\.[0-9]+)?$' |
@PATTERN_SCALAR_BY_DELIMITERS: {} |
|
# Settings |
@settings: {} |
|
|
# Configure YAML inline. |
# |
# @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise |
# @param [Function] objectDecoder A function to deserialize custom objects, null otherwise |
# |
@configure: (exceptionOnInvalidType = null, objectDecoder = null) -> |
# Update settings |
@settings.exceptionOnInvalidType = exceptionOnInvalidType |
@settings.objectDecoder = objectDecoder |
return |
|
|
# Converts a YAML string to a JavaScript object. |
# |
# @param [String] value A YAML string |
# @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise |
# @param [Function] objectDecoder A function to deserialize custom objects, null otherwise |
# |
# @return [Object] A JavaScript object representing the YAML string |
# |
# @throw [ParseException] |
# |
@parse: (value, exceptionOnInvalidType = false, objectDecoder = null) -> |
# Update settings from last call of Inline.parse() |
@settings.exceptionOnInvalidType = exceptionOnInvalidType |
@settings.objectDecoder = objectDecoder |
|
if not value? |
return '' |
|
value = Utils.trim value |
|
if 0 is value.length |
return '' |
|
# Keep a context object to pass through static methods |
context = {exceptionOnInvalidType, objectDecoder, i: 0} |
|
switch value.charAt(0) |
when '[' |
result = @parseSequence value, context |
++context.i |
when '{' |
result = @parseMapping value, context |
++context.i |
else |
result = @parseScalar value, null, ['"', "'"], context |
|
# Some comments are allowed at the end |
if @PATTERN_TRAILING_COMMENTS.replace(value[context.i..], '') isnt '' |
throw new ParseException 'Unexpected characters near "'+value[context.i..]+'".' |
|
return result |
|
|
# Dumps a given JavaScript variable to a YAML string. |
# |
# @param [Object] value The JavaScript variable to convert |
# @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise |
# @param [Function] objectEncoder A function to serialize custom objects, null otherwise |
# |
# @return [String] The YAML string representing the JavaScript object |
# |
# @throw [DumpException] |
# |
@dump: (value, exceptionOnInvalidType = false, objectEncoder = null) -> |
if not value? |
return 'null' |
type = typeof value |
if type is 'object' |
if value instanceof Date |
return value.toISOString() |
else if objectEncoder? |
result = objectEncoder value |
if typeof result is 'string' or result? |
return result |
return @dumpObject value |
if type is 'boolean' |
return (if value then 'true' else 'false') |
if Utils.isDigits(value) |
return (if type is 'string' then "'"+value+"'" else String(parseInt(value))) |
if Utils.isNumeric(value) |
return (if type is 'string' then "'"+value+"'" else String(parseFloat(value))) |
if type is 'number' |
return (if value is Infinity then '.Inf' else (if value is -Infinity then '-.Inf' else (if isNaN(value) then '.NaN' else value))) |
if Escaper.requiresDoubleQuoting value |
return Escaper.escapeWithDoubleQuotes value |
if Escaper.requiresSingleQuoting value |
return Escaper.escapeWithSingleQuotes value |
if '' is value |
return '""' |
if Utils.PATTERN_DATE.test value |
return "'"+value+"'"; |
if value.toLowerCase() in ['null','~','true','false'] |
return "'"+value+"'" |
# Default |
return value; |
|
|
# Dumps a JavaScript object to a YAML string. |
# |
# @param [Object] value The JavaScript object to dump |
# @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise |
# @param [Function] objectEncoder A function do serialize custom objects, null otherwise |
# |
# @return string The YAML string representing the JavaScript object |
# |
@dumpObject: (value, exceptionOnInvalidType, objectSupport = null) -> |
# Array |
if value instanceof Array |
output = [] |
for val in value |
output.push @dump val |
return '['+output.join(', ')+']' |
|
# Mapping |
else |
output = [] |
for key, val of value |
output.push @dump(key)+': '+@dump(val) |
return '{'+output.join(', ')+'}' |
|
|
# Parses a scalar to a YAML string. |
# |
# @param [Object] scalar |
# @param [Array] delimiters |
# @param [Array] stringDelimiters |
# @param [Object] context |
# @param [Boolean] evaluate |
# |
# @return [String] A YAML string |
# |
# @throw [ParseException] When malformed inline YAML string is parsed |
# |
@parseScalar: (scalar, delimiters = null, stringDelimiters = ['"', "'"], context = null, evaluate = true) -> |
unless context? |
context = exceptionOnInvalidType: @settings.exceptionOnInvalidType, objectDecoder: @settings.objectDecoder, i: 0 |
{i} = context |
|
if scalar.charAt(i) in stringDelimiters |
# Quoted scalar |
output = @parseQuotedScalar scalar, context |
{i} = context |
|
if delimiters? |
tmp = Utils.ltrim scalar[i..], ' ' |
if not(tmp.charAt(0) in delimiters) |
throw new ParseException 'Unexpected characters ('+scalar[i..]+').' |
|
else |
# "normal" string |
if not delimiters |
output = scalar[i..] |
i += output.length |
|
# Remove comments |
strpos = output.indexOf ' #' |
if strpos isnt -1 |
output = Utils.rtrim output[0...strpos] |
|
else |
joinedDelimiters = delimiters.join('|') |
pattern = @PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters] |
unless pattern? |
pattern = new Pattern '^(.+?)('+joinedDelimiters+')' |
@PATTERN_SCALAR_BY_DELIMITERS[joinedDelimiters] = pattern |
if match = pattern.exec scalar[i..] |
output = match[1] |
i += output.length |
else |
throw new ParseException 'Malformed inline YAML string ('+scalar+').' |
|
|
if evaluate |
output = @evaluateScalar output, context |
|
context.i = i |
return output |
|
|
# Parses a quoted scalar to YAML. |
# |
# @param [String] scalar |
# @param [Object] context |
# |
# @return [String] A YAML string |
# |
# @throw [ParseException] When malformed inline YAML string is parsed |
# |
@parseQuotedScalar: (scalar, context) -> |
{i} = context |
|
unless match = @PATTERN_QUOTED_SCALAR.exec scalar[i..] |
throw new ParseException 'Malformed inline YAML string ('+scalar[i..]+').' |
|
output = match[0].substr(1, match[0].length - 2) |
|
if '"' is scalar.charAt(i) |
output = Unescaper.unescapeDoubleQuotedString output |
else |
output = Unescaper.unescapeSingleQuotedString output |
|
i += match[0].length |
|
context.i = i |
return output |
|
|
# Parses a sequence to a YAML string. |
# |
# @param [String] sequence |
# @param [Object] context |
# |
# @return [String] A YAML string |
# |
# @throw [ParseException] When malformed inline YAML string is parsed |
# |
@parseSequence: (sequence, context) -> |
output = [] |
len = sequence.length |
{i} = context |
i += 1 |
|
# [foo, bar, ...] |
while i < len |
context.i = i |
switch sequence.charAt(i) |
when '[' |
# Nested sequence |
output.push @parseSequence sequence, context |
{i} = context |
when '{' |
# Nested mapping |
output.push @parseMapping sequence, context |
{i} = context |
when ']' |
return output |
when ',', ' ', "\n" |
# Do nothing |
else |
isQuoted = (sequence.charAt(i) in ['"', "'"]) |
value = @parseScalar sequence, [',', ']'], ['"', "'"], context |
{i} = context |
|
if not(isQuoted) and typeof(value) is 'string' and (value.indexOf(': ') isnt -1 or value.indexOf(":\n") isnt -1) |
# Embedded mapping? |
try |
value = @parseMapping '{'+value+'}' |
catch e |
# No, it's not |
|
|
output.push value |
|
--i |
|
++i |
|
throw new ParseException 'Malformed inline YAML string '+sequence |
|
|
# Parses a mapping to a YAML string. |
# |
# @param [String] mapping |
# @param [Object] context |
# |
# @return [String] A YAML string |
# |
# @throw [ParseException] When malformed inline YAML string is parsed |
# |
@parseMapping: (mapping, context) -> |
output = {} |
len = mapping.length |
{i} = context |
i += 1 |
|
# {foo: bar, bar:foo, ...} |
shouldContinueWhileLoop = false |
while i < len |
context.i = i |
switch mapping.charAt(i) |
when ' ', ',', "\n" |
++i |
context.i = i |
shouldContinueWhileLoop = true |
when '}' |
return output |
|
if shouldContinueWhileLoop |
shouldContinueWhileLoop = false |
continue |
|
# Key |
key = @parseScalar mapping, [':', ' ', "\n"], ['"', "'"], context, false |
{i} = context |
|
# Value |
done = false |
|
while i < len |
context.i = i |
switch mapping.charAt(i) |
when '[' |
# Nested sequence |
value = @parseSequence mapping, context |
{i} = context |
# Spec: Keys MUST be unique; first one wins. |
# Parser cannot abort this mapping earlier, since lines |
# are processed sequentially. |
if output[key] == undefined |
output[key] = value |
done = true |
when '{' |
# Nested mapping |
value = @parseMapping mapping, context |
{i} = context |
# Spec: Keys MUST be unique; first one wins. |
# Parser cannot abort this mapping earlier, since lines |
# are processed sequentially. |
if output[key] == undefined |
output[key] = value |
done = true |
when ':', ' ', "\n" |
# Do nothing |
else |
value = @parseScalar mapping, [',', '}'], ['"', "'"], context |
{i} = context |
# Spec: Keys MUST be unique; first one wins. |
# Parser cannot abort this mapping earlier, since lines |
# are processed sequentially. |
if output[key] == undefined |
output[key] = value |
done = true |
--i |
|
++i |
|
if done |
break |
|
throw new ParseException 'Malformed inline YAML string '+mapping |
|
|
# Evaluates scalars and replaces magic values. |
# |
# @param [String] scalar |
# |
# @return [String] A YAML string |
# |
@evaluateScalar: (scalar, context) -> |
scalar = Utils.trim(scalar) |
scalarLower = scalar.toLowerCase() |
|
switch scalarLower |
when 'null', '', '~' |
return null |
when 'true' |
return true |
when 'false' |
return false |
when '.inf' |
return Infinity |
when '.nan' |
return NaN |
when '-.inf' |
return Infinity |
else |
firstChar = scalarLower.charAt(0) |
switch firstChar |
when '!' |
firstSpace = scalar.indexOf(' ') |
if firstSpace is -1 |
firstWord = scalarLower |
else |
firstWord = scalarLower[0...firstSpace] |
switch firstWord |
when '!' |
if firstSpace isnt -1 |
return parseInt @parseScalar(scalar[2..]) |
return null |
when '!str' |
return Utils.ltrim scalar[4..] |
when '!!str' |
return Utils.ltrim scalar[5..] |
when '!!int' |
return parseInt(@parseScalar(scalar[5..])) |
when '!!bool' |
return Utils.parseBoolean(@parseScalar(scalar[6..]), false) |
when '!!float' |
return parseFloat(@parseScalar(scalar[7..])) |
when '!!timestamp' |
return Utils.stringToDate(Utils.ltrim(scalar[11..])) |
else |
unless context? |
context = exceptionOnInvalidType: @settings.exceptionOnInvalidType, objectDecoder: @settings.objectDecoder, i: 0 |
{objectDecoder, exceptionOnInvalidType} = context |
|
if objectDecoder |
# If objectDecoder function is given, we can do custom decoding of custom types |
trimmedScalar = Utils.rtrim scalar |
firstSpace = trimmedScalar.indexOf(' ') |
if firstSpace is -1 |
return objectDecoder trimmedScalar, null |
else |
subValue = Utils.ltrim trimmedScalar[firstSpace+1..] |
unless subValue.length > 0 |
subValue = null |
return objectDecoder trimmedScalar[0...firstSpace], subValue |
|
if exceptionOnInvalidType |
throw new ParseException 'Custom object support when parsing a YAML file has been disabled.' |
|
return null |
when '0' |
if '0x' is scalar[0...2] |
return Utils.hexDec scalar |
else if Utils.isDigits scalar |
return Utils.octDec scalar |
else if Utils.isNumeric scalar |
return parseFloat scalar |
else |
return scalar |
when '+' |
if Utils.isDigits scalar |
raw = scalar |
cast = parseInt(raw) |
if raw is String(cast) |
return cast |
else |
return raw |
else if Utils.isNumeric scalar |
return parseFloat scalar |
else if @PATTERN_THOUSAND_NUMERIC_SCALAR.test scalar |
return parseFloat(scalar.replace(',', '')) |
return scalar |
when '-' |
if Utils.isDigits(scalar[1..]) |
if '0' is scalar.charAt(1) |
return -Utils.octDec(scalar[1..]) |
else |
raw = scalar[1..] |
cast = parseInt(raw) |
if raw is String(cast) |
return -cast |
else |
return -raw |
else if Utils.isNumeric scalar |
return parseFloat scalar |
else if @PATTERN_THOUSAND_NUMERIC_SCALAR.test scalar |
return parseFloat(scalar.replace(',', '')) |
return scalar |
else |
if date = Utils.stringToDate(scalar) |
return date |
else if Utils.isNumeric(scalar) |
return parseFloat scalar |
else if @PATTERN_THOUSAND_NUMERIC_SCALAR.test scalar |
return parseFloat(scalar.replace(',', '')) |
return scalar |
|
module.exports = Inline |