psych-pure 0.1.3 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62e40e29dab03fe116feadefc2a62967ec840deb52bee18bff89ae1318bf4d02
4
- data.tar.gz: 3f5612a277682bbef3a698e03cc3bde6e9da02dc3f3024d9978aff34af8a624c
3
+ metadata.gz: f1767eb97091e21f83527221b9ef4f156d9db40998b9008422b3dc017ff4ada6
4
+ data.tar.gz: b7e1d3b11886a31fda7fd7aed9937e2c26eaafa6d5e242a65bb89c8a02b00be7
5
5
  SHA512:
6
- metadata.gz: 5be228176e34e4e908e1ea6af1cebbf41b59fa311de3e4a5abec21aefd9e6028f87473714b9e2c0ccf554bec1a379986b9323744c94ebdf9144cdc08d9116991
7
- data.tar.gz: 4ba02fec03109e76b40883918d1d46125bdd8c1afb9d486a5af2decb868ebc6c967229dda3758e6281b5df709a89b2e6f704d747bad7f307d748861aba580b98
6
+ metadata.gz: 7103d8ceae77bf15b2f21f9462f65fe495bf6e90206749fded264d192beb1caee6ae1a0585703ce914d7268a1e57f498f4cc1b824c21b3c5fe95edfb1dc1e320
7
+ data.tar.gz: 570ba899c74d5873ba243771ae3ff71605b59f81b1567f5f0712314aa2f8efbbdfab659c4e9bde63609ce2ef5ccce46352624c0f458ef6a8ddd31826edcfa9da
data/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ 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.4] - 2025-11-10
10
+
11
+ - Fix up comment handling preceding sequence elements.
12
+ - Properly update hashes in mutation methods that are loaded through `Psych::Pure.load`.
13
+ - Properly raise syntax errors when the parser does not finish.
14
+
9
15
  ## [0.1.3] - 2025-10-24
10
16
 
11
17
  - Fix up roundtripping when using `<<` inside mappings.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Psych
4
4
  module Pure
5
- VERSION = "0.1.3"
5
+ VERSION = "0.1.4"
6
6
  end
7
7
  end
data/lib/psych/pure.rb CHANGED
@@ -176,6 +176,20 @@ module Psych
176
176
  def trailing_comment(comment)
177
177
  @trailing << comment
178
178
  end
179
+
180
+ # Execute the given block without the leading comments being visible. This
181
+ # is used when a node has already handled its child nodes' leading
182
+ # comments, so they should not be processed again.
183
+ def without_leading
184
+ leading = @leading
185
+
186
+ begin
187
+ @leading = []
188
+ yield
189
+ ensure
190
+ @leading = leading
191
+ end
192
+ end
179
193
  end
180
194
 
181
195
  # Wraps a Ruby object with its node from the source input.
@@ -209,7 +223,11 @@ module Psych
209
223
  @value_node = value_node
210
224
  end
211
225
 
212
- def replace(value_node)
226
+ def replace_key(key_node)
227
+ @key_node = key_node
228
+ end
229
+
230
+ def replace_value(value_node)
213
231
  @value_node = value_node
214
232
  end
215
233
  end
@@ -217,49 +235,245 @@ module Psych
217
235
  # The node associated with the hash.
218
236
  attr_reader :psych_node
219
237
 
238
+ # The list of key/value pairs within the hash.
239
+ attr_reader :psych_keys
240
+
220
241
  def initialize(object, psych_node)
221
242
  super(object)
222
243
  @psych_node = psych_node
223
244
  @psych_keys = []
224
245
  end
225
246
 
226
- def psych_keys
227
- @psych_keys.map do |psych_key|
228
- [psych_key.key_node, psych_key.value_node]
229
- end
247
+ def set!(key_node, value_node)
248
+ @psych_keys << PsychKey.new(key_node, value_node)
249
+ __getobj__[key_node.__getobj__] = value_node
250
+ end
251
+
252
+ def join!(key_node, value_node)
253
+ @psych_keys << PsychKey.new(key_node, value_node)
254
+ merge!(value_node)
255
+ end
256
+
257
+ def psych_assocs
258
+ @psych_keys.map { |psych_key| [psych_key.key_node, psych_key.value_node] }
259
+ end
260
+
261
+ # Override Hash mutation methods to keep @psych_keys in sync.
262
+
263
+ def initialize_clone(obj, freeze: nil)
264
+ super
265
+ @psych_keys = obj.psych_keys.map(&:dup)
266
+ end
267
+
268
+ def initialize_dup(obj)
269
+ super
270
+ @psych_keys = obj.psych_keys.map(&:dup)
230
271
  end
231
272
 
232
273
  def []=(key, value)
