typecast.rb

zimba tm, 01/12/2011 10:01 PM

Download (6.15 KB)

 
1
###
2
#   Copyright 2010 STVS SA <stvs@stvs.ch>
3
###
4
# = typecast.rb
5
#
6
# == Copyright (c) 2004 Jonas Pfenniger
7
#
8
#   Ruby License
9
#
10
#   This module is free software. You may use, modify, and/or redistribute this
11
#   software under the same terms as Ruby.
12
#
13
#   This program is distributed in the hope that it will be useful, but WITHOUT
14
#   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15
#   FOR A PARTICULAR PURPOSE.
16
#
17
# == Author(s)
18
#
19
# * Jonas Pfenniger
20
#
21
# == Changelog
22
#
23
# 06.06.2006 - 3v1l d4y
24
#
25
#  * Removed transformation options.
26
#  * Removed StringIO typecast. It is not required by default.
27
#  * Added TypeCastException for better error reporting while coding.
28
#
29
# == Developer Notes
30
#
31
#   TODO Consider how this might fit in with method signitures, overloading,
32
#        and expiremental euphoria-like type system.
33
#
34
#   TODO Look to implement to_int, to_mailtext, to_r, to_rfc822text and to_str.
35

    
36

    
37
# Author::    Jonas Pfenniger
38
# Copyright:: Copyright (c) 2004 Jonas Pfenniger
39
# License::   Ruby License
40

    
41
require 'time'
42

    
43
#require 'alt/str_reflection'
44
class String
45
  def methodize
46
    to_s.gsub(/([A-Z])/, '_\1').downcase.gsub(/^_/,'').gsub(/(::|\/)_?/, '__')
47
  end
48
  
49
  def modulize
50
    to_s.gsub(/(__|\/)(.?)/){ "::" + $2.upcase }.gsub(/(^|_)(.)/){ $2.upcase }
51
  end
52
end
53

    
54
# = TypeCast
55
#
56
# Provides a generic simple type conversion utility. All the ruby core
57
# conversions are available by default.
58
#
59
# To implement a new type conversion, you have two choices :
60
#
61
# Take :
62
#
63
#  class CustomType
64
#    def initialize(my_var)
65
#      @my_var = my_var
66
#    end
67
#  end
68
#
69
# * Define a to_class_name instance method
70
#
71
#  class CustomType
72
#    def to_string
73
#      my_var.to_s
74
#    end
75
#  end
76
#
77
#  c = CustomType.new 1234
78
#  s.cast_to String   =>  "1234" (String)
79
#
80
# * Define a from_class_name class method
81
#
82
#  class CustomType
83
#    def self.from_string(str)
84
#      self.new(str)
85
#    end
86
#  end
87
#
88
#  "1234".cast_to CustomType  =>  #<CustomType:0xb7d1958c @my_var="1234">
89
#
90
#
91
# Those two methods are equivalent in the result. It was coded like that to
92
# avoid the pollution of core classes with tons of to_* methods.
93
#
94
# The standard methods to_s, to_f, to_i, to_a and to_sym are also used by
95
# this system if available.
96
#
97
# == Usage
98
#
99
#  "1234".cast_to Float     => 1234.0  (Float)
100
#  Time.cast_from("6:30")   => 1234.0   (Time)
101
#
102
# == FAQ
103
#
104
# Why didn't you name the `cast_to` method to `to` ?
105
#
106
#   Even if it would make the syntax more friendly, I suspect it could cause
107
#   a lot of collisions with already existing code. The goal is that each
108
#   time you call cast_to, you either get your result, either a
109
#   TypeCastException
110
#
111

    
112
class TypeCastException < Exception; end
113

    
114
#
115

    
116
class Object
117

    
118
  # class TypeCastException < Exception; end
119

    
120
  # Cast an object to another
121
  #
122
  #    1234.cast_to(String)  => "1234"
123
  #
124
  def cast_to(klass)
125
    klass.cast_from(self)
126
  end
127

    
128
  # Cast on object from another
129
  #
130
  #   String.cast_from(1234) => "1234"
131
  #
132
  def cast_from(object)
133
    method_to = "to_#{self.name.methodize}".to_sym
