|
###
|
|
# Copyright 2010 STVS SA <stvs@stvs.ch>
|
|
###
|
|
# = typecast.rb
|
|
#
|
|
# == Copyright (c) 2004 Jonas Pfenniger
|
|
#
|
|
# Ruby License
|
|
#
|
|
# This module is free software. You may use, modify, and/or redistribute this
|
|
# software under the same terms as Ruby.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE.
|
|
#
|
|
# == Author(s)
|
|
#
|
|
# * Jonas Pfenniger
|
|
#
|
|
# == Changelog
|
|
#
|
|
# 06.06.2006 - 3v1l d4y
|
|
#
|
|
# * Removed transformation options.
|
|
# * Removed StringIO typecast. It is not required by default.
|
|
# * Added TypeCastException for better error reporting while coding.
|
|
#
|
|
# == Developer Notes
|
|
#
|
|
# TODO Consider how this might fit in with method signitures, overloading,
|
|
# and expiremental euphoria-like type system.
|
|
#
|
|
# TODO Look to implement to_int, to_mailtext, to_r, to_rfc822text and to_str.
|
|
|
|
|
|
# Author:: Jonas Pfenniger
|
|
# Copyright:: Copyright (c) 2004 Jonas Pfenniger
|
|
# License:: Ruby License
|
|
|
|
require 'time'
|
|
|
|
#require 'alt/str_reflection'
|
|
class String
|
|
def methodize
|
|
to_s.gsub(/([A-Z])/, '_\1').downcase.gsub(/^_/,'').gsub(/(::|\/)_?/, '__')
|
|
end
|
|
|
|
def modulize
|
|
to_s.gsub(/(__|\/)(.?)/){ "::" + $2.upcase }.gsub(/(^|_)(.)/){ $2.upcase }
|
|
end
|
|
end
|
|
|
|
# = TypeCast
|
|
#
|
|
# Provides a generic simple type conversion utility. All the ruby core
|
|
# conversions are available by default.
|
|
#
|
|
# To implement a new type conversion, you have two choices :
|
|
#
|
|
# Take :
|
|
#
|
|
# class CustomType
|
|
# def initialize(my_var)
|
|
# @my_var = my_var
|
|
# end
|
|
# end
|
|
#
|
|
# * Define a to_class_name instance method
|
|
#
|
|
# class CustomType
|
|
# def to_string
|
|
# my_var.to_s
|
|
# end
|
|
# end
|
|
#
|
|
# c = CustomType.new 1234
|
|
# s.cast_to String => "1234" (String)
|
|
#
|
|
# * Define a from_class_name class method
|
|
#
|
|
# class CustomType
|
|
# def self.from_string(str)
|
|
# self.new(str)
|
|
# end
|
|
# end
|
|
#
|
|
# "1234".cast_to CustomType => #<CustomType:0xb7d1958c @my_var="1234">
|
|
#
|
|
#
|
|
# Those two methods are equivalent in the result. It was coded like that to
|
|
# avoid the pollution of core classes with tons of to_* methods.
|
|
#
|
|
# The standard methods to_s, to_f, to_i, to_a and to_sym are also used by
|
|
# this system if available.
|
|
#
|
|
# == Usage
|
|
#
|
|
# "1234".cast_to Float => 1234.0 (Float)
|
|
# Time.cast_from("6:30") => 1234.0 (Time)
|
|
#
|
|
# == FAQ
|
|
#
|
|
# Why didn't you name the `cast_to` method to `to` ?
|
|
#
|
|
# Even if it would make the syntax more friendly, I suspect it could cause
|
|
# a lot of collisions with already existing code. The goal is that each
|
|
# time you call cast_to, you either get your result, either a
|
|
# TypeCastException
|
|
#
|
|
|
|
class TypeCastException < Exception; end
|
|
|
|
#
|
|
|
|
class Object
|
|
|
|
# class TypeCastException < Exception; end
|
|
|
|
# Cast an object to another
|
|
#
|
|
# 1234.cast_to(String) => "1234"
|
|
#
|
|
def cast_to(klass)
|
|
klass.cast_from(self)
|
|
end
|
|
|
|
# Cast on object from another
|
|
#
|
|
# String.cast_from(1234) => "1234"
|
|
#
|
|
def cast_from(object)
|
|
method_to = "to_#{self.name.methodize}".to_sym
|
|
if object.respond_to? method_to
|
|
return object.send(method_to)
|
|
end
|
|
|
|
method_from = "from_#{object.class.name.methodize}".to_sym
|
|
if respond_to? method_from
|
|
return send(method_from, object)
|
|
end
|
|
|
|
raise TypeCastException, "TypeCasting from #{object.class.name} to #{self.name} not supported"
|
|
end
|
|
end
|
|
|
|
# Extend the ruby core
|
|
|
|
class Array
|
|
class << self
|
|
def cast_from(object)
|
|
return super
|
|
rescue TypeCastException
|
|
return object.to_a if object.respond_to? :to_a
|
|
raise
|
|
end
|
|
end
|
|
end
|
|
|
|
class Float
|
|
class << self
|
|
def cast_from(object)
|
|
return super
|
|
rescue TypeCastException
|
|
return object.to_f if object.respond_to? :to_f
|
|
raise
|
|
end
|
|
end
|
|
end
|
|
|
|
class Integer
|
|
class << self
|
|
def cast_from(object)
|
|
return super
|
|
rescue TypeCastException
|
|
return object.to_i if object.respond_to? :to_i
|
|
raise
|
|
end
|
|
end
|
|
end
|
|
|
|
class String
|
|
class << self
|
|
def cast_from(object)
|
|
return super
|
|
rescue TypeCastException
|
|
return object.to_s if object.respond_to? :to_s
|
|
raise
|
|
end
|
|
end
|
|
end
|
|
|
|
class Symbol
|
|
class << self
|
|
def cast_from(object)
|
|
return super
|
|
rescue TypeCastException
|
|
return object.to_sym if object.respond_to? :to_sym
|
|
raise
|
|
end
|
|
end
|
|
end
|
|
|
|
# Extensions
|
|
|
|
class Class
|
|
class << self
|
|
|
|
# "string".cast_to Class #=> String
|
|
|
|
def from_string(string)
|
|
string = string.to_s.modulize
|
|
base = string.sub!(/^::/, '') ? Object : (self.kind_of?(Module) ? self : self.class )
|
|
klass = string.split(/::/).inject(base){ |mod, name| mod.const_get(name) }
|
|
return klass if klass.kind_of? Class
|
|
nil
|
|
rescue
|
|
nil
|
|
end
|
|
|
|
alias_method :from_symbol, :from_string
|
|
|
|
end
|
|
end
|
|
|
|
class Time
|
|
class << self
|
|
def from_string(string, options={})
|
|
parse(string)
|
|
rescue
|
|
nil
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
# _____ _
|
|
# |_ _|__ ___| |_
|
|
# | |/ _ \/ __| __|
|
|
# | | __/\__ \ |_
|
|
# |_|\___||___/\__|
|
|
#
|
|
=begin test
|
|
|
|
require 'test/unit'
|
|
|
|
class TestClass
|
|
attr_accessor :my_var
|
|
def initialize(my_var); @my_var = my_var; end
|
|
|
|
def to_string(options={})
|
|
@my_var
|
|
end
|
|
|
|
class << self
|
|
def from_string(string, options={})
|
|
self.new( string )
|
|
end
|
|
end
|
|
end
|
|
|
|
class TC_TypeCast < Test::Unit::TestCase
|
|
|
|
def setup
|
|
@test_string = "this is a test"
|
|
@test_class = TestClass.new(@test_string)
|
|
end
|
|
|
|
def test_to_string
|
|
assert_equal( '1234', 1234.cast_to(String) )
|
|
end
|
|
|
|
def test_custom_to_string
|
|
assert_equal( @test_string, @test_class.cast_to(String) )
|
|
end
|
|
|
|
def test_custom_from_string
|
|
assert_equal( @test_class.my_var, @test_string.cast_to(TestClass).my_var )
|
|
end
|
|
|
|
def test_string_to_class
|
|
assert_equal( Test::Unit::TestCase, "Test::Unit::TestCase".cast_to(Class) )
|
|
end
|
|
|
|
def test_string_to_time
|
|
assert_equal( "Mon Oct 10 00:00:00 2005", "2005-10-10".cast_to(Time).strftime("%a %b %d %H:%M:%S %Y") )
|
|
end
|
|
|
|
def test_no_converter
|
|
"sfddsf".cast_to( ::Regexp )
|
|
assert(1+1==3, 'should not get here')
|
|
rescue TypeCastException => ex
|
|
assert_equal(TypeCastException, ex.class)
|
|
end
|
|
end
|
|
|
|
=end
|