psych-pure 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/psych/pure.rb CHANGED
@@ -9,33 +9,184 @@ require "stringio"
9
9
  module Psych
10
10
  # A YAML parser written in Ruby.
11
11
  module Pure
12
+ # An internal exception is an exception that should not have occurred. It is
13
+ # effectively an assertion.
14
+ class InternalException < Exception
15
+ def initialize(message = "An internal exception occurred")
16
+ super(message)
17
+ end
18
+ end
19
+
20
+ # A source is wraps the input string and provides methods to access line and
21
+ # column information from a byte offset.
22
+ class Source
23
+ def initialize(string)
24
+ @line_offsets = []
25
+ @trimmable_lines = []
26
+
27
+ offset = 0
28
+ string.each_line do |line|
29
+ @line_offsets << offset
30
+ @trimmable_lines << line.match?(/\A(?: *#.*)?\n\z/)
31
+ offset += line.bytesize
32
+ end
33
+
34
+ @line_offsets << offset
35
+ @trimmable_lines << true
36
+ end
37
+
38
+ def trim(offset)
39
+ while (l = line(offset)) != 0 && (offset == @line_offsets[l]) && @trimmable_lines[l - 1]
40
+ offset = @line_offsets[l - 1]
41
+ end
42
+
43
+ offset
44
+ end
45
+
46
+ def line(offset)
47
+ index = @line_offsets.bsearch_index { |line_offset| line_offset > offset }
48
+ return @line_offsets.size - 1 if index.nil?
49
+ index - 1
50
+ end
51
+
52
+ def column(offset)
53
+ offset - @line_offsets[line(offset)]
54
+ end
55
+ end
56
+
57
+ # A location represents a range of bytes in the input string.
58
+ class Location
59
+ protected attr_reader :pos_end
60
+
61
+ def initialize(source, pos_start, pos_end)
62
+ @source = source
63
+ @pos_start = pos_start
64
+ @pos_end = pos_end
65
+ end
66
+
67
+ def start_line
68
+ @source.line(@pos_start)
69
+ end
70
+
71
+ def start_column
72
+ @source.column(@pos_start)
73
+ end
74
+
75
+ def end_line
76
+ @source.line(@pos_end)
77
+ end
78
+
79
+ def end_column
80
+ @source.column(@pos_end)
81
+ end
82
+
83
+ def join(other)
84
+ @pos_end = other.pos_end
85
+ end
86
+
87
+ # Trim trailing whitespace and comments from this location.
88
+ def trim
89
+ Location.new(@source, @pos_start, @source.trim(@pos_end))
90
+ end
91
+
92
+ def to_a
93
+ [start_line, start_column, end_line, end_column]
94
+ end
95
+
96
+ def self.point(source, pos)
97
+ new(source, pos, pos)
98
+ end
99
+ end
100
+
12
101
  # Represents a comment in the input.
13
102
  class Comment
14
- attr_reader :value, :start_line, :start_column, :end_line, :end_column
103
+ attr_reader :location, :value
15
104
 
16
- def initialize(value, start_line, start_column, end_line, end_column, inline)
105
+ def initialize(location, value, inline)
106
+ @location = location
17
107
  @value = value
18
- @start_line = start_line
19
- @start_column = start_column
20
- @end_line = end_line
21
- @end_column = end_column
22
108
  @inline = inline
23
109
  end
24
110
 
25
111
  def inline?
26
112
  @inline
27
113
  end
114
+
115
+ def start_line
116
+ location.start_line
117
+ end
118
+
119
+ def start_column
120
+ location.start_column
121
+ end
122
+
123
+ def end_line
124
+ location.end_line
125
+ end
126
+
127
+ def end_column
128
+ location.end_column
129
+ end
130
+ end
131
+
132
+ # Represents the set of comments on a node.
133
+ class Comments
134
+ attr_reader :leading, :trailing
135
+
136
+ def initialize
137
+ @leading = []
138
+ @trailing = []
139
+ end
140
+
141
+ def leading_comment(comment)
142
+ @leading << comment
143
+ end
144
+
145
+ def trailing_comment(comment)
146
+ @trailing << comment
147
+ end
148
+ end
149
+
150
+ # Wraps a Ruby object with its node from the source input.
151
+ class LoadedObject < SimpleDelegator
152
+ # The node associated with the object.
153
+ attr_reader :psych_node
154
+
155
+ # Whether or not this object has been modified. If it has, then we cannot
156
+ # rely on the source formatting, and need to format it ourselves.
157
+ attr_reader :dirty
158
+
159
+ def initialize(object, psych_node, dirty = false)
160
+ super(object)
161
+ @psych_node = psych_node
162
+ @dirty = dirty
163
+ end
28
164
  end
29
165
 
30
- # Wraps a Ruby object with its leading and trailing YAML comments from the
31
- # source input.
32
- class YAMLCommentsDelegator < SimpleDelegator
33
- attr_reader :yaml_leading_comments, :yaml_trailing_comments
166
+ # Wraps a Ruby hash with its node from the source input.
167
+ class LoadedHash < SimpleDelegator
168
+ # The node associated with the hash.
169
+ attr_reader :psych_node
34
170
 
35
- def initialize(object, yaml_leading_comments, yaml_trailing_comments)
171
+ # The nodes associated with the keys of the hash.
172
+ attr_reader :psych_node_keys
173
+
174
+ def initialize(object, psych_node, psych_node_keys = {})
36
175
  super(object)
37
- @yaml_leading_comments = yaml_leading_comments
38
- @yaml_trailing_comments = yaml_trailing_comments
176
+ @psych_node = psych_node
177
+ @psych_node_keys = psych_node_keys
178
+ end
179
+
180
+ def []=(key, value)
181
+ if (previous = self[key])
182
+ if previous.is_a?(LoadedObject)
183
+ value = LoadedObject.new(value, previous.psych_node, true)
184
+ elsif previous.is_a?(LoadedHash)
185
+ value = LoadedHash.new(value, previous.psych_node, previous.psych_node_keys)
186
+ end
187
+ end
188
+
189
+ super(key, value)
39
190
  end
40
191
  end
41
192
 
@@ -45,7 +196,7 @@ module Psych
45
196
  # Extend the Handler to be able to handle comments coming out of the
46
197
  # parser.
47
198
  module Handler
48
- def comment(value, start_line, start_column, end_line, end_column, inline)
199
+ def comment(value)
49
200
  end
50
201
  end
51
202
 
@@ -55,8 +206,8 @@ module Psych
55
206
  @comments ||= []
56
207
  end
57
208
 
58
- def comment(value, start_line, start_column, end_line, end_column, inline)
59
- comments << Comment.new(value, start_line, start_column, end_line, end_column, inline)
209
+ def comment(value)
210
+ comments << value
60
211
  end
61
212
 
62
213
  def end_stream
@@ -96,10 +247,11 @@ module Psych
96
247
  preceding = nil
97
248
  following = nil
98
249
 
99
- comment_start_line = comment.start_line
100
- comment_start_column = comment.start_column
101
- comment_end_line = comment.end_line
102
- comment_end_column = comment.end_column
250
+ location = comment.location
251
+ comment_start_line = location.start_line
252
+ comment_start_column = location.start_column
253
+ comment_end_line = location.end_line
254
+ comment_end_column = location.end_column
103
255
 
104
256
  left = 0
105
257
  right = candidates.length
@@ -151,54 +303,99 @@ module Psych
151
303
  # Extend the document stream to be able to attach comments to the
152
304
  # document.
153
305
  module DocumentStream
306
+ def start_document(version, tag_directives, implicit)
307
+ node = Nodes::Document.new(version, tag_directives, implicit)
308
+ set_start_location(node)
309
+ push(node)
310
+ end
311
+
154
312
  def end_document(implicit_end = !streaming?)
155
313
  @last.implicit_end = implicit_end
156
- @block.call(attach_comments(pop))
314
+ node = pop
315
+ set_end_location(node)
316
+ attach_comments(node)
317
+ @block.call(node)
157
318
  end
158
319
  end
159
320
 
160
321
  # Extend the nodes to be able to store comments.
161
322
  module Node
162
- def leading_comments
163
- @leading_comments ||= []
164
- end
165
-
166
323
  def leading_comment(comment)
167
- leading_comments << comment
324
+ comments.leading_comment(comment)
168
325
  end
169
326
 
170
- def trailing_comments
171
- @trailing_comments ||= []
327
+ def trailing_comment(comment)
328
+ comments.trailing_comment(comment)
172
329
  end
173
330
 
174
- def trailing_comment(comment)
175
- trailing_comments << comment
331
+ def comments
332
+ @comments ||= Comments.new
176
333
  end
177
334
 
178
335
  def comments?
179
- (defined?(@leading_comments) && @leading_comments.any?) ||
180
- (defined?(@trailing_comments) && @trailing_comments.any?)
336
+ defined?(@comments)
337
+ end
338
+
339
+ def to_ruby(symbolize_names: false, freeze: false, strict_integer: false, comments: false)
340
+ Visitors::ToRuby.create(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer, comments: comments).accept(self)
181
341
  end
182
342
  end
183
343
 
184
344
  # Extend the ToRuby visitor to be able to attach comments to the resulting
185
345
  # Ruby objects.
186
346
  module ToRuby
347
+ attr_reader :comments
348
+
349
+ def initialize(ss, class_loader, symbolize_names: false, freeze: false, comments: false)
350
+ super(ss, class_loader, symbolize_names: symbolize_names, freeze: freeze)
351
+ @comments = comments
352
+ @nodeless_objs = {}.compare_by_identity
353
+ @nodeless_keys = {}.compare_by_identity
354
+ end
355
+
187
356
  def accept(node)
188
357
  result = super
189
358
 
190
- if node.comments?
191
- result =
192
- YAMLCommentsDelegator.new(
193
- result,
194
- node.leading_comments,
195
- node.trailing_comments
196
- )
359
+ if @comments
360
+ case result
361
+ when LoadedObject, LoadedHash
362
+ # skip
363
+ when Hash
364
+ if !@nodeless_objs.key?(result)
365
+ nodeless_obj = {}
366
+ nodeless_key = {}
367
+
368
+ result.each do |key, value|
369
+ if key.is_a?(LoadedObject) || key.is_a?(LoadedHash)
370
+ nodeless_obj[key.__getobj__] = value
371
+ nodeless_key[key.__getobj__] = key
372
+ else
373
+ nodeless_obj[key] = value
374
+ end
375
+ end
376
+
377
+ @nodeless_objs[result] = nodeless_obj
378
+ @nodeless_keys[result] = nodeless_key
379
+ end
380
+
381
+ result = LoadedHash.new(@nodeless_objs.fetch(result), node, @nodeless_keys.fetch(result))
382
+ else
383
+ result = LoadedObject.new(result, node)
384
+ end
197
385
  end
198
386
 
199
387
  result
200
388
  end
201
389
  end
390
+
391
+ # Extend the ToRuby singleton to be able to pass the comments option.
392
+ module ToRubySingleton
393
+ def create(symbolize_names: false, freeze: false, strict_integer: false, comments: false)
394
+ class_loader = ClassLoader.new
395
+ scanner = ScalarScanner.new(class_loader, strict_integer: strict_integer)
396
+ new(scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze, comments: comments)
397
+ end
398
+ end
202
399
  end
203
400
 
204
401
  ::Psych::Handler.prepend(CommentExtensions::Handler)
@@ -206,68 +403,7 @@ module Psych
206
403
  ::Psych::Handlers::DocumentStream.prepend(CommentExtensions::DocumentStream)
207
404
  ::Psych::Nodes::Node.prepend(CommentExtensions::Node)
208
405
  ::Psych::Visitors::ToRuby.prepend(CommentExtensions::ToRuby)
209
-
210
- # An internal exception is an exception that should not have occurred. It is
211
- # effectively an assertion.
212
- class InternalException < Exception
213
- def initialize(message = "An internal exception occurred")
214
- super(message)
215
- end
216
- end
217
-
218
- # A source is wraps the input string and provides methods to access line and
219
- # column information from a byte offset.
220
- class Source
221
- def initialize(string)
222
- @line_offsets = []
223
-
224
- offset = 0
225
- string.each_line do |line|
226
- @line_offsets << offset
227
- offset += line.bytesize
228
- end
229
-
230
- @line_offsets << offset
231
- end
232
-
233
- def line(offset)
234
- index = @line_offsets.bsearch_index { |line_offset| line_offset > offset }
235
- return @line_offsets.size - 1 if index.nil?
236
- index - 1
237
- end
238
-
239
- def column(offset)
240
- offset - @line_offsets[line(offset)]
241
- end
242
- end
243
-
244
- # A location represents a range of bytes in the input string.
245
- class Location
246
- protected attr_reader :pos_end
247
-
248
- def initialize(source, pos_start, pos_end)
249
- @source = source
250
- @pos_start = pos_start
251
- @pos_end = pos_end
252
- end
253
-
254
- def join(other)
255
- @pos_end = other.pos_end
256
- end
257
-
258
- def to_a
259
- [
260
- @source.line(@pos_start),
261
- @source.column(@pos_start),
262
- @source.line(@pos_end),
263
- @source.column(@pos_end)
264
- ]
265
- end
266
-
267
- def self.point(source, pos)
268
- new(source, pos, pos)
269
- end
270
- end
406
+ ::Psych::Visitors::ToRuby.singleton_class.prepend(CommentExtensions::ToRubySingleton)
271
407
 
272
408
  # An alias event represents a reference to a previously defined anchor.
273
409
  class Alias
@@ -349,7 +485,7 @@ module Psych
349
485
  end
350
486
 
351
487
  def accept(handler)
352
- handler.event_location(*@location)
488
+ handler.event_location(*@location.trim)
353
489
  handler.end_mapping
354
490
  end
355
491
  end
@@ -369,7 +505,7 @@ module Psych
369
505
  end
370
506
 
371
507
  def accept(handler)
372
- handler.event_location(*@location)
508
+ handler.event_location(*@location.trim)
373
509
  handler.scalar(
374
510
  @value,
375
511
  @anchor,
@@ -409,7 +545,7 @@ module Psych
409
545
  end
410
546
 
411
547
  def accept(handler)
412
- handler.event_location(*@location)
548
+ handler.event_location(*@location.trim)
413
549
  handler.end_sequence
414
550
  end
415
551
  end
@@ -489,7 +625,7 @@ module Psych
489
625
 
490
626
  # This parser can optionally parse comments and attach them to the
491
627
  # resulting tree, if the option is passed.
492
- @comments = false
628
+ @comments = nil
493
629
  end
494
630
 
495
631
  # Top-level parse function that starts the parsing process.
@@ -512,10 +648,12 @@ module Psych
512
648
  @scanner = StringScanner.new(yaml)
513
649
  @filename = filename
514
650
  @source = Source.new(yaml)
515
- @comments = comments
651
+ @comments = {} if comments
516
652
 
517
653
  raise_syntax_error("Parser failed to complete") unless parse_l_yaml_stream
518
654
  raise_syntax_error("Parser finished before end of input") unless @scanner.eos?
655
+
656
+ @comments = nil if comments
519
657
  true
520
658
  end
521
659
 
@@ -621,10 +759,18 @@ module Psych
621
759
  # :section: Event handling
622
760
  # ------------------------------------------------------------------------
623
761
 
762
+ # Flush the comments into the handler once we get to a safe point.
763
+ def comments_flush
764
+ return unless @comments
765
+ @comments.each { |_, comment| @handler.comment(comment) }
766
+ @comments.clear
767
+ end
768
+
624
769
  # If there is a document end event, then flush it to the list of events
625
770
  # and reset back to the starting state to parse the next document.
626
771
  def document_end_event_flush
627
772
  if @document_end_event
773
+ comments_flush
628
774
  @document_end_event.accept(@handler)
629
775
  @document_start_event = DocumentStart.new(Location.point(@source, @scanner.pos))
630
776
  @tag_directives = @document_start_event.tag_directives
@@ -985,7 +1131,7 @@ module Psych
985
1131
  pos = @scanner.pos - 1
986
1132
  star { parse_nb_char }
987
1133
 
988
- @handler.comment(from(pos), *Location.new(@source, pos, @scanner.pos), inline) if @comments
1134
+ @comments[pos] ||= Comment.new(Location.new(@source, pos, @scanner.pos), from(pos), inline) if @comments
989
1135
  true
990
1136
  end
991
1137
 
@@ -2325,7 +2471,6 @@ module Psych
2325
2471
  } then
2326
2472
  @in_scalar = false
2327
2473
  lines = events_cache_pop
2328
- lines.pop if lines.length > 0 && lines.last.empty?
2329
2474
  value = lines.map { |line| "#{line}\n" }.join
2330
2475
 
2331
2476
  case t
@@ -2334,7 +2479,7 @@ module Psych
2334
2479
  when :strip
2335
2480
  value.sub!(/\n+\z/, "")
2336
2481
  when :keep
2337
- value.sub!(/\n(\n+)\z/) { $1 } if !value.match?(/\S/)
2482
+ # nothing
2338
2483
  else
2339
2484
  raise InternalException, t.inspect
2340
2485
  end
@@ -2353,6 +2498,8 @@ module Psych
2353
2498
  # l-empty(n,block-in)*
2354
2499
  # s-indent(n) nb-char+
2355
2500
  def parse_l_nb_literal_text(n)
2501
+ events_cache_size = @events_cache[-1].size
2502
+
2356
2503
  try do
2357
2504
  if star { parse_l_empty(n, :block_in) } && parse_s_indent(n)
2358
2505
  pos_start = @scanner.pos
@@ -2361,6 +2508,12 @@ module Psych
2361
2508
  events_push(from(pos_start))
2362
2509
  true
2363
2510
  end
2511
+ else
2512
+ # When parsing all of the l_empty calls, we may have added a bunch
2513
+ # of empty lines to the events cache. We need to clear those out
2514
+ # here.
2515
+ @events_cache[-1].slice!(events_cache_size..-1)
2516
+ false
2364
2517
  end
2365
2518
  end
2366
2519
  end
@@ -2926,12 +3079,12 @@ module Psych
2926
3079
  def parse_l_yaml_stream
2927
3080
  events_push_flush_properties(StreamStart.new(Location.point(@source, @scanner.pos)))
2928
3081
 
2929
- @document_start_event = DocumentStart.new(Location.point(@source, @scanner.pos))
2930
- @tag_directives = @document_start_event.tag_directives
2931
- @document_end_event = nil
2932
-
2933
3082
  if try {
2934
3083
  if parse_l_document_prefix
3084
+ @document_start_event = DocumentStart.new(Location.point(@source, @scanner.pos))
3085
+ @tag_directives = @document_start_event.tag_directives
3086
+ @document_end_event = nil
3087
+
2935
3088
  parse_l_any_document
2936
3089
  star do
2937
3090
  try do
@@ -2989,12 +3142,11 @@ module Psych
2989
3142
  # aliases, since we may find that we need to add an anchor after the
2990
3143
  # object has already been flushed.
2991
3144
  class Node
2992
- attr_reader :value, :leading_comments, :trailing_comments
3145
+ attr_reader :value, :psych_node
2993
3146
 
2994
- def initialize(value, leading_comments, trailing_comments)
3147
+ def initialize(value, psych_node)
2995
3148
  @value = value
2996
- @leading_comments = leading_comments
2997
- @trailing_comments = trailing_comments
3149
+ @psych_node = psych_node
2998
3150
  @anchor = nil
2999
3151
  end
3000
3152
 
@@ -3012,7 +3164,7 @@ module Psych
3012
3164
 
3013
3165
  # Represents an array of nodes.
3014
3166
  class ArrayNode < Node
3015
- attr_accessor :anchor
3167
+ attr_accessor :anchor, :tag
3016
3168
 
3017
3169
  def accept(visitor)
3018
3170
  visitor.visit_array(self)
@@ -3021,7 +3173,7 @@ module Psych
3021
3173
 
3022
3174
  # Represents a hash of nodes.
3023
3175
  class HashNode < Node
3024
- attr_accessor :anchor
3176
+ attr_accessor :anchor, :tag
3025
3177
 
3026
3178
  def accept(visitor)
3027
3179
  visitor.visit_hash(self)
@@ -3035,6 +3187,14 @@ module Psych
3035
3187
  # Represents a generic object that is not matched by any of the other node
3036
3188
  # types.
3037
3189
  class ObjectNode < Node
3190
+ # The explicit tag associated with the object.
3191
+ attr_accessor :tag
3192
+
3193
+ # Whether or not this object was modified after being loaded. In this
3194
+ # case we cannot rely on the source formatting, and need to instead
3195
+ # format the value ourselves.
3196
+ attr_accessor :dirty
3197
+
3038
3198
  def accept(visitor)
3039
3199
  visitor.visit_object(self)
3040
3200
  end
@@ -3060,6 +3220,8 @@ module Psych
3060
3220
 
3061
3221
  # Represents a string object.
3062
3222
  class StringNode < Node
3223
+ attr_accessor :tag
3224
+
3063
3225
  def accept(visitor)
3064
3226
  visitor.visit_string(self)
3065
3227
  end
@@ -3080,14 +3242,10 @@ module Psych
3080
3242
  # Visit an ArrayNode.
3081
3243
  def visit_array(node)
3082
3244
  with_comments(node) do |value|
3083
- if (anchor = node.anchor)
3084
- @q.text("&#{anchor} ")
3085
- end
3086
-
3087
- if value.empty?
3088
- @q.text("[]")
3245
+ if value.empty? || ((psych_node = node.psych_node).is_a?(Nodes::Sequence) && psych_node.style == Nodes::Sequence::FLOW)
3246
+ visit_array_contents_flow(node.anchor, node.tag, value)
3089
3247
  else
3090
- visit_array_contents(value)
3248
+ visit_array_contents_block(node.anchor, node.tag, value)
3091
3249
  end
3092
3250
  end
3093
3251
  end
@@ -3095,16 +3253,10 @@ module Psych
3095
3253
  # Visit a HashNode.
3096
3254
  def visit_hash(node)
3097
3255
  with_comments(node) do |value|
3098
- if (anchor = node.anchor)
3099
- @q.text("&#{anchor}")
3100
- end
3101
-
3102
- if value.empty?
3103
- @q.text(" ") if anchor
3104
- @q.text("{}")
3256
+ if value.empty? || ((psych_node = node.psych_node).is_a?(Nodes::Mapping) && psych_node.style == Nodes::Mapping::FLOW)
3257
+ visit_hash_contents_flow(node.anchor, node.tag, value)
3105
3258
  else
3106
- @q.breakable if anchor
3107
- visit_hash_contents(value)
3259
+ visit_hash_contents_block(node.anchor, node.tag, value)
3108
3260
  end
3109
3261
  end
3110
3262
  end
@@ -3112,50 +3264,76 @@ module Psych
3112
3264
  # Visit an ObjectNode.
3113
3265
  def visit_object(node)
3114
3266
  with_comments(node) do |value|
3115
- @q.text(Psych.dump(value, indentation: @q.indent)[/\A--- (.+)\n\z/m, 1]) # TODO
3267
+ if (tag = node.tag)
3268
+ @q.text("#{tag} ")
3269
+ end
3270
+
3271
+ if !node.dirty && (psych_node = node.psych_node)
3272
+ @q.text(psych_node.value)
3273
+ else
3274
+ @q.text(dump_object(value))
3275
+ end
3116
3276
  end
3117
3277
  end
3118
3278
 
3119
3279
  # Visit an OmapNode.
3120
3280
  def visit_omap(node)
3121
3281
  with_comments(node) do |value|
3122
- if (anchor = node.anchor)
3123
- @q.text("&#{anchor} ")
3124
- end
3125
-
3126
- @q.text("!!omap")
3127
- @q.breakable
3128
-
3129
- visit_array_contents(value)
3282
+ visit_array_contents_block(node.anchor, "!!omap", value)
3130
3283
  end
3131
3284
  end
3132
3285
 
3133
3286
  # Visit a SetNode.
3134
3287
  def visit_set(node)
3135
3288
  with_comments(node) do |value|
3136
- if (anchor = node.anchor)
3137
- @q.text("&#{anchor} ")
3138
- end
3139
-
3140
- @q.text("!set")
3141
- @q.breakable
3142
-
3143
- visit_hash_contents(node.value)
3289
+ visit_hash_contents_block(node.anchor, "!set", value)
3144
3290
  end
3145
3291
  end
3146
3292
 
3147
3293
  # Visit a StringNode.
3148
- alias visit_string visit_object
3294
+ def visit_string(node)
3295
+ with_comments(node) do |value|
3296
+ if (tag = node.tag)
3297
+ @q.text("#{tag} ")
3298
+ end
3299
+
3300
+ @q.text(dump_object(value))
3301
+ end
3302
+ end
3149
3303
 
3150
3304
  private
3151
3305
 
3306
+ # TODO: Certain objects require special formatting. Usually this
3307
+ # involves scanning the object itself and determining what kind of YAML
3308
+ # object it is, then dumping it back out. We rely on Psych itself to do
3309
+ # this formatting for us.
3310
+ #
3311
+ # Note this is the one place where we indirectly rely on libyaml,
3312
+ # because Psych delegates to libyaml to dump the object. This is less
3313
+ # than ideal, because it means in some circumstances we have an indirect
3314
+ # dependency. Ideally this would all be removed in favor of our own
3315
+ # formatting.
3316
+ def dump_object(value)
3317
+ Psych.dump(value, indentation: @q.indent)[/\A--- (.+?)(?:\n\.\.\.)?\n\z/m, 1]
3318
+ end
3319
+
3152
3320
  # Shortcut to visit a node by passing this visitor to the accept method.
3153
3321
  def visit(node)
3154
3322
  node.accept(self)
3155
3323
  end
3156
3324
 
3157
- # Visit the elements within an array.
3158
- def visit_array_contents(contents)
3325
+ # Visit the elements within an array in the block format.
3326
+ def visit_array_contents_block(anchor, tag, contents)
3327
+ if anchor
3328
+ @q.text("&#{anchor}")
3329
+ tag ? @q.text(" ") : @q.breakable
3330
+ end
3331
+
3332
+ if tag
3333
+ @q.text(tag)
3334
+ @q.breakable
3335
+ end
3336
+
3159
3337
  @q.seplist(contents, -> { @q.breakable }) do |element|
3160
3338
  @q.text("-")
3161
3339
  next if element.is_a?(NilNode)
@@ -3163,94 +3341,188 @@ module Psych
3163
3341
  @q.text(" ")
3164
3342
  @q.nest(2) { visit(element) }
3165
3343
  end
3344
+
3345
+ @q.current_group.break
3166
3346
  end
3167
3347
 
3168
- # Visit the key/value pairs within a hash.
3169
- def visit_hash_contents(contents)
3170
- @q.seplist(contents, -> { @q.breakable }) do |key, value|
3171
- inlined = false
3348
+ # Visit the elements within an array in the flow format.
3349
+ def visit_array_contents_flow(anchor, tag, contents)
3350
+ @q.group do
3351
+ @q.text("&#{anchor} ") if anchor
3352
+ @q.text("#{tag} ") if tag
3353
+ @q.text("[")
3172
3354
 
3173
- case key
3174
- when NilNode
3175
- @q.text("! ''")
3176
- when ArrayNode, HashNode, OmapNode, SetNode
3177
- if key.anchor.nil?
3178
- @q.text("? ")
3179
- @q.nest(2) { visit(key) }
3180
- @q.breakable
3181
- inlined = true
3182
- else
3183
- visit(key)
3355
+ unless contents.empty?
3356
+ @q.nest(2) do
3357
+ @q.breakable("")
3358
+ @q.seplist(contents, -> { @q.comma_breakable }) { |element| visit(element) }
3184
3359
  end
3185
- when AliasNode, ObjectNode
3360
+ @q.breakable("")
3361
+ end
3362
+
3363
+ @q.text("]")
3364
+ end
3365
+ end
3366
+
3367
+ # Visit a key value pair within a hash.
3368
+ def visit_hash_key_value(key, value)
3369
+ inlined = false
3370
+
3371
+ case key
3372
+ when NilNode
3373
+ @q.text("! ''")
3374
+ when ArrayNode, HashNode, OmapNode, SetNode
3375
+ if key.anchor.nil?
3376
+ @q.text("? ")
3377
+ @q.nest(2) { visit(key) }
3378
+ @q.breakable
3379
+ inlined = true
3380
+ else
3186
3381
  visit(key)
3187
- when StringNode
3188
- if key.value.include?("\n")
3189
- @q.text("? ")
3190
- visit(key)
3191
- @q.breakable
3192
- inlined = true
3193
- else
3194
- visit(key)
3195
- end
3196
3382
  end
3383
+ when AliasNode, ObjectNode
3384
+ visit(key)
3385
+ when StringNode
3386
+ if key.value.include?("\n")
3387
+ @q.text("? ")
3388
+ visit(key)
3389
+ @q.breakable
3390
+ inlined = true
3391
+ else
3392
+ visit(key)
3393
+ end
3394
+ else
3395
+ raise InternalException
3396
+ end
3197
3397
 
3198
- @q.text(":")
3398
+ @q.text(":")
3199
3399
 
3200
- case value
3201
- when NilNode
3202
- # skip
3203
- when OmapNode, SetNode
3400
+ case value
3401
+ when NilNode
3402
+ # skip
3403
+ when OmapNode, SetNode
3404
+ @q.text(" ")
3405
+ @q.nest(2) { visit(value) }
3406
+ when ArrayNode
3407
+ if ((psych_node = value.psych_node).is_a?(Nodes::Sequence) && psych_node.style == Nodes::Sequence::FLOW) || value.value.empty?
3408
+ @q.text(" ")
3409
+ visit(value)
3410
+ elsif inlined || value.anchor || value.tag || value.value.empty?
3204
3411
  @q.text(" ")
3205
3412
  @q.nest(2) { visit(value) }
3206
- when ArrayNode
3207
- if value.value.empty?
3208
- @q.text(" []")
3209
- elsif inlined || value.anchor
3210
- @q.text(" ")
3211
- @q.nest(2) { visit(value) }
3212
- else
3413
+ else
3414
+ @q.breakable
3415
+ visit(value)
3416
+ end
3417
+ when HashNode
3418
+ if ((psych_node = value.psych_node).is_a?(Nodes::Mapping) && psych_node.style == Nodes::Mapping::FLOW) || value.value.empty?
3419
+ @q.text(" ")
3420
+ visit(value)
3421
+ elsif inlined || value.anchor || value.tag
3422
+ @q.text(" ")
3423
+ @q.nest(2) { visit(value) }
3424
+ else
3425
+ @q.nest(2) do
3213
3426
  @q.breakable
3214
3427
  visit(value)
3215
3428
  end
3216
- when HashNode
3217
- if value.value.empty?
3218
- @q.text(" {}")
3219
- elsif inlined || value.anchor
3220
- @q.text(" ")
3221
- @q.nest(2) { visit(value) }
3222
- else
3223
- @q.nest(2) do
3224
- @q.breakable
3225
- visit(value)
3429
+ end
3430
+ when AliasNode, ObjectNode, StringNode
3431
+ @q.text(" ")
3432
+ @q.nest(2) { visit(value) }
3433
+ else
3434
+ raise InternalException
3435
+ end
3436
+ end
3437
+
3438
+ # Visit the key/value pairs within a hash in the block format.
3439
+ def visit_hash_contents_block(anchor, tag, children)
3440
+ if anchor
3441
+ @q.text("&#{anchor}")
3442
+ tag ? @q.text(" ") : @q.breakable
3443
+ end
3444
+
3445
+ if tag
3446
+ @q.text(tag)
3447
+ @q.breakable
3448
+ end
3449
+
3450
+ ((0...children.length) % 2).each do |index|
3451
+ @q.breakable if index != 0
3452
+ visit_hash_key_value(children[index], children[index + 1])
3453
+ end
3454
+
3455
+ @q.current_group.break
3456
+ end
3457
+
3458
+ # Visit the key/value pairs within a hash in the flow format.
3459
+ def visit_hash_contents_flow(anchor, tag, children)
3460
+ @q.group do
3461
+ @q.text("&#{anchor} ") if anchor
3462
+ @q.text("#{tag} ") if tag
3463
+ @q.text("{")
3464
+
3465
+ unless children.empty?
3466
+ @q.nest(2) do
3467
+ @q.breakable
3468
+
3469
+ ((0...children.length) % 2).each do |index|
3470
+ @q.comma_breakable if index != 0
3471
+ visit_hash_key_value(children[index], children[index + 1])
3226
3472
  end
3227
3473
  end
3228
- when AliasNode, ObjectNode, StringNode
3229
- @q.text(" ")
3230
- @q.nest(2) { visit(value) }
3474
+ @q.breakable
3231
3475
  end
3476
+
3477
+ @q.text("}")
3232
3478
  end
3233
3479
  end
3234
3480
 
3235
3481
  # Print out the leading and trailing comments of a node, as well as
3236
3482
  # yielding the value of the node to the block.
3237
3483
  def with_comments(node)
3238
- node.leading_comments.each do |comment|
3239
- @q.text(comment.value)
3484
+ if (comments = node.psych_node&.comments) && (leading = comments.leading).any?
3485
+ line = nil
3486
+
3487
+ leading.each do |comment|
3488
+ while line && line < comment.start_line
3489
+ @q.breakable
3490
+ line += 1
3491
+ end
3492
+
3493
+ @q.text(comment.value)
3494
+ line = comment.end_line
3495
+ end
3496
+
3240
3497
  @q.breakable
3241
3498
  end
3242
3499
 
3243
3500
  yield node.value
3244
3501
 
3245
- if (trailing_comments = node.trailing_comments).any?
3246
- if trailing_comments[0].inline?
3247
- inline_comment = trailing_comments.shift
3502
+ if comments && (trailing = comments.trailing).any?
3503
+ line = nil
3504
+ index = 0
3505
+
3506
+ if trailing[0].inline?
3507
+ inline_comment = trailing[0]
3508
+ index += 1
3509
+
3248
3510
  @q.trailer { @q.text(" "); @q.text(inline_comment.value) }
3511
+ line = inline_comment.end_line
3249
3512
  end
3250
3513
 
3251
- trailing_comments.each do |comment|
3252
- @q.breakable
3514
+ trailing[index..-1].each do |comment|
3515
+ if line.nil?
3516
+ @q.breakable
3517
+ else
3518
+ while line < comment.start_line
3519
+ @q.breakable
3520
+ line += 1
3521
+ end
3522
+ end
3523
+
3253
3524
  @q.text(comment.value)
3525
+ line = comment.end_line
3254
3526
  end
3255
3527
  end
3256
3528
  end
@@ -3294,14 +3566,42 @@ module Psych
3294
3566
  # This is the main entrypoint into this object. It is responsible for
3295
3567
  # pushing a new object onto the emitter, which is then represented as a
3296
3568
  # YAML document.
3297
- def <<(object)
3569
+ def emit(object)
3298
3570
  if @started
3299
- @io << "...\n---"
3571
+ @io << "...\n"
3300
3572
  else
3301
- @io << "---"
3302
3573
  @started = true
3303
3574
  end
3304
3575
 
3576
+ # Very rare circumstance here that there are leading comments attached
3577
+ # to the root object of a document that occur before the --- marker. In
3578
+ # this case we want to output them first here, then dump the object.
3579
+ if (object.is_a?(LoadedObject) || object.is_a?(LoadedHash)) && (psych_node = object.psych_node).comments? && (leading = psych_node.comments.leading).any?
3580
+ leading = [*leading]
3581
+ line = psych_node.start_line - 1
3582
+
3583
+ while leading.any? && leading.last.start_line == line
3584
+ leading.pop
3585
+ line -= 1
3586
+ end
3587
+
3588
+ psych_node.comments.leading.slice!(0, leading.length)
3589
+ line = nil
3590
+
3591
+ leading.each do |comment|
3592
+ if line && (line < comment.start_line)
3593
+ @io << "\n" * (comment.start_line - line - 1)
3594
+ end
3595
+
3596
+ @io << comment.value
3597
+ @io << "\n"
3598
+
3599
+ line = comment.start_line
3600
+ end
3601
+ end
3602
+
3603
+ @io << "---"
3604
+
3305
3605
  if (node = dump(object)).is_a?(NilNode)
3306
3606
  @io << "\n"
3307
3607
  else
@@ -3324,60 +3624,74 @@ module Psych
3324
3624
 
3325
3625
  private
3326
3626
 
3327
- # Walk through the given object and convert it into a tree of nodes.
3328
- def dump(base_object)
3329
- object = base_object
3330
- leading_comments = []
3331
- trailing_comments = []
3332
-
3333
- if base_object.is_a?(YAMLCommentsDelegator)
3334
- object = base_object.__getobj__
3335
- leading_comments.concat(base_object.yaml_leading_comments)
3336
- trailing_comments.concat(base_object.yaml_trailing_comments)
3627
+ # Dump the tag value for a given node.
3628
+ def dump_tag(value)
3629
+ case value
3630
+ when nil, "tag:yaml.org,2002:binary"
3631
+ nil
3632
+ when /\Atag:yaml.org,2002:(.+)\z/
3633
+ "!!#{$1}"
3634
+ else
3635
+ value
3337
3636
  end
3637
+ end
3338
3638
 
3339
- if object.nil?
3340
- NilNode.new(object, leading_comments, trailing_comments)
3341
- elsif @object_nodes.key?(object)
3342
- AliasNode.new(
3343
- @object_nodes[object].anchor = (@object_anchors[object] ||= (@object_anchor += 1)),
3344
- leading_comments,
3345
- trailing_comments
3346
- )
3639
+ # Walk through the given object and convert it into a tree of nodes.
3640
+ def dump(base_object)
3641
+ if base_object.nil?
3642
+ NilNode.new(nil, nil)
3347
3643
  else
3348
- case object
3349
- when Psych::Omap
3350
- @object_nodes[object] =
3351
- OmapNode.new(
3352
- object.map { |(key, value)| HashNode.new({ dump(key) => dump(value) }, [], []) },
3353
- leading_comments,
3354
- trailing_comments
3355
- )
3356
- when Psych::Set
3357
- @object_nodes[object] =
3358
- SetNode.new(
3359
- object.to_h { |key, value| [dump(key), dump(value)] },
3360
- leading_comments,
3361
- trailing_comments
3362
- )
3363
- when Array
3364
- @object_nodes[object] =
3365
- ArrayNode.new(
3366
- object.map { |element| dump(element) },
3367
- leading_comments,
3368
- trailing_comments
3369
- )
3370
- when Hash
3371
- @object_nodes[object] =
3372
- HashNode.new(
3373
- object.to_h { |key, value| [dump(key), dump(value)] },
3374
- leading_comments,
3375
- trailing_comments
3376
- )
3377
- when String
3378
- StringNode.new(object, leading_comments, trailing_comments)
3644
+ object = base_object
3645
+ psych_node = nil
3646
+ dirty = false
3647
+
3648
+ if base_object.is_a?(LoadedObject)
3649
+ object = base_object.__getobj__
3650
+ psych_node = base_object.psych_node
3651
+ dirty = base_object.dirty
3652
+ elsif base_object.is_a?(LoadedHash)
3653
+ object = base_object.__getobj__
3654
+ psych_node = base_object.psych_node
3655
+ end
3656
+
3657
+ if @object_nodes.key?(object)
3658
+ AliasNode.new(@object_nodes[object].anchor = (@object_anchors[object] ||= (@object_anchor += 1)), nil)
3379
3659
  else
3380
- ObjectNode.new(object, leading_comments, trailing_comments)
3660
+ case object
3661
+ when Psych::Omap
3662
+ @object_nodes[object] = OmapNode.new(object.map { |(key, value)| HashNode.new([dump(key), dump(value)], nil) }, psych_node)
3663
+ when Psych::Set
3664
+ @object_nodes[object] = SetNode.new(object.flat_map { |key, value| [dump(key), dump(value)] }, psych_node)
3665
+ when Array
3666
+ dumped = ArrayNode.new(object.map { |element| dump(element) }, psych_node)
3667
+ dumped.tag = dump_tag(psych_node&.tag)
3668
+
3669
+ @object_nodes[object] = dumped
3670
+ when Hash
3671
+ contents =
3672
+ if base_object.is_a?(LoadedHash)
3673
+ psych_node_keys = base_object.psych_node_keys
3674
+ object.flat_map { |key, value| [dump(psych_node_keys.fetch(key, key)), dump(value)] }
3675
+ else
3676
+ object.flat_map { |key, value| [dump(key), dump(value)] }
3677
+ end
3678
+
3679
+ dumped = HashNode.new(contents, psych_node)
3680
+ dumped.tag = dump_tag(psych_node&.tag)
3681
+
3682
+ @object_nodes[object] = dumped
3683
+ when String
3684
+ dumped = StringNode.new(object, psych_node)
3685
+ dumped.tag = dump_tag(psych_node&.tag)
3686
+
3687
+ dumped
3688
+ else
3689
+ dumped = ObjectNode.new(object, psych_node)
3690
+ dumped.tag = dump_tag(psych_node&.tag)
3691
+ dumped.dirty = dirty
3692
+
3693
+ dumped
3694
+ end
3381
3695
  end
3382
3696
  end
3383
3697
  end
@@ -3417,7 +3731,13 @@ module Psych
3417
3731
  private
3418
3732
 
3419
3733
  # Dump the given object, ensuring that it is a permitted object.
3420
- def dump(object)
3734
+ def dump(base_object)
3735
+ object = base_object
3736
+
3737
+ if base_object.is_a?(LoadedObject) || base_object.is_a?(LoadedHash)
3738
+ object = base_object.__getobj__
3739
+ end
3740
+
3421
3741
  if !@aliases && @object_nodes.key?(object)
3422
3742
  raise BadAlias, "Tried to dump an aliased object"
3423
3743
  end
@@ -3435,7 +3755,7 @@ module Psych
3435
3755
  end
3436
3756
 
3437
3757
  # --------------------------------------------------------------------------
3438
- # :section: Public API specific to Psych::Pure
3758
+ # :section: Public API mirroring Psych
3439
3759
  # --------------------------------------------------------------------------
3440
3760
 
3441
3761
  # Create a new default parser.
@@ -3443,6 +3763,22 @@ module Psych
3443
3763
  Pure::Parser.new(TreeBuilder.new)
3444
3764
  end
3445
3765
 
3766
+ def self.parse(yaml, filename: nil, comments: false)
3767
+ parse_stream(yaml, filename: filename, comments: comments) do |node|
3768
+ return node
3769
+ end
3770
+
3771
+ false
3772
+ end
3773
+
3774
+ def self.parse_file(filename, fallback: false, comments: false)
3775
+ result = File.open(filename, "r:bom|utf-8") do |f|
3776
+ parse(f, filename: filename, comments: comments)
3777
+ end
3778
+
3779
+ result || fallback
3780
+ end
3781
+
3446
3782
  # Parse a YAML stream and return the root node.
3447
3783
  def self.parse_stream(yaml, filename: nil, comments: false, &block)
3448
3784
  if block_given?
@@ -3455,6 +3791,76 @@ module Psych
3455
3791
  end
3456
3792
  end
3457
3793
 
3794
+ def self.unsafe_load(yaml, filename: nil, fallback: false, symbolize_names: false, freeze: false, strict_integer: false, comments: false)
3795
+ result = parse(yaml, filename: filename, comments: comments)
3796
+ return fallback unless result
3797
+
3798
+ result.to_ruby(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer, comments: comments)
3799
+ end
3800
+
3801
+ def self.safe_load(yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false, comments: false)
3802
+ result = parse(yaml, filename: filename, comments: comments)
3803
+ return fallback unless result
3804
+
3805
+ class_loader = ClassLoader::Restricted.new(permitted_classes.map(&:to_s), permitted_symbols.map(&:to_s))
3806
+ scanner = ScalarScanner.new(class_loader, strict_integer: strict_integer)
3807
+ visitor =
3808
+ if aliases
3809
+ Visitors::ToRuby.new(scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze, comments: comments)
3810
+ else
3811
+ Visitors::NoAliasRuby.new(scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze, comments: comments)
3812
+ end
3813
+
3814
+ visitor.accept(result)
3815
+ end
3816
+
3817
+ def self.load(yaml, permitted_classes: [Symbol], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false, comments: false)
3818
+ safe_load(
3819
+ yaml,
3820
+ permitted_classes: permitted_classes,
3821
+ permitted_symbols: permitted_symbols,
3822
+ aliases: aliases,
3823
+ filename: filename,
3824
+ fallback: fallback,
3825
+ symbolize_names: symbolize_names,
3826
+ freeze: freeze,
3827
+ strict_integer: strict_integer,
3828
+ comments: comments
3829
+ )
3830
+ end
3831
+
3832
+ def self.load_stream(yaml, filename: nil, fallback: [], comments: false, **kwargs)
3833
+ result =
3834
+ if block_given?
3835
+ parse_stream(yaml, filename: filename, comments: comments) do |node|
3836
+ yield node.to_ruby(**kwargs)
3837
+ end
3838
+ else
3839
+ parse_stream(yaml, filename: filename, comments: comments).children.map { |node| node.to_ruby(**kwargs) }
3840
+ end
3841
+
3842
+ return fallback if result.is_a?(Array) && result.empty?
3843
+ result
3844
+ end
3845
+
3846
+ def self.unsafe_load_file(filename, **kwargs)
3847
+ File.open(filename, "r:bom|utf-8") do |f|
3848
+ self.unsafe_load(f, filename: filename, **kwargs)
3849
+ end
3850
+ end
3851
+
3852
+ def self.safe_load_file(filename, **kwargs)
3853
+ File.open(filename, "r:bom|utf-8") do |f|
3854
+ self.safe_load(f, filename: filename, **kwargs)
3855
+ end
3856
+ end
3857
+
3858
+ def self.load_file(filename, **kwargs)
3859
+ File.open(filename, "r:bom|utf-8") do |f|
3860
+ self.load(f, filename: filename, **kwargs)
3861
+ end
3862
+ end
3863
+
3458
3864
  # Dump an object to a YAML string.
3459
3865
  def self.dump(o, io = nil, options = {})
3460
3866
  if Hash === io
@@ -3464,7 +3870,7 @@ module Psych
3464
3870
 
3465
3871
  real_io = io || StringIO.new
3466
3872
  emitter = Emitter.new(real_io, options)
3467
- emitter << o
3873
+ emitter.emit(o)
3468
3874
  io || real_io.string
3469
3875
  end
3470
3876
 
@@ -3478,7 +3884,7 @@ module Psych
3478
3884
 
3479
3885
  real_io = io || StringIO.new
3480
3886
  emitter = SafeEmitter.new(real_io, options)
3481
- emitter << o
3887
+ emitter.emit(o)
3482
3888
  io || real_io.string
3483
3889
  end
3484
3890
 
@@ -3486,92 +3892,8 @@ module Psych
3486
3892
  def self.dump_stream(*objects)
3487
3893
  real_io = io || StringIO.new
3488
3894
  emitter = Emitter.new(real_io, {})
3489
- objects.each { |object| emitter << object }
3895
+ objects.each { |object| emitter.emit(object) }
3490
3896
  io || real_io.string
3491
3897
  end
3492
-
3493
- # --------------------------------------------------------------------------
3494
- # :section: Public API copied directly from Psych
3495
- # --------------------------------------------------------------------------
3496
-
3497
- def self.unsafe_load yaml, filename: nil, fallback: false, symbolize_names: false, freeze: false, strict_integer: false, comments: false
3498
- result = parse(yaml, filename: filename, comments: comments)
3499
- return fallback unless result
3500
- result.to_ruby(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer)
3501
- end
3502
-
3503
- def self.safe_load yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false, comments: false
3504
- result = parse(yaml, filename: filename, comments: comments)
3505
- return fallback unless result
3506
-
3507
- class_loader = ClassLoader::Restricted.new(permitted_classes.map(&:to_s),
3508
- permitted_symbols.map(&:to_s))
3509
- scanner = ScalarScanner.new class_loader, strict_integer: strict_integer
3510
- visitor = if aliases
3511
- Visitors::ToRuby.new scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze
3512
- else
3513
- Visitors::NoAliasRuby.new scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze
3514
- end
3515
- result = visitor.accept result
3516
- result
3517
- end
3518
-
3519
- def self.load yaml, permitted_classes: [Symbol], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false, comments: false
3520
- safe_load yaml, permitted_classes: permitted_classes,
3521
- permitted_symbols: permitted_symbols,
3522
- aliases: aliases,
3523
- filename: filename,
3524
- fallback: fallback,
3525
- symbolize_names: symbolize_names,
3526
- freeze: freeze,
3527
- strict_integer: strict_integer,
3528
- comments: comments
3529
- end
3530
-
3531
- def self.parse yaml, filename: nil, comments: false
3532
- parse_stream(yaml, filename: filename, comments: comments) do |node|
3533
- return node
3534
- end
3535
-
3536
- false
3537
- end
3538
-
3539
- def self.parse_file filename, fallback: false, comments: false
3540
- result = File.open filename, 'r:bom|utf-8' do |f|
3541
- parse f, filename: filename, comments: comments
3542
- end
3543
- result || fallback
3544
- end
3545
-
3546
- def self.load_stream yaml, filename: nil, fallback: [], comments: false, **kwargs
3547
- result = if block_given?
3548
- parse_stream(yaml, filename: filename, comments: comments) do |node|
3549
- yield node.to_ruby(**kwargs)
3550
- end
3551
- else
3552
- parse_stream(yaml, filename: filename, comments: comments).children.map { |node| node.to_ruby(**kwargs) }
3553
- end
3554
-
3555
- return fallback if result.is_a?(Array) && result.empty?
3556
- result
3557
- end
3558
-
3559
- def self.unsafe_load_file filename, **kwargs
3560
- File.open(filename, 'r:bom|utf-8') { |f|
3561
- self.unsafe_load f, filename: filename, **kwargs
3562
- }
3563
- end
3564
-
3565
- def self.safe_load_file filename, **kwargs
3566
- File.open(filename, 'r:bom|utf-8') { |f|
3567
- self.safe_load f, filename: filename, **kwargs
3568
- }
3569
- end
3570
-
3571
- def self.load_file filename, **kwargs
3572
- File.open(filename, 'r:bom|utf-8') { |f|
3573
- self.load f, filename: filename, **kwargs
3574
- }
3575
- end
3576
3898
  end
3577
3899
  end