psych-pure 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8599351cebdf6abf0c3438762b7f56a0110fafda0a4c857618305085425805d9
4
- data.tar.gz: c9975fe7570058d7aae9a64a46601e8720d46679dfa538290ff45fecf3b3842e
3
+ metadata.gz: d126f1c02cd1f7eba6b74a19f76dc938a6c828dc8dfea96a5663b3b4c8ac5bac
4
+ data.tar.gz: 6df58931a63090a6248a44d94986057fa60faa5c6ec2015b832da7a4d1fefe7a
5
5
  SHA512:
6
- metadata.gz: c964ce5739215506401fd0c7eca965932a54adb4f3c6e5d56f89a5bafe00d98b71b7fe9f8621adc8e5eb403a23a2b7813cee2bf5d88006929ff4d47c5ed5500d
7
- data.tar.gz: 55ead208f357b2f52267f505610815fc248ca8e8fbbc248d57ad8a6c7c40d56f7fadea28f7e1a13bbef18d47fe09783076c1d5ed73c87c4f2a36a50c5541bdc3
6
+ metadata.gz: 00f44a5e4889ac4674d844f5dbfbff8a39fb1126f40b7135a33165539a3e6d27d5d5303b0289259471ba3d8c3dfd7e58e75f2fa8c5339258239a89b58e314c2d
7
+ data.tar.gz: feaacc73c47c767edf1f454de1ecf4db4c812557f10578d88f715508b1975c524edc9acf5c21b40f77c40e1fc9eb25e7e79ee6496d8322cfb49e0030d457be3c
data/CHANGELOG.md CHANGED
@@ -6,11 +6,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- ## [0.1.0] - 2020-10-08
9
+ ## [0.1.1] - 2025-02-13
10
+
11
+ - Fix up comment handling to preserve within hashes.
12
+ - Do not duplicate comments when the parser backtracks.
13
+ - Trim locations of sequences and mappings before trailing comments.
14
+
15
+ ## [0.1.0] - 2025-02-12
10
16
 
11
17
  ### Added
12
18
 
13
19
  - 🎉 Initial release. 🎉
14
20
 
15
- [unreleased]: https://github.com/kddnewton/psych-pure/compare/v0.1.0...HEAD
21
+ [unreleased]: https://github.com/kddnewton/psych-pure/compare/v0.1.1...HEAD
22
+ [0.1.1]: https://github.com/kddnewton/psych-pure/compare/v0.1.0...v0.1.1
16
23
  [0.1.0]: https://github.com/kddnewton/psych-pure/compare/24de62...v0.1.0
data/README.md CHANGED
@@ -41,7 +41,7 @@ Or install it yourself as:
41
41
 
42
42
  All of the various `parse` APIs come with the additional `comments:` keyword option. This option tells the parser to parse out comments and attach them to the resulting tree. Nodes in the tree are then responsible for maintaining their own leading and trailing comments.
43
43
 
44
- All of the various `load` APIs also come with the additional `comments:` keyword option. This also gets fed into the parser. Because `load` is responsible for loading Ruby objects, the comments are then attached to the loaded objects via `Psych::Pure::YAMLCommentsDelegator`, which is a `SimpleDelegator` that wraps the objects and stores the leading and trailing comments. Those objects are then taken into account in the various `dump` APIs to dump out the comments as well. For example:
44
+ All of the various `load` APIs also come with the additional `comments:` keyword option. This also gets fed into the parser. Because `load` is responsible for loading Ruby objects, the comments are then attached to the loaded objects via delegators that wraps the objects and stores the leading and trailing comments. Those objects are then taken into account in the various `dump` APIs to dump out the comments as well. For example:
45
45
 
