scratch – Diff between revs 75 and 125
?pathlinks?
Rev 75 | Rev 125 | |||
---|---|---|---|---|
1 | |
1 | |
|
2 | Inline = require './Inline' |
2 | Inline = require './Inline' |
|
3 | Pattern = require './Pattern' |
3 | Pattern = require './Pattern' |
|
4 | Utils = require './Utils' |
4 | Utils = require './Utils' |
|
5 | ParseException = require './Exception/ParseException' |
5 | ParseException = require './Exception/ParseException' |
|
- | 6 | ParseMore = require './Exception/ParseMore' |
||
6 | |
7 | |
|
7 | # Parser parses YAML strings to convert them to JavaScript objects. |
8 | # Parser parses YAML strings to convert them to JavaScript objects. |
|
8 | # |
9 | # |
|
9 | class Parser |
10 | class Parser |
|
10 | |
11 | |
|
11 | # Pre-compiled patterns |
12 | # Pre-compiled patterns |
|
12 | # |
13 | # |
|
13 | PATTERN_FOLDED_SCALAR_ALL: new Pattern '^(?:(?<type>![^\\|>]*)\\s+)?(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$' |
14 | PATTERN_FOLDED_SCALAR_ALL: new Pattern '^(?:(?<type>![^\\|>]*)\\s+)?(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$' |
|
14 | PATTERN_FOLDED_SCALAR_END: new Pattern '(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$' |
15 | PATTERN_FOLDED_SCALAR_END: new Pattern '(?<separator>\\||>)(?<modifiers>\\+|\\-|\\d+|\\+\\d+|\\-\\d+|\\d+\\+|\\d+\\-)?(?<comments> +#.*)?$' |
|
15 | PATTERN_SEQUENCE_ITEM: new Pattern '^\\-((?<leadspaces>\\s+)(?<value>.+?))?\\s*$' |
16 | PATTERN_SEQUENCE_ITEM: new Pattern '^\\-((?<leadspaces>\\s+)(?<value>.+?))?\\s*$' |
|
16 | PATTERN_ANCHOR_VALUE: new Pattern '^&(?<ref>[^ ]+) *(?<value>.*)' |
17 | PATTERN_ANCHOR_VALUE: new Pattern '^&(?<ref>[^ ]+) *(?<value>.*)' |
|
17 | PATTERN_COMPACT_NOTATION: new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\{\\[].*?) *\\:(\\s+(?<value>.+?))?\\s*$' |
18 | PATTERN_COMPACT_NOTATION: new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\{\\[].*?) *\\:(\\s+(?<value>.+?))?\\s*$' |
|
18 | PATTERN_MAPPING_ITEM: new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\[\\{].*?) *\\:(\\s+(?<value>.+?))?\\s*$' |
19 | PATTERN_MAPPING_ITEM: new Pattern '^(?<key>'+Inline.REGEX_QUOTED_STRING+'|[^ \'"\\[\\{].*?) *\\:(\\s+(?<value>.+?))?\\s*$' |
|
19 | PATTERN_DECIMAL: new Pattern '\\d+' |
20 | PATTERN_DECIMAL: new Pattern '\\d+' |
|
20 | PATTERN_INDENT_SPACES: new Pattern '^ +' |
21 | PATTERN_INDENT_SPACES: new Pattern '^ +' |
|
21 | PATTERN_TRAILING_LINES: new Pattern '(\n*)$' |
22 | PATTERN_TRAILING_LINES: new Pattern '(\n*)$' |
|
22 | PATTERN_YAML_HEADER: new Pattern '^\\%YAML[: ][\\d\\.]+.*\n' |
23 | PATTERN_YAML_HEADER: new Pattern '^\\%YAML[: ][\\d\\.]+.*\n', 'm' |
|
23 | PATTERN_LEADING_COMMENTS: new Pattern '^(\\#.*?\n)+' |
24 | PATTERN_LEADING_COMMENTS: new Pattern '^(\\#.*?\n)+', 'm' |
|
24 | PATTERN_DOCUMENT_MARKER_START: new Pattern '^\\-\\-\\-.*?\n' |
25 | PATTERN_DOCUMENT_MARKER_START: new Pattern '^\\-\\-\\-.*?\n', 'm' |
|
25 | PATTERN_DOCUMENT_MARKER_END: new Pattern '^\\.\\.\\.\\s*$' |
26 | PATTERN_DOCUMENT_MARKER_END: new Pattern '^\\.\\.\\.\\s*$', 'm' |
|
26 | PATTERN_FOLDED_SCALAR_BY_INDENTATION: {} |
27 | PATTERN_FOLDED_SCALAR_BY_INDENTATION: {} |
|
27 | |
28 | |
|
28 | # Context types |
29 | # Context types |
|
29 | # |
30 | # |
|
30 | CONTEXT_NONE: 0 |
31 | CONTEXT_NONE: 0 |
|
31 | CONTEXT_SEQUENCE: 1 |
32 | CONTEXT_SEQUENCE: 1 |
|
32 | CONTEXT_MAPPING: 2 |
33 | CONTEXT_MAPPING: 2 |
|
33 | |
34 | |
|
34 | |
35 | |
|
35 | # Constructor |
36 | # Constructor |
|
36 | # |
37 | # |
|
37 | # @param [Integer] offset The offset of YAML document (used for line numbers in error messages) |
38 | # @param [Integer] offset The offset of YAML document (used for line numbers in error messages) |
|
38 | # |
39 | # |
|
39 | constructor: (@offset = 0) -> |
40 | constructor: (@offset = 0) -> |
|
40 | @lines = [] |
41 | @lines = [] |
|
41 | @currentLineNb = -1 |
42 | @currentLineNb = -1 |
|
42 | @currentLine = '' |
43 | @currentLine = '' |
|
43 | @refs = {} |
44 | @refs = {} |
|
44 | |
45 | |
|
45 | |
46 | |
|
46 | # Parses a YAML string to a JavaScript value. |
47 | # Parses a YAML string to a JavaScript value. |
|
47 | # |
48 | # |
|
48 | # @param [String] value A YAML string |
49 | # @param [String] value A YAML string |
|
49 | # @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise |
50 | # @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types (a JavaScript resource or object), false otherwise |
|
50 | # @param [Function] objectDecoder A function to deserialize custom objects, null otherwise |
51 | # @param [Function] objectDecoder A function to deserialize custom objects, null otherwise |
|
51 | # |
52 | # |
|
52 | # @return [Object] A JavaScript value |
53 | # @return [Object] A JavaScript value |
|
53 | # |
54 | # |
|
54 | # @throw [ParseException] If the YAML is not valid |
55 | # @throw [ParseException] If the YAML is not valid |
|
55 | # |
56 | # |
|
56 | parse: (value, exceptionOnInvalidType = false, objectDecoder = null) -> |
57 | parse: (value, exceptionOnInvalidType = false, objectDecoder = null) -> |
|
57 | @currentLineNb = -1 |
58 | @currentLineNb = -1 |
|
58 | @currentLine = '' |
59 | @currentLine = '' |
|
59 | @lines = @cleanup(value).split "\n" |
60 | @lines = @cleanup(value).split "\n" |
|
60 | |
61 | |
|
61 | data = null |
62 | data = null |
|
62 | context = @CONTEXT_NONE |
63 | context = @CONTEXT_NONE |
|
63 | allowOverwrite = false |
64 | allowOverwrite = false |
|
64 | while @moveToNextLine() |
65 | while @moveToNextLine() |
|
65 | if @isCurrentLineEmpty() |
66 | if @isCurrentLineEmpty() |
|
66 | continue |
67 | continue |
|
67 | |
68 | |
|
68 | # Tab? |
69 | # Tab? |
|
69 | if "\t" is @currentLine[0] |
70 | if "\t" is @currentLine[0] |
|
70 | throw new ParseException 'A YAML file cannot contain tabs as indentation.', @getRealCurrentLineNb() + 1, @currentLine |
71 | throw new ParseException 'A YAML file cannot contain tabs as indentation.', @getRealCurrentLineNb() + 1, @currentLine |
|
71 | |
72 | |
|
72 | isRef = mergeNode = false |
73 | isRef = mergeNode = false |
|
73 | if values = @PATTERN_SEQUENCE_ITEM.exec @currentLine |
74 | if values = @PATTERN_SEQUENCE_ITEM.exec @currentLine |
|
74 | if @CONTEXT_MAPPING is context |
75 | if @CONTEXT_MAPPING is context |
|
75 | throw new ParseException 'You cannot define a sequence item when in a mapping' |
76 | throw new ParseException 'You cannot define a sequence item when in a mapping' |
|
76 | context = @CONTEXT_SEQUENCE |
77 | context = @CONTEXT_SEQUENCE |
|
77 | data ?= [] |
78 | data ?= [] |
|
78 | |
79 | |
|
79 | if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value |
80 | if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value |
|
80 | isRef = matches.ref |
81 | isRef = matches.ref |
|
81 | values.value = matches.value |
82 | values.value = matches.value |
|
82 | |
83 | |
|
83 | # Array |
84 | # Array |
|
84 | if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0 |
85 | if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0 |
|
85 | if @currentLineNb < @lines.length - 1 and not @isNextLineUnIndentedCollection() |
86 | if @currentLineNb < @lines.length - 1 and not @isNextLineUnIndentedCollection() |
|
86 | c = @getRealCurrentLineNb() + 1 |
87 | c = @getRealCurrentLineNb() + 1 |
|
87 | parser = new Parser c |
88 | parser = new Parser c |
|
88 | parser.refs = @refs |
89 | parser.refs = @refs |
|
89 | data.push parser.parse(@getNextEmbedBlock(null, true), exceptionOnInvalidType, objectDecoder) |
90 | data.push parser.parse(@getNextEmbedBlock(null, true), exceptionOnInvalidType, objectDecoder) |
|
90 | else |
91 | else |
|
91 | data.push null |
92 | data.push null |
|
92 | |
93 | |
|
93 | else |
94 | else |
|
94 | if values.leadspaces?.length and matches = @PATTERN_COMPACT_NOTATION.exec values.value |
95 | if values.leadspaces?.length and matches = @PATTERN_COMPACT_NOTATION.exec values.value |
|
95 | |
96 | |
|
96 | # This is a compact notation element, add to next block and parse |
97 | # This is a compact notation element, add to next block and parse |
|
97 | c = @getRealCurrentLineNb() |
98 | c = @getRealCurrentLineNb() |
|
98 | parser = new Parser c |
99 | parser = new Parser c |
|
99 | parser.refs = @refs |
100 | parser.refs = @refs |
|
100 | |
101 | |
|
101 | block = values.value |
102 | block = values.value |
|
102 | indent = @getCurrentLineIndentation() |
103 | indent = @getCurrentLineIndentation() |
|
103 | if @isNextLineIndented(false) |
104 | if @isNextLineIndented(false) |
|
104 | block += "\n"+@getNextEmbedBlock(indent + values.leadspaces.length + 1, true) |
105 | block += "\n"+@getNextEmbedBlock(indent + values.leadspaces.length + 1, true) |
|
105 | |
106 | |
|
106 | data.push parser.parse block, exceptionOnInvalidType, objectDecoder |
107 | data.push parser.parse block, exceptionOnInvalidType, objectDecoder |
|
107 | |
108 | |
|
108 | else |
109 | else |
|
109 | data.push @parseValue values.value, exceptionOnInvalidType, objectDecoder |
110 | data.push @parseValue values.value, exceptionOnInvalidType, objectDecoder |
|
110 | |
111 | |
|
111 | else if (values = @PATTERN_MAPPING_ITEM.exec @currentLine) and values.key.indexOf(' #') is -1 |
112 | else if (values = @PATTERN_MAPPING_ITEM.exec @currentLine) and values.key.indexOf(' #') is -1 |
|
112 | if @CONTEXT_SEQUENCE is context |
113 | if @CONTEXT_SEQUENCE is context |
|
113 | throw new ParseException 'You cannot define a mapping item when in a sequence' |
114 | throw new ParseException 'You cannot define a mapping item when in a sequence' |
|
114 | context = @CONTEXT_MAPPING |
115 | context = @CONTEXT_MAPPING |
|
115 | data ?= {} |
116 | data ?= {} |
|
116 | |
117 | |
|
117 | # Force correct settings |
118 | # Force correct settings |
|
118 | Inline.configure exceptionOnInvalidType, objectDecoder |
119 | Inline.configure exceptionOnInvalidType, objectDecoder |
|
119 | try |
120 | try |
|
120 | key = Inline.parseScalar values.key |
121 | key = Inline.parseScalar values.key |
|
121 | catch e |
122 | catch e |
|
122 | e.parsedLine = @getRealCurrentLineNb() + 1 |
123 | e.parsedLine = @getRealCurrentLineNb() + 1 |
|
123 | e.snippet = @currentLine |
124 | e.snippet = @currentLine |
|
124 | |
125 | |
|
125 | throw e |
126 | throw e |
|
126 | |
127 | |
|
127 | if '<<' is key |
128 | if '<<' is key |
|
128 | mergeNode = true |
129 | mergeNode = true |
|
129 | allowOverwrite = true |
130 | allowOverwrite = true |
|
130 | if values.value?.indexOf('*') is 0 |
131 | if values.value?.indexOf('*') is 0 |
|
131 | refName = values.value[1..] |
132 | refName = values.value[1..] |
|
132 | unless @refs[refName]? |
133 | unless @refs[refName]? |
|
133 | throw new ParseException 'Reference "'+refName+'" does not exist.', @getRealCurrentLineNb() + 1, @currentLine |
134 | throw new ParseException 'Reference "'+refName+'" does not exist.', @getRealCurrentLineNb() + 1, @currentLine |
|
134 | |
135 | |
|
135 | refValue = @refs[refName] |
136 | refValue = @refs[refName] |
|
136 | |
137 | |
|
137 | if typeof refValue isnt 'object' |
138 | if typeof refValue isnt 'object' |
|
138 | throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine |
139 | throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine |
|
139 | |
140 | |
|
140 | if refValue instanceof Array |
141 | if refValue instanceof Array |
|
141 | # Merge array with object |
142 | # Merge array with object |
|
142 | for value, i in refValue |
143 | for value, i in refValue |
|
143 | data[String(i)] ?= value |
144 | data[String(i)] ?= value |
|
144 | else |
145 | else |
|
145 | # Merge objects |
146 | # Merge objects |
|
146 | for key, value of refValue |
147 | for key, value of refValue |
|
147 | data[key] ?= value |
148 | data[key] ?= value |
|
148 | |
149 | |
|
149 | else |
150 | else |
|
150 | if values.value? and values.value isnt '' |
151 | if values.value? and values.value isnt '' |
|
151 | value = values.value |
152 | value = values.value |
|
152 | else |
153 | else |
|
153 | value = @getNextEmbedBlock() |
154 | value = @getNextEmbedBlock() |
|
154 | |
155 | |
|
155 | c = @getRealCurrentLineNb() + 1 |
156 | c = @getRealCurrentLineNb() + 1 |
|
156 | parser = new Parser c |
157 | parser = new Parser c |
|
157 | parser.refs = @refs |
158 | parser.refs = @refs |
|
158 | parsed = parser.parse value, exceptionOnInvalidType |
159 | parsed = parser.parse value, exceptionOnInvalidType |
|
159 | |
160 | |
|
160 | unless typeof parsed is 'object' |
161 | unless typeof parsed is 'object' |
|
161 | throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine |
162 | throw new ParseException 'YAML merge keys used with a scalar value instead of an object.', @getRealCurrentLineNb() + 1, @currentLine |
|
162 | |
163 | |
|
163 | if parsed instanceof Array |
164 | if parsed instanceof Array |
|
164 | # If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes |
165 | # If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes |
|
165 | # and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier |
166 | # and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier |
|
166 | # in the sequence override keys specified in later mapping nodes. |
167 | # in the sequence override keys specified in later mapping nodes. |
|
167 | for parsedItem in parsed |
168 | for parsedItem in parsed |
|
168 | unless typeof parsedItem is 'object' |
169 | unless typeof parsedItem is 'object' |
|
169 | throw new ParseException 'Merge items must be objects.', @getRealCurrentLineNb() + 1, parsedItem |
170 | throw new ParseException 'Merge items must be objects.', @getRealCurrentLineNb() + 1, parsedItem |
|
170 | |
171 | |
|
171 | if parsedItem instanceof Array |
172 | if parsedItem instanceof Array |
|
172 | # Merge array with object |
173 | # Merge array with object |
|
173 | for value, i in parsedItem |
174 | for value, i in parsedItem |
|
174 | k = String(i) |
175 | k = String(i) |
|
175 | unless data.hasOwnProperty(k) |
176 | unless data.hasOwnProperty(k) |
|
176 | data[k] = value |
177 | data[k] = value |
|
177 | else |
178 | else |
|
178 | # Merge objects |
179 | # Merge objects |
|
179 | for key, value of parsedItem |
180 | for key, value of parsedItem |
|
180 | unless data.hasOwnProperty(key) |
181 | unless data.hasOwnProperty(key) |
|
181 | data[key] = value |
182 | data[key] = value |
|
182 | |
183 | |
|
183 | else |
184 | else |
|
184 | # If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the |
185 | # If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the |
|
185 | # current mapping, unless the key already exists in it. |
186 | # current mapping, unless the key already exists in it. |
|
186 | for key, value of parsed |
187 | for key, value of parsed |
|
187 | unless data.hasOwnProperty(key) |
188 | unless data.hasOwnProperty(key) |
|
188 | data[key] = value |
189 | data[key] = value |
|
189 | |
190 | |
|
190 | else if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value |
191 | else if values.value? and matches = @PATTERN_ANCHOR_VALUE.exec values.value |
|
191 | isRef = matches.ref |
192 | isRef = matches.ref |
|
192 | values.value = matches.value |
193 | values.value = matches.value |
|
193 | |
194 | |
|
194 | |
195 | |
|
195 | if mergeNode |
196 | if mergeNode |
|
196 | # Merge keys |
197 | # Merge keys |
|
197 | else if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0 |
198 | else if not(values.value?) or '' is Utils.trim(values.value, ' ') or Utils.ltrim(values.value, ' ').indexOf('#') is 0 |
|
198 | # Hash |
199 | # Hash |
|
199 | # if next line is less indented or equal, then it means that the current value is null |
200 | # if next line is less indented or equal, then it means that the current value is null |
|
200 | if not(@isNextLineIndented()) and not(@isNextLineUnIndentedCollection()) |
201 | if not(@isNextLineIndented()) and not(@isNextLineUnIndentedCollection()) |
|
201 | # Spec: Keys MUST be unique; first one wins. |
202 | # Spec: Keys MUST be unique; first one wins. |
|
202 | # But overwriting is allowed when a merge node is used in current block. |
203 | # But overwriting is allowed when a merge node is used in current block. |
|
203 | if allowOverwrite or data[key] is undefined |
204 | if allowOverwrite or data[key] is undefined |
|
204 | data[key] = null |
205 | data[key] = null |
|
205 | |
206 | |
|
206 | else |
207 | else |
|
207 | c = @getRealCurrentLineNb() + 1 |
208 | c = @getRealCurrentLineNb() + 1 |
|
208 | parser = new Parser c |
209 | parser = new Parser c |
|
209 | parser.refs = @refs |
210 | parser.refs = @refs |
|
210 | val = parser.parse @getNextEmbedBlock(), exceptionOnInvalidType, objectDecoder |
211 | val = parser.parse @getNextEmbedBlock(), exceptionOnInvalidType, objectDecoder |
|
211 | |
212 | |
|
212 | # Spec: Keys MUST be unique; first one wins. |
213 | # Spec: Keys MUST be unique; first one wins. |
|
213 | # But overwriting is allowed when a merge node is used in current block. |
214 | # But overwriting is allowed when a merge node is used in current block. |
|
214 | if allowOverwrite or data[key] is undefined |
215 | if allowOverwrite or data[key] is undefined |
|
215 | data[key] = val |
216 | data[key] = val |
|
216 | |
217 | |
|
217 | else |
218 | else |
|
218 | val = @parseValue values.value, exceptionOnInvalidType, objectDecoder |
219 | val = @parseValue values.value, exceptionOnInvalidType, objectDecoder |
|
219 | |
220 | |
|
220 | # Spec: Keys MUST be unique; first one wins. |
221 | # Spec: Keys MUST be unique; first one wins. |
|
221 | # But overwriting is allowed when a merge node is used in current block. |
222 | # But overwriting is allowed when a merge node is used in current block. |
|
222 | if allowOverwrite or data[key] is undefined |
223 | if allowOverwrite or data[key] is undefined |
|
223 | data[key] = val |
224 | data[key] = val |
|
224 | |
225 | |
|
225 | else |
226 | else |
|
226 | # 1-liner optionally followed by newline |
227 | # 1-liner optionally followed by newline |
|
227 | lineCount = @lines.length |
228 | lineCount = @lines.length |
|
228 | if 1 is lineCount or (2 is lineCount and Utils.isEmpty(@lines[1])) |
229 | if 1 is lineCount or (2 is lineCount and Utils.isEmpty(@lines[1])) |
|
229 | try |
230 | try |
|
230 | value = Inline.parse @lines[0], exceptionOnInvalidType, objectDecoder |
231 | value = Inline.parse @lines[0], exceptionOnInvalidType, objectDecoder |
|
231 | catch e |
232 | catch e |
|
232 | e.parsedLine = @getRealCurrentLineNb() + 1 |
233 | e.parsedLine = @getRealCurrentLineNb() + 1 |
|
233 | e.snippet = @currentLine |
234 | e.snippet = @currentLine |
|
234 | |
235 | |
|
235 | throw e |
236 | throw e |
|
236 | |
237 | |
|
237 | if typeof value is 'object' |
238 | if typeof value is 'object' |
|
238 | if value instanceof Array |
239 | if value instanceof Array |
|
239 | first = value[0] |
240 | first = value[0] |
|
240 | else |
241 | else |
|
241 | for key of value |
242 | for key of value |
|
242 | first = value[key] |
243 | first = value[key] |
|
243 | break |
244 | break |
|
244 | |
245 | |
|
245 | if typeof first is 'string' and first.indexOf('*') is 0 |
246 | if typeof first is 'string' and first.indexOf('*') is 0 |
|
246 | data = [] |
247 | data = [] |
|
247 | for alias in value |
248 | for alias in value |
|
248 | data.push @refs[alias[1..]] |
249 | data.push @refs[alias[1..]] |
|
249 | value = data |
250 | value = data |
|
250 | |
251 | |
|
251 | return value |
252 | return value |
|
252 | |
253 | |
|
253 | else if Utils.ltrim(value).charAt(0) in ['[', '{'] |
254 | else if Utils.ltrim(value).charAt(0) in ['[', '{'] |
|
254 | try |
255 | try |
|
255 | return Inline.parse value, exceptionOnInvalidType, objectDecoder |
256 | return Inline.parse value, exceptionOnInvalidType, objectDecoder |
|
256 | catch e |
257 | catch e |
|
257 | e.parsedLine = @getRealCurrentLineNb() + 1 |
258 | e.parsedLine = @getRealCurrentLineNb() + 1 |
|
258 | e.snippet = @currentLine |
259 | e.snippet = @currentLine |
|
259 | |
260 | |
|
260 | throw e |
261 | throw e |
|
261 | |
262 | |
|
262 | throw new ParseException 'Unable to parse.', @getRealCurrentLineNb() + 1, @currentLine |
263 | throw new ParseException 'Unable to parse.', @getRealCurrentLineNb() + 1, @currentLine |
|
263 | |
264 | |
|
264 | if isRef |
265 | if isRef |
|
265 | if data instanceof Array |
266 | if data instanceof Array |
|
266 | @refs[isRef] = data[data.length-1] |
267 | @refs[isRef] = data[data.length-1] |
|
267 | else |
268 | else |
|
268 | lastKey = null |
269 | lastKey = null |
|
269 | for key of data |
270 | for key of data |
|
270 | lastKey = key |
271 | lastKey = key |
|
271 | @refs[isRef] = data[lastKey] |
272 | @refs[isRef] = data[lastKey] |
|
272 | |
273 | |
|
273 | |
274 | |
|
274 | if Utils.isEmpty(data) |
275 | if Utils.isEmpty(data) |
|
275 | return null |
276 | return null |
|
276 | else |
277 | else |
|
277 | return data |
278 | return data |
|
278 | |
279 | |
|
279 | |
280 | |
|
280 | |
281 | |
|
281 | # Returns the current line number (takes the offset into account). |
282 | # Returns the current line number (takes the offset into account). |
|
282 | # |
283 | # |
|
283 | # @return [Integer] The current line number |
284 | # @return [Integer] The current line number |
|
284 | # |
285 | # |
|
285 | getRealCurrentLineNb: -> |
286 | getRealCurrentLineNb: -> |
|
286 | return @currentLineNb + @offset |
287 | return @currentLineNb + @offset |
|
287 | |
288 | |
|
288 | |
289 | |
|
289 | # Returns the current line indentation. |
290 | # Returns the current line indentation. |
|
290 | # |
291 | # |
|
291 | # @return [Integer] The current line indentation |
292 | # @return [Integer] The current line indentation |
|
292 | # |
293 | # |
|
293 | getCurrentLineIndentation: -> |
294 | getCurrentLineIndentation: -> |
|
294 | return @currentLine.length - Utils.ltrim(@currentLine, ' ').length |
295 | return @currentLine.length - Utils.ltrim(@currentLine, ' ').length |
|
295 | |
296 | |
|
296 | |
297 | |
|
297 | # Returns the next embed block of YAML. |
298 | # Returns the next embed block of YAML. |
|
298 | # |
299 | # |
|
299 | # @param [Integer] indentation The indent level at which the block is to be read, or null for default |
300 | # @param [Integer] indentation The indent level at which the block is to be read, or null for default |
|
300 | # |
301 | # |
|
301 | # @return [String] A YAML string |
302 | # @return [String] A YAML string |
|
302 | # |
303 | # |
|
303 | # @throw [ParseException] When indentation problem are detected |
304 | # @throw [ParseException] When indentation problem are detected |
|
304 | # |
305 | # |
|
305 | getNextEmbedBlock: (indentation = null, includeUnindentedCollection = false) -> |
306 | getNextEmbedBlock: (indentation = null, includeUnindentedCollection = false) -> |
|
306 | @moveToNextLine() |
307 | @moveToNextLine() |
|
307 | |
308 | |
|
308 | if not indentation? |
309 | if not indentation? |
|
309 | newIndent = @getCurrentLineIndentation() |
310 | newIndent = @getCurrentLineIndentation() |
|
310 | |
311 | |
|
311 | unindentedEmbedBlock = @isStringUnIndentedCollectionItem @currentLine |
312 | unindentedEmbedBlock = @isStringUnIndentedCollectionItem @currentLine |
|
312 | |
313 | |
|
313 | if not(@isCurrentLineEmpty()) and 0 is newIndent and not(unindentedEmbedBlock) |
314 | if not(@isCurrentLineEmpty()) and 0 is newIndent and not(unindentedEmbedBlock) |
|
314 | throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine |
315 | throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine |
|
315 | |
316 | |
|
316 | else |
317 | else |
|
317 | newIndent = indentation |
318 | newIndent = indentation |
|
318 | |
319 | |
|
319 | |
320 | |
|
320 | data = [@currentLine[newIndent..]] |
321 | data = [@currentLine[newIndent..]] |
|
321 | |
322 | |
|
322 | unless includeUnindentedCollection |
323 | unless includeUnindentedCollection |
|
323 | isItUnindentedCollection = @isStringUnIndentedCollectionItem @currentLine |
324 | isItUnindentedCollection = @isStringUnIndentedCollectionItem @currentLine |
|
324 | |
325 | |
|
325 | # Comments must not be removed inside a string block (ie. after a line ending with "|") |
326 | # Comments must not be removed inside a string block (ie. after a line ending with "|") |
|
326 | # They must not be removed inside a sub-embedded block as well |
327 | # They must not be removed inside a sub-embedded block as well |
|
327 | removeCommentsPattern = @PATTERN_FOLDED_SCALAR_END |
328 | removeCommentsPattern = @PATTERN_FOLDED_SCALAR_END |
|
328 | removeComments = not removeCommentsPattern.test @currentLine |
329 | removeComments = not removeCommentsPattern.test @currentLine |
|
329 | |
330 | |
|
330 | while @moveToNextLine() |
331 | while @moveToNextLine() |
|
331 | indent = @getCurrentLineIndentation() |
332 | indent = @getCurrentLineIndentation() |
|
332 | |
333 | |
|
333 | if indent is newIndent |
334 | if indent is newIndent |
|
334 | removeComments = not removeCommentsPattern.test @currentLine |
335 | removeComments = not removeCommentsPattern.test @currentLine |
|
335 | |
- | ||
336 | if isItUnindentedCollection and not @isStringUnIndentedCollectionItem(@currentLine) and indent is newIndent |
336 | |
|
337 | @moveToPreviousLine() |
337 | if removeComments and @isCurrentLineComment() |
|
338 | break |
338 | continue |
|
339 | |
339 | |
|
340 | if @isCurrentLineBlank() |
340 | if @isCurrentLineBlank() |
|
341 | data.push @currentLine[newIndent..] |
341 | data.push @currentLine[newIndent..] |
|
342 | continue |
342 | continue |
|
343 | |
343 | |
|
344 | if removeComments and @isCurrentLineComment() |
344 | if isItUnindentedCollection and not @isStringUnIndentedCollectionItem(@currentLine) and indent is newIndent |
|
345 | if indent is newIndent |
345 | @moveToPreviousLine() |
|
346 | continue |
346 | break |
|
347 | |
347 | |
|
348 | if indent >= newIndent |
348 | if indent >= newIndent |
|
349 | data.push @currentLine[newIndent..] |
349 | data.push @currentLine[newIndent..] |
|
350 | else if Utils.ltrim(@currentLine).charAt(0) is '#' |
350 | else if Utils.ltrim(@currentLine).charAt(0) is '#' |
|
351 | # Don't add line with comments |
351 | # Don't add line with comments |
|
352 | else if 0 is indent |
352 | else if 0 is indent |
|
353 | @moveToPreviousLine() |
353 | @moveToPreviousLine() |
|
354 | break |
354 | break |
|
355 | else |
355 | else |
|
356 | throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine |
356 | throw new ParseException 'Indentation problem.', @getRealCurrentLineNb() + 1, @currentLine |
|
357 | |
357 | |
|
358 | |
358 | |
|
359 | return data.join "\n" |
359 | return data.join "\n" |
|
360 | |
360 | |
|
361 | |
361 | |
|
362 | # Moves the parser to the next line. |
362 | # Moves the parser to the next line. |
|
363 | # |
363 | # |
|
364 | # @return [Boolean] |
364 | # @return [Boolean] |
|
365 | # |
365 | # |
|
366 | moveToNextLine: -> |
366 | moveToNextLine: -> |
|
367 | if @currentLineNb >= @lines.length - 1 |
367 | if @currentLineNb >= @lines.length - 1 |
|
368 | return false |
368 | return false |
|
369 | |
369 | |
|
370 | @currentLine = @lines[++@currentLineNb]; |
370 | @currentLine = @lines[++@currentLineNb]; |
|
371 | |
371 | |
|
372 | return true |
372 | return true |
|
373 | |
373 | |
|
374 | |
374 | |
|
375 | # Moves the parser to the previous line. |
375 | # Moves the parser to the previous line. |
|
376 | # |
376 | # |
|
377 | moveToPreviousLine: -> |
377 | moveToPreviousLine: -> |
|
378 | @currentLine = @lines[--@currentLineNb] |
378 | @currentLine = @lines[--@currentLineNb] |
|
379 | return |
379 | return |
|
380 | |
380 | |
|
381 | |
381 | |
|
382 | # Parses a YAML value. |
382 | # Parses a YAML value. |
|
383 | # |
383 | # |
|
384 | # @param [String] value A YAML value |
384 | # @param [String] value A YAML value |
|
385 | # @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types false otherwise |
385 | # @param [Boolean] exceptionOnInvalidType true if an exception must be thrown on invalid types false otherwise |
|
386 | # @param [Function] objectDecoder A function to deserialize custom objects, null otherwise |
386 | # @param [Function] objectDecoder A function to deserialize custom objects, null otherwise |
|
387 | # |
387 | # |
|
388 | # @return [Object] A JavaScript value |
388 | # @return [Object] A JavaScript value |
|
389 | # |
389 | # |
|
390 | # @throw [ParseException] When reference does not exist |
390 | # @throw [ParseException] When reference does not exist |
|
391 | # |
391 | # |
|
392 | parseValue: (value, exceptionOnInvalidType, objectDecoder) -> |
392 | parseValue: (value, exceptionOnInvalidType, objectDecoder) -> |
|
393 | if 0 is value.indexOf('*') |
393 | if 0 is value.indexOf('*') |
|
394 | pos = value.indexOf '#' |
394 | pos = value.indexOf '#' |
|
395 | if pos isnt -1 |
395 | if pos isnt -1 |
|
396 | value = value.substr(1, pos-2) |
396 | value = value.substr(1, pos-2) |
|
397 | else |
397 | else |
|
398 | value = value[1..] |
398 | value = value[1..] |
|
399 | |
399 | |
|
400 | if @refs[value] is undefined |
400 | if @refs[value] is undefined |
|
401 | throw new ParseException 'Reference "'+value+'" does not exist.', @currentLine |
401 | throw new ParseException 'Reference "'+value+'" does not exist.', @currentLine |
|
402 | |
402 | |
|
403 | return @refs[value] |
403 | return @refs[value] |
|
404 | |
404 | |
|
405 | |
405 | |
|
406 | if matches = @PATTERN_FOLDED_SCALAR_ALL.exec value |
406 | if matches = @PATTERN_FOLDED_SCALAR_ALL.exec value |
|
407 | modifiers = matches.modifiers ? '' |
407 | modifiers = matches.modifiers ? '' |
|
408 | |
408 | |
|
409 | foldedIndent = Math.abs(parseInt(modifiers)) |
409 | foldedIndent = Math.abs(parseInt(modifiers)) |
|
410 | if isNaN(foldedIndent) then foldedIndent = 0 |
410 | if isNaN(foldedIndent) then foldedIndent = 0 |
|
411 | val = @parseFoldedScalar matches.separator, @PATTERN_DECIMAL.replace(modifiers, ''), foldedIndent |
411 | val = @parseFoldedScalar matches.separator, @PATTERN_DECIMAL.replace(modifiers, ''), foldedIndent |
|
412 | if matches.type? |
412 | if matches.type? |
|
413 | # Force correct settings |
413 | # Force correct settings |
|
414 | Inline.configure exceptionOnInvalidType, objectDecoder |
414 | Inline.configure exceptionOnInvalidType, objectDecoder |
|
415 | return Inline.parseScalar matches.type+' '+val |
415 | return Inline.parseScalar matches.type+' '+val |
|
416 | else |
416 | else |
|
417 | return val |
417 | return val |
|
418 | |
- | ||
419 | try |
- | ||
420 | return Inline.parse value, exceptionOnInvalidType, objectDecoder |
- | ||
421 | catch e |
418 | |
|
422 | # Try to parse multiline compact sequence or mapping |
419 | # Value can be multiline compact sequence or mapping or string |
|
423 | if value.charAt(0) in ['[', '{'] and e instanceof ParseException and @isNextLineIndented() |
420 | if value.charAt(0) in ['[', '{', '"', "'"] |
|
424 | value += "\n" + @getNextEmbedBlock() |
421 | while true |
|
425 | try |
422 | try |
|
426 | return Inline.parse value, exceptionOnInvalidType, objectDecoder |
423 | return Inline.parse value, exceptionOnInvalidType, objectDecoder |
|
427 | catch e |
424 | catch e |
|
- | 425 | if e instanceof ParseMore and @moveToNextLine() |
||
- | 426 | value += "\n" + Utils.trim(@currentLine, ' ') |
||
- | 427 | else |
||
428 | e.parsedLine = @getRealCurrentLineNb() + 1 |
428 | e.parsedLine = @getRealCurrentLineNb() + 1 |
|
429 | e.snippet = @currentLine |
429 | e.snippet = @currentLine |
|
430 | |
- | ||
431 | throw e |
430 | throw e |
|
432 | |
- | ||
433 | else |
431 | else |
|
434 | e.parsedLine = @getRealCurrentLineNb() + 1 |
432 | if @isNextLineIndented() |
|
435 | e.snippet = @currentLine |
433 | value += "\n" + @getNextEmbedBlock() |
|
436 | |
- | ||
437 | throw e |
434 | return Inline.parse value, exceptionOnInvalidType, objectDecoder |
|
438 | |
435 | |
|
439 | return |
436 | return |
|
440 | |
437 | |
|
441 | |
438 | |
|
442 | # Parses a folded scalar. |
439 | # Parses a folded scalar. |
|
443 | # |
440 | # |
|
444 | # @param [String] separator The separator that was used to begin this folded scalar (| or >) |
441 | # @param [String] separator The separator that was used to begin this folded scalar (| or >) |
|
445 | # @param [String] indicator The indicator that was used to begin this folded scalar (+ or -) |
442 | # @param [String] indicator The indicator that was used to begin this folded scalar (+ or -) |
|
446 | # @param [Integer] indentation The indentation that was used to begin this folded scalar |
443 | # @param [Integer] indentation The indentation that was used to begin this folded scalar |
|
447 | # |
444 | # |
|
448 | # @return [String] The text value |
445 | # @return [String] The text value |
|
449 | # |
446 | # |
|
450 | parseFoldedScalar: (separator, indicator = '', indentation = 0) -> |
447 | parseFoldedScalar: (separator, indicator = '', indentation = 0) -> |
|
451 | notEOF = @moveToNextLine() |
448 | notEOF = @moveToNextLine() |
|
452 | if not notEOF |
449 | if not notEOF |
|
453 | return '' |
450 | return '' |
|
454 | |
451 | |
|
455 | isCurrentLineBlank = @isCurrentLineBlank() |
452 | isCurrentLineBlank = @isCurrentLineBlank() |
|
456 | text = '' |
453 | text = '' |
|
457 | |
454 | |
|
458 | # Leading blank lines are consumed before determining indentation |
455 | # Leading blank lines are consumed before determining indentation |
|
459 | while notEOF and isCurrentLineBlank |
456 | while notEOF and isCurrentLineBlank |
|
460 | # newline only if not EOF |
457 | # newline only if not EOF |
|
461 | if notEOF = @moveToNextLine() |
458 | if notEOF = @moveToNextLine() |
|
462 | text += "\n" |
459 | text += "\n" |
|
463 | isCurrentLineBlank = @isCurrentLineBlank() |
460 | isCurrentLineBlank = @isCurrentLineBlank() |
|
464 | |
461 | |
|
465 | |
462 | |
|
466 | # Determine indentation if not specified |
463 | # Determine indentation if not specified |
|
467 | if 0 is indentation |
464 | if 0 is indentation |
|
468 | if matches = @PATTERN_INDENT_SPACES.exec @currentLine |
465 | if matches = @PATTERN_INDENT_SPACES.exec @currentLine |
|
469 | indentation = matches[0].length |
466 | indentation = matches[0].length |
|
470 | |
467 | |
|
471 | |
468 | |
|
472 | if indentation > 0 |
469 | if indentation > 0 |
|
473 | pattern = @PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] |
470 | pattern = @PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] |
|
474 | unless pattern? |
471 | unless pattern? |
|
475 | pattern = new Pattern '^ {'+indentation+'}(.*)$' |
472 | pattern = new Pattern '^ {'+indentation+'}(.*)$' |
|
476 | Parser::PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] = pattern |
473 | Parser::PATTERN_FOLDED_SCALAR_BY_INDENTATION[indentation] = pattern |
|
477 | |
474 | |
|
478 | while notEOF and (isCurrentLineBlank or matches = pattern.exec @currentLine) |
475 | while notEOF and (isCurrentLineBlank or matches = pattern.exec @currentLine) |
|
479 | if isCurrentLineBlank |
476 | if isCurrentLineBlank |
|
480 | text += @currentLine[indentation..] |
477 | text += @currentLine[indentation..] |
|
481 | else |
478 | else |
|
482 | text += matches[1] |
479 | text += matches[1] |
|
483 | |
480 | |
|
484 | # newline only if not EOF |
481 | # newline only if not EOF |
|
485 | if notEOF = @moveToNextLine() |
482 | if notEOF = @moveToNextLine() |
|
486 | text += "\n" |
483 | text += "\n" |
|
487 | isCurrentLineBlank = @isCurrentLineBlank() |
484 | isCurrentLineBlank = @isCurrentLineBlank() |
|
488 | |
485 | |
|
489 | else if notEOF |
486 | else if notEOF |
|
490 | text += "\n" |
487 | text += "\n" |
|
491 | |
488 | |
|
492 | |
489 | |
|
493 | if notEOF |
490 | if notEOF |
|
494 | @moveToPreviousLine() |
491 | @moveToPreviousLine() |
|
495 | |
492 | |
|
496 | |
493 | |
|
497 | # Remove line breaks of each lines except the empty and more indented ones |
494 | # Remove line breaks of each lines except the empty and more indented ones |
|
498 | if '>' is separator |
495 | if '>' is separator |
|
499 | newText = '' |
496 | newText = '' |
|
500 | for line in text.split "\n" |
497 | for line in text.split "\n" |
|
501 | if line.length is 0 or line.charAt(0) is ' ' |
498 | if line.length is 0 or line.charAt(0) is ' ' |
|
502 | newText = Utils.rtrim(newText, ' ') + line + "\n" |
499 | newText = Utils.rtrim(newText, ' ') + line + "\n" |
|
503 | else |
500 | else |
|
504 | newText += line + ' ' |
501 | newText += line + ' ' |
|
505 | text = newText |
502 | text = newText |
|
506 | |
503 | |
|
507 | if '+' isnt indicator |
504 | if '+' isnt indicator |
|
508 | # Remove any extra space or new line as we are adding them after |
505 | # Remove any extra space or new line as we are adding them after |
|
509 | text = Utils.rtrim(text) |
506 | text = Utils.rtrim(text) |
|
510 | |
507 | |
|
511 | # Deal with trailing newlines as indicated |
508 | # Deal with trailing newlines as indicated |
|
512 | if '' is indicator |
509 | if '' is indicator |
|
513 | text = @PATTERN_TRAILING_LINES.replace text, "\n" |
510 | text = @PATTERN_TRAILING_LINES.replace text, "\n" |
|
514 | else if '-' is indicator |
511 | else if '-' is indicator |
|
515 | text = @PATTERN_TRAILING_LINES.replace text, '' |
512 | text = @PATTERN_TRAILING_LINES.replace text, '' |
|
516 | |
513 | |
|
517 | return text |
514 | return text |
|
518 | |
515 | |
|
519 | |
516 | |
|
520 | # Returns true if the next line is indented. |
517 | # Returns true if the next line is indented. |
|
521 | # |
518 | # |
|
522 | # @return [Boolean] Returns true if the next line is indented, false otherwise |
519 | # @return [Boolean] Returns true if the next line is indented, false otherwise |
|
523 | # |
520 | # |
|
524 | isNextLineIndented: (ignoreComments = true) -> |
521 | isNextLineIndented: (ignoreComments = true) -> |
|
525 | currentIndentation = @getCurrentLineIndentation() |
522 | currentIndentation = @getCurrentLineIndentation() |
|
526 | EOF = not @moveToNextLine() |
523 | EOF = not @moveToNextLine() |
|
527 | |
524 | |
|
528 | if ignoreComments |
525 | if ignoreComments |
|
529 | while not(EOF) and @isCurrentLineEmpty() |
526 | while not(EOF) and @isCurrentLineEmpty() |
|
530 | EOF = not @moveToNextLine() |
527 | EOF = not @moveToNextLine() |
|
531 | else |
528 | else |
|
532 | while not(EOF) and @isCurrentLineBlank() |
529 | while not(EOF) and @isCurrentLineBlank() |
|
533 | EOF = not @moveToNextLine() |
530 | EOF = not @moveToNextLine() |
|
534 | |
531 | |
|
535 | if EOF |
532 | if EOF |
|
536 | return false |
533 | return false |
|
537 | |
534 | |
|
538 | ret = false |
535 | ret = false |
|
539 | if @getCurrentLineIndentation() > currentIndentation |
536 | if @getCurrentLineIndentation() > currentIndentation |
|
540 | ret = true |
537 | ret = true |
|
541 | |
538 | |
|
542 | @moveToPreviousLine() |
539 | @moveToPreviousLine() |
|
543 | |
540 | |
|
544 | return ret |
541 | return ret |
|
545 | |
542 | |
|
546 | |
543 | |
|
547 | # Returns true if the current line is blank or if it is a comment line. |
544 | # Returns true if the current line is blank or if it is a comment line. |
|
548 | # |
545 | # |
|
549 | # @return [Boolean] Returns true if the current line is empty or if it is a comment line, false otherwise |
546 | # @return [Boolean] Returns true if the current line is empty or if it is a comment line, false otherwise |
|
550 | # |
547 | # |
|
551 | isCurrentLineEmpty: -> |
548 | isCurrentLineEmpty: -> |
|
552 | trimmedLine = Utils.trim(@currentLine, ' ') |
549 | trimmedLine = Utils.trim(@currentLine, ' ') |
|
553 | return trimmedLine.length is 0 or trimmedLine.charAt(0) is '#' |
550 | return trimmedLine.length is 0 or trimmedLine.charAt(0) is '#' |
|
554 | |
551 | |
|
555 | |
552 | |
|
556 | # Returns true if the current line is blank. |
553 | # Returns true if the current line is blank. |
|
557 | # |
554 | # |
|
558 | # @return [Boolean] Returns true if the current line is blank, false otherwise |
555 | # @return [Boolean] Returns true if the current line is blank, false otherwise |
|
559 | # |
556 | # |
|
560 | isCurrentLineBlank: -> |
557 | isCurrentLineBlank: -> |
|
561 | return '' is Utils.trim(@currentLine, ' ') |
558 | return '' is Utils.trim(@currentLine, ' ') |
|
562 | |
559 | |
|
563 | |
560 | |
|
564 | # Returns true if the current line is a comment line. |
561 | # Returns true if the current line is a comment line. |
|
565 | # |
562 | # |
|
566 | # @return [Boolean] Returns true if the current line is a comment line, false otherwise |
563 | # @return [Boolean] Returns true if the current line is a comment line, false otherwise |
|
567 | # |
564 | # |
|
568 | isCurrentLineComment: -> |
565 | isCurrentLineComment: -> |
|
569 | # Checking explicitly the first char of the trim is faster than loops or strpos |
566 | # Checking explicitly the first char of the trim is faster than loops or strpos |
|
570 | ltrimmedLine = Utils.ltrim(@currentLine, ' ') |
567 | ltrimmedLine = Utils.ltrim(@currentLine, ' ') |
|
571 | |
568 | |
|
572 | return ltrimmedLine.charAt(0) is '#' |
569 | return ltrimmedLine.charAt(0) is '#' |
|
573 | |
570 | |
|
574 | |
571 | |
|
575 | # Cleanups a YAML string to be parsed. |
572 | # Cleanups a YAML string to be parsed. |
|
576 | # |
573 | # |
|
577 | # @param [String] value The input YAML string |
574 | # @param [String] value The input YAML string |
|
578 | # |
575 | # |
|
579 | # @return [String] A cleaned up YAML string |
576 | # @return [String] A cleaned up YAML string |
|
580 | # |
577 | # |
|
581 | cleanup: (value) -> |
578 | cleanup: (value) -> |
|
582 | if value.indexOf("\r") isnt -1 |
579 | if value.indexOf("\r") isnt -1 |
|
583 | value = value.split("\r\n").join("\n").split("\r").join("\n") |
580 | value = value.split("\r\n").join("\n").split("\r").join("\n") |
|
584 | |
581 | |
|
585 | # Strip YAML header |
582 | # Strip YAML header |
|
586 | count = 0 |
583 | count = 0 |
|
587 | [value, count] = @PATTERN_YAML_HEADER.replaceAll value, '' |
584 | [value, count] = @PATTERN_YAML_HEADER.replaceAll value, '' |
|
588 | @offset += count |
585 | @offset += count |
|
589 | |
586 | |
|
590 | # Remove leading comments |
587 | # Remove leading comments |
|
591 | [trimmedValue, count] = @PATTERN_LEADING_COMMENTS.replaceAll value, '', 1 |
588 | [trimmedValue, count] = @PATTERN_LEADING_COMMENTS.replaceAll value, '', 1 |
|
592 | if count is 1 |
589 | if count is 1 |
|
593 | # Items have been removed, update the offset |
590 | # Items have been removed, update the offset |
|
594 | @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n") |
591 | @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n") |
|
595 | value = trimmedValue |
592 | value = trimmedValue |
|
596 | |
593 | |
|
597 | # Remove start of the document marker (---) |
594 | # Remove start of the document marker (---) |
|
598 | [trimmedValue, count] = @PATTERN_DOCUMENT_MARKER_START.replaceAll value, '', 1 |
595 | [trimmedValue, count] = @PATTERN_DOCUMENT_MARKER_START.replaceAll value, '', 1 |
|
599 | if count is 1 |
596 | if count is 1 |
|
600 | # Items have been removed, update the offset |
597 | # Items have been removed, update the offset |
|
601 | @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n") |
598 | @offset += Utils.subStrCount(value, "\n") - Utils.subStrCount(trimmedValue, "\n") |
|
602 | value = trimmedValue |
599 | value = trimmedValue |
|
603 | |
600 | |
|
604 | # Remove end of the document marker (...) |
601 | # Remove end of the document marker (...) |
|
605 | value = @PATTERN_DOCUMENT_MARKER_END.replace value, '' |
602 | value = @PATTERN_DOCUMENT_MARKER_END.replace value, '' |
|
606 | |
603 | |
|
607 | # Ensure the block is not indented |
604 | # Ensure the block is not indented |
|
608 | lines = value.split("\n") |
605 | lines = value.split("\n") |
|
609 | smallestIndent = -1 |
606 | smallestIndent = -1 |
|
610 | for line in lines |
607 | for line in lines |
|
611 | continue if Utils.trim(line, ' ').length == 0 |
608 | continue if Utils.trim(line, ' ').length == 0 |
|
612 | indent = line.length - Utils.ltrim(line).length |
609 | indent = line.length - Utils.ltrim(line).length |
|
613 | if smallestIndent is -1 or indent < smallestIndent |
610 | if smallestIndent is -1 or indent < smallestIndent |
|
614 | smallestIndent = indent |
611 | smallestIndent = indent |
|
615 | if smallestIndent > 0 |
612 | if smallestIndent > 0 |
|
616 | for line, i in lines |
613 | for line, i in lines |
|
617 | lines[i] = line[smallestIndent..] |
614 | lines[i] = line[smallestIndent..] |
|
618 | value = lines.join("\n") |
615 | value = lines.join("\n") |
|
619 | |
616 | |
|
620 | return value |
617 | return value |
|
621 | |
618 | |
|
622 | |
619 | |
|
623 | # Returns true if the next line starts unindented collection |
620 | # Returns true if the next line starts unindented collection |
|
624 | # |
621 | # |
|
625 | # @return [Boolean] Returns true if the next line starts unindented collection, false otherwise |
622 | # @return [Boolean] Returns true if the next line starts unindented collection, false otherwise |
|
626 | # |
623 | # |
|
627 | isNextLineUnIndentedCollection: (currentIndentation = null) -> |
624 | isNextLineUnIndentedCollection: (currentIndentation = null) -> |
|
628 | currentIndentation ?= @getCurrentLineIndentation() |
625 | currentIndentation ?= @getCurrentLineIndentation() |
|
629 | notEOF = @moveToNextLine() |
626 | notEOF = @moveToNextLine() |
|
630 | |
627 | |
|
631 | while notEOF and @isCurrentLineEmpty() |
628 | while notEOF and @isCurrentLineEmpty() |
|
632 | notEOF = @moveToNextLine() |
629 | notEOF = @moveToNextLine() |
|
633 | |
630 | |
|
634 | if false is notEOF |
631 | if false is notEOF |
|
635 | return false |
632 | return false |
|
636 | |
633 | |
|
637 | ret = false |
634 | ret = false |
|
638 | if @getCurrentLineIndentation() is currentIndentation and @isStringUnIndentedCollectionItem(@currentLine) |
635 | if @getCurrentLineIndentation() is currentIndentation and @isStringUnIndentedCollectionItem(@currentLine) |
|
639 | ret = true |
636 | ret = true |
|
640 | |
637 | |
|
641 | @moveToPreviousLine() |
638 | @moveToPreviousLine() |
|
642 | |
639 | |
|
643 | return ret |
640 | return ret |
|
644 | |
641 | |
|
645 | |
642 | |
|
646 | # Returns true if the string is un-indented collection item |
643 | # Returns true if the string is un-indented collection item |
|
647 | # |
644 | # |
|
648 | # @return [Boolean] Returns true if the string is un-indented collection item, false otherwise |
645 | # @return [Boolean] Returns true if the string is un-indented collection item, false otherwise |
|
649 | # |
646 | # |
|
650 | isStringUnIndentedCollectionItem: -> |
647 | isStringUnIndentedCollectionItem: -> |
|
651 | return @currentLine is '-' or @currentLine[0...2] is '- ' |
648 | return @currentLine is '-' or @currentLine[0...2] is '- ' |
|
652 | |
649 | |
|
653 | |
650 | |
|
654 | module.exports = Parser |
651 | module.exports = Parser |
|
655 | |
652 | |