psych 5.1.2 → 5.2.6

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.
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require 'stringio'
3
2
  require_relative '../class_loader'
4
3
  require_relative '../scalar_scanner'
5
4
 
@@ -56,6 +55,8 @@ module Psych
56
55
  #
57
56
  # See also Psych::Visitors::Emitter
58
57
  def yaml io = nil, options = {}
58
+ require "stringio" unless defined?(StringIO)
59
+
59
60
  real_io = io || StringIO.new(''.encode('utf-8'))
60
61
 
61
62
  Visitors::Emitter.new(real_io, options).accept self
@@ -11,18 +11,18 @@ module Psych
11
11
  # Base 60, [-+]inf and NaN are handled separately
12
12
  FLOAT = /^(?:[-+]?([0-9][0-9_,]*)?\.[0-9]*([eE][-+][0-9]+)?(?# base 10))$/x
13
13
 
14
- # Taken from http://yaml.org/type/int.html
15
- INTEGER_STRICT = /^(?:[-+]?0b[0-1_]+ (?# base 2)
16
- |[-+]?0[0-7_]+ (?# base 8)
17
- |[-+]?(0|[1-9][0-9_]*) (?# base 10)
18
- |[-+]?0x[0-9a-fA-F_]+ (?# base 16))$/x
14
+ # Taken from http://yaml.org/type/int.html and modified to ensure at least one numerical symbol exists
15
+ INTEGER_STRICT = /^(?:[-+]?0b[_]*[0-1][0-1_]* (?# base 2)
16
+ |[-+]?0[_]*[0-7][0-7_]* (?# base 8)
17
+ |[-+]?(0|[1-9][0-9_]*) (?# base 10)
18
+ |[-+]?0x[_]*[0-9a-fA-F][0-9a-fA-F_]* (?# base 16))$/x
19
19
 
20
20
  # Same as above, but allows commas.
21
21
  # Not to YML spec, but kept for backwards compatibility
22
- INTEGER_LEGACY = /^(?:[-+]?0b[0-1_,]+ (?# base 2)
23
- |[-+]?0[0-7_,]+ (?# base 8)
22
+ INTEGER_LEGACY = /^(?:[-+]?0b[_,]*[0-1][0-1_,]* (?# base 2)
23
+ |[-+]?0[_,]*[0-7][0-7_,]* (?# base 8)
24
24
  |[-+]?(?:0|[1-9](?:[0-9]|,[0-9]|_[0-9])*) (?# base 10)
25
- |[-+]?0x[0-9a-fA-F_,]+ (?# base 16))$/x
25
+ |[-+]?0x[_,]*[0-9a-fA-F][0-9a-fA-F_,]* (?# base 16))$/x
26
26
 
27
27
  attr_reader :class_loader
28
28
 
@@ -61,7 +61,6 @@ module Psych
61
61
  string
62
62
  end
63
63
  elsif string.match?(/^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/)
64
- require 'date'
65
64
  begin
66
65
  class_loader.date.strptime(string, '%F', Date::GREGORIAN)
67
66
  rescue ArgumentError
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Psych
4
4
  # The version of Psych you are using
5
- VERSION = '5.1.2'
5
+ VERSION = '5.2.6'
6
6
 
7
7
  if RUBY_ENGINE == 'jruby'
8
- DEFAULT_SNAKEYAML_VERSION = '2.7'.freeze
8
+ DEFAULT_SNAKEYAML_VERSION = '2.9'.freeze
9
9
  end
10
10
  end
@@ -36,7 +36,7 @@ module Psych
36
36
 
37
37
  unless @domain_types.empty? || !target.tag
38
38
  key = target.tag.sub(/^[!\/]*/, '').sub(/(,\d+)\//, '\1:')
39
- key = "tag:#{key}" unless key =~ /^(?:tag:|x-private)/
39
+ key = "tag:#{key}" unless key.match?(/^(?:tag:|x-private)/)
40
40
 
41
41
  if @domain_types.key? key
42
42
  value, block = @domain_types[key]
@@ -79,7 +79,6 @@ module Psych
79
79
  class_loader.big_decimal._load o.value
80
80
  when "!ruby/object:DateTime"
81
81
  class_loader.date_time
82
- require 'date' unless defined? DateTime
83
82
  t = @ss.parse_time(o.value)
84
83
  DateTime.civil(*t.to_a[0, 6].reverse, Rational(t.utc_offset, 86400)) +
85
84
  (t.subsec/86400)
@@ -97,11 +96,11 @@ module Psych
97
96
  Float(@ss.tokenize(o.value))
98
97
  when "!ruby/regexp"
99
98
  klass = class_loader.regexp
100
- o.value =~ /^\/(.*)\/([mixn]*)$/m
101
- source = $1
99
+ matches = /^\/(?<string>.*)\/(?<options>[mixn]*)$/m.match(o.value)
100
+ source = matches[:string].gsub('\/', '/')
102
101
  options = 0
103
102
  lang = nil
104
- $2&.each_char do |option|
103
+ matches[:options].each_char do |option|
105
104
  case option
106
105
  when 'x' then options |= Regexp::EXTENDED
107
106
  when 'i' then options |= Regexp::IGNORECASE
@@ -198,6 +197,32 @@ module Psych
198
197
  s
199
198
  end
200
199
 
200
+ when /^!ruby\/data(-with-ivars)?(?::(.*))?$/
201
+ data = register(o, resolve_class($2).allocate) if $2
202
+ members = {}
203
+
204
+ if $1 # data-with-ivars
205
+ ivars = {}
206
+ o.children.each_slice(2) do |type, vars|
207
+ case accept(type)
208
+ when 'members'
209
+ revive_data_members(members, vars)
210
+ data ||= allocate_anon_data(o, members)
211
+ when 'ivars'
212
+ revive_hash(ivars, vars)
213
+ end
214
+ end
215
+ ivars.each do |ivar, v|
216
+ data.instance_variable_set ivar, v
217
+ end
218
+ else
219
+ revive_data_members(members, o)
220
+ end
221
+ data ||= allocate_anon_data(o, members)
222
+ init_struct(data, **members)
223
+ data.freeze
224
+ data
225
+
201
226
  when /^!ruby\/object:?(.*)?$/
202
227
  name = $1 || 'Object'
203
228
 
@@ -341,6 +366,20 @@ module Psych
341
366
  list
342
367
  end
343
368
 
369
+ def allocate_anon_data node, members
370
+ klass = class_loader.data.define(*members.keys)
371
+ register(node, klass.allocate)
372
+ end
373
+
374
+ def revive_data_members hash, o
375
+ o.children.each_slice(2) do |k,v|
376
+ name = accept(k)
377
+ value = accept(v)
378
+ hash[class_loader.symbolize(name)] = value
379
+ end
380
+ hash
381
+ end
382
+
344
383
  def revive_hash hash, o, tagged= false
345
384
  o.children.each_slice(2) { |k,v|
346
385
  key = accept(k)
@@ -17,19 +17,15 @@ module Psych
17
17
  def initialize
18
18
  @obj_to_id = {}.compare_by_identity
19
19
  @obj_to_node = {}.compare_by_identity
20
- @targets = []
21
20
  @counter = 0
22
21
  end
23
22
 
24
23
  def register target, node
25
- @targets << target
26
24
  @obj_to_node[target] = node
27
25
  end
28
26
 
29
27
  def key? target
30
28
  @obj_to_node.key? target
31
- rescue NoMethodError
32
- false
33
29
  end
34
30
 
35
31
  def id_for target
@@ -69,6 +65,7 @@ module Psych
69
65
  fail(ArgumentError, "Invalid line_width #{@line_width}, must be non-negative or -1 for unlimited.")
70
66
  end
71
67
  end
68
+ @stringify_names = options[:stringify_names]
72
69
  @coders = []
73
70
 
74
71
  @dispatch_cache = Hash.new do |h,klass|
@@ -76,7 +73,7 @@ module Psych
76
73
 
77
74
  method = respond_to?(method) ? method : h[klass.superclass]
78
75
 
79
- raise(TypeError, "Can't dump #{target.class}") unless method
76
+ raise(TypeError, "can't dump #{klass.name}") unless method
80
77
 
81
78
  h[klass] = method
82
79
  end.compare_by_identity
@@ -165,6 +162,44 @@ module Psych
165
162
 
166
163
  alias :visit_Delegator :visit_Object
167
164
 
165
+ def visit_Data o
166
+ ivars = o.instance_variables
167
+ if ivars.empty?
168
+ tag = ['!ruby/data', o.class.name].compact.join(':')
169
+ register o, @emitter.start_mapping(nil, tag, false, Nodes::Mapping::BLOCK)
170
+ o.members.each do |member|
171
+ @emitter.scalar member.to_s, nil, nil, true, false, Nodes::Scalar::ANY
172
+ accept o.send member
173
+ end
174
+ @emitter.end_mapping
175
+
176
+ else
177
+ tag = ['!ruby/data-with-ivars', o.class.name].compact.join(':')
178
+ node = @emitter.start_mapping(nil, tag, false, Psych::Nodes::Mapping::BLOCK)
179
+ register(o, node)
180
+
181
+ # Dump the members
182
+ accept 'members'
183
+ @emitter.start_mapping nil, nil, true, Nodes::Mapping::BLOCK
184
+ o.members.each do |member|
185
+ @emitter.scalar member.to_s, nil, nil, true, false, Nodes::Scalar::ANY
186
+ accept o.send member
187
+ end
188
+ @emitter.end_mapping
189
+
190
+ # Dump the ivars
191
+ accept 'ivars'
192
+ @emitter.start_mapping nil, nil, true, Nodes::Mapping::BLOCK
193
+ ivars.each do |ivar|
194
+ accept ivar.to_s
195
+ accept o.instance_variable_get ivar
196
+ end
197
+ @emitter.end_mapping
198
+
199
+ @emitter.end_mapping
200
+ end
201
+ end unless RUBY_VERSION < "3.2"
202
+
168
203
  def visit_Struct o
169
204
  tag = ['!ruby/struct', o.class.name].compact.join(':')
170
205
 
@@ -192,7 +227,8 @@ module Psych
192
227
  end
193
228
 
194
229
  def visit_Date o
195
- register o, visit_Integer(o.gregorian)
230
+ formatted = format_date o
231
+ register o, @emitter.scalar(formatted, nil, nil, true, false, Nodes::Scalar::ANY)
196
232
  end
197
233
 
198
234
  def visit_DateTime o
@@ -264,20 +300,20 @@ module Psych
264
300
  style = Nodes::Scalar::LITERAL
265
301
  plain = false
266
302
  quote = false
267
- elsif o =~ /\n(?!\Z)/ # match \n except blank line at the end of string
303
+ elsif o.match?(/\n(?!\Z)/) # match \n except blank line at the end of string
268
304
  style = Nodes::Scalar::LITERAL
269
305
  elsif o == '<<'
270
306
  style = Nodes::Scalar::SINGLE_QUOTED
271
307
  tag = 'tag:yaml.org,2002:str'
272
308
  plain = false
273
309
  quote = false
274
- elsif o == 'y' || o == 'n'
310
+ elsif o == 'y' || o == 'Y' || o == 'n' || o == 'N'
275
311
  style = Nodes::Scalar::DOUBLE_QUOTED
276
312
  elsif @line_width && o.length > @line_width
277
313
  style = Nodes::Scalar::FOLDED
278
- elsif o =~ /^[^[:word:]][^"]*$/
314
+ elsif o.match?(/^[^[:word:]][^"]*$/)
279
315
  style = Nodes::Scalar::DOUBLE_QUOTED
280
- elsif not String === @ss.tokenize(o) or /\A0[0-7]*[89]/ =~ o
316
+ elsif not String === @ss.tokenize(o) or /\A0[0-7]*[89]/.match?(o)
281
317
  style = Nodes::Scalar::SINGLE_QUOTED
282
318
  end
283
319
 
@@ -327,7 +363,7 @@ module Psych
327
363
  if o.class == ::Hash
328
364
  register(o, @emitter.start_mapping(nil, nil, true, Psych::Nodes::Mapping::BLOCK))
329
365
  o.each do |k,v|
330
- accept k
366
+ accept(@stringify_names && Symbol === k ? k.to_s : k)
331
367
  accept v
332
368
  end
333
369
  @emitter.end_mapping
@@ -340,7 +376,7 @@ module Psych
340
376
  register(o, @emitter.start_mapping(nil, '!set', false, Psych::Nodes::Mapping::BLOCK))
341
377
 
342
378
  o.each do |k,v|
343
- accept k
379
+ accept(@stringify_names && Symbol === k ? k.to_s : k)
344
380
  accept v
345
381
  end
346
382
 
@@ -489,6 +525,10 @@ module Psych
489
525
  end
490
526
  end
491
527
 
528
+ def format_date date
529
+ date.strftime("%Y-%m-%d")
530
+ end
531
+
492
532
  def register target, yaml_obj
493
533
  @st.register target, yaml_obj
494
534
  yaml_obj
data/lib/psych.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require 'date'
3
+
2
4
  require_relative 'psych/versions'
3
5
  case RUBY_ENGINE
4
6
  when 'jruby'
@@ -21,7 +23,6 @@ require_relative 'psych/parser'
21
23
  require_relative 'psych/omap'
22
24
  require_relative 'psych/set'
23
25
  require_relative 'psych/coder'
24
- require_relative 'psych/core_ext'
25
26
  require_relative 'psych/stream'
26
27
  require_relative 'psych/json/tree_builder'
27
28
  require_relative 'psych/json/stream'
@@ -84,7 +85,7 @@ require_relative 'psych/class_loader'
84
85
  # Psych.safe_load_file("data.yml", permitted_classes: [Date])
85
86
  # Psych.load_file("trusted_database.yml")
86
87
  #
87
- # ==== Exception handling
88
+ # ==== \Exception handling
88
89
  #
89
90
  # begin
90
91
  # # The second argument changes only the exception contents
@@ -148,7 +149,7 @@ require_relative 'psych/class_loader'
148
149
  # # Returns Psych::Nodes::Document
149
150
  # Psych.parse_file('database.yml')
150
151
  #
151
- # ==== Exception handling
152
+ # ==== \Exception handling
152
153
  #
153
154
  # begin
154
155
  # # The second argument changes only the exception contents
@@ -340,7 +341,7 @@ module Psych
340
341
  # provided, the object contained in the first document will be returned.
341
342
  # +filename+ will be used in the exception message if any exception
342
343
  # is raised while parsing. If +yaml+ is empty, it returns
343
- # the specified +fallback+ return value, which defaults to +false+.
344
+ # the specified +fallback+ return value, which defaults to +nil+.
344
345
  #
345
346
  # Raises a Psych::SyntaxError when a YAML syntax error is detected.
346
347
  #
@@ -479,6 +480,7 @@ module Psych
479
480
  #
480
481
  # Default: <tt>2</tt>.
481
482
  # [<tt>:line_width</tt>] Max character to wrap line at.
483
+ # For unlimited line width use <tt>-1</tt>.
482
484
  #
483
485
  # Default: <tt>0</tt> (meaning "wrap at 81").
484
486
  # [<tt>:canonical</tt>] Write "canonical" YAML form (very verbose, yet
@@ -489,6 +491,10 @@ module Psych
489
491
  #
490
492
  # Default: <tt>false</tt>.
491
493
  #
494
+ # [<tt>:stringify_names</tt>] Dump symbol keys in Hash objects as string.
495
+ #
496
+ # Default: <tt>false</tt>.
497
+ #
492
498
  # Example:
493
499
  #
494
500
  # # Dump an array, get back a YAML string
@@ -502,6 +508,9 @@ module Psych
502
508
  #
503
509
  # # Dump an array to an IO with indentation set
504
510
  # Psych.dump(['a', ['b']], StringIO.new, indentation: 3)
511
+ #
512
+ # # Dump hash with symbol keys as string
513
+ # Psych.dump({a: "b"}, stringify_names: true) # => "---\na: b\n"
505
514
  def self.dump o, io = nil, options = {}
506
515
  if Hash === io
507
516
  options = io
@@ -552,6 +561,7 @@ module Psych
552
561
  #
553
562
  # Default: <tt>2</tt>.
554
563
  # [<tt>:line_width</tt>] Max character to wrap line at.
564
+ # For unlimited line width use <tt>-1</tt>.
555
565
  #
556
566
  # Default: <tt>0</tt> (meaning "wrap at 81").
557
567
  # [<tt>:canonical</tt>] Write "canonical" YAML form (very verbose, yet
@@ -562,6 +572,10 @@ module Psych
562
572
  #
563
573
  # Default: <tt>false</tt>.
564
574
  #
575
+ # [<tt>:stringify_names</tt>] Dump symbol keys in Hash objects as string.
576
+ #
577
+ # Default: <tt>false</tt>.
578
+ #
565
579
  # Example:
566
580
  #
567
581
  # # Dump an array, get back a YAML string
@@ -575,6 +589,9 @@ module Psych
575
589
  #
576
590
  # # Dump an array to an IO with indentation set
577
591
  # Psych.safe_dump(['a', ['b']], StringIO.new, indentation: 3)
592
+ #
593
+ # # Dump hash with symbol keys as string
594
+ # Psych.dump({a: "b"}, stringify_names: true) # => "---\na: b\n"
578
595
  def self.safe_dump o, io = nil, options = {}
579
596
  if Hash === io
580
597
  options = io
@@ -636,6 +653,35 @@ module Psych
636
653
  result
637
654
  end
638
655
 
656
+ ###
657
+ # Load multiple documents given in +yaml+. Returns the parsed documents
658
+ # as a list.
659
+ #
660
+ # Example:
661
+ #
662
+ # Psych.safe_load_stream("--- foo\n...\n--- bar\n...") # => ['foo', 'bar']
663
+ #
664
+ # list = []
665
+ # Psych.safe_load_stream("--- foo\n...\n--- bar\n...") do |ruby|
666
+ # list << ruby
667
+ # end
668
+ # list # => ['foo', 'bar']
669
+ #
670
+ def self.safe_load_stream yaml, filename: nil, permitted_classes: [], aliases: false
671
+ documents = parse_stream(yaml, filename: filename).children.map do |child|
672
+ stream = Psych::Nodes::Stream.new
673
+ stream.children << child
674
+ safe_load(stream.to_yaml, permitted_classes: permitted_classes, aliases: aliases)
675
+ end
676
+
677
+ if block_given?
678
+ documents.each { |doc| yield doc }
679
+ nil
680
+ else
681
+ documents
682
+ end
683
+ end
684
+
639
685
  ###
640
686
  # Load the document contained in +filename+. Returns the yaml contained in
641
687
  # +filename+ as a Ruby object, or if the file is empty, it returns
@@ -653,7 +699,7 @@ module Psych
653
699
  ###
654
700
  # Safely loads the document contained in +filename+. Returns the yaml contained in
655
701
  # +filename+ as a Ruby object, or if the file is empty, it returns
656
- # the specified +fallback+ return value, which defaults to +false+.
702
+ # the specified +fallback+ return value, which defaults to +nil+.
657
703
  # See safe_load for options.
658
704
  def self.safe_load_file filename, **kwargs
659
705
  File.open(filename, 'r:bom|utf-8') { |f|
@@ -664,7 +710,7 @@ module Psych
664
710
  ###
665
711
  # Loads the document contained in +filename+. Returns the yaml contained in
666
712
  # +filename+ as a Ruby object, or if the file is empty, it returns
667
- # the specified +fallback+ return value, which defaults to +false+.
713
+ # the specified +fallback+ return value, which defaults to +nil+.
668
714
  # See load for options.
669
715
  def self.load_file filename, **kwargs
670
716
  File.open(filename, 'r:bom|utf-8') { |f|
@@ -743,3 +789,5 @@ module Psych
743
789
  self.domain_types = {}
744
790
  # :startdoc:
745
791
  end
792
+
793
+ require_relative 'psych/core_ext'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: psych
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.2
4
+ version: 5.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Patterson
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-12-19 00:00:00.000000000 Z
13
+ date: 2025-05-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: stringio
@@ -26,6 +26,20 @@ dependencies:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
28
  version: '0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: date
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
29
43
  description: |
30
44
  Psych is a YAML parser and emitter. Psych leverages libyaml[https://pyyaml.org/wiki/LibYAML]
31
45
  for its YAML parsing and emitting capabilities. In addition to wrapping libyaml,
@@ -97,6 +111,7 @@ licenses:
97
111
  - MIT
98
112
  metadata:
99
113
  msys2_mingw_dependencies: libyaml
114
+ changelog_uri: https://github.com/ruby/psych/releases
100
115
  post_install_message:
101
116
  rdoc_options:
102
117
  - "--main"
@@ -114,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
129
  - !ruby/object:Gem::Version
115
130
  version: '0'
116
131
  requirements: []
117
- rubygems_version: 3.5.1
132
+ rubygems_version: 3.5.22
118
133
  signing_key:
119
134
  specification_version: 4
120
135
  summary: Psych is a YAML parser and emitter