mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
d21bd1c06d
* ext/syck/rubyext.c (transfer_find_i): removed use of String#=~ in favor of Regexp#match. * lib/yaml.rb: YAML::try_implicit returns. * lib/yaml/rubytypes.rb: Regexps added for type matching. * lib/yaml/emitter.rb: fix String + nil error. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@3955 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
583 lines
14 KiB
Ruby
583 lines
14 KiB
Ruby
require 'date'
|
|
#
|
|
# Type conversions
|
|
#
|
|
|
|
class Class
|
|
def to_yaml( opts = {} )
|
|
raise ArgumentError, "can't dump anonymous class %s" % self.class
|
|
end
|
|
end
|
|
|
|
class Object
|
|
def is_complex_yaml?
|
|
true
|
|
end
|
|
def to_yaml_type
|
|
"!ruby/object:#{self.class}"
|
|
end
|
|
def to_yaml_properties
|
|
instance_variables.sort
|
|
end
|
|
def to_yaml( opts = {} )
|
|
YAML::quick_emit( self.object_id, opts ) { |out|
|
|
out.map( self.to_yaml_type ) { |map|
|
|
to_yaml_properties.each { |m|
|
|
map.add( m[1..-1], instance_eval( m ) )
|
|
}
|
|
}
|
|
}
|
|
end
|
|
end
|
|
|
|
YAML.add_ruby_type( /^object/ ) { |type, val|
|
|
type, obj_class = YAML.read_type_class( type, Object )
|
|
YAML.object_maker( obj_class, val )
|
|
}
|
|
|
|
#
|
|
# Maps: Hash#to_yaml
|
|
#
|
|
class Hash
|
|
def is_complex_yaml?
|
|
true
|
|
end
|
|
def to_yaml_type
|
|
if self.class == Hash or self.class == YAML::SpecialHash
|
|
"!map"
|
|
else
|
|
"!ruby/hash:#{self.class}"
|
|
end
|
|
end
|
|
def to_yaml( opts = {} )
|
|
opts[:DocType] = self.class if Hash === opts
|
|
YAML::quick_emit( self.object_id, opts ) { |out|
|
|
hash_type = to_yaml_type
|
|
if not out.options[:ExplicitTypes] and hash_type == "!map"
|
|
hash_type = ""
|
|
end
|
|
out.map( hash_type ) { |map|
|
|
#
|
|
# Sort the hash
|
|
#
|
|
if out.options[:SortKeys]
|
|
map.concat( self.sort )
|
|
else
|
|
map.concat( self.to_a )
|
|
end
|
|
}
|
|
}
|
|
end
|
|
end
|
|
|
|
hash_proc = Proc.new { |type, val|
|
|
if Array === val
|
|
val = Hash.[]( *val ) # Convert the map to a sequence
|
|
elsif Hash === val
|
|
type, obj_class = YAML.read_type_class( type, Hash )
|
|
if obj_class != Hash
|
|
o = obj_class.new
|
|
o.update( val )
|
|
val = o
|
|
end
|
|
else
|
|
raise YAML::Error, "Invalid map explicitly tagged !map: " + val.inspect
|
|
end
|
|
val
|
|
}
|
|
YAML.add_builtin_type( 'map', &hash_proc )
|
|
YAML.add_ruby_type( /^hash/, &hash_proc )
|
|
|
|
module YAML
|
|
|
|
#
|
|
# Ruby-specific collection: !ruby/flexhash
|
|
#
|
|
class FlexHash < Array
|
|
def []( k )
|
|
self.assoc( k ).to_a[1]
|
|
end
|
|
def []=( k, *rest )
|
|
val, set = rest.reverse
|
|
if ( tmp = self.assoc( k ) ) and not set
|
|
tmp[1] = val
|
|
else
|
|
self << [ k, val ]
|
|
end
|
|
val
|
|
end
|
|
def has_key?( k )
|
|
self.assoc( k ) ? true : false
|
|
end
|
|
def is_complex_yaml?
|
|
true
|
|
end
|
|
def to_yaml( opts = {} )
|
|
YAML::quick_emit( self.object_id, opts ) { |out|
|
|
out.seq( "!ruby/flexhash" ) { |seq|
|
|
self.each { |v|
|
|
if v[1]
|
|
seq.add( Hash.[]( *v ) )
|
|
else
|
|
seq.add( v[0] )
|
|
end
|
|
}
|
|
}
|
|
}
|
|
end
|
|
end
|
|
|
|
YAML.add_ruby_type( 'flexhash' ) { |type, val|
|
|
if Array === val
|
|
p = FlexHash.new
|
|
val.each { |v|
|
|
if Hash === v
|
|
p.concat( v.to_a ) # Convert the map to a sequence
|
|
else
|
|
p << [ v, nil ]
|
|
end
|
|
}
|
|
p
|
|
else
|
|
raise YAML::Error, "Invalid !ruby/flexhash: " + val.inspect
|
|
end
|
|
}
|
|
end
|
|
|
|
#
|
|
# Structs: export as a !map
|
|
#
|
|
class Struct
|
|
def is_complex_yaml?
|
|
true
|
|
end
|
|
def to_yaml( opts = {} )
|
|
YAML::quick_emit( self.object_id, opts ) { |out|
|
|
#
|
|
# Basic struct is passed as a YAML map
|
|
#
|
|
struct_name = self.class.name.gsub( "Struct:", "" )
|
|
out.map( "!ruby/struct#{struct_name}" ) { |map|
|
|
self.members.each { |m|
|
|
map.add( m, self[m] )
|
|
}
|
|
}
|
|
}
|
|
end
|
|
end
|
|
|
|
YAML.add_ruby_type( /^struct/ ) { |type, val|
|
|
if Hash === val
|
|
struct_type = nil
|
|
|
|
#
|
|
# Use existing Struct if it exists
|
|
#
|
|
begin
|
|
struct_name, struct_type = YAML.read_type_class( type, Struct )
|
|
rescue NameError
|
|
end
|
|
if not struct_type
|
|
struct_def = [ type.split( ':', 4 ).last ]
|
|
struct_type = Struct.new( *struct_def.concat( val.keys.collect { |k| k.intern } ) )
|
|
end
|
|
|
|
#
|
|
# Set the Struct properties
|
|
#
|
|
st = struct_type.new
|
|
st.members.each { |m|
|
|
st.send( "#{m}=", val[m] )
|
|
}
|
|
st
|
|
else
|
|
raise YAML::Error, "Invalid Ruby Struct: " + val.inspect
|
|
end
|
|
}
|
|
|
|
#
|
|
# Sequences: Array#to_yaml
|
|
#
|
|
class Array
|
|
def is_complex_yaml?
|
|
true
|
|
end
|
|
def to_yaml_type
|
|
if self.class == Array
|
|
"!seq"
|
|
else
|
|
"!ruby/array:#{self.class}"
|
|
end
|
|
end
|
|
def to_yaml( opts = {} )
|
|
opts[:DocType] = self.class if Hash === opts
|
|
YAML::quick_emit( self.object_id, opts ) { |out|
|
|
array_type = to_yaml_type
|
|
if not out.options[:ExplicitTypes] and array_type == "!seq"
|
|
array_type = ""
|
|
end
|
|
|
|
out.seq( array_type ) { |seq|
|
|
seq.concat( self )
|
|
}
|
|
}
|
|
end
|
|
end
|
|
|
|
array_proc = Proc.new { |type, val|
|
|
if Array === val
|
|
type, obj_class = YAML.read_type_class( type, Array )
|
|
if obj_class != Array
|
|
o = obj_class.new
|
|
o.concat( val )
|
|
val = o
|
|
end
|
|
val
|
|
else
|
|
val.to_a
|
|
end
|
|
}
|
|
YAML.add_builtin_type( 'seq', &array_proc )
|
|
YAML.add_ruby_type( /^array/, &array_proc )
|
|
|
|
#
|
|
# Exception#to_yaml
|
|
#
|
|
class Exception
|
|
def is_complex_yaml?
|
|
true
|
|
end
|
|
def to_yaml_type
|
|
"!ruby/exception:#{self.class}"
|
|
end
|
|
def to_yaml( opts = {} )
|
|
YAML::quick_emit( self.object_id, opts ) { |out|
|
|
out.map( self.to_yaml_type ) { |map|
|
|
map.add( 'message', self.message )
|
|
to_yaml_properties.each { |m|
|
|
map.add( m[1..-1], instance_eval( m ) )
|
|
}
|
|
}
|
|
}
|
|
end
|
|
end
|
|
|
|
YAML.add_ruby_type( /^exception/ ) { |type, val|
|
|
type, obj_class = YAML.read_type_class( type, Exception )
|
|
o = YAML.object_maker( obj_class, { 'mesg' => val.delete( 'message' ) }, true )
|
|
val.each_pair { |k,v|
|
|
o.instance_eval "@#{k} = v"
|
|
}
|
|
o
|
|
}
|
|
|
|
|
|
#
|
|
# String#to_yaml
|
|
#
|
|
class String
|
|
def is_complex_yaml?
|
|
( self =~ /\n.+/ ? true : false )
|
|
end
|
|
def is_binary_data?
|
|
( self.count( "^ -~", "^\r\n" ) / self.size > 0.3 || self.count( "\x00" ) > 0 )
|
|
end
|
|
def to_yaml( opts = {} )
|
|
complex = false
|
|
if self.is_complex_yaml?
|
|
complex = true
|
|
elsif opts[:BestWidth].to_i > 0
|
|
if self.length > opts[:BestWidth] and opts[:UseFold]
|
|
complex = true
|
|
end
|
|
end
|
|
YAML::quick_emit( complex ? self.object_id : nil, opts ) { |out|
|
|
if complex
|
|
if self.is_binary_data?
|
|
out.binary_base64( self )
|
|
elsif self =~ /^ |#{YAML::ESCAPE_CHAR}| $/
|
|
complex = false
|
|
else
|
|
out.node_text( self )
|
|
end
|
|
end
|
|
if not complex
|
|
ostr = if out.options[:KeepValue]
|
|
self
|
|
elsif empty?
|
|
"''"
|
|
elsif self =~ /^[^#{YAML::WORD_CHAR}]|#{YAML::ESCAPE_CHAR}|[#{YAML::SPACE_INDICATORS}]( |$)| $|\n|\'/
|
|
"\"#{YAML.escape( self )}\""
|
|
elsif YAML.detect_implicit( self ) != 'str'
|
|
"\"#{YAML.escape( self )}\""
|
|
else
|
|
self
|
|
end
|
|
out.simple( ostr )
|
|
end
|
|
}
|
|
end
|
|
end
|
|
|
|
YAML.add_builtin_type( 'str' ) { |type,val| val.to_s }
|
|
YAML.add_builtin_type( 'binary' ) { |type,val|
|
|
enctype = "m"
|
|
if String === val
|
|
val.gsub( /\s+/, '' ).unpack( enctype )[0]
|
|
else
|
|
raise YAML::Error, "Binary data must be represented by a string: " + val.inspect
|
|
end
|
|
}
|
|
|
|
#
|
|
# Symbol#to_yaml
|
|
#
|
|
class Symbol
|
|
def is_complex_yaml?
|
|
false
|
|
end
|
|
def to_yaml( opts = {} )
|
|
YAML::quick_emit( nil, opts ) { |out|
|
|
out << "!ruby/sym "
|
|
self.id2name.to_yaml( :Emitter => out )
|
|
}
|
|
end
|
|
end
|
|
|
|
symbol_proc = Proc.new { |type, val|
|
|
if String === val
|
|
val.intern
|
|
else
|
|
raise YAML::Error, "Invalid Symbol: " + val.inspect
|
|
end
|
|
}
|
|
YAML.add_ruby_type( 'symbol', &symbol_proc )
|
|
YAML.add_ruby_type( 'sym', &symbol_proc )
|
|
|
|
#
|
|
# Range#to_yaml
|
|
#
|
|
class Range
|
|
def is_complex_yaml?
|
|
false
|
|
end
|
|
def to_yaml( opts = {} )
|
|
YAML::quick_emit( nil, opts ) { |out|
|
|
out << "!ruby/range "
|
|
self.inspect.to_yaml( :Emitter => out )
|
|
}
|
|
end
|
|
end
|
|
|
|
YAML.add_ruby_type( 'range' ) { |type, val|
|
|
if String === val and val =~ /^(.*[^.])(\.{2,3})([^.].*)$/
|
|
r1, rdots, r2 = $1, $2, $3
|
|
Range.new( YAML.try_implicit( r1 ), YAML.try_implicit( r2 ), rdots.length == 3 )
|
|
elsif Hash === val
|
|
Range.new( val['begin'], val['end'], val['exclude_end?'] )
|
|
else
|
|
raise YAML::Error, "Invalid Range: " + val.inspect
|
|
end
|
|
}
|
|
|
|
#
|
|
# Make an RegExp
|
|
#
|
|
class Regexp
|
|
def is_complex_yaml?
|
|
false
|
|
end
|
|
def to_yaml( opts = {} )
|
|
YAML::quick_emit( nil, opts ) { |out|
|
|
out << "!ruby/regexp "
|
|
self.inspect.to_yaml( :Emitter => out )
|
|
}
|
|
end
|
|
end
|
|
|
|
regexp_proc = Proc.new { |type, val|
|
|
if String === val and val =~ /^\/(.*)\/([mix]*)$/
|
|
val = { 'REGEXP' => $1, 'MODIFIERS' => $2 }
|
|
end
|
|
if Hash === val
|
|
mods = nil
|
|
unless val['MODIFIERS'].to_s.empty?
|
|
mods = 0x00
|
|
if val['MODIFIERS'].include?( 'x' )
|
|
mods |= Regexp::EXTENDED
|
|
elsif val['MODIFIERS'].include?( 'i' )
|
|
mods |= Regexp::IGNORECASE
|
|
elsif val['MODIFIERS'].include?( 'm' )
|
|
mods |= Regexp::POSIXLINE
|
|
end
|
|
end
|
|
Regexp::compile( val['REGEXP'], mods )
|
|
else
|
|
raise YAML::Error, "Invalid Regular expression: " + val.inspect
|
|
end
|
|
}
|
|
YAML.add_domain_type( "perl.yaml.org,2002", /^regexp/, ®exp_proc )
|
|
YAML.add_ruby_type( 'regexp', ®exp_proc )
|
|
|
|
#
|
|
# Emit a Time object as an ISO 8601 timestamp
|
|
#
|
|
class Time
|
|
def is_complex_yaml?
|
|
false
|
|
end
|
|
def to_yaml( opts = {} )
|
|
YAML::quick_emit( nil, opts ) { |out|
|
|
tz = "Z"
|
|
# from the tidy Tobias Peters <t-peters@gmx.de> Thanks!
|
|
unless self.utc?
|
|
utc_same_instant = self.dup.utc
|
|
utc_same_writing = Time.utc(year,month,day,hour,min,sec,usec)
|
|
difference_to_utc = utc_same_writing - utc_same_instant
|
|
if (difference_to_utc < 0)
|
|
difference_sign = '-'
|
|
absolute_difference = -difference_to_utc
|
|
else
|
|
difference_sign = '+'
|
|
absolute_difference = difference_to_utc
|
|
end
|
|
difference_minutes = (absolute_difference/60).round
|
|
tz = "%s%02d:%02d" % [ difference_sign, difference_minutes / 60, difference_minutes % 60]
|
|
end
|
|
( self.strftime( "%Y-%m-%d %H:%M:%S." ) +
|
|
"%06d %s" % [usec, tz] ).
|
|
to_yaml( :Emitter => out, :KeepValue => true )
|
|
}
|
|
end
|
|
end
|
|
|
|
YAML.add_builtin_type( 'time#ymd' ) { |type, val|
|
|
if val =~ /\A(\d{4})\-(\d{1,2})\-(\d{1,2})\Z/
|
|
Date.new($1.to_i, $2.to_i, $3.to_i)
|
|
else
|
|
raise YAML::TypeError, "Invalid !time string: " + val.inspect
|
|
end
|
|
}
|
|
|
|
#
|
|
# Emit a Date object as a simple implicit
|
|
#
|
|
class Date
|
|
def is_complex_yaml?
|
|
false
|
|
end
|
|
def to_yaml( opts = {} )
|
|
opts[:KeepValue] = true
|
|
self.to_s.to_yaml( opts )
|
|
end
|
|
end
|
|
|
|
#
|
|
# Send Integer, Booleans, NilClass to String
|
|
#
|
|
class Numeric
|
|
def is_complex_yaml?
|
|
false
|
|
end
|
|
def to_yaml( opts = {} )
|
|
str = self.to_s
|
|
if str == "Infinity"
|
|
str = ".Inf"
|
|
elsif str == "-Infinity"
|
|
str = "-.Inf"
|
|
elsif str == "NaN"
|
|
str = ".NaN"
|
|
end
|
|
opts[:KeepValue] = true
|
|
str.to_yaml( opts )
|
|
end
|
|
end
|
|
|
|
YAML.add_builtin_type( 'float' ) { |type, val|
|
|
if val =~ /\A[-+]?[\d][\d,]*\.[\d,]*[eE][-+][0-9]+\Z/ # Float (exponential)
|
|
$&.tr( ',', '' ).to_f
|
|
elsif val =~ /\A[-+]?[\d][\d,]*\.[\d,]*\Z/ # Float (fixed)
|
|
$&.tr( ',', '' ).to_f
|
|
elsif val =~ /\A([-+]?)\.(inf|Inf|INF)\Z/ # Float (english)
|
|
( $1 == "-" ? -1.0/0.0 : 1.0/0.0 )
|
|
elsif val =~ /\A\.(nan|NaN|NAN)\Z/
|
|
0.0/0.0
|
|
elsif type == :Implicit
|
|
:InvalidType
|
|
else
|
|
val.to_f
|
|
end
|
|
}
|
|
|
|
YAML.add_builtin_type( 'int' ) { |type, val|
|
|
if val =~ /\A[-+]?0[0-7,]+\Z/ # Integer (octal)
|
|
$&.oct
|
|
elsif val =~ /\A[-+]?0x[0-9a-fA-F,]+\Z/ # Integer (hex)
|
|
$&.hex
|
|
elsif val =~ /\A[-+]?\d[\d,]*\Z/ # Integer (canonical)
|
|
$&.tr( ',', '' ).to_i
|
|
elsif val =~ /\A([-+]?)(\d[\d,]*(?::[0-5]?[0-9])+)\Z/
|
|
sign = ( $1 == '-' ? -1 : 1 )
|
|
digits = $2.split( /:/ ).collect { |x| x.to_i }
|
|
val = 0; digits.each { |x| val = ( val * 60 ) + x }; val *= sign
|
|
elsif type == :Implicit
|
|
:InvalidType
|
|
else
|
|
val.to_i
|
|
end
|
|
}
|
|
|
|
class TrueClass
|
|
def is_complex_yaml?
|
|
false
|
|
end
|
|
def to_yaml( opts = {} )
|
|
opts[:KeepValue] = true
|
|
"true".to_yaml( opts )
|
|
end
|
|
end
|
|
|
|
class FalseClass
|
|
def is_complex_yaml?
|
|
false
|
|
end
|
|
def to_yaml( opts = {} )
|
|
opts[:KeepValue] = true
|
|
"false".to_yaml( opts )
|
|
end
|
|
end
|
|
|
|
YAML.add_builtin_type( 'bool' ) { |type, val|
|
|
if val =~ /\A(\+|true|True|TRUE|yes|Yes|YES|on|On|ON)\Z/
|
|
true
|
|
elsif val =~ /\A(\-|false|False|FALSE|no|No|NO|off|Off|OFF)\Z/
|
|
false
|
|
elsif type == :Implicit
|
|
:InvalidType
|
|
else
|
|
raise YAML::TypeError, "Invalid !bool string: " + val.inspect
|
|
end
|
|
}
|
|
|
|
class NilClass
|
|
def is_complex_yaml?
|
|
false
|
|
end
|
|
def to_yaml( opts = {} )
|
|
opts[:KeepValue] = true
|
|
"".to_yaml( opts )
|
|
end
|
|
end
|
|
|
|
YAML.add_builtin_type( 'null' ) { |type, val|
|
|
if val =~ /\A(\~|null|Null|NULL)\Z/
|
|
nil
|
|
elsif val.empty?
|
|
nil
|
|
elsif type == :Implicit
|
|
:InvalidType
|
|
else
|
|
raise YAML::TypeError, "Invalid !null string: " + val.inspect
|
|
end
|
|
}
|
|
|