233
- if begin
234
- @psych_keys.none? do |psych_key|
235
- key_node = psych_key.key_node
236
- key_node_inner =
237
- if key_node.is_a?(LoadedHash) || key_node.is_a?(LoadedObject)
238
- key_node.__getobj__
239
- else
240
- key_node
241
- end
274
+ super(key, value)
242
275
 
243
- if key_node_inner.eql?(key)
244
- psych_key.replace(value)
245
- true
276
+ if (psych_key = @psych_keys.reverse_each.find { |psych_key| psych_compare?(psych_key.key_node, key) })
277
+ psych_key.replace_value(value)
278
+ else
279
+ @psych_keys << PsychKey.new(key, value)
280
+ end
281
+
282
+ value
283
+ end
284
+
285
+ alias store []=
286
+
287
+ def clear
288
+ super
289
+ @psych_keys.clear
290
+
291
+ self
292
+ end
293
+
294
+ def compact!
295
+ mutated = false
296
+ @psych_keys.each do |psych_key|
297
+ if psych_unwrap(psych_key.value_node).nil?
298
+ mutated = true
299
+ delete(psych_unwrap(psych_key.key_node))
300
+ end
301
+ end
302
+
303
+ self if mutated
304
+ end
305
+
306
+ def compact
307
+ dup.compact!
308
+ end
309
+
310
+ def delete(key)
311
+ result = super
312
+ psych_delete(key)
313
+
314
+ result
315
+ end
316
+
317
+ def delete_if(&block)
318
+ super do |key, value|
319
+ yield(key, psych_unwrap(value)).tap do |result|
320
+ psych_delete(key) if result
321
+ end
322
+ end
323
+
324
+ self
325
+ end
326
+
327
+ def except(*keys)
328
+ dup.delete_if { |key, _| keys.include?(key) }
329
+ end
330
+
331
+ def filter!(&block)
332
+ mutated = false
333
+ super do |key, value|
334
+ yield(key, psych_unwrap(value)).tap do |result|
335
+ unless result
336
+ psych_delete(key)
337
+ mutated = true
246
338
  end
247
339
  end
248
- end then
249
- @psych_keys << PsychKey.new(key, value)
250
340
  end
251
341
 
252
- super(key, value)
342
+ self if mutated
253
343
  end
254
344
 
255
- def set!(key_node, value_node)
256
- @psych_keys << PsychKey.new(key_node, value_node)
257
- __getobj__[key_node.__getobj__] = value_node
345
+ def filter(&block)
346
+ dup.filter!(&block)
258
347
  end
259
348
 
260
- def join!(key_node, value_node)
261
- @psych_keys << PsychKey.new(key_node, value_node)
262
- merge!(value_node)
349
+ def invert
350
+ result = LoadedHash.new({}, @psych_node)
351
+ each { |key, value| result[psych_unwrap(value)] = key }
352
+ result
353
+ end
354
+
355
+ def keep_if(&block)
356
+ super do |key, value|
357
+ yield(key, psych_unwrap(value)).tap do |result|
358
+ psych_delete(key) unless result
359
+ end
360
+ end
361
+
362
+ self
363
+ end
364
+
365
+ alias select! keep_if
366
+
367
+ def merge!(*others)
368
+ super
369
+ others.each do |other|
370
+ other.each do |key, value|
371
+ psych_delete(key)
372
+ @psych_keys << PsychKey.new(key, value)
373
+ end
374
+ end
375
+
376
+ self
377
+ end
378
+
379
+ alias update merge!
380
+
381
+ def merge(*others)
382
+ dup.merge!(*others)
383
+ end
384
+
385
+ def reject!(&block)
386
+ mutated = false
387
+ super do |key, value|
388
+ yield(key, psych_unwrap(value)).tap do |result|
389
+ if result
390
+ psych_delete(key)
391
+ mutated = true
392
+ end
393
+ end
394
+ end
395
+
396
+ self if mutated
397
+ end
398
+
399
+ def reject(&block)
400
+ dup.reject!(&block)
401
+ end
402
+
403
+ def replace(other)
404
+ super
405
+
406
+ @psych_keys.clear
407
+ other.each { |key, value| @psych_keys << PsychKey.new(key, value) }
408
+
409
+ self
410
+ end
411
+
412
+ def shift
413
+ unless empty?
414
+ key, value = super
415
+ psych_delete(key)
416
+
417
+ [key, value]
418
+ end
419
+ end
420
+
421
+ def slice(*keys)
422
+ dup.select! { |key, _| keys.include?(key) }
423
+ end
424
+
425
+ def transform_keys!(&block)
426
+ super do |key|
427
+ yield(key).tap do |result|
428
+ @psych_keys
429
+ .reverse_each
430
+ .find { |psych_key| psych_compare?(psych_key.key_node, key) }
431
+ &.replace_key(result)
432
+ end
433
+ end
434
+
435
+ self
436
+ end
437
+
438
+ def transform_keys(&block)
439
+ dup.transform_keys!(&block)
440
+ end
441
+
442
+ def transform_values!(&block)
443
+ super do |value|
444
+ yield(psych_unwrap(value)).tap do |result|
445
+ @psych_keys
446
+ .reverse_each
447
+ .find { |psych_key| psych_compare?(psych_key.value_node, value) }
448
+ &.replace_value(result)
449
+ end
450
+ end
451
+ end
452
+
453
+ def transform_values(&block)
454
+ dup.transform_values!(&block)
455
+ end
456
+
457
+ private
458
+
459
+ def psych_compare?(psych_node, value)
460
+ if compare_by_identity?
461
+ psych_unwrap(psych_node).equal?(value)
462
+ else
463
+ psych_unwrap(psych_node).eql?(value)
464
+ end
465
+ end
466
+
467
+ def psych_delete(key)
468
+ @psych_keys.reject! { |psych_key| psych_compare?(psych_key.key_node, key) }
469
+ end
470
+
471
+ def psych_unwrap(node)
472
+ if node.is_a?(LoadedHash) || node.is_a?(LoadedObject)
473
+ node.__getobj__
474
+ else
475
+ node
476
+ end
263
477
  end
