conversion 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
File without changes
data/README ADDED
@@ -0,0 +1,3 @@
1
+ README for conversion
2
+ =====================
3
+
data/Rakefile ADDED
@@ -0,0 +1,85 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ include FileUtils
11
+ require File.join(File.dirname(__FILE__), 'lib', 'conversion', 'version')
12
+
13
+ AUTHOR = "Gwendal Roué"
14
+ EMAIL = "gr@pierlis.com"
15
+ DESCRIPTION = "Conversion module obviously allows object conversion and extends attr_writer/accessor"
16
+ RUBYFORGE_PROJECT = "conversion"
17
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
18
+ BIN_FILES = %w( )
19
+
20
+
21
+ NAME = "conversion"
22
+ REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
23
+ VERS = ENV['VERSION'] || (Conversion::VERSION::STRING + (REV ? ".#{REV}" : ""))
24
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
25
+ RDOC_OPTS = ['--quiet', '--title', "conversion documentation",
26
+ "--opname", "index.html",
27
+ "--line-numbers",
28
+ "--main", "README",
29
+ "--inline-source"]
30
+
31
+ desc "Packages up conversion gem."
32
+ task :default => [:test]
33
+ task :package => [:clean]
34
+
35
+ Rake::TestTask.new("test") { |t|
36
+ t.libs << "test"
37
+ t.pattern = "test/**/*_test.rb"
38
+ t.verbose = true
39
+ }
40
+
41
+ spec =
42
+ Gem::Specification.new do |s|
43
+ s.name = NAME
44
+ s.version = VERS
45
+ s.platform = Gem::Platform::RUBY
46
+ s.has_rdoc = true
47
+ s.extra_rdoc_files = ["README", "CHANGELOG"]
48
+ s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
49
+ s.summary = DESCRIPTION
50
+ s.description = DESCRIPTION
51
+ s.author = AUTHOR
52
+ s.email = EMAIL
53
+ s.homepage = HOMEPATH
54
+ s.executables = BIN_FILES
55
+ s.rubyforge_project = RUBYFORGE_PROJECT
56
+ s.bindir = "bin"
57
+ s.require_path = "lib"
58
+ s.autorequire = "conversion"
59
+
60
+ #s.add_dependency('activesupport', '>=1.3.1')
61
+ #s.required_ruby_version = '>= 1.8.2'
62
+
63
+ s.files = %w(README CHANGELOG Rakefile) +
64
+ Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
65
+ Dir.glob("ext/**/*.{h,c,rb}") +
66
+ Dir.glob("examples/**/*.rb") +
67
+ Dir.glob("tools/*.rb")
68
+
69
+ # s.extensions = FileList["ext/**/extconf.rb"].to_a
70
+ end
71
+
72
+ Rake::GemPackageTask.new(spec) do |p|
73
+ p.need_tar = true
74
+ p.gem_spec = spec
75
+ end
76
+
77
+ task :install do
78
+ name = "#{NAME}-#{VERS}.gem"
79
+ sh %{rake package}
80
+ sh %{sudo gem install pkg/#{name}}
81
+ end
82
+
83
+ task :uninstall => [:clean] do
84
+ sh %{sudo gem uninstall #{NAME}}
85
+ end
@@ -0,0 +1,137 @@
1
+ unless Kernel.method_defined?(:singleton_class)
2
+ module Kernel
3
+ # Returns the singleton class of self
4
+ def singleton_class
5
+ class << self; self; end
6
+ end
7
+ end
8
+ end
9
+
10
+ module Conversion
11
+ # Classes that include Conversion::Accessors can manage the way they store their attributes.
12
+ #
13
+ # See Conversion::Accessors::ClassMethods
14
+ module Accessors
15
+
16
+ # Options for Class.attr_writer and Class.attr_accessor reserved by Conversion module.
17
+ ACCESSOR_OPTIONS = [:store_as, :store_mode]
18
+
19
+ # Append Conversion::Accessors features to base (the class that includes Conversion::Accessors)
20
+ def self.append_features(base) #:nodoc:
21
+ super
22
+ singleton_class.instance_eval do
23
+ alias_method :attr_accessor_before_conversion, :attr_accessor
24
+ alias_method :attr_writer_before_conversion, :attr_writer
25
+ end
26
+ base.extend(ClassMethods)
27
+ base.store_enumerable_attributes(false)
28
+ end
29
+
30
+ # This module is included in classes that includes Conversion::Accessors
31
+ module ClassMethods
32
+
33
+ @@attribute_conversion_mode = nil
34
+
35
+ # Defines readers and setters for each symbol argument.
36
+ #
37
+ # Option keys may contain :store_as and :store_mode, which are used as the target argument and :mode option of the Conversion.converter method.
38
+ #
39
+ # class A
40
+ # include Conversion::Accessors
41
+ # attr_accessor :attr1, :store_as => Integer
42
+ # attr_accessor :attr2, :store_as => Integer, :store_mode => :strong_type_checking
43
+ # end
44
+ # a = A.new
45
+ # a.attr1 = '1' # stores 1 in a.attr1
46
+ # a.attr2 = '1' # raises ArgumentError
47
+
48
+ def attr_accessor(*args)
49
+ options = args.last.is_a?(Hash) ? args.pop : {}
50
+ accessor_options = ACCESSOR_OPTIONS.inject({}) { |h, accessor_option|
51
+ option_value = options[accessor_option]
52
+ h[accessor_option] = option_value if option_value
53
+ h
54
+ }
55
+ unless accessor_options.empty?
56
+ attr_reader(*args)
57
+ args.each { |symbol| attr_writer(symbol, accessor_options) }
58
+ else
59
+ attr_accessor_before_conversion(*args)
60
+ end
61
+ nil
62
+ end
63
+
64
+ # Defines setters for each symbol argument.
65
+ #
66
+ # Option keys may contain :store_as and :store_mode, which are used as the target argument and :mode option of the Conversion.converter method.
67
+ #
68
+ # class A
69
+ # include Conversion::Accessors
70
+ # attr_writer :attr1, :store_as => Integer
71
+ # attr_writer :attr2, :store_as => Integer, :store_mode => :strong_type_checking
72
+ # end
73
+ # a = A.new
74
+ # a.attr1 = '1' # stores 1 in a.attr1
75
+ # a.attr2 = '1' # raises ArgumentError
76
+ def attr_writer(*args)
77
+ options = args.last.is_a?(Hash) ? args.pop : {}
78
+ if options
79
+ target = options[:store_as]
80
+ mode = options[:store_mode] || @@attribute_conversion_mode
81
+ converter = if @@convert_enumerable_attributes
82
+ Conversion.entries_converter(target, :mode=>mode)
83
+ else
84
+ Conversion.converter(target, :mode=>mode)
85
+ end
86
+ if converter
87
+ args.each { |symbol|
88
+ define_method("#{symbol}=") { |*value|
89
+ instance_variable_set("@#{symbol}", converter.call(*value))
90
+ }
91
+ }
92
+ return nil
93
+ end
94
+ end
95
+ attr_writer_before_conversion(*args)
96
+ nil
97
+ end
98
+
99
+ # Specifies whether entries should be converted when an enumerable is stored in an attribute
100
+ #
101
+ # class A
102
+ # include Conversion::Accessors
103
+ # attr_accessor :attr1, :store_as => Integer
104
+ #
105
+ # store_enumerable_attributes
106
+ # attr_accessor :attr2, :store_as => Integer
107
+ #
108
+ # store_enumerable_attributes false
109
+ # attr_accessor :attr3, :store_as => Integer
110
+ # end
111
+ # a = A.new
112
+ # a.attr1 = ['1'] # raises
113
+ # a.attr2 = ['1'] # stores [1] in a.attr2
114
+ # a.attr3 = ['1'] # raises
115
+ def store_enumerable_attributes(bool=true)
116
+ @@convert_enumerable_attributes = bool
117
+ end
118
+
119
+ # Specifies the default storage mode for attributes defined after this method is called
120
+ #
121
+ # class A
122
+ # include Conversion::Accessors
123
+ # attr_accessor :attr1, :store_as => Integer
124
+ #
125
+ # store_attributes_with_mode :weak
126
+ # attr_accessor :attr2, :store_as => Integer
127
+ # end
128
+ # a = A.new
129
+ # a.attr1 = 'abc' # raises
130
+ # a.attr2 = 'abc' # stores 'abc' in a.attr2
131
+ def store_attributes_with_mode(mode)
132
+ @@attribute_conversion_mode = mode
133
+ end
134
+
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,107 @@
1
+ class Bignum
2
+ class << self
3
+ # Returns a Proc that converts to Integer
4
+ #
5
+ # Bignum.to_converter_proc.call('1') # => 1
6
+ #
7
+ # Note that the result may not be a Bignum.
8
+ #
9
+ # See Integer.to_converter_proc, Conversion.converter, Object#convert_to
10
+ def to_converter_proc
11
+ Integer.to_converter_proc
12
+ end
13
+ end
14
+ end
15
+
16
+ class Fixnum
17
+ class << self
18
+ # Returns a Proc that converts to Integer
19
+ #
20
+ # Fixnum.to_converter_proc.call('1') # => 1
21
+ #
22
+ # Note that the result may not be a Fixnum.
23
+ #
24
+ # See Conversion.converter, Object#convert_to
25
+ def to_converter_proc
26
+ Integer.to_converter_proc
27
+ end
28
+ end
29
+ end
30
+
31
+ class Float
32
+ class << self
33
+ # Returns a Proc that converts to Float
34
+ #
35
+ # Float.to_converter_proc.call(1) # => 1.0
36
+ #
37
+ # See Conversion.converter, Object#convert_to
38
+ def to_converter_proc
39
+ proc { |value| value.is_a?(Float) ? value : Float(value) }
40
+ end
41
+ end
42
+ end
43
+
44
+ class Integer
45
+ class << self
46
+ # Returns a Proc that converts to Integer
47
+ #
48
+ # Integer.to_converter_proc.call(1) # => 1
49
+ # Integer.to_converter_proc.call(1.9) # => 2
50
+ # Integer.to_converter_proc.call('1.9') # => 2
51
+ # Integer.to_converter_proc.call('abc') # ArgumentError: invalid value for Float(): "abc"
52
+ #
53
+ # See Conversion.converter, Object#convert_to
54
+ def to_converter_proc
55
+ proc do |value|
56
+ if value.is_a?(Float)
57
+ value.round
58
+ else
59
+ begin
60
+ Integer(value)
61
+ rescue Exception
62
+ Float(value).round
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ class NilClass
71
+ class << self
72
+ # returns a Proc that converts to nil
73
+ #
74
+ # NilClass.to_converter_proc.call(1) # => nil
75
+ #
76
+ # See Conversion.converter, Object#convert_to
77
+ def to_converter_proc
78
+ proc { |value| nil }
79
+ end
80
+ end
81
+ end
82
+
83
+ class String
84
+ class << self
85
+ # returns a Proc that converts to String
86
+ #
87
+ # String.to_converter_proc.call(1) # => '1'
88
+ #
89
+ # See Conversion.converter, Object#convert_to
90
+ def to_converter_proc
91
+ proc { |value| value.to_s }
92
+ end
93
+ end
94
+ end
95
+
96
+ class Symbol
97
+ class << self
98
+ # returns a Proc that converts to Symbol
99
+ #
100
+ # Symbol.to_converter_proc.call('abc') # => :abc
101
+ #
102
+ # See Conversion.converter, Object#convert_to
103
+ def to_converter_proc
104
+ proc { |value| value.to_sym }
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,336 @@
1
+ # = Conversion Module
2
+ #
3
+ # Author:: Gwendal Roué (mailto:gr@pierlis.com)
4
+ # Copyright:: Copyright (c) 2006 Pierlis
5
+ # License:: Distributes under the same terms as Ruby
6
+ #
7
+ # -------
8
+ #
9
+ # The Conversion Module provides a framework for:
10
+ #
11
+ # - <b>converting values</b> to others,
12
+ # - helping classes define <b>attributes setters</b> that convert their input.
13
+ #
14
+ # -------
15
+ #
16
+ # == Converting objects to others
17
+ #
18
+ # === Type casting
19
+ #
20
+ # For instance, Integer conversion:
21
+ #
22
+ # Conversion.converter(Integer).call('1') # => 1
23
+ # '1'.convert_to(Integer) # => 1
24
+ # '1.4'.convert_to(Integer) # => 1
25
+ # 1.9.convert_to(Integer) # => 2
26
+ #
27
+ # Conversion.entries_converter(Integer).call(['2','3'])
28
+ # # => [2, 3]
29
+ # {:a=>'1', :b=>['2','3']}.convert_entries_to(Integer)
30
+ # # => {:a=>1, :b=>[2, 3]}
31
+ #
32
+ # === Free conversion
33
+ #
34
+ # Conversion is eventually based on Proc objects, so:
35
+ #
36
+ # 1.convert_to(proc { |x| x.succ }) # => 2
37
+ # 1.convert_to(:succ) # => 2
38
+ #
39
+ # == Class Attributes Management
40
+ #
41
+ # If your class includes the Conversion::Accessors module, its <tt>attr_accessor</tt> and <tt>attr_writer</tt> methods now accept some options:
42
+ #
43
+ # class Money
44
+ # include Conversion::Accessors
45
+ # attr_accessor :amount, :store_as => Float
46
+ # attr_accessor :currency, :store_as => Symbol
47
+ #
48
+ # def initialize(amount, currency=:euro)
49
+ # self.amount = amount
50
+ # self.currency = currency
51
+ # end
52
+ #
53
+ # def inspect() "#{amount} #{currency}" end
54
+ # end
55
+ #
56
+ # Money.new('1e2', :dollar) # 100.0 dollar
57
+ # 100.convert_to(Money) # 100.0 euro
58
+ #
59
+ # == Conversion Modes
60
+ #
61
+ # Conversion behavior can be altered with <i>conversion modes</i>. Module comes with five of them, called <tt>human_input</tt>, <tt>nil_on_failure</tt>, <tt>stable_nil</tt>, <tt>strong_type_checking</tt>, and <tt>weak</tt>, which we'll define below.
62
+ #
63
+ # Each of them alters the conversion process in a way that may be considered helpful.
64
+ #
65
+ # Let's start with a strong example, a class that fills itself from data that may come from a HTML form:
66
+ #
67
+ # class MyFormInput
68
+ # include Conversion::Accessors
69
+ #
70
+ # # alter the storage
71
+ # store_attributes_with_mode :human_input
72
+ #
73
+ # # define attributes
74
+ # attr_accessor :start_date, :end_date, :store_as => Date
75
+ # attr_accessor :amount, :store_as => Float
76
+ #
77
+ # # constructor
78
+ # def initialize(params)
79
+ # self.start_date = params[:start_date]
80
+ # self.end_date = params[:end_date]
81
+ # self.amount = params[:amount]
82
+ # end
83
+ # end
84
+ #
85
+ # Let's give some user data to our class:
86
+ #
87
+ # # - start_date is a m/d/y string
88
+ # # - end_date is a blank string
89
+ # # - amount contains an unbreakable space and a comma (thank you Excel)
90
+ #
91
+ # params = {:start_date => '9/18/1973', :end_date => ' ', :amount=>' 1 234,5'}
92
+ # f = MyFormInput.new(params)
93
+ #
94
+ # And let's check the stored data:
95
+ #
96
+ # f.start_date # => #<Date: ...>
97
+ # f.end_date # => nil
98
+ # f.amount # => 1234.5
99
+ #
100
+ # Ouééé !
101
+ #
102
+ # The <tt>human_input</tt> mode is <i>weak</i> in the way that we'll see below: when incoming data can't be converted, it is stored as is. This mimics the ActiveRecord::Base behavior, which strictly separates data storage from data validation.
103
+ #
104
+ # f.amount = "I'm richer that you"
105
+ # f.amount # => "I'm richer that you"
106
+ #
107
+ # Let's dig into our five modes now:
108
+ #
109
+ # === <tt>human_input</tt>
110
+ #
111
+ # ... was the real motivation behind the Conversion module:
112
+ #
113
+ # ' 1 234.5 '.convert_to(Integer, :mode=>:human_input) # => 1235
114
+ # '09/18/1973'.convert_to(Date, :mode=>:human_input) # => #<Date: ...>
115
+ #
116
+ # === <tt>nil_on_failure</tt>
117
+ #
118
+ # ... silently discards any conversion error and returns nil instead.
119
+ #
120
+ # 'abc'.convert_to(Integer, :mode=>:nil_on_failure) # => nil
121
+ # 123.convert_to(Bignum, :mode=>:nil_on_failure) # => nil
122
+ # 1234567890.convert_to(Fixnum, :mode=>:nil_on_failure) # => nil
123
+ # ['1','2','a'].convert_entries_to(Integer, :mode=>:nil_on_failure)
124
+ # # => [1, 2, nil]
125
+ #
126
+ # === <tt>stable_nil</tt>
127
+ #
128
+ # This mode makes sure nil is always converted to nil:
129
+ #
130
+ # nil.convert_to(Integer) # => 0
131
+ # nil.convert_to(Integer, :mode=>:stable_nil) # => nil
132
+ # nil.convert_to(:succ) # NoMethodError
133
+ # nil.convert_to(:succ, :mode=>:stable_nil) # => nil
134
+ #
135
+ # === <tt>strong_type_checking</tt>
136
+ #
137
+ # ... requires incoming data to already have the target type, except for nil:
138
+ #
139
+ # '1'.convert_to(Integer) # => 1
140
+ # '1'.convert_to(Integer, :mode=>:strong_type_checking) # ArgumentError
141
+ # nil.convert_to(Integer, :mode=>:strong_type_checking) # => nil
142
+ # [1,'2'].convert_entries_to(Integer, :mode=>:strong_type_checking)
143
+ # # ArgumentError
144
+ #
145
+ # === <tt>weak</tt>
146
+ #
147
+ # ... silently discards any conversion error and leaves data unchanged
148
+ #
149
+ # 'abc'.convert_to(Integer, :mode=>:weak) # => 'abc'
150
+ # 1.convert_to(:upcase, :mode=>:weak) # => 1
151
+ # ['1','2','a'].convert_to(Integer, :mode=>:weak) # => ["1", "2", "a"]
152
+ # ['1','2','a'].convert_entries_to(Integer, :mode=>:weak) # => [1, 2, "a"]
153
+ #
154
+ # == Hook up your own conversions
155
+ #
156
+ # Conversion module leaves much room for your own conversions.
157
+ #
158
+ # Read carefully the documentation for these methods :
159
+ # - Conversion.converter
160
+ # - Conversion.base_converter
161
+ #
162
+ # You'll understand how those methods are triggered:
163
+ # - Conversion.human_input_converter
164
+ # - Conversion.weak_converter
165
+ # - generally, Conversion.<mode>_converter
166
+ #
167
+ # And when those are:
168
+ # - Integer.to_converter_proc
169
+ # - Date.to_human_input_converter_proc
170
+ # - generally, <conversion_target>.to_<mode>_converter_proc
171
+ #
172
+ # After that, you'll be able to write code like:
173
+ #
174
+ # # human_input converts dates from 'm/d/y'.
175
+ # # That's a pity there's no support for the French 'd/m/y' format.
176
+ # # Let's add it by creating our own french_human_input conversion mode :
177
+ #
178
+ # class Date
179
+ # class << self
180
+ # def to_french_human_input_converter_proc
181
+ # proc { |value|
182
+ # # clean and trim the value
183
+ # value = value.convert_to(Conversion::HumanInputString)
184
+ # if value.nil?
185
+ # nil
186
+ # else
187
+ # # "d/m/y" => date(y,m,d)
188
+ # elements = value.split(/[-\/]/).convert_entries_to(Integer)
189
+ # begin
190
+ # Date.civil(elements[2], elements[1], elements[0])
191
+ # rescue
192
+ # value
193
+ # end
194
+ # end
195
+ # }
196
+ # end
197
+ # end
198
+ # end
199
+ #
200
+ # '18/09/1973'.convert_to(Date, :mode=>french_human_input) => #<Date: ...>
201
+ #
202
+ # == Invariants
203
+ #
204
+ # Whatever the object, it is true that:
205
+ # object.convert_to(object.class).equal?(object)
206
+ # # 1 -> Integer -> 1
207
+ #
208
+ # For... many objects, it is true that:
209
+ # object.convert_to(Conversion::HumanInputString).convert_to(object.class, :mode=>:human_input) == object
210
+ # # 1 -> Conversion::HumanInputString -> '1' -> Integer -> 1
211
+ #
212
+ # On that last invariant, see Conversion::HumanInputString.to_converter_proc
213
+
214
+ module Conversion
215
+ class << self
216
+ # Builds a converter Proc that converts to target, with options.
217
+ #
218
+ # Default converter is returned unless options contains a :mode (see Conversion.base_converter), and ArgumentError si raised if :mode is unsupported.
219
+ #
220
+ # Precisely, if target has a "to_#{mode}_converter_proc" method, it is used (see Integer.to_converter_proc, Date.to_human_input_converter_proc).
221
+ #
222
+ # Else, if Conversion has a "#{mode}_converter" method, it is used (see Conversion.weak_converter).
223
+ #
224
+ # See also: Object#convert_to
225
+ def converter(target, options={})
226
+ mode = options[:mode]
227
+ begin
228
+ target_mode = (mode == :base) ? nil : mode
229
+ target_builder = ['to', target_mode, 'converter_proc'].compact.join('_')
230
+ return target.send(target_builder)
231
+ rescue NoMethodError
232
+ begin
233
+ converter_mode = (mode.nil?) ? 'base' : mode.to_s
234
+ converter_builder = converter_mode+'_converter'
235
+ proc = send(converter_builder, target)
236
+ rescue NoMethodError
237
+ raise ArgumentError.new("#{options[:mode].inspect} is not a valid conversion mode")
238
+ end
239
+
240
+ begin
241
+ target.singleton_class.instance_eval { define_method(target_builder) { proc } }
242
+ rescue TypeError
243
+ # target.singleton_class.instance_eval fails for some instances, like symbols
244
+ end
245
+
246
+ proc
247
+ end
248
+ end
249
+
250
+ # Builds an enumerable converter Proc that converts to target, with options.
251
+ #
252
+ # Default converter is returned unless options contains a :mode (see Conversion.base_converter), and ArgumentError is raised if :mode is unsupported.
253
+ #
254
+ # See also: Conversion.converter, Object#convert_entries_to
255
+ def entries_converter(target, options={})
256
+ converter(target, options).convert_to(Conversion::EnumerableConverter)
257
+ end
258
+
259
+ # You're not supposed to call this method. Use Conversion.converter or Conversion.entries_converter instead.
260
+ #
261
+ # Returns a Proc that is the default converter to target.
262
+ #
263
+ # - If target is nil, returns nil
264
+ # - If target responds to :to_proc, returns target.to_proc
265
+ # - If target is a String, raise TypeError
266
+ # - If target is a Symbol, returns a Proc that send the target message to its parameter
267
+ # - If target is a Class, returns a Proc that creates a new object from its parameters
268
+ def base_converter(target)
269
+ converter =
270
+ # if target.respond_to?(:to_converter_proc)
271
+ # target.to_converter_proc
272
+ #
273
+ # elsif target.nil?
274
+ if target.nil?
275
+ nil
276
+
277
+ elsif target.respond_to?(:to_proc)
278
+ target.to_proc
279
+
280
+ elsif target.is_a?(String)
281
+ raise TypeError.new("Strings can't be coerced into Proc")
282
+
283
+ elsif target.is_a?(Symbol)
284
+ proc { |value| value.send(target) }
285
+
286
+ elsif target.is_a?(Class) &&
287
+ target.respond_to?(:new) &&
288
+ target.method(:new).arity != 0
289
+ proc { |*value|
290
+ if value.length == 1 && value.first.is_a?(target)
291
+ value.first
292
+ else
293
+ target.new(*value)
294
+ end
295
+ }
296
+ end
297
+
298
+ converter || raise(ArgumentError.new("#{target.inspect} can't be coerced into Proc"))
299
+ end
300
+
301
+ end
302
+
303
+ module EnumerableConverter #:nodoc:
304
+ class << self
305
+ # Returns a Proc that converts a converter Proc to an enumerable converter Proc
306
+
307
+ # See Conversion.converter, Object#convert_to
308
+ def to_converter_proc
309
+ proc { |converter|
310
+ converter.nil? ? nil :
311
+ proc { |value|
312
+ # challenging recursive mechanism found at http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/20469
313
+ proc { |builder| proc { |f| f.call(f) }.call( proc { |f| builder.call(proc { |*value| f.call(f).call(*value) }) }) }.call(proc { |recurse|
314
+ # the recursive proc
315
+ proc { |value|
316
+ if value.is_a?(Hash)
317
+ value.inject(value.dup.clear) { |h, (key, entry)|
318
+ h[key] = recurse.call(entry)
319
+ h
320
+ }
321
+ elsif value.is_a?(Enumerable) && !value.is_a?(String)
322
+ value.inject(value.dup.clear) { |e, entry|
323
+ e << recurse.call(entry)
324
+ }
325
+ else
326
+ converter.call(value)
327
+ end
328
+ }
329
+ }).call(value)
330
+ }
331
+ }
332
+ end
333
+ end
334
+ end
335
+ end
336
+
@@ -0,0 +1,31 @@
1
+ require 'date'
2
+
3
+ class Date
4
+ class << self
5
+ # Returns a human_input converter Proc that converts "m/d/y" dates to Date object
6
+ def to_human_input_converter_proc
7
+ proc { |value|
8
+ value = value.convert_to(Conversion::HumanInputString)
9
+ if value.nil?
10
+ nil
11
+ else
12
+ # "m/d/y" => date(y,m,d)
13
+ elements = value.split(/[-\/]/).convert_entries_to(Integer)
14
+ begin
15
+ Date.civil(elements[2], elements[0], elements[1])
16
+ rescue
17
+ value
18
+ end
19
+ end
20
+ }
21
+ end
22
+ end
23
+
24
+ # Returns "m/d/y"
25
+ #
26
+ # See Conversion::HumanInputString.to_converter_proc
27
+ def to_human_input_string
28
+ # date(y,m,d) => "m/d/y"
29
+ "#{month}/#{day}/#{year}"
30
+ end
31
+ end