* io.c (rb_open_file): encoding in mode string was ignored if perm is
[ruby-svn.git] / lib / yaml / rubytypes.rb
blob35b719196ec5fea6049972f45e4b4a9fb3aaf180
1 # -*- mode: ruby; ruby-indent-level: 4; tab-width: 4 -*- vim: sw=4 ts=4
2 require 'date'
4 class Class
5         def to_yaml( opts = {} )
6                 raise TypeError, "can't dump anonymous class %s" % self.class
7         end
8 end
10 class Object
11     yaml_as "tag:ruby.yaml.org,2002:object"
12     def to_yaml_style; end
13     def to_yaml_properties; instance_variables.sort; end
14         def to_yaml( opts = {} )
15                 YAML::quick_emit( self, opts ) do |out|
16             out.map( taguri, to_yaml_style ) do |map|
17                                 to_yaml_properties.each do |m|
18                     map.add( m[1..-1], instance_variable_get( m ) )
19                 end
20             end
21         end
22         end
23 end
25 class Hash
26     yaml_as "tag:ruby.yaml.org,2002:hash"
27     yaml_as "tag:yaml.org,2002:map"
28     def yaml_initialize( tag, val )
29         if Array === val
30             update Hash.[]( *val )              # Convert the map to a sequence
31         elsif Hash === val
32             update val
33         else
34             raise YAML::TypeError, "Invalid map explicitly tagged #{ tag }: " + val.inspect
35         end
36     end
37         def to_yaml( opts = {} )
38                 YAML::quick_emit( self, opts ) do |out|
39             out.map( taguri, to_yaml_style ) do |map|
40                 each do |k, v|
41                     map.add( k, v )
42                 end
43             end
44         end
45         end
46 end
48 class Struct
49     yaml_as "tag:ruby.yaml.org,2002:struct"
50     def self.yaml_tag_class_name; self.name.gsub( "Struct::", "" ); end
51     def self.yaml_tag_read_class( name ); "Struct::#{ name }"; end
52     def self.yaml_new( klass, tag, val )
53         if Hash === val
54             struct_type = nil
56             #
57             # Use existing Struct if it exists
58             #
59             props = {}
60             val.delete_if { |k,v| props[k] = v if k =~ /^@/ }
61             begin
62                 struct_name, struct_type = YAML.read_type_class( tag, Struct )
63             rescue NameError
64             end
65             if not struct_type
66                 struct_def = [ tag.split( ':', 4 ).last ]
67                 struct_type = Struct.new( *struct_def.concat( val.keys.collect { |k| k.intern } ) ) 
68             end
70             #
71             # Set the Struct properties
72             #
73             st = YAML::object_maker( struct_type, {} )
74             st.members.each do |m|
75                 st.send( "#{m}=", val[m] )
76             end
77             props.each do |k,v|
78                 st.instance_variable_set(k, v)
79             end
80             st
81         else
82             raise YAML::TypeError, "Invalid Ruby Struct: " + val.inspect
83         end
84     end
85         def to_yaml( opts = {} )
86                 YAML::quick_emit( self, opts ) do |out|
87                         #
88                         # Basic struct is passed as a YAML map
89                         #
90             out.map( taguri, to_yaml_style ) do |map|
91                                 self.members.each do |m|
92                     map.add( m, self[m] )
93                 end
94                                 self.to_yaml_properties.each do |m|
95                     map.add( m, instance_variable_get( m ) )
96                 end
97             end
98         end
99         end
102 class Array
103     yaml_as "tag:ruby.yaml.org,2002:array"
104     yaml_as "tag:yaml.org,2002:seq"
105     def yaml_initialize( tag, val ); concat( val.to_a ); end
106         def to_yaml( opts = {} )
107                 YAML::quick_emit( self, opts ) do |out|
108             out.seq( taguri, to_yaml_style ) do |seq|
109                 each do |x|
110                     seq.add( x )
111                 end
112             end
113         end
114         end
117 class Exception
118     yaml_as "tag:ruby.yaml.org,2002:exception"
119     def Exception.yaml_new( klass, tag, val )
120         o = YAML.object_maker( klass, { 'mesg' => val.delete( 'message' ) } )
121         val.each_pair do |k,v|
122             o.instance_variable_set("@#{k}", v)
123         end
124         o
125     end
126         def to_yaml( opts = {} )
127                 YAML::quick_emit( self, opts ) do |out|
128             out.map( taguri, to_yaml_style ) do |map|
129                 map.add( 'message', message )
130                                 to_yaml_properties.each do |m|
131                     map.add( m[1..-1], instance_variable_get( m ) )
132                 end
133             end
134         end
135         end
138 class String
139     yaml_as "tag:ruby.yaml.org,2002:string"
140     yaml_as "tag:yaml.org,2002:binary"
141     yaml_as "tag:yaml.org,2002:str"
142     def is_complex_yaml?
143         to_yaml_style or not to_yaml_properties.empty? or self =~ /\n.+/
144     end
145     def is_binary_data?
146         ( self.count( "^ -~", "^\r\n" ).fdiv(self.size) > 0.3 || self.index( "\x00" ) ) unless empty?
147     end
148     def String.yaml_new( klass, tag, val )
149         val = val.unpack("m")[0] if tag == "tag:yaml.org,2002:binary"
150         val = { 'str' => val } if String === val
151         if Hash === val
152             s = klass.allocate
153             # Thank you, NaHi
154             String.instance_method(:initialize).
155                   bind(s).
156                   call( val.delete( 'str' ) )
157             val.each { |k,v| s.instance_variable_set( k, v ) }
158             s
159         else
160             raise YAML::TypeError, "Invalid String: " + val.inspect
161         end
162     end
163         def to_yaml( opts = {} )
164                 YAML::quick_emit( is_complex_yaml? ? self : nil, opts ) do |out|
165             if is_binary_data?
166                 out.scalar( "tag:yaml.org,2002:binary", [self].pack("m"), :literal )
167             elsif to_yaml_properties.empty?
168                 out.scalar( taguri, self, self =~ /^:/ ? :quote2 : to_yaml_style )
169             else
170                 out.map( taguri, to_yaml_style ) do |map|
171                     map.add( 'str', "#{self}" )
172                     to_yaml_properties.each do |m|
173                         map.add( m, instance_variable_get( m ) )
174                     end
175                 end
176             end
177         end
178         end
181 class Symbol
182     yaml_as "tag:ruby.yaml.org,2002:symbol"
183     yaml_as "tag:ruby.yaml.org,2002:sym"
184     def Symbol.yaml_new( klass, tag, val )
185         if String === val
186             val = YAML::load( val ) if val =~ /\A(["']).*\1\z/
187             val.intern
188         else
189             raise YAML::TypeError, "Invalid Symbol: " + val.inspect
190         end
191     end
192         def to_yaml( opts = {} )
193                 YAML::quick_emit( nil, opts ) do |out|
194             out.scalar( "tag:yaml.org,2002:str", self.inspect, :plain )
195         end
196         end
199 class Range
200     yaml_as "tag:ruby.yaml.org,2002:range"
201     def Range.yaml_new( klass, tag, val )
202         inr = %r'(\w+|[+-]?\d+(?:\.\d+)?(?:e[+-]\d+)?|"(?:[^\\"]|\\.)*")'
203         opts = {}
204         if String === val and val =~ /^#{inr}(\.{2,3})#{inr}$/o
205             r1, rdots, r2 = $1, $2, $3
206             opts = {
207                 'begin' => YAML.load( "--- #{r1}" ),
208                 'end' => YAML.load( "--- #{r2}" ),
209                 'excl' => rdots.length == 3
210             }
211             val = {}
212         elsif Hash === val
213             opts['begin'] = val.delete('begin')
214             opts['end'] = val.delete('end')
215             opts['excl'] = val.delete('excl')
216         end
217         if Hash === opts
218             r = YAML::object_maker( klass, {} )
219             # Thank you, NaHi
220             Range.instance_method(:initialize).
221                   bind(r).
222                   call( opts['begin'], opts['end'], opts['excl'] )
223             val.each { |k,v| r.instance_variable_set( k, v ) }
224             r
225         else
226             raise YAML::TypeError, "Invalid Range: " + val.inspect
227         end
228     end
229         def to_yaml( opts = {} )
230                 YAML::quick_emit( self, opts ) do |out|
231             # if self.begin.is_complex_yaml? or self.begin.respond_to? :to_str or
232             #   self.end.is_complex_yaml? or self.end.respond_to? :to_str or
233             #   not to_yaml_properties.empty?
234                 out.map( taguri, to_yaml_style ) do |map|
235                     map.add( 'begin', self.begin )
236                     map.add( 'end', self.end )
237                     map.add( 'excl', self.exclude_end? )
238                     to_yaml_properties.each do |m|
239                         map.add( m, instance_variable_get( m ) )
240                     end
241                 end
242             # else
243             #     out.scalar( taguri ) do |sc|
244             #         sc.embed( self.begin )
245             #         sc.concat( self.exclude_end? ? "..." : ".." )
246             #         sc.embed( self.end )
247             #     end
248             # end
249         end
250         end
253 class Regexp
254     yaml_as "tag:ruby.yaml.org,2002:regexp"
255     def Regexp.yaml_new( klass, tag, val )
256         if String === val and val =~ /^\/(.*)\/([mix]*)$/
257             val = { 'regexp' => $1, 'mods' => $2 }
258         end
259         if Hash === val
260             mods = nil
261             unless val['mods'].to_s.empty?
262                 mods = 0x00
263                 mods |= Regexp::EXTENDED if val['mods'].include?( 'x' )
264                 mods |= Regexp::IGNORECASE if val['mods'].include?( 'i' )
265                 mods |= Regexp::MULTILINE if val['mods'].include?( 'm' )
266             end
267             val.delete( 'mods' )
268             r = YAML::object_maker( klass, {} )
269             Regexp.instance_method(:initialize).
270                   bind(r).
271                   call( val.delete( 'regexp' ), mods )
272             val.each { |k,v| r.instance_variable_set( k, v ) }
273             r
274         else
275             raise YAML::TypeError, "Invalid Regular expression: " + val.inspect
276         end
277     end
278         def to_yaml( opts = {} )
279                 YAML::quick_emit( nil, opts ) do |out|
280             if to_yaml_properties.empty?
281                 out.scalar( taguri, self.inspect, :plain )
282             else
283                 out.map( taguri, to_yaml_style ) do |map|
284                     src = self.inspect
285                     if src =~ /\A\/(.*)\/([a-z]*)\Z/
286                         map.add( 'regexp', $1 )
287                         map.add( 'mods', $2 )
288                     else
289                                 raise YAML::TypeError, "Invalid Regular expression: " + src
290                     end
291                     to_yaml_properties.each do |m|
292                         map.add( m, instance_variable_get( m ) )
293                     end
294                 end
295             end
296         end
297         end
300 class Time
301     yaml_as "tag:ruby.yaml.org,2002:time"
302     yaml_as "tag:yaml.org,2002:timestamp"
303     def Time.yaml_new( klass, tag, val )
304         if Hash === val
305             t = val.delete( 'at' )
306             val.each { |k,v| t.instance_variable_set( k, v ) }
307             t
308         else
309             raise YAML::TypeError, "Invalid Time: " + val.inspect
310         end
311     end
312         def to_yaml( opts = {} )
313                 YAML::quick_emit( self, opts ) do |out|
314             tz = "Z"
315             # from the tidy Tobias Peters <t-peters@gmx.de> Thanks!
316             unless self.utc?
317                 utc_same_instant = self.dup.utc
318                 utc_same_writing = Time.utc(year,month,day,hour,min,sec,usec)
319                 difference_to_utc = utc_same_writing - utc_same_instant
320                 if (difference_to_utc < 0) 
321                     difference_sign = '-'
322                     absolute_difference = -difference_to_utc
323                 else
324                     difference_sign = '+'
325                     absolute_difference = difference_to_utc
326                 end
327                 difference_minutes = (absolute_difference/60).round
328                 tz = "%s%02d:%02d" % [ difference_sign, difference_minutes / 60, difference_minutes % 60]
329             end
330             standard = self.strftime( "%Y-%m-%d %H:%M:%S" )
331             standard += ".%06d" % [usec] if usec.nonzero?
332             standard += " %s" % [tz]
333             if to_yaml_properties.empty?
334                 out.scalar( taguri, standard, :plain )
335             else
336                 out.map( taguri, to_yaml_style ) do |map|
337                     map.add( 'at', standard )
338                     to_yaml_properties.each do |m|
339                         map.add( m, instance_variable_get( m ) )
340                     end
341                 end
342             end
343         end
344         end
347 class Date
348     yaml_as "tag:yaml.org,2002:timestamp#ymd"
349         def to_yaml( opts = {} )
350                 YAML::quick_emit( self, opts ) do |out|
351             out.scalar( "tag:yaml.org,2002:timestamp", self.to_s, :plain )
352         end
353         end
356 class Integer
357     yaml_as "tag:yaml.org,2002:int"
358         def to_yaml( opts = {} )
359                 YAML::quick_emit( nil, opts ) do |out|
360             out.scalar( "tag:yaml.org,2002:int", self.to_s, :plain )
361         end
362         end
365 class Float
366     yaml_as "tag:yaml.org,2002:float"
367         def to_yaml( opts = {} )
368                 YAML::quick_emit( nil, opts ) do |out|
369             str = self.to_s
370             if str == "Infinity"
371                 str = ".Inf"
372             elsif str == "-Infinity"
373                 str = "-.Inf"
374             elsif str == "NaN"
375                 str = ".NaN"
376             end
377             out.scalar( "tag:yaml.org,2002:float", str, :plain )
378         end
379         end
382 class TrueClass
383     yaml_as "tag:yaml.org,2002:bool#yes"
384         def to_yaml( opts = {} )
385                 YAML::quick_emit( nil, opts ) do |out|
386             out.scalar( taguri, "true", :plain )
387         end
388         end
391 class FalseClass
392     yaml_as "tag:yaml.org,2002:bool#no"
393         def to_yaml( opts = {} )
394                 YAML::quick_emit( nil, opts ) do |out|
395             out.scalar( taguri, "false", :plain )
396         end
397         end
400 class NilClass 
401     yaml_as "tag:yaml.org,2002:null"
402         def to_yaml( opts = {} )
403                 YAML::quick_emit( nil, opts ) do |out|
404             out.scalar( taguri, "", :plain )
405         end
406         end