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
|