summaryrefslogtreecommitdiff
path: root/lib/yaml/rubytypes.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/yaml/rubytypes.rb')
-rw-r--r--lib/yaml/rubytypes.rb558
1 files changed, 558 insertions, 0 deletions
diff --git a/lib/yaml/rubytypes.rb b/lib/yaml/rubytypes.rb
new file mode 100644
index 0000000000..b6f1e37705
--- /dev/null
+++ b/lib/yaml/rubytypes.rb
@@ -0,0 +1,558 @@
+require 'date'
+#
+# Type conversions
+#
+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.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.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.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.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|
+ type =~ /^struct:(\w+)/
+ if Hash === val
+ type = $1
+ struct_type = nil
+ struct_def = []
+ struct_name = ""
+ if $1.to_s.length > 1
+ struct_name = $1[0..$1.length]
+ struct_def << struct_name
+ end
+
+ #
+ # Use existing Struct if it exists
+ #
+ begin
+ struct_type = Struct.const_get( struct_name )
+ rescue NameError
+ end
+ if not struct_type
+ 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.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 )
+
+#
+# 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.id : nil, opts ) { |out|
+ if complex
+ if self.is_binary_data?
+ out.binary_base64( self )
+ else
+ out.node_text( self )
+ end
+ else
+ ostr = if out.options[:KeepValue]
+ self
+ elsif empty?
+ "''"
+ elsif YAML.detect_implicit( self ) != 'str'
+ "\"#{YAML.escape( self )}\""
+ elsif self =~ /#{YAML::ESCAPE_CHAR}|[#{YAML::SPACE_INDICATORS}] |\n|\'/
+ "\"#{YAML.escape( self )}\""
+ elsif self =~ /^[^#{YAML::WORD_CHAR}]/
+ "\"#{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/, &regexp_proc )
+YAML.add_ruby_type( Regexp, &regexp_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' ) { |type, val|
+ if val =~ /\A(\d{4})\-(\d{1,2})\-(\d{1,2})[Tt](\d{2})\:(\d{2})\:(\d{2})(\.\d{1,2})?(Z|[-+][0-9][0-9](?:\:[0-9][0-9])?)\Z/
+ YAML.mktime( *$~.to_a[1,8] )
+ elsif val =~ /\A(\d{4})\-(\d{1,2})\-(\d{1,2})[ \t]+(\d{2})\:(\d{2})\:(\d{2})(\.\d+)?[ \t]+(Z|[-+][0-9][0-9](?:\:[0-9][0-9])?)\Z/
+ YAML.mktime( *$~.to_a[1,8] )
+ elsif val =~ /\A(\d{4})\-(\d{1,2})\-(\d{1,2})[ \t]+(\d{2})\:(\d{2})\:(\d{2})(\.\d{1,2})?\Z/
+ YAML.mktime( *$~.to_a[1,7] )
+ elsif val =~ /\A(\d{4})\-(\d{1,2})\-(\d{1,2})\Z/
+ Date.new($1.to_i, $2.to_i, $3.to_i)
+ elsif type == :Implicit
+ :InvalidType
+ 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
+}
+