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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -2
- data/README.md +1 -1
- data/lib/psych/pure/version.rb +1 -1
- data/lib/psych/pure.rb +676 -354
- data/psych-pure.gemspec +1 -0
- metadata +22 -4
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 :
|
103
|
+
attr_reader :location, :value
|
15
104
|
|
16
|
-
def initialize(
|
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
|
31
|
-
|
32
|
-
|
33
|
-
attr_reader :
|
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
|
-
|
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
|
-
@
|
38
|
-
@
|
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
|
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
|
59
|
-
comments <<
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
-
|
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
|
-
|
324
|
+
comments.leading_comment(comment)
|
168
325
|
end
|
169
326
|
|
170
|
-
def
|
171
|
-
|
327
|
+
def trailing_comment(comment)
|
328
|
+
comments.trailing_comment(comment)
|
172
329
|
end
|
173
330
|
|
174
|
-
def
|
175
|
-
|
331
|
+
def comments
|
332
|
+
@comments ||= Comments.new
|
176
333
|
end
|
177
334
|
|
178
335
|
def comments?
|
179
|
-
|
180
|
-
|
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
|
191
|
-
result
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
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 =
|
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
|
-
@
|
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
|
-
|
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, :
|
3145
|
+
attr_reader :value, :psych_node
|
2993
3146
|
|
2994
|
-
def initialize(value,
|
3147
|
+
def initialize(value, psych_node)
|
2995
3148
|
@value = value
|
2996
|
-
@
|
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 (
|
3084
|
-
|
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
|
-
|
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 (
|
3099
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
3169
|
-
def
|
3170
|
-
@q.
|
3171
|
-
|
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
|
-
|
3174
|
-
|
3175
|
-
|
3176
|
-
|
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
|
-
|
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
|
-
|
3398
|
+
@q.text(":")
|
3199
3399
|
|
3200
|
-
|
3201
|
-
|
3202
|
-
|
3203
|
-
|
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
|
-
|
3207
|
-
|
3208
|
-
|
3209
|
-
|
3210
|
-
|
3211
|
-
|
3212
|
-
|
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
|
-
|
3217
|
-
|
3218
|
-
|
3219
|
-
|
3220
|
-
|
3221
|
-
|
3222
|
-
|
3223
|
-
|
3224
|
-
|
3225
|
-
|
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
|
-
|
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.
|
3239
|
-
|
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 (
|
3246
|
-
|
3247
|
-
|
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
|
-
|
3252
|
-
|
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
|
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
|
-
#
|
3328
|
-
def
|
3329
|
-
|
3330
|
-
|
3331
|
-
|
3332
|
-
|
3333
|
-
|
3334
|
-
|
3335
|
-
|
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
|
-
|
3340
|
-
|
3341
|
-
|
3342
|
-
|
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
|
-
|
3349
|
-
|
3350
|
-
|
3351
|
-
|
3352
|
-
|
3353
|
-
|
3354
|
-
|
3355
|
-
|
3356
|
-
|
3357
|
-
|
3358
|
-
|
3359
|
-
|
3360
|
-
|
3361
|
-
|
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
|
-
|
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(
|
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
|
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
|
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
|
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
|
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
|