From f1827a2fafaa50ba89f35c8c05beffd28b1dd6e6 Mon Sep 17 00:00:00 2001 From: why Date: Tue, 13 Sep 2005 03:58:33 +0000 Subject: * lib/yaml.rb: reworking YAML::Stream to use the new emitter. * lib/yaml/stream.rb: ditto. * lib/yaml/rubytypes.rb: added Object#yaml_new. * lib/yaml/tag.rb: the tag_subclasses? method now shows up in the class. allow taguri to be set using an accessor. continue support of Object#to_yaml_type. * ext/syck/rubyext.c: new emitter code. yaml_new and yaml_initialize get called, should they be present. consolidated all the diaspora of internal node types into the family below YAML::Syck::Node -- Map, Seq, Scalar -- all of whom are SyckNode structs pointing to Ruby data. moved Object#yaml_new into the node_import and made it the default behavior. the target_class is always called wih yaml_new, prepended a parameter, which is the klass. loaded nodes through GenericResolver show their style. new Resolver#tagurize converts type ids to taguris. * ext/syck/implicit.re: were 'y' and 'n' seriously omitted?? * ext/syck/emitter.c: renovated emitter, walks the tree in advance. consolidated redundant block_styles struct into the scalar_style struct. (this means loaded nodes can now be sent back to emitter and preserve at least its very basic formatting.) * ext/syck/gram.c: headless documents of any kind allowed. * ext/syck/node.c: new syck_replace_str methods and syck_empty_* methods for rewriting node contents, while keeping the ID and other setup info. added syck_seq_assign. * ext/syck/syck.h: reflect block_styles and new node functions. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@9141 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/yaml/basenode.rb | 2 +- lib/yaml/error.rb | 1 + lib/yaml/rubytypes.rb | 742 +++++++++++++++++++------------------------------- lib/yaml/stream.rb | 18 +- lib/yaml/syck.rb | 8 - lib/yaml/tag.rb | 86 ++++++ lib/yaml/types.rb | 151 +++++----- 7 files changed, 444 insertions(+), 564 deletions(-) create mode 100644 lib/yaml/tag.rb (limited to 'lib/yaml') diff --git a/lib/yaml/basenode.rb b/lib/yaml/basenode.rb index e88a76f3d2..a98f6d890a 100644 --- a/lib/yaml/basenode.rb +++ b/lib/yaml/basenode.rb @@ -25,7 +25,7 @@ module YAML matches.each { |m| result.push m.last } - self.class.new( 'seq', result ) + YAML.transfer( 'seq', result ) end end diff --git a/lib/yaml/error.rb b/lib/yaml/error.rb index a9df22749b..15865a9aa9 100644 --- a/lib/yaml/error.rb +++ b/lib/yaml/error.rb @@ -29,5 +29,6 @@ module YAML class Error < StandardError; end class ParseError < Error; end + class TypeError < StandardError; end end diff --git a/lib/yaml/rubytypes.rb b/lib/yaml/rubytypes.rb index 643b30b22c..ca7529217e 100644 --- a/lib/yaml/rubytypes.rb +++ b/lib/yaml/rubytypes.rb @@ -1,9 +1,9 @@ # -*- 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 @@ -11,564 +11,377 @@ class Class 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 + yaml_as "tag:ruby.yaml.org,2002:object" + def to_yaml_style; 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| + YAML::quick_emit( object_id, opts ) do |out| + out.map( taguri, to_yaml_style ) do |map| + to_yaml_properties.each do |m| map.add( m[1..-1], instance_variable_get( m ) ) - } - } - } + end + end + end 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" + yaml_as "tag:ruby.yaml.org,2002:hash" + yaml_as "tag:yaml.org,2002:map" + def yaml_initialize( tag, val ) + if Array === val + update Hash.[]( *val ) # Convert the map to a sequence + elsif Hash === val + update val else - "!ruby/hash:#{self.class}" + raise YAML::TypeError, "Invalid map explicitly tagged #{ tag }: " + val.inspect 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 ) + YAML::quick_emit( object_id, opts ) do |out| + out.map( taguri, to_yaml_style ) do |map| + each do |k, v| + map.add( k, v ) 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.allocate - 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 end # # Structs: export as a !map # class Struct - def is_complex_yaml? - true + yaml_as "tag:ruby.yaml.org,2002:struct" + def self.yaml_tag_class_name; self.name.gsub( "Struct::", "" ); end + def self.yaml_tag_read_class( name ); "Struct::#{ name }"; end + def self.yaml_new( klass, tag, 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( tag, Struct ) + rescue NameError + end + if not struct_type + struct_def = [ tag.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 do |m| + st.send( "#{m}=", val[m] ) + end + props.each do |k,v| + st.instance_variable_set(k, v) + end + st + else + raise YAML::TypeError, "Invalid Ruby Struct: " + val.inspect + end end def to_yaml( opts = {} ) - YAML::quick_emit( self.object_id, opts ) { |out| + YAML::quick_emit( object_id, opts ) do |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| + out.map( taguri, to_yaml_style ) do |map| + self.members.each do |m| map.add( m, self[m] ) - } - self.to_yaml_properties.each { |m| + end + self.to_yaml_properties.each do |m| map.add( m, instance_variable_get( m ) ) - } - } - } + end + end + end 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 + yaml_as "tag:ruby.yaml.org,2002:array" + yaml_as "tag:yaml.org,2002:seq" + def yaml_initialize( tag, val ); concat( val.to_a ); 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 = "" + YAML::quick_emit( object_id, opts ) do |out| + out.seq( taguri, to_yaml_style ) do |seq| + each do |x| + seq.add( x ) + end end - - out.seq( array_type ) { |seq| - seq.concat( self ) - } - } + end 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.allocate - 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}" + yaml_as "tag:ruby.yaml.org,2002:exception" + def Exception.yaml_new( klass, tag, val ) + o = YAML.object_maker( klass, { 'mesg' => val.delete( 'message' ) } ) + val.each_pair do |k,v| + o.instance_variable_set("@#{k}", v) + end + o 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| + YAML::quick_emit( object_id, opts ) do |out| + out.map( taguri, to_yaml_style ) do |map| + map.add( 'message', message ) + to_yaml_properties.each do |m| map.add( m[1..-1], instance_variable_get( m ) ) - } - } - } + end + end + end 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 + yaml_as "tag:ruby.yaml.org,2002:string" + yaml_as "tag:yaml.org,2002:str" def is_complex_yaml? - to_yaml_fold or not to_yaml_properties.empty? or self =~ /\n.+/ + to_yaml_style 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 }" + ( self.count( "^ -~", "^\r\n" ) / self.size > 0.3 || self.count( "\x00" ) > 0 ) unless empty? end - def to_yaml_fold - nil + def String.yaml_new( klass, tag, val ) + val = { 'str' => val } if String === val + if Hash === val + s = klass.allocate + # 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::TypeError, "Invalid String: " + val.inspect + end 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 + YAML::quick_emit( is_complex_yaml? ? object_id : nil, opts ) do |out| + if is_binary_data? + out.scalar( "tag:yaml.org,2002:binary", [self].pack("m"), :literal ) + elsif to_yaml_properties.empty? + out.scalar( taguri, self, to_yaml_style ) 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? + out.map( taguri, to_yaml_style ) do |map| + map.add( 'str', "#{self}" ) + to_yaml_properties.each do |m| + map.add( m, instance_variable_get( m ) ) + end + end end - } + 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 + yaml_as "tag:ruby.yaml.org,2002:symbol" + yaml_as "tag:ruby.yaml.org,2002:sym" + # yaml_implicit /^:/, :yaml_new + def Symbol.yaml_new( klass, tag, val ) + if String === val + val.intern + else + raise YAML::TypeError, "Invalid Symbol: " + val.inspect + end end def to_yaml( opts = {} ) - YAML::quick_emit( nil, opts ) { |out| - out << ":" - self.id2name.to_yaml( :Emitter => out ) - } + YAML::quick_emit( nil, opts ) do |out| + out.scalar( taguri, self.id2name, :plain ) + end 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 +# TODO: Rework the Range as a sequence (simpler) # class Range - def is_complex_yaml? - true - end - def to_yaml_type - "!ruby/range#{ if self.class != ::Range; ":#{ self.class }"; end }" + yaml_as "tag:ruby.yaml.org,2002:range" + def Range.yaml_new( klass, tag, val ) + 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( klass, {} ) + # 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::TypeError, "Invalid Range: " + val.inspect + 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| + YAML::quick_emit( object_id, opts ) do |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( taguri, to_yaml_style ) do |map| map.add( 'begin', self.begin ) map.add( 'end', self.end ) map.add( 'excl', self.exclude_end? ) - to_yaml_properties.each { |m| + to_yaml_properties.each do |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 + # else + # out.scalar( taguri ) do |sc| + # sc.embed( self.begin ) + # sc.concat( self.exclude_end? ? "..." : ".." ) + # sc.embed( self.end ) + # end + # end + 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 }" + yaml_as "tag:ruby.yaml.org,2002:regexp" + def Regexp.yaml_new( klass, tag, val ) + 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( klass, {} ) + 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::TypeError, "Invalid Regular expression: " + val.inspect + end end def to_yaml( opts = {} ) - YAML::quick_emit( nil, opts ) { |out| - if self.is_complex_yaml? - out.map( self.to_yaml_type ) { |map| + YAML::quick_emit( nil, opts ) do |out| + if to_yaml_properties.empty? + out.scalar( taguri, self.inspect, :plain ) + else + out.map( taguri, to_yaml_style ) do |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 + raise YAML::TypeError, "Invalid Regular expression: " + src end - to_yaml_properties.each { |m| + to_yaml_properties.each do |m| map.add( m, instance_variable_get( m ) ) - } - } - else - out << "#{ to_yaml_type } " - self.inspect.to_yaml( :Emitter => out ) + end + end end - } + 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 }" + yaml_as "tag:ruby.yaml.org,2002:time" + yaml_as "tag:yaml.org,2002:timestamp" + def Time.yaml_new( klass, tag, val ) + if Hash === val + t = val.delete( 'at' ) + val.each { |k,v| t.instance_variable_set( k, v ) } + t + else + raise YAML::TypeError, "Invalid Time: " + val.inspect + 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 ) ) - } - } + YAML::quick_emit( object_id, opts ) do |out| + 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] + if to_yaml_properties.empty? + out.scalar( taguri, standard, :plain ) 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 + out.map( taguri, to_yaml_style ) do |map| + map.add( 'at', standard ) + to_yaml_properties.each do |m| + map.add( m, instance_variable_get( m ) ) 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 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 + yaml_as "tag:yaml.org,2002:timestamp#ymd" def to_yaml( opts = {} ) - opts[:KeepValue] = true - self.to_s.to_yaml( opts ) + YAML::quick_emit( object_id, opts ) do |out| + out.scalar( "tag:yaml.org,2002:timestamp", self.to_s, :plain ) + end end end @@ -576,50 +389,51 @@ 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 ) + YAML::quick_emit( nil, opts ) do |out| + str = self.to_s + if str == "Infinity" + str = ".Inf" + elsif str == "-Infinity" + str = "-.Inf" + elsif str == "NaN" + str = ".NaN" + end + out.scalar( taguri, str, :plain ) + end end end +class Fixnum + yaml_as "tag:yaml.org,2002:int" +end +class Float + yaml_as "tag:yaml.org,2002:float" +end class TrueClass - def is_complex_yaml? - false - end + yaml_as "tag:yaml.org,2002:bool#yes" def to_yaml( opts = {} ) - opts[:KeepValue] = true - "true".to_yaml( opts ) + YAML::quick_emit( nil, opts ) do |out| + out.scalar( taguri, "true", :plain ) + end end end class FalseClass - def is_complex_yaml? - false - end + yaml_as "tag:yaml.org,2002:bool#no" def to_yaml( opts = {} ) - opts[:KeepValue] = true - "false".to_yaml( opts ) + YAML::quick_emit( nil, opts ) do |out| + out.scalar( taguri, "false", :plain ) + end end end class NilClass - def is_complex_yaml? - false - end + yaml_as "tag:yaml.org,2002:null" def to_yaml( opts = {} ) - opts[:KeepValue] = true - "".to_yaml( opts ) + YAML::quick_emit( nil, opts ) do |out| + out.scalar( taguri, "", :plain ) + end end end diff --git a/lib/yaml/stream.rb b/lib/yaml/stream.rb index 943c51526b..060fbc4200 100644 --- a/lib/yaml/stream.rb +++ b/lib/yaml/stream.rb @@ -24,19 +24,15 @@ module YAML @documents[ doc_num ] = doc end - def emit - opts = @options.dup - opts[:UseHeader] = true if @documents.length > 1 - ct = 0 - out = YAML::Syck::Emitter.new( opts ) + def emit( io = nil ) + # opts = @options.dup + # opts[:UseHeader] = true if @documents.length > 1 + out = YAML.emitter + out.reset( io || io2 = StringIO.new ) @documents.each { |v| - if ct > 0 - out << "\n--- " - end - v.to_yaml( :Emitter => out ) - ct += 1 + v.to_yaml( out ) } - out.end_object + io || ( io2.rewind; io2.read ) end end diff --git a/lib/yaml/syck.rb b/lib/yaml/syck.rb index 267067feb5..faf57e8036 100644 --- a/lib/yaml/syck.rb +++ b/lib/yaml/syck.rb @@ -4,7 +4,6 @@ # require 'syck' require 'yaml/basenode' -require 'yaml/baseemitter' module YAML module Syck @@ -16,12 +15,5 @@ module YAML include YAML::BaseNode end - # - # Mixin BaseEmitter functionality - # - class Emitter - include YAML::BaseEmitter - end - end end diff --git a/lib/yaml/tag.rb b/lib/yaml/tag.rb new file mode 100644 index 0000000000..0e25e1f85a --- /dev/null +++ b/lib/yaml/tag.rb @@ -0,0 +1,86 @@ +# -*- mode: ruby; ruby-indent-level: 4; tab-width: 4 -*- vim: sw=4 ts=4 +# $Id$ +# +# = yaml/tag.rb: methods for associating a taguri to a class. +# +# Author:: why the lucky stiff +# +module YAML + # A dictionary of taguris which map to + # Ruby classes. + @@tagged_classes = {} + + # + # Associates a taguri _tag_ with a Ruby class _cls_. The taguri is used to give types + # to classes when loading YAML. Taguris are of the form: + # + # tag:authorityName,date:specific + # + # The +authorityName+ is a domain name or email address. The +date+ is the date the type + # was issued in YYYY or YYYY-MM or YYYY-MM-DD format. The +specific+ is a name for + # the type being added. + # + # For example, built-in YAML types have 'yaml.org' as the +authorityName+ and '2002' as the + # +date+. The +specific+ is simply the name of the type: + # + # tag:yaml.org,2002:int + # tag:yaml.org,2002:float + # tag:yaml.org,2002:timestamp + # + # The domain must be owned by you on the +date+ declared. If you don't own any domains on the + # date you declare the type, you can simply use an e-mail address. + # + # tag:why@ruby-lang.org,2004:notes/personal + # + def YAML.tag_class( tag, cls ) + if @@tagged_classes.has_key? tag + warn "class #{ @@tagged_classes[tag] } held ownership of the #{ tag } tag" + end + @@tagged_classes[tag] = cls + end + + # Returns the complete dictionary of taguris, paired with classes. The key for + # the dictionary is the full taguri. The value for each key is the class constant + # associated to that taguri. + # + # YAML.tagged_classes["tag:yaml.org,2002:int"] => Integer + # + def YAML.tagged_classes + @@tagged_classes + end +end + +class Module # :nodoc: all + # Adds a taguri _tag_ to a class, used when dumping or loading the class + # in YAML. See YAML::tag_class for detailed information on typing and + # taguris. + def yaml_as( tag, sc = true ) + class_eval <<-"end;" + attr_accessor :taguri + def taguri + if respond_to? :to_yaml_type + YAML::tagurize( to_yaml_type[1..-1] ) + else + return @taguri if @taguri + tag = #{ tag.dump } + if self.class.yaml_tag_subclasses? and self.class != YAML::tagged_classes[tag] + tag = "\#{ tag }:\#{ self.class.yaml_tag_class_name }" + end + tag + end + end + def self.yaml_tag_subclasses?; #{ sc ? 'true' : 'false' }; end + end; + YAML::tag_class tag, self + end + # Transforms the subclass name into a name suitable for display + # in a subclassed tag. + def yaml_tag_class_name + self.name + end + # Transforms the subclass name found in the tag into a Ruby + # constant name. + def yaml_tag_read_class( name ) + name + end +end diff --git a/lib/yaml/types.rb b/lib/yaml/types.rb index f7772cb3a0..c890f22bfa 100644 --- a/lib/yaml/types.rb +++ b/lib/yaml/types.rb @@ -8,15 +8,14 @@ module YAML # Default private type # class PrivateType + def self.tag_subclasses?; false; end attr_accessor :type_id, :value def initialize( type, val ) @type_id = type; @value = val + @value.taguri = "x-private:#{ @type_id }" end def to_yaml( opts = {} ) - YAML::quick_emit( self.object_id, opts ) { |out| - out << " !!#{@type_id}" - value.to_yaml( :Emitter => out ) - } + @value.to_yaml( opts ) end end @@ -24,29 +23,37 @@ module YAML # Default domain type # class DomainType + def self.tag_subclasses?; false; end attr_accessor :domain, :type_id, :value def initialize( domain, type, val ) @domain = domain; @type_id = type; @value = val + @value.taguri = "tag:#{ @domain }:#{ @type_id }" end - def to_yaml_type - dom = @domain.dup - if dom =~ /\.yaml\.org,2002$/ - dom = $` - end - "#{dom}/#{@type_id}" - end def to_yaml( opts = {} ) - YAML::quick_emit( self.object_id, opts ) { |out| - out << " !#{to_yaml_type} " - value.to_yaml( :Emitter => out ) - } + @value.to_yaml( opts ) + end + end + + # + # Unresolved objects + # + class Object + def self.tag_subclasses?; false; end + def to_yaml( opts = {} ) + YAML::quick_emit( object_id, opts ) do |out| + out.map( "tag:ruby.yaml.org,2002:object:#{ @class }", to_yaml_style ) do |map| + @ivars.each do |k,v| + map.add( k, v ) + end + end + end end end # # YAML Hash class to support comments and defaults # - class SpecialHash < Object::Hash + class SpecialHash < ::Hash attr_accessor :default def inspect self.default.to_s @@ -69,12 +76,27 @@ module YAML # # Builtin collection: !omap # - class Omap < Array + class Omap < ::Array + yaml_as "tag:yaml.org,2002:omap" + def yaml_initialize( tag, val ) + if Array === val + val.each do |v| + if Hash === v + concat( v.to_a ) # Convert the map to a sequence + else + raise YAML::Error, "Invalid !omap entry: " + val.inspect + end + end + else + raise YAML::Error, "Invalid !omap: " + val.inspect + end + self + end def self.[]( *vals ) o = Omap.new - 0.step( vals.length - 1, 2 ) { |i| + 0.step( vals.length - 1, 2 ) do |i| o[vals[i]] = vals[i+1] - } + end o end def []( k ) @@ -96,36 +118,35 @@ module YAML true end def to_yaml( opts = {} ) - YAML::quick_emit( self.object_id, opts ) { |out| - out.seq( "!omap" ) { |seq| - self.each { |v| + YAML::quick_emit( self.object_id, opts ) do |out| + out.seq( taguri, to_yaml_style ) do |seq| + self.each do |v| seq.add( Hash[ *v ] ) - } - } - } - end - end - - YAML.add_builtin_type( "omap" ) { |type, val| - if Array === val - p = Omap.new - val.each { |v| - if Hash === v - p.concat( v.to_a ) # Convert the map to a sequence - else - raise YAML::Error, "Invalid !omap entry: " + val.inspect + end end - } - else - raise YAML::Error, "Invalid !omap: " + val.inspect + end end - p - } + end # # Builtin collection: !pairs # - class Pairs < Array + class Pairs < ::Array + yaml_as "tag:yaml.org,2002:pairs" + def yaml_initialize( tag, val ) + if Array === val + val.each do |v| + if Hash === v + concat( v.to_a ) # Convert the map to a sequence + else + raise YAML::Error, "Invalid !pairs entry: " + val.inspect + end + end + else + raise YAML::Error, "Invalid !pairs: " + val.inspect + end + self + end def self.[]( *vals ) p = Pairs.new 0.step( vals.length - 1, 2 ) { |i| @@ -147,50 +168,20 @@ module YAML true end def to_yaml( opts = {} ) - YAML::quick_emit( self.object_id, opts ) { |out| - out.seq( "!pairs" ) { |seq| - self.each { |v| + YAML::quick_emit( self.object_id, opts ) do |out| + out.seq( taguri, to_yaml_style ) do |seq| + self.each do |v| seq.add( Hash[ *v ] ) - } - } - } - end - end - - YAML.add_builtin_type( "pairs" ) { |type, val| - if Array === val - p = Pairs.new - val.each { |v| - if Hash === v - p.concat( v.to_a ) # Convert the map to a sequence - else - raise YAML::Error, "Invalid !pairs entry: " + val.inspect + end end - } - else - raise YAML::Error, "Invalid !pairs: " + val.inspect + end end - p - } + end # # Builtin collection: !set # - class Set < Hash - def to_yaml_type - "!set" - end + class Set < ::Hash + yaml_as "tag:yaml.org,2002:set" end - - YAML.add_builtin_type( 'set' ) { |type, val| - if Array === val - val = Set[ *val ] - elsif Hash === val - Set[ val ] - else - raise YAML::Error, "Invalid map explicitly tagged !map: " + val.inspect - end - val - } - end -- cgit v1.2.3