264
478
  end
265
479
 
@@ -762,9 +976,7 @@ module Psych
762
976
  @source = Source.new(yaml)
763
977
  @comments = {} if comments
764
978
 
765
- raise_syntax_error("Parser failed to complete") unless parse_l_yaml_stream
766
- raise_syntax_error("Parser finished before end of input") unless @scanner.eos?
767
-
979
+ parse_l_yaml_stream
768
980
  @comments = nil if comments
769
981
  true
770
982
  end
@@ -1261,11 +1473,8 @@ module Psych
1261
1473
  # b-comment
1262
1474
  def parse_s_b_comment
1263
1475
  try do
1264
- try do
1265
- if parse_s_separate_in_line
1266
- parse_c_nb_comment_text(true)
1267
- true
1268
- end
1476
+ if parse_s_separate_in_line
1477
+ parse_c_nb_comment_text(true)
1269
1478
  end
1270
1479
 
1271
1480
  parse_b_comment
@@ -2064,12 +2273,9 @@ module Psych
2064
2273
  if parse_ns_flow_seq_entry(n, c)
2065
2274
  parse_s_separate(n, c)
2066
2275
 
2067
- try do
2068
- if match(",")
2069
- parse_s_separate(n, c)
2070
- parse_ns_s_flow_seq_entries(n, c)
2071
- true
2072
- end
2276
+ if match(",")
2277
+ parse_s_separate(n, c)
2278
+ parse_ns_s_flow_seq_entries(n, c)
2073
2279
  end
2074
2280
 
2075
2281
  true
@@ -3195,34 +3401,32 @@ module Psych
3195
3401
  def parse_l_yaml_stream
3196
3402
  events_push_flush_properties(StreamStart.new(Location.point(@source, @scanner.pos)))
3197
3403
 
3198
- if try {
3199
- if parse_l_document_prefix
3200
- @document_start_event = DocumentStart.new(Location.point(@source, @scanner.pos))
3201
- @tag_directives = @document_start_event.tag_directives
3202
- @document_end_event = nil
3404
+ star { parse_l_document_prefix }
3405
+ @document_start_event = DocumentStart.new(Location.point(@source, @scanner.pos))
3406
+ @tag_directives = @document_start_event.tag_directives
3407
+ @document_end_event = nil
3408
+ parse_l_any_document
3203
3409
 
3204
- parse_l_any_document
3205
- star do
3206
- try do
3207
- if parse_l_document_suffix
3208
- star { parse_l_document_prefix }
3209
- parse_l_any_document
3210
- true
3211
- end
3212
- end ||
3213
- try do
3214
- if parse_l_document_prefix
3215
- parse_l_explicit_document
3216
- true
3217
- end
3218
- end
3410
+ star do
3411
+ try do
3412
+ if parse_l_document_suffix
3413
+ star { parse_l_document_prefix }
3414
+ parse_l_any_document
3415
+ true
3416
+ end
3417
+ end ||
3418
+ try do
3419
+ if parse_l_document_prefix
3420
+ parse_l_explicit_document
3421
+ true
3219
3422
  end
3220
3423
  end
3221
- } then
3222
- document_end_event_flush
3223
- events_push_flush_properties(StreamEnd.new(Location.point(@source, @scanner.pos)))
3224
- true
3225
3424
  end