134
    if object.respond_to? method_to
135
      return object.send(method_to)
136
    end
137

    
138
    method_from = "from_#{object.class.name.methodize}".to_sym
139
    if respond_to? method_from
140
      return send(method_from, object)
141
    end
142

    
143
    raise TypeCastException, "TypeCasting from #{object.class.name} to #{self.name} not supported"
144
  end
145
end
146

    
147
# Extend the ruby core
148

    
149
class Array
150
  class << self
151
    def cast_from(object)
152
      return super
153
    rescue TypeCastException
154
      return object.to_a if object.respond_to? :to_a
155
      raise
156
    end
157
  end
158
end
159

    
160
class Float
161
  class << self
162
    def cast_from(object)
163
      return super
164
    rescue TypeCastException
165
      return object.to_f if object.respond_to? :to_f
166
      raise
167
    end
168
  end
169
end
170

    
171
class Integer
172
  class << self
173
    def cast_from(object)
174
      return super
175
    rescue TypeCastException
176
      return object.to_i if object.respond_to? :to_i
177
      raise
178
    end
179
  end
180
end
181

    
182
class String
183
  class << self
184
    def cast_from(object)
185
      return super
186
    rescue TypeCastException
187
      return object.to_s if object.respond_to? :to_s
188
      raise
189
    end
190
  end
191
end
192

    
193
class Symbol
194
  class << self
195
    def cast_from(object)
196
      return super
197
    rescue TypeCastException
198
      return object.to_sym if object.respond_to? :to_sym
199
      raise
200
    end
201
  end
202
end
203

    
204
# Extensions
205

    
206
class Class
207
  class << self
208

    
209
    # "string".cast_to Class         #=> String
210

    
211
    def from_string(string)
212
      string = string.to_s.modulize
213
      base   = string.sub!(/^::/, '') ? Object : (self.kind_of?(Module) ? self : self.class )
214
      klass  = string.split(/::/).inject(base){ |mod, name| mod.const_get(name) }
215
      return klass if klass.kind_of? Class
216
      nil
217
    rescue
218
      nil
219
    end
220

    
221
    alias_method :from_symbol, :from_string
222

    
223
  end
224
end
225

    
226
class Time
227
  class << self
228
    def from_string(string, options={})
229
      parse(string)
230
    rescue
231
      nil
232
    end
233
  end
234
end
235

    
236

    
237
#  _____         _
238
# |_   _|__  ___| |_
239
#   | |/ _ \/ __| __|
240
#   | |  __/\__ \ |_
241
#   |_|\___||___/\__|
242
#
243
=begin test
244

245
  require 'test/unit'
246

247
  class TestClass
248
    attr_accessor :my_var
249
    def initialize(my_var); @my_var = my_var; end
250

251
    def to_string(options={})
252
      @my_var
253
    end
254

255
    class << self
256
      def from_string(string, options={})
257
        self.new( string )
258
      end
259
    end
260
  end
261

262
  class TC_TypeCast < Test::Unit::TestCase
263

264
    def setup
265
      @test_string = "this is a test"
266
      @test_class = TestClass.new(@test_string)
267
    end
268

269
    def test_to_string
270
      assert_equal( '1234', 1234.cast_to(String) )
271
    end
272

273
    def test_custom_to_string
274
      assert_equal( @test_string, @test_class.cast_to(String) )
275
    end
276

277
    def test_custom_from_string
278
      assert_equal( @test_class.my_var, @test_string.cast_to(TestClass).my_var )
279
    end
280

281
    def test_string_to_class
282
      assert_equal( Test::Unit::TestCase, "Test::Unit::TestCase".cast_to(Class) )
283
    end
284

285
    def test_string_to_time
286
      assert_equal( "Mon Oct 10 00:00:00 2005", "2005-10-10".cast_to(Time).strftime("%a %b %d %H:%M:%S %Y") )
287
    end
288

289
    def test_no_converter
290
      "sfddsf".cast_to( ::Regexp )
291
      assert(1+1==3, 'should not get here')
292
    rescue TypeCastException => ex
293
      assert_equal(TypeCastException, ex.class)
294
    end
295
  end
296

297
=end