# -*- mode: ruby; ruby-indent-level: 4; tab-width: 4 -*- vim: sw=4 ts=4 require 'date' # # Type conversions # class Class def to_yaml( opts = {} ) raise TypeError, "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_variable_get( 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_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] ) } self.to_yaml_properties.each { |m| map.add( m, instance_variable_get( m ) ) } } } end end YAML.add_ruby_type( /^struct/ ) { |type, val| if Hash === val struct_type = nil # # Use existing Struct if it exists # props = {} val.delete_if { |k,v| props[k] = v if k =~ /^@/ } 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 = YAML::object_maker( struct_type, {} ) st.members.each { |m| st.send( "#{m}=", val[m] ) } props.each { |k,v| st.instance_variable_set(k, v) } 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_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_variable_get( 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' ) } ) val.each_pair { |k,v| o.instance_variable_set("@#{k}", v) } o } # # String#to_yaml # class String def is_complex_yaml? to_yaml_fold or not to_yaml_properties.empty? or self =~ /\n.+/ end def is_binary_data? ( self.count( "^ -~", "^\r\n" ) / self.size > 0.3 || self.count( "\x00" ) > 0 ) end def to_yaml_type "!ruby/string#{ ":#{ self.class }" if self.class != ::String }" end def to_yaml_fold nil 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 not to_yaml_properties.empty? out.map( self.to_yaml_type ) { |map| map.add( 'str', "#{self}" ) to_yaml_properties.each { |m| map.add( m, instance_variable_get( m ) ) } } elsif self.is_binary_data? out.binary_base64( self ) elsif self =~ /#{YAML::ESCAPE_CHAR}/ out.node_text( self, '"' ) else out.node_text( self, to_yaml_fold ) end else ostr = if out.options(:KeepValue) self elsif empty? "''" elsif self =~ /^[^#{YAML::WORD_CHAR}\/]| \#|#{YAML::ESCAPE_CHAR}|[#{YAML::SPACE_INDICATORS}]( |$)| $|\n|\'/ out.node_text( self, '"' ); nil elsif YAML.detect_implicit( self ) != 'str' out.node_text( self, '"' ); nil else self end out.simple( ostr ) unless ostr.nil? end } end end YAML.add_ruby_type( /^string/ ) { |type, val| type, obj_class = YAML.read_type_class( type, ::String ) if Hash === val s = YAML::object_maker( obj_class, {} ) # Thank you, NaHi String.instance_method(:initialize). bind(s). call( val.delete( 'str' ) ) val.each { |k,v| s.instance_variable_set( k, v ) } s else raise YAML::Error, "Invalid 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 << ":" self.id2name.to_yaml( :Emitter => out ) } end end symbol_proc = Proc.new { |type, val| if String === val val = YAML::load( "--- #{val}") if 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? true end def to_yaml_type "!ruby/range#{ if self.class != ::Range; ":#{ self.class }"; end }" end def to_yaml( opts = {} ) YAML::quick_emit( self.object_id, opts ) { |out| if self.begin.is_complex_yaml? or self.begin.respond_to? :to_str or self.end.is_complex_yaml? or self.end.respond_to? :to_str or not to_yaml_properties.empty? out.map( to_yaml_type ) { |map| map.add( 'begin', self.begin ) map.add( 'end', self.end ) map.add( 'excl', self.exclude_end? ) to_yaml_properties.each { |m| map.add( m, instance_variable_get( m ) ) } } else out << "#{ to_yaml_type } '" self.begin.to_yaml(:Emitter => out) out << ( self.exclude_end? ? "..." : ".." ) self.end.to_yaml(:Emitter => out) out << "'" end } end end YAML.add_ruby_type( /^range/ ) { |type, val| type, obj_class = YAML.read_type_class( type, ::Range ) inr = %r'(\w+|[+-]?\d+(?:\.\d+)?(?:e[+-]\d+)?|"(?:[^\\"]|\\.)*")' opts = {} if String === val and val =~ /^#{inr}(\.{2,3})#{inr}$/o r1, rdots, r2 = $1, $2, $3 opts = { 'begin' => YAML.load( "--- #{r1}" ), 'end' => YAML.load( "--- #{r2}" ), 'excl' => rdots.length == 3 } val = {} elsif Hash === val opts['begin'] = val.delete('begin') opts['end'] = val.delete('end') opts['excl'] = val.delete('excl') end if Hash === opts r = YAML::object_maker( obj_class, {} ) # Thank you, NaHi Range.instance_method(:initialize). bind(r). call( opts['begin'], opts['end'], opts['excl'] ) val.each { |k,v| r.instance_variable_set( k, v ) } r else raise YAML::Error, "Invalid Range: " + val.inspect end } # # Make an Regexp # class Regexp def is_complex_yaml? self.class != Regexp or not to_yaml_properties.empty? end def to_yaml_type "!ruby/regexp#{ if self.class != ::Regexp; ":#{ self.class }"; end }" end def to_yaml( opts = {} ) YAML::quick_emit( nil, opts ) { |out| if self.is_complex_yaml? out.map( self.to_yaml_type ) { |map| src = self.inspect if src =~ /\A\/(.*)\/([a-z]*)\Z/ map.add( 'regexp', $1 ) map.add( 'mods', $2 ) else raise YAML::Error, "Invalid Regular expression: " + src end to_yaml_properties.each { |m| map.add( m, instance_variable_get( m ) ) } } else out << "#{ to_yaml_type } " self.inspect.to_yaml( :Emitter => out ) end } end end regexp_proc = Proc.new { |type, val| type, obj_class = YAML.read_type_class( type, ::Regexp ) if String === val and val =~ /^\/(.*)\/([mix]*)$/ val = { 'regexp' => $1, 'mods' => $2 } end if Hash === val mods = nil unless val['mods'].to_s.empty? mods = 0x00 mods |= Regexp::EXTENDED if val['mods'].include?( 'x' ) mods |= Regexp::IGNORECASE if val['mods'].include?( 'i' ) mods |= Regexp::MULTILINE if val['mods'].include?( 'm' ) end val.delete( 'mods' ) r = YAML::object_maker( obj_class, {} ) Regexp.instance_method(:initialize). bind(r). call( val.delete( 'regexp' ), mods ) val.each { |k,v| r.instance_variable_set( k, v ) } r 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? self.class != Time or not to_yaml_properties.empty? end def to_yaml_type "!ruby/time#{ if self.class != ::Time; ":#{ self.class }"; end }" end def to_yaml( opts = {} ) YAML::quick_emit( nil, opts ) { |out| if self.is_complex_yaml? out.map( self.to_yaml_type ) { |map| map.add( 'at', ::Time.at( self ) ) to_yaml_properties.each { |m| map.add( m, instance_variable_get( m ) ) } } else tz = "Z" # from the tidy Tobias Peters 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 standard = self.strftime( "%Y-%m-%d %H:%M:%S" ) standard += ".%06d" % [usec] if usec.nonzero? standard += " %s" % [tz] standard.to_yaml( :Emitter => out, :KeepValue => true ) end } end end YAML.add_ruby_type( /^time/ ) { |type, val| type, obj_class = YAML.read_type_class( type, ::Time ) if Hash === val t = obj_class.at( val.delete( 'at' ) ) val.each { |k,v| t.instance_variable_set( k, v ) } t else raise YAML::Error, "Invalid Time: " + 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 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 class NilClass def is_complex_yaml? false end def to_yaml( opts = {} ) opts[:KeepValue] = true "".to_yaml( opts ) end end