3425
+
3426
+ raise_syntax_error("Parser finished before end of input") unless @scanner.eos?
3427
+ document_end_event_flush
3428
+ events_push_flush_properties(StreamEnd.new(Location.point(@source, @scanner.pos)))
3429
+ true
3226
3430
  end
3227
3431
 
3228
3432
  # ------------------------------------------------------------------------
@@ -3298,6 +3502,9 @@ module Psych
3298
3502
 
3299
3503
  # Represents the nil value.
3300
3504
  class NilNode < Node
3505
+ def accept(visitor)
3506
+ raise "Visiting NilNode is not supported"
3507
+ end
3301
3508
  end
3302
3509
 
3303
3510
  # Represents a generic object that is not matched by any of the other node
@@ -3468,12 +3675,38 @@ module Psych
3468
3675
  @q.breakable
3469
3676
  end
3470
3677
 
3471
- @q.seplist(contents, -> { @q.breakable }) do |element|
3678
+ current_line = nil
3679
+ contents.each_with_index do |element, index|
3680
+ psych_node = element.psych_node
3681
+ leading = psych_node&.comments&.leading
3682
+
3683
+ if index > 0
3684
+ @q.breakable
3685
+
3686
+ if current_line && psych_node
3687
+ start_line = (leading&.first || psych_node).start_line
3688
+ @q.breakable if start_line - current_line >= 2
3689
+ end
3690
+ end
3691
+
3692
+ current_line = psych_node&.end_line
3693
+ visit_leading_comments(leading) if leading&.any?
3694
+
3695
+ if psych_node && (trailing = psych_node.comments.trailing).any?
3696
+ current_line = trailing.last.end_line
3697
+ end
3698
+
3472
3699
  @q.text("-")
3473
3700
  next if element.is_a?(NilNode)
3474
3701
 
3475
3702
  @q.text(" ")
3476
- @q.nest(2) { visit(element) }
3703
+ @q.nest(2) do
3704
+ if psych_node
3705
+ psych_node.comments.without_leading { visit(element) }
3706
+ else
3707
+ visit(element)
3708
+ end
3709
+ end
3477
3710
  end
3478
3711
 
3479
3712
  @q.current_group.break
@@ -3629,23 +3862,29 @@ module Psych
3629
3862
  end
3630
3863
  end
3631
3864
 
3865
+ # Visit the leading comments for a node, printing them out with proper
3866
+ # line breaks.
3867
+ def visit_leading_comments(comments)
3868
+ line = nil
3869
+
3870
+ comments.each do |comment|
3871
+ while line && line < comment.start_line
3872
+ @q.breakable
3873
+ line += 1
3874
+ end
3875
+
3876
+ @q.text(comment.value)
3877
+ line = comment.end_line
3878
+ end
3879
+
3880
+ @q.breakable
3881
+ end
3882
+
3632
3883
  # Print out the leading and trailing comments of a node, as well as
3633
3884
  # yielding the value of the node to the block.
3634
3885
  def with_comments(node)
3635
3886
  if (comments = node.psych_node&.comments) && (leading = comments.leading).any?
3636
- line = nil
3637
-
3638
- leading.each do |comment|
3639
- while line && line < comment.start_line
3640
- @q.breakable
3641
- line += 1
3642
- end
3643
-
3644
- @q.text(comment.value)
3645
- line = comment.end_line
3646
- end
3647
-
3648
- @q.breakable
3887
+ visit_leading_comments(leading)
3649
3888
  end
3650
3889
 
3651
3890
  yield node.value
@@ -3835,7 +4074,7 @@ module Psych
3835
4074
  when Hash
3836
4075
  contents =
3837
4076
  if base_object.is_a?(LoadedHash)
3838
- base_object.psych_keys.flat_map { |(key, value)| [dump(key), dump(value)] }
4077
+ base_object.psych_assocs.flat_map { |(key, value)| [dump(key), dump(value)] }
3839
4078
  else
3840
4079
  object.flat_map { |key, value| [dump(key), dump(value)] }
3841
4080
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: psych-pure
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Newton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-24 00:00:00.000000000 Z
11
+ date: 2025-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: psych
@@ -113,7 +113,7 @@ licenses:
113
113
  - MIT
114
114
  metadata:
115
115
  bug_tracker_uri: https://github.com/kddnewton/psych-pure/issues
116
- changelog_uri: https://github.com/kddnewton/psych-pure/blob/v0.1.3/CHANGELOG.md
116
+ changelog_uri: https://github.com/kddnewton/psych-pure/blob/v0.1.4/CHANGELOG.md
117
117
  source_code_uri: https://github.com/kddnewton/psych-pure
118
118
  rubygems_mfa_required: 'true'
119
119
  post_install_message: