conversion 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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