[ruby/psych] Add support for ruby 3.2 Data objects

788b844c83
This commit is contained in:
nick evans 2024-10-28 15:41:01 -04:00 committed by git
parent bd1d6e8cd7
commit a397e4d4b0
7 changed files with 130 additions and 0 deletions

View file

@ -6,6 +6,7 @@ module Psych
class ClassLoader # :nodoc:
BIG_DECIMAL = 'BigDecimal'
COMPLEX = 'Complex'
DATA = 'Data' unless RUBY_VERSION < "3.2"
DATE = 'Date'
DATE_TIME = 'DateTime'
EXCEPTION = 'Exception'

View file

@ -197,6 +197,14 @@ module Psych
s
end
when /^!ruby\/data(?::(.*))?$/
data = register(o, resolve_class($1).allocate) if $1
members = {}
revive_data_members(members, o)
data ||= allocate_anon_data(o, members)
data.send(:initialize, **members)
data
when /^!ruby\/object:?(.*)?$/
name = $1 || 'Object'
@ -340,6 +348,20 @@ module Psych
list
end
def allocate_anon_data node, members
klass = class_loader.data.define(*members.keys)
register(node, klass.allocate)
end
def revive_data_members hash, o
o.children.each_slice(2) do |k,v|
name = accept(k)
value = accept(v)
hash[class_loader.symbolize(name)] = value
end
hash
end
def revive_hash hash, o, tagged= false
o.children.each_slice(2) { |k,v|
key = accept(k)

View file

@ -162,6 +162,16 @@ module Psych
alias :visit_Delegator :visit_Object
def visit_Data o
tag = ['!ruby/data', o.class.name].compact.join(':')
register o, @emitter.start_mapping(nil, tag, false, Nodes::Mapping::BLOCK)
o.members.each do |member|
@emitter.scalar member.to_s, nil, nil, true, false, Nodes::Scalar::ANY
accept o.send member
end
@emitter.end_mapping
end
def visit_Struct o
tag = ['!ruby/struct', o.class.name].compact.join(':')

View file

@ -31,6 +31,11 @@ module Psych
assert_reference_trip Struct.new(:foo).new(1)
end
def test_data_has_references
omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
assert_reference_trip Data.define(:foo).new(1)
end
def assert_reference_trip obj
yml = Psych.dump([obj, obj])
assert_match(/\*-?\d+/, yml)

View file

@ -114,6 +114,38 @@ module Psych
end
end
D = Data.define(:d) unless RUBY_VERSION < "3.2"
def test_data_depends_on_sym
omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
assert_safe_cycle(D.new(nil), permitted_classes: [D, Symbol])
assert_raise(Psych::DisallowedClass) do
cycle D.new(nil), permitted_classes: [D]
end
end
def test_anon_data
omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
assert Psych.safe_load(<<-eoyml, permitted_classes: [Data, Symbol])
--- !ruby/data
foo: bar
eoyml
assert_raise(Psych::DisallowedClass) do
Psych.safe_load(<<-eoyml, permitted_classes: [Data])
--- !ruby/data
foo: bar
eoyml
end
assert_raise(Psych::DisallowedClass) do
Psych.safe_load(<<-eoyml, permitted_classes: [Symbol])
--- !ruby/data
foo: bar
eoyml
end
end
def test_safe_load_default_fallback
assert_nil Psych.safe_load("")
end

View file

@ -6,6 +6,7 @@ require_relative 'helper'
# [ruby-core:01946]
module Psych_Tests
StructTest = Struct::new( :c )
DataTest = Data.define( :c ) unless RUBY_VERSION < "3.2"
end
class Psych_Unit_Tests < Psych::TestCase
@ -1075,6 +1076,44 @@ EOY
end
def test_ruby_data
omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
Object.remove_const :MyBookData if Object.const_defined?(:MyBookData)
# Ruby Data value objects
book_class = Data.define(:author, :title, :year, :isbn)
Object.const_set(:MyBookData, book_class)
assert_to_yaml(
[ book_class.new( "Yukihiro Matsumoto", "Ruby in a Nutshell", 2002, "0-596-00214-9" ),
book_class.new( [ 'Dave Thomas', 'Andy Hunt' ], "The Pickaxe", 2002,
book_class.new( "This should be the ISBN", "but I have more data here", 2002, "None" )
)
], <<EOY
- !ruby/data:MyBookData
author: Yukihiro Matsumoto
title: Ruby in a Nutshell
year: 2002
isbn: 0-596-00214-9
- !ruby/data:MyBookData
author:
- Dave Thomas
- Andy Hunt
title: The Pickaxe
year: 2002
isbn: !ruby/data:MyBookData
author: This should be the ISBN
title: but I have more data here
year: 2002
isbn: None
EOY
)
assert_to_yaml( Psych_Tests::DataTest.new( 123 ), <<EOY )
--- !ruby/data:Psych_Tests::DataTest
c: 123
EOY
end
def test_ruby_rational
assert_to_yaml( Rational(1, 2), <<EOY )
--- !ruby/object:Rational

View file

@ -73,6 +73,27 @@ module Psych
assert_equal s.method, obj.method
end
D = Data.define(:foo) unless RUBY_VERSION < "3.2"
def test_data
omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
assert_cycle D.new('bar')
end
def test_data_anon
omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
d = Data.define(:foo).new('bar')
obj = Psych.unsafe_load(Psych.dump(d))
assert_equal d.foo, obj.foo
end
def test_data_override_method
omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
d = Data.define(:method).new('override')
obj = Psych.unsafe_load(Psych.dump(d))
assert_equal d.method, obj.method
end
def test_exception
ex = Exception.new 'foo'
loaded = Psych.unsafe_load(Psych.dump(ex))