46
46
  ```ruby
47
47
  result = Psych::Pure.load("- a # comment1\n- c # comment2\n", comments: true)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Psych
4
4
  module Pure
5
- VERSION = "0.1.0"
5
+ VERSION = "0.1.1"
6
6
  end
7
7
  end
data/lib/psych/pure.rb CHANGED
@@ -9,33 +9,185 @@ 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
28
148
  end
29
149
 
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
150
+ # Wraps a Ruby object with its comments from the source input.
151
+ class CommentsObject < SimpleDelegator
152
+ attr_reader :psych_comments
34
153
 
35
- def initialize(object, yaml_leading_comments, yaml_trailing_comments)
154
+ def initialize(object, psych_comments)
155
+ @psych_comments = psych_comments
36
156
  super(object)
37
- @yaml_leading_comments = yaml_leading_comments
38
- @yaml_trailing_comments = yaml_trailing_comments
157
+ end
158
+ end
159
+
160
+ # Wraps a Ruby hash with its comments from the source input.
161
+ class CommentsHash < SimpleDelegator
162
+ attr_reader :psych_comments, :psych_key_comments
163
+
164
+ def initialize(object, psych_comments, psych_key_comments = {})
165
+ @psych_comments = psych_comments
166
+ @psych_key_comments = psych_key_comments
167
+ commentless = {}
168
+
169
+ object.each do |key, value|
170
+ if key.is_a?(CommentsObject)
171
+ @psych_key_comments[key.__getobj__] = key.psych_comments
172
+ commentless[key.__getobj__] = value
173
+ else
174
+ commentless[key] = value
175
+ end
176
+ end
177
+
178
+ super(commentless)
179
+ end
180
+
181
+ def []=(key, value)
182
+ if (previous = self[key])
183
+ if previous.is_a?(CommentsObject)
184
+ value = CommentsObject.new(value, previous.psych_comments)
185
+ elsif previous.is_a?(CommentsHash)
186
+ value = CommentsHash.new(value, previous.psych_comments, previous.psych_key_comments)
187
+ end
188
+ end
189
+
190
+ super(key, value)
39
191
  end
40
192
  end
41
193
 
@@ -45,7 +197,7 @@ module Psych
45
197
  # Extend the Handler to be able to handle comments coming out of the
46
198
  # parser.
47
199
  module Handler
48
- def comment(value, start_line, start_column, end_line, end_column, inline)
200
+ def comment(value)
49
201
  end
50
202
  end
51
203
 
@@ -55,8 +207,8 @@ module Psych
55
207
  @comments ||= []
56
208
  end
57
209
 
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)
210
+ def comment(value)
211
+ comments << value
60
212
  end
61
213
 
62
214
  def end_stream
@@ -96,10 +248,11 @@ module Psych
96
248
  preceding = nil
97
249
  following = nil
98
250
 
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
251
+ location = comment.location
252
+ comment_start_line = location.start_line
253
+ comment_start_column = location.start_column
254
+ comment_end_line = location.end_line
255
+ comment_end_column = location.end_column
103
256
 
104
257
  left = 0
105
258
  right = candidates.length
@@ -159,46 +312,60 @@ module Psych
159
312
 
160
313
  # Extend the nodes to be able to store comments.
161
314
  module Node
162
- def leading_comments
163
- @leading_comments ||= []
164
- end
165
-
166
315
  def leading_comment(comment)
167
- leading_comments << comment
316
+ comments.leading_comment(comment)
168
317
  end
169
318
 
170
- def trailing_comments
171
- @trailing_comments ||= []
319
+ def trailing_comment(comment)
320
+ comments.trailing_comment(comment)
172
321
  end
173
322
 
174
- def trailing_comment(comment)
175
- trailing_comments << comment
323
+ def comments
324
+ @comments ||= Comments.new
176
325
  end
177
326
 
178
327
  def comments?
179
- (defined?(@leading_comments) && @leading_comments.any?) ||
180
- (defined?(@trailing_comments) && @trailing_comments.any?)
328
+ defined?(@comments)
329
+ end
330
+
331
+ def to_ruby(symbolize_names: false, freeze: false, strict_integer: false, comments: false)
332
+ Visitors::ToRuby.create(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer, comments: comments).accept(self)
181
333
  end
182
334
  end
183
335
 
184
336
  # Extend the ToRuby visitor to be able to attach comments to the resulting
185
337
  # Ruby objects.
186
338
  module ToRuby
339
+ attr_reader :comments
340
+
341
+ def initialize(ss, class_loader, symbolize_names: false, freeze: false, comments: false)
342
+ super(ss, class_loader, symbolize_names: symbolize_names, freeze: freeze)
343
+ @comments = comments
344
+ end
345
+
187
346
  def accept(node)
188
347
  result = super
189
348
 
190
- if node.comments?
191
- result =
192
- YAMLCommentsDelegator.new(
193
- result,
194
- node.leading_comments,
195
- node.trailing_comments
196
- )
349
+ if @comments
350
+ if result.is_a?(Hash)
351
+ result = CommentsHash.new(result, node.comments? ? node.comments : nil)
352
+ elsif node.comments?
353
+ result = CommentsObject.new(result, node.comments)
354
+ end
197
355
  end
198
356
 
199
357
  result
200
358
  end
201
359
  end
360
+
361
+ # Extend the ToRuby singleton to be able to pass the comments option.
362
+ module ToRubySingleton
363
+ def create(symbolize_names: false, freeze: false, strict_integer: false, comments: false)
364
+ class_loader = ClassLoader.new
365
+ scanner = ScalarScanner.new(class_loader, strict_integer: strict_integer)
366
+ new(scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze, comments: comments)
367
+ end
368
+ end
202
369
  end
203
370
 
204
371
  ::Psych::Handler.prepend(CommentExtensions::Handler)
@@ -206,68 +373,7 @@ module Psych
206
373
  ::Psych::Handlers::DocumentStream.prepend(CommentExtensions::DocumentStream)
207
374
  ::Psych::Nodes::Node.prepend(CommentExtensions::Node)
208
375
  ::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
376
+ ::Psych::Visitors::ToRuby.singleton_class.prepend(CommentExtensions::ToRubySingleton)
271
377
 
272
378
  # An alias event represents a reference to a previously defined anchor.
273
379
  class Alias
@@ -349,7 +455,7 @@ module Psych
349
455
  end
350
456
 
351
457
  def accept(handler)
352
- handler.event_location(*@location)
458
+ handler.event_location(*@location.trim)
353
459
  handler.end_mapping
354
460
  end
355
461
  end
@@ -409,7 +515,7 @@ module Psych
409
515
  end
410
516
 
411
517
  def accept(handler)
412
- handler.event_location(*@location)
518
+ handler.event_location(*@location.trim)
413
519
  handler.end_sequence
414
520
  end
415
521
  end
@@ -489,7 +595,7 @@ module Psych
489
595
 
490
596
  # This parser can optionally parse comments and attach them to the
491
597
  # resulting tree, if the option is passed.
492
- @comments = false
598
+ @comments = nil
493
599
  end
494
600
 
495
601
  # Top-level parse function that starts the parsing process.
@@ -512,10 +618,12 @@ module Psych
512
618
  @scanner = StringScanner.new(yaml)
513
619
  @filename = filename
514
620
  @source = Source.new(yaml)
515
- @comments = comments
621
+ @comments = {} if comments
516
622
 
517
623
  raise_syntax_error("Parser failed to complete") unless parse_l_yaml_stream
518
624
  raise_syntax_error("Parser finished before end of input") unless @scanner.eos?
625
+
626
+ @comments = nil if comments
519
627
  true
520
628
  end
521
629
 
@@ -621,10 +729,18 @@ module Psych
621
729
  # :section: Event handling
622
730
  # ------------------------------------------------------------------------
623
731
 
732
+ # Flush the comments into the handler once we get to a safe point.
733
+ def comments_flush
734
+ return unless @comments
735
+ @comments.each { |_, comment| @handler.comment(comment) }
736
+ @comments.clear
737
+ end
738
+
624
739
  # If there is a document end event, then flush it to the list of events
625
740
  # and reset back to the starting state to parse the next document.
626
741
  def document_end_event_flush
627
742
  if @document_end_event
743
+ comments_flush
628
744
  @document_end_event.accept(@handler)
629
745
  @document_start_event = DocumentStart.new(Location.point(@source, @scanner.pos))
630
746
  @tag_directives = @document_start_event.tag_directives
@@ -985,7 +1101,7 @@ module Psych
985
1101
  pos = @scanner.pos - 1
986
1102
  star { parse_nb_char }
987
1103
 
988
- @handler.comment(from(pos), *Location.new(@source, pos, @scanner.pos), inline) if @comments
1104
+ @comments[pos] ||= Comment.new(Location.new(@source, pos, @scanner.pos), from(pos), inline) if @comments
989
1105
  true
990
1106
  end
991
1107
 
@@ -2623,7 +2739,7 @@ module Psych
2623
2739
 
2624
2740
  if try { plus { try { parse_s_indent(n + m) && parse_ns_l_block_map_entry(n + m) } } }
2625
2741
  events_cache_flush
2626
- events_push_flush_properties(MappingEnd.new(Location.point(@source, @scanner.pos)))
2742
+ events_push_flush_properties(MappingEnd.new(Location.point(@source, @scanner.pos))) # TODO
2627
2743
  true
2628
2744
  else
2629
2745
  events_cache_pop
@@ -2739,7 +2855,7 @@ module Psych
2739
2855
  star { try { parse_s_indent(n) && parse_ns_l_block_map_entry(n) } }
2740
2856
  } then
2741
2857
  events_cache_flush
2742
- events_push_flush_properties(MappingEnd.new(Location.point(@source, @scanner.pos)))
2858
+ events_push_flush_properties(MappingEnd.new(Location.point(@source, @scanner.pos))) # TODO
2743
2859
  true
2744
2860
  else
2745
2861
  events_cache_pop
@@ -2989,12 +3105,11 @@ module Psych
2989
3105
  # aliases, since we may find that we need to add an anchor after the
2990
3106
  # object has already been flushed.
2991
3107
  class Node
2992
- attr_reader :value, :leading_comments, :trailing_comments
3108
+ attr_reader :value, :comments
2993
3109
 
2994
- def initialize(value, leading_comments, trailing_comments)
3110
+ def initialize(value, comments)
2995
3111
  @value = value
2996
- @leading_comments = leading_comments
2997
- @trailing_comments = trailing_comments
3112
+ @comments = comments
2998
3113
  @anchor = nil
2999
3114
  end
3000
3115
 
@@ -3235,22 +3350,48 @@ module Psych
3235
3350
  # Print out the leading and trailing comments of a node, as well as
3236
3351
  # yielding the value of the node to the block.
3237
3352
  def with_comments(node)
3238
- node.leading_comments.each do |comment|
3239
- @q.text(comment.value)
3353
+ if (comments = node.comments) && (leading = comments.leading).any?
3354
+ line = nil
3355
+
3356
+ leading.each do |comment|
3357
+ while line && line < comment.start_line
3358
+ @q.breakable
3359
+ line += 1
3360
+ end
3361
+
3362
+ @q.text(comment.value)
3363
+ line = comment.end_line
3364
+ end
3365
+
3240
3366
  @q.breakable
3241
3367
  end
3242
3368
 
3243
3369
  yield node.value
3244
3370
 
3245
- if (trailing_comments = node.trailing_comments).any?
3246
- if trailing_comments[0].inline?
3247
- inline_comment = trailing_comments.shift
3371
+ if comments && (trailing = comments.trailing).any?
3372
+ line = nil
3373
+ index = 0
3374
+
3375
+ if trailing[0].inline?
3376
+ inline_comment = trailing[0]
3377
+ index += 1
3378
+
3248
3379
  @q.trailer { @q.text(" "); @q.text(inline_comment.value) }
3380
+ line = inline_comment.end_line
3249
3381
  end
3250
3382
 
3251
- trailing_comments.each do |comment|
3252
- @q.breakable
3383
+ trailing[index..-1].each do |comment|
3384
+ if line.nil?
3385
+ @q.breakable
3386
+ else
3387
+ while line < comment.start_line
3388
+ @q.breakable
3389
+ line += 1
3390
+ end
3391
+ end
3392
+
3253
3393
  @q.text(comment.value)
3394
+ line = comment.end_line
3254
3395
  end
3255
3396
  end
3256
3397
  end
@@ -3325,59 +3466,39 @@ module Psych
3325
3466
  private
3326
3467
 
3327
3468
  # Walk through the given object and convert it into a tree of nodes.
3328
- def dump(base_object)
3469
+ def dump(base_object, comments = nil)
3329
3470
  object = base_object
3330
- leading_comments = []
3331
- trailing_comments = []
3332
3471
 
3333
- if base_object.is_a?(YAMLCommentsDelegator)
3472
+ if base_object.is_a?(CommentsObject) || base_object.is_a?(CommentsHash)
3334
3473
  object = base_object.__getobj__
3335
- leading_comments.concat(base_object.yaml_leading_comments)
3336
- trailing_comments.concat(base_object.yaml_trailing_comments)
3474
+ comments = base_object.psych_comments
3337
3475
  end
3338
3476
 
3339
3477
  if object.nil?
3340
- NilNode.new(object, leading_comments, trailing_comments)
3478
+ NilNode.new(object, comments)
3341
3479
  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
- )
3480
+ AliasNode.new(@object_nodes[object].anchor = (@object_anchors[object] ||= (@object_anchor += 1)), comments)
3347
3481
  else
3348
3482
  case object
3349
3483
  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
- )
3484
+ @object_nodes[object] = OmapNode.new(object.map { |(key, value)| HashNode.new({ dump(key) => dump(value) }, nil) }, comments)
3356
3485
  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
- )
3486
+ @object_nodes[object] = SetNode.new(object.to_h { |key, value| [dump(key), dump(value)] }, comments)
3363
3487
  when Array
3364
- @object_nodes[object] =
3365
- ArrayNode.new(
3366
- object.map { |element| dump(element) },
3367
- leading_comments,
3368
- trailing_comments
3369
- )
3488
+ @object_nodes[object] = ArrayNode.new(object.map { |element| dump(element) }, comments)
3370
3489
  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
- )
3490
+ dumped =
3491
+ if base_object.is_a?(CommentsHash)
3492
+ object.to_h { |key, value| [dump(key, base_object.psych_key_comments[key]), dump(value)] }
3493
+ else
3494
+ object.to_h { |key, value| [dump(key), dump(value)] }
3495
+ end
3496
+
3497
+ @object_nodes[object] = HashNode.new(dumped, comments)
3377
3498
  when String
3378
- StringNode.new(object, leading_comments, trailing_comments)
3499
+ StringNode.new(object, comments)
3379
3500
  else
3380
- ObjectNode.new(object, leading_comments, trailing_comments)
3501
+ ObjectNode.new(object, comments)
3381
3502
  end
3382
3503
  end
3383
3504
  end
@@ -3417,7 +3538,13 @@ module Psych
3417
3538
  private
3418
3539
 
3419
3540
  # Dump the given object, ensuring that it is a permitted object.
3420
- def dump(object)
3541
+ def dump(base_object, comments = nil)
3542
+ object = base_object
3543
+
3544
+ if base_object.is_a?(CommentsObject) || base_object.is_a?(CommentsHash)
3545
+ object = base_object.__getobj__
3546
+ end
3547
+
3421
3548
  if !@aliases && @object_nodes.key?(object)
3422
3549
  raise BadAlias, "Tried to dump an aliased object"
3423
3550
  end
@@ -3435,7 +3562,7 @@ module Psych
3435
3562
  end
3436
3563
 
3437
3564
  # --------------------------------------------------------------------------
3438
- # :section: Public API specific to Psych::Pure
3565
+ # :section: Public API mirroring Psych
3439
3566
  # --------------------------------------------------------------------------
3440
3567
 
3441
3568
  # Create a new default parser.
@@ -3443,6 +3570,22 @@ module Psych
3443
3570
  Pure::Parser.new(TreeBuilder.new)
3444
3571
  end
3445
3572
 
3573
+ def self.parse(yaml, filename: nil, comments: false)
3574
+ parse_stream(yaml, filename: filename, comments: comments) do |node|
3575
+ return node
3576
+ end
3577
+
3578
+ false
3579
+ end
3580
+
3581
+ def self.parse_file(filename, fallback: false, comments: false)
3582
+ result = File.open(filename, "r:bom|utf-8") do |f|
3583
+ parse(f, filename: filename, comments: comments)
3584
+ end
3585
+
3586
+ result || fallback
3587
+ end
3588
+
3446
3589
  # Parse a YAML stream and return the root node.
3447
3590
  def self.parse_stream(yaml, filename: nil, comments: false, &block)
3448
3591
  if block_given?
@@ -3455,6 +3598,76 @@ module Psych
3455
3598
  end
3456
3599
  end
3457
3600
 
3601
+ def self.unsafe_load(yaml, filename: nil, fallback: false, symbolize_names: false, freeze: false, strict_integer: false, comments: false)
3602
+ result = parse(yaml, filename: filename, comments: comments)
3603
+ return fallback unless result
3604
+
3605
+ result.to_ruby(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer, comments: comments)
3606
+ end
3607
+
3608
+ 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)
3609
+ result = parse(yaml, filename: filename, comments: comments)
3610
+ return fallback unless result
3611
+
3612
+ class_loader = ClassLoader::Restricted.new(permitted_classes.map(&:to_s), permitted_symbols.map(&:to_s))
3613
+ scanner = ScalarScanner.new(class_loader, strict_integer: strict_integer)
3614
+ visitor =
3615
+ if aliases
3616
+ Visitors::ToRuby.new(scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze, comments: comments)
3617
+ else
3618
+ Visitors::NoAliasRuby.new(scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze, comments: comments)
3619
+ end
3620
+
3621
+ visitor.accept(result)
3622
+ end
3623
+
3624
+ 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)
3625
+ safe_load(
3626
+ yaml,
3627
+ permitted_classes: permitted_classes,
3628
+ permitted_symbols: permitted_symbols,
3629
+ aliases: aliases,
3630
+ filename: filename,
3631
+ fallback: fallback,
3632
+ symbolize_names: symbolize_names,
3633
+ freeze: freeze,
3634
+ strict_integer: strict_integer,
3635
+ comments: comments
3636
+ )
3637
+ end
3638
+
3639
+ def self.load_stream(yaml, filename: nil, fallback: [], comments: false, **kwargs)
3640
+ result =
3641
+ if block_given?
3642
+ parse_stream(yaml, filename: filename, comments: comments) do |node|
3643
+ yield node.to_ruby(**kwargs)
3644
+ end
3645
+ else
3646
+ parse_stream(yaml, filename: filename, comments: comments).children.map { |node| node.to_ruby(**kwargs) }
3647
+ end
3648
+
3649
+ return fallback if result.is_a?(Array) && result.empty?
3650
+ result
3651
+ end
3652
+
3653
+ def self.unsafe_load_file(filename, **kwargs)
3654
+ File.open(filename, "r:bom|utf-8") do |f|
3655
+ self.unsafe_load(f, filename: filename, **kwargs)
3656
+ end
3657
+ end
3658
+
3659
+ def self.safe_load_file(filename, **kwargs)
3660
+ File.open(filename, "r:bom|utf-8") do |f|
3661
+ self.safe_load(f, filename: filename, **kwargs)
3662
+ end
3663
+ end
3664
+
3665
+ def self.load_file(filename, **kwargs)
3666
+ File.open(filename, "r:bom|utf-8") do |f|
3667
+ self.load(f, filename: filename, **kwargs)
3668
+ end
3669
+ end
3670
+
3458
3671
  # Dump an object to a YAML string.
3459
3672
  def self.dump(o, io = nil, options = {})
3460
3673
  if Hash === io
@@ -3489,89 +3702,5 @@ module Psych
3489
3702
  objects.each { |object| emitter << object }
3490
3703
  io || real_io.string
3491
3704
  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
3705
  end
3577
3706
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: psych-pure
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Newton
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-12 00:00:00.000000000 Z
10
+ date: 2025-02-13 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: psych
@@ -97,7 +97,7 @@ licenses:
97
97
  - MIT
98
98
  metadata:
99
99
  bug_tracker_uri: https://github.com/kddnewton/psych-pure/issues
100
- changelog_uri: https://github.com/kddnewton/psych-pure/blob/v0.1.0/CHANGELOG.md
100
+ changelog_uri: https://github.com/kddnewton/psych-pure/blob/v0.1.1/CHANGELOG.md
101
101
  source_code_uri: https://github.com/kddnewton/psych-pure
102
102
  rubygems_mfa_required: 'true'
103
103
  rdoc_options: []