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