myrrha 1.0.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.
Files changed (49) hide show
  1. data/CHANGELOG.md +5 -0
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +41 -0
  4. data/LICENCE.md +22 -0
  5. data/Manifest.txt +13 -0
  6. data/README.md +337 -0
  7. data/Rakefile +23 -0
  8. data/examples/String#toXXX.rb +40 -0
  9. data/examples/coerce.rb +26 -0
  10. data/examples/coerce_foo.rb +12 -0
  11. data/examples/coerce_foo2.rb +17 -0
  12. data/examples/coerce_foo3.rb +23 -0
  13. data/examples/coerce_intro.rb +10 -0
  14. data/examples/coerce_noext.rb +6 -0
  15. data/examples/examples_helper.rb +17 -0
  16. data/examples/friendly_but_safe_api.rb +73 -0
  17. data/examples/to_ruby_literal.rb +16 -0
  18. data/examples/to_ruby_literal_foo.rb +14 -0
  19. data/examples/to_ruby_literal_foo2.rb +17 -0
  20. data/examples/to_ruby_literal_foo3.rb +21 -0
  21. data/examples/to_ruby_literal_noext.rb +5 -0
  22. data/lib/myrrha/coerce.rb +93 -0
  23. data/lib/myrrha/loader.rb +0 -0
  24. data/lib/myrrha/to_ruby_literal.rb +76 -0
  25. data/lib/myrrha/version.rb +14 -0
  26. data/lib/myrrha/with_core_ext.rb +2 -0
  27. data/lib/myrrha.rb +295 -0
  28. data/myrrha.gemspec +191 -0
  29. data/myrrha.noespec +37 -0
  30. data/spec/coercions/test_append.rb +11 -0
  31. data/spec/coercions/test_belongs_to.rb +29 -0
  32. data/spec/coercions/test_convert.rb +23 -0
  33. data/spec/coercions/test_dup.rb +21 -0
  34. data/spec/coercions/test_subdomain.rb +11 -0
  35. data/spec/shared/a_value.rb +33 -0
  36. data/spec/spec_helper.rb +34 -0
  37. data/spec/test_assumptions.rb +18 -0
  38. data/spec/test_coerce.rb +119 -0
  39. data/spec/test_myrrha.rb +87 -0
  40. data/spec/test_to_ruby_literal.rb +19 -0
  41. data/spec/test_value.rb +6 -0
  42. data/tasks/debug_mail.rake +78 -0
  43. data/tasks/debug_mail.txt +13 -0
  44. data/tasks/examples.rake +13 -0
  45. data/tasks/gem.rake +68 -0
  46. data/tasks/spec_test.rake +79 -0
  47. data/tasks/unit_test.rake +77 -0
  48. data/tasks/yard.rake +51 -0
  49. metadata +195 -0
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # 1.0.0 / 2011-07-22
2
+
3
+ * Enhancements
4
+
5
+ * Birthday!
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org'
2
+ gemspec :name => "myrrha"
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ myrrha (1.0.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ bluecloth (2.0.11)
10
+ diff-lcs (1.1.2)
11
+ highline (1.6.2)
12
+ noe (1.3.0)
13
+ highline (~> 1.6.0)
14
+ quickl (~> 0.2.0)
15
+ wlang (~> 0.10.1)
16
+ quickl (0.2.2)
17
+ rake (0.9.2)
18
+ rspec (2.6.0)
19
+ rspec-core (~> 2.6.0)
20
+ rspec-expectations (~> 2.6.0)
21
+ rspec-mocks (~> 2.6.0)
22
+ rspec-core (2.6.4)
23
+ rspec-expectations (2.6.0)
24
+ diff-lcs (~> 1.1.2)
25
+ rspec-mocks (2.6.0)
26
+ wlang (0.10.2)
27
+ yard (0.7.2)
28
+
29
+ PLATFORMS
30
+ java
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bluecloth (~> 2.0.9)
35
+ bundler (~> 1.0)
36
+ myrrha!
37
+ noe (~> 1.3.0)
38
+ rake (~> 0.9.2)
39
+ rspec (~> 2.6.0)
40
+ wlang (~> 0.10.1)
41
+ yard (~> 0.7.2)
data/LICENCE.md ADDED
@@ -0,0 +1,22 @@
1
+ # The MIT Licence
2
+
3
+ Copyright (c) 2011 - Bernard Lambeau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,13 @@
1
+ examples/**/*
2
+ lib/**/*
3
+ spec/**/*
4
+ tasks/**/*
5
+ Rakefile
6
+ CHANGELOG.md
7
+ Gemfile
8
+ Gemfile.lock
9
+ LICENCE.md
10
+ Manifest.txt
11
+ myrrha.gemspec
12
+ myrrha.noespec
13
+ README.md
data/README.md ADDED
@@ -0,0 +1,337 @@
1
+ # Myrrha
2
+
3
+ ## Description
4
+
5
+ Myrrha provides the coercion framework which is missing to Ruby, IMHO. Coercions
6
+ are simply defined as a set of rules for converting values from source to target
7
+ domains (in an abstract sense). As a typical and useful example, it comes bundled
8
+ with a coerce() method providing a unique entry point for converting a string to
9
+ a numeric, a boolean, a date, a time, an URI, and so on.
10
+
11
+ ### Install
12
+
13
+ % [sudo] gem install myrrha
14
+
15
+ ### Bundler & Require
16
+
17
+ # Bug fixes (tiny) do not even add new default rules to coerce and
18
+ # to\_ruby\_literal. Minor version can, which could break your code.
19
+ # Therefore, please always use:
20
+ gem "alf", "~> 1.0.0"
21
+
22
+ ## Links
23
+
24
+ * http://rubydoc.info/github/blambeau/myrrha/master/frames (read this file there!)
25
+ * http://github.com/blambeau/myrrha (source code)
26
+ * http://rubygems.org/gems/myrrha (download)
27
+
28
+ ## The missing <code>coerce()</code>
29
+
30
+ Myrrha.coerce(:anything, Domain)
31
+ coerce(:anything, Domain) # with core extensions
32
+
33
+ ### What for?
34
+
35
+ Having a single entry point for coercing values from one data-type (typically
36
+ a String) to another one is very useful. Unfortunately, Ruby does not provide
37
+ such a unique entry point... Thanks to Myrrah, the following scenario is
38
+ possible and even straightforward:
39
+
40
+ require 'myrrha/with_core_ext'
41
+ require 'myrrha/coerce'
42
+ require 'date'
43
+
44
+ values = ["12", "true", "2011-07-20"]
45
+ types = [Integer, Boolean, Date]
46
+ values.zip(types).collect do |value,domain|
47
+ coerce(value, domain)
48
+ end
49
+ # => [12, true, #<Date: 2011-07-20 (...)>]
50
+
51
+ ### Example
52
+
53
+ require 'myrrha/with_core_ext'
54
+ require 'myrrha/coerce'
55
+
56
+ # it works on numerics
57
+ coerce("12", Integer) # => 12
58
+ coerce("12.0", Float) # => 12.0
59
+
60
+ # but also on regexp (through Regexp.compile)
61
+ coerce("[a-z]+", Regexp) # => /[a-z]+/
62
+
63
+ # and, yes, on Boolean (sorry Matz!)
64
+ coerce("true", Boolean) # => true
65
+ coerce("false", Boolean) # => false
66
+
67
+ # and on date and time (through Date/Time.parse)
68
+ require 'date'
69
+ require 'time'
70
+ coerce("2011-07-20", Date) # => #<Date: 2011-07-20 (4911525/2,0,2299161)>
71
+ coerce("2011-07-20 10:57", Time) # => 2011-07-20 10:57:00 +0200
72
+
73
+ # why not on URI?
74
+ require 'uri'
75
+ coerce('http://google.com', URI) # => #<URI::HTTP:0x8281ce0 URL:http://google.com>
76
+
77
+ # on nil, it always returns nil
78
+ coerce(nil, Integer) # => nil
79
+
80
+ ### No core extension? no problem!
81
+
82
+ require 'myrrha/coerce'
83
+
84
+ Myrrha.coerce("12", Integer) # => 12
85
+ Myrrha.coerce("12.0", Float) # => 12.0
86
+
87
+ Myrrha.coerce("true", Myrrha::Boolean) # => true
88
+ # [... and so on ...]
89
+
90
+ ### Adding your own coercions
91
+
92
+ The easiest way to add additional coercions is to implement a <code>coerce</code>
93
+ method on you class; it will be used in priority.
94
+
95
+ class Foo
96
+ def initialize(arg)
97
+ @arg = arg
98
+ end
99
+ def self.coerce(arg)
100
+ Foo.new(arg)
101
+ end
102
+ end
103
+
104
+ Myrrha.coerce(:hello, Foo)
105
+ # => #<Foo:0x869eee0 @arg=:hello>
106
+
107
+ If <code>Foo</code> is not your code and you don't want to make core extensions
108
+ by adding a <code>coerce</code> class method, you can simply add new rules to
109
+ Myrrha itself:
110
+
111
+ Myrrha::Coerce.append do |r|
112
+ r.coercion(Symbol, Foo) do |value, _|
113
+ Foo.new(value)
114
+ end
115
+ end
116
+
117
+ Myrrha.coerce(:hello, Foo)
118
+ # => #<Foo:0x8866f84 @arg=:hello>
119
+
120
+ Now, doing so, the new coercion rule will be shared with all Myrrha users, which
121
+ might be intrusive. Why not using your own set of coercion rules?
122
+
123
+ MyRules = Myrrha::Coerce.dup.append do |r|
124
+ r.coercion(Symbol, Foo) do |value, _|
125
+ Foo.new(value)
126
+ end
127
+ end
128
+
129
+ # Myrrha.coerce is actually a shortcut for:
130
+ Myrrha::Coerce.apply(:hello, Foo)
131
+ # => Myrrha::Error: Unable to coerce `hello` to Foo
132
+
133
+ MyRules.apply(:hello, Foo)
134
+ # => #<Foo:0x8b7d254 @arg=:hello>
135
+
136
+ ## The missing <code>to\_ruby\_literal()</code>
137
+
138
+ Myrrha.to_ruby_literal([:anything])
139
+ [:anything].to_ruby_literal # with core extensions
140
+
141
+ ### What for?
142
+
143
+ <code>Object#to\_ruby\_literal</code> has a very simple specification. Given an
144
+ object o that can be considered as a true _value_, the result of
145
+ <code>o.to\_ruby\_literal</code> must be such that the following invariant
146
+ holds:
147
+
148
+ Kernel.eval(o.to_ruby_literal) == o
149
+
150
+ That is, parsing & evaluating the literal yields the same value. When generating
151
+ (human-readable) ruby code, having a unique entry point that respects the
152
+ specification is very useful.
153
+
154
+ For almost all ruby classes, but not all, using o.inspect respects the
155
+ invariant. For example, the following is true:
156
+
157
+ Kernel.eval("hello".inspect) == "hello" # => true
158
+ Kernel.eval([1, 2, 3].inspect) == [1, 2, 3] # => true
159
+ Kernel.eval({:key => :value}.inspect) == {:key => :value} # => true
160
+ # => true
161
+
162
+ Unfortunately, this is not always the case:
163
+
164
+ Kernel.eval(Date.today.inspect) == Date.today
165
+ # => false
166
+ # => because Date.today.inspect yields "#<Date: 2011-07-20 ...", which is a comment
167
+
168
+ ### Example
169
+
170
+ Myrrha implements a very simple set of rules for implementing
171
+ <code>Object#to\_ruby\_literal</code> that works:
172
+
173
+ require 'date'
174
+ require 'myrrha/with_core_ext'
175
+ require 'myrrha/to_ruby_literal'
176
+
177
+ 1.to_ruby_literal # => "1"
178
+ Date.today.to_ruby_literal # => "Marshal.load('...')"
179
+ ["hello", Date.today].to_ruby_literal # => "['hello', Marshal.load('...')]"
180
+
181
+ Myrrha implements a best-effort strategy to return a human readable string. It
182
+ simply fallbacks to <code>Marshal.load(...)</code> when the strategy fails:
183
+
184
+ (1..10).to_ruby_literal # => "1..10"
185
+
186
+ today = Date.today
187
+ (today..today+1).to_ruby_literal # => "Marshal.load('...')"
188
+
189
+ ### No core extension? no problem!
190
+
191
+ require 'date'
192
+ require 'myrrha/to_ruby_literal'
193
+
194
+ Myrrha.to_ruby_literal(1) # => 1
195
+ Myrrha.to_ruby_literal(Date.today) # => Marshal.load("...")
196
+ # [... and so on ...]
197
+
198
+ ### Adding your own rules
199
+
200
+ The easiest way is simply to override <code>to\_ruby\_literal</code> in your
201
+ class
202
+
203
+ class Foo
204
+ attr_reader :arg
205
+ def initialize(arg)
206
+ @arg = arg
207
+ end
208
+ def to_ruby_literal
209
+ "Foo.new(#{arg.inspect})"
210
+ end
211
+ end
212
+
213
+ Myrrha.to_ruby_literal(Foo.new(:hello))
214
+ # => "Foo.new(:hello)"
215
+
216
+ As with coerce, contributing your own rule to Myrrha is possible:
217
+
218
+ Myrrha::ToRubyLiteral.append do |r|
219
+ r.coercion(Foo) do |foo, _|
220
+ "Foo.new(#{foo.arg.inspect})"
221
+ end
222
+ end
223
+
224
+ Myrrha.to_ruby_literal(Foo.new(:hello))
225
+ # => "Foo.new(:hello)"
226
+
227
+ And building your own set of rules is possible as well:
228
+
229
+ MyRules = Myrrha::ToRubyLiteral.dup.append do |r|
230
+ r.coercion(Foo) do |foo, _|
231
+ "Foo.new(#{foo.arg.inspect})"
232
+ end
233
+ end
234
+
235
+ # Myrrha.to_ruby_literal is actually a shortcut for:
236
+ Myrrha::ToRubyLiteral.apply(Foo.new(:hello))
237
+ # => "Marshal.load('...')"
238
+
239
+ MyRules.apply(Foo.new(:hello))
240
+ # => "Foo.new(:hello)"
241
+
242
+ ### Limitation
243
+
244
+ As the feature fallbacks to marshaling, everything which is marshalable will
245
+ work. As usual, <code>to\_ruby\_literal(Proc)</code> won't work.
246
+
247
+ ## The general coercion framework
248
+
249
+ A set of coercion rules can simply be created from scratch as follows:
250
+
251
+ Rules = Myrrha.coercions do |r|
252
+
253
+ # `upon` rules are tried in priority if PRE holds
254
+ r.upon(SourceDomain) do |value, requested_domain|
255
+
256
+ # PRE: - user wants to coerce `value` to a requested_domain
257
+ # - belongs_to?(value, SourceDomain)
258
+
259
+ # implement the coercion or throw(:newrule)
260
+ returned_value = something(value)
261
+
262
+ # POST: belongs_to?(returned_value, requested_domain)
263
+
264
+ end
265
+
266
+ # `coercion` rules are then tried in order if PRE holds
267
+ r.coercion(SourceDomain, TargetDomain) do |value, requested_domain|
268
+
269
+ # PRE: - user wants to coerce `value` to a requested_domain
270
+ # - belongs_to?(value, SourceDomain)
271
+ # - subdomain?(TargetDomain, requested_domain)
272
+
273
+ # implement the coercion or throw(:newrule)
274
+ returned_value = something(value)
275
+
276
+ # POST: returned_value belongs to requested_domain
277
+
278
+ end
279
+
280
+ # fallback rules are tried if everything else has failed
281
+ r.fallback(SourceDomain) do |value, requested_domain|
282
+
283
+ # exactly the same as upon rules
284
+
285
+ end
286
+
287
+ end
288
+
289
+ When the user invokes <code>Rules.apply(value, domain)</code> all rules for
290
+ which PRE holds are executed in order, until one succeed (chain of
291
+ responsibility design pattern). This means that coercions always execute in
292
+ <code>O(number of rules)</code>.
293
+
294
+ ### <code>belongs\_to?</code> and <code>subdomain?</code>
295
+
296
+ The pseudo-code given above relies on two main abstractions. Suppose the user
297
+ makes a call to <code>coerce(value, requested_domain)</code>:
298
+
299
+ * <code>belongs\_to?(value, SourceDomain)</code> is true iif
300
+ * <code>SourceDomain</code> is a <code>Proc</code> of arity 2, and
301
+ <code>SourceDomain.call(value, requested_domain)</code> yields true
302
+ * <code>SourceDomain</code> is a <code>Proc</code> of arity 1, and
303
+ <code>SourceDomain.call(value)</code> yields true
304
+ * <code>SourceDomain === value</code> yields true
305
+
306
+ * <code>subdomain?(SourceDomain,TargetDomain)</code> is true iif
307
+ * <code>SourceDomain == TargetDomain</code> yields true
308
+ * SourceDomain and TargetDomain are both classes and the latter is a super
309
+ class of the former
310
+
311
+ ### Advanced rule examples
312
+
313
+ Rules = Myrrha.coercions do |r|
314
+
315
+ # A 'catch-all' upon rule, always fired
316
+ catch_all = lambda{|v,rd| true}
317
+ r.upon(catch_all) do |value, requested_domain|
318
+ if you_can_coerce?(value)
319
+ # then do it!
320
+ else
321
+ throw(:next_rule)
322
+ end
323
+ end
324
+
325
+ # Delegate every call to the requested domain if it responds to compile
326
+ compilable = lambda{|v,rd| rd.respond_to?(:compile)}
327
+ r.upon(compilable) do |value, requested_domain|
328
+ requested_domain.compile(value)
329
+ end
330
+
331
+ # A fallback strategy if everything else fails
332
+ r.fallback(Object) do |value, requested_domain|
333
+ # always fired after everything else
334
+ # this is your last change, an Myrrha::Error will be raised if you fail
335
+ end
336
+
337
+ end
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ begin
2
+ gem "bundler", "~> 1.0"
3
+ require "bundler/setup"
4
+ rescue LoadError => ex
5
+ puts ex.message
6
+ abort "Bundler failed to load, (did you run 'gem install bundler' ?)"
7
+ end
8
+
9
+ # Dynamically load the gem spec
10
+ $gemspec_file = File.expand_path('../myrrha.gemspec', __FILE__)
11
+ $gemspec = Kernel.eval(File.read($gemspec_file))
12
+
13
+ # We run tests by default
14
+ task :default => :test
15
+
16
+ #
17
+ # Install all tasks found in tasks folder
18
+ #
19
+ # See .rake files there for complete documentation.
20
+ #
21
+ Dir["tasks/*.rake"].each do |taskfile|
22
+ instance_eval File.read(taskfile), taskfile
23
+ end
@@ -0,0 +1,40 @@
1
+ require File.expand_path('../examples_helper', __FILE__)
2
+ require 'date'
3
+
4
+ # In many cases, arguments coming from the user (in console mode, in web,
5
+ # and so on) are Strings, that must be converted to other types: numerics,
6
+ # booleans, and so on.
7
+ #
8
+ # The String class could be extended with to_Integer, to_Float, to_Date,
9
+ # to_Time and so on, but this is not a good idea... for obvious reasons.
10
+ #
11
+ # With Myrrha, building a set of rules for coercing strings is easy:
12
+
13
+ rules = Myrrha.coercions do |r|
14
+ r.coercion String, Integer, lambda{|s,t| Integer(s) }
15
+ r.coercion String, Float, lambda{|s,t| Float(s) }
16
+ r.coercion String, Date, lambda{|s,t| Date.parse(s) }
17
+ # ... add your own rules here ...
18
+ end
19
+
20
+ # Integers are recognized correctly
21
+ rules.coerce("12", Integer).should be_a(Integer)
22
+ rules.coerce("12", Integer).should eq(12)
23
+
24
+ # And so are Floats
25
+ rules.coerce("12.0", Float).should be_a(Float)
26
+ rules.coerce("12.0", Float).should eq(12.0)
27
+
28
+ #
29
+ # Interestingly, coercion to Numeric works as well. Because
30
+ # of the order in which the rules are defined, Integer are
31
+ # return in priority!
32
+ #
33
+ rules.coerce("12", Numeric).should be_a(Integer)
34
+ rules.coerce("12.0", Numeric).should be_a(Float)
35
+
36
+ # And dates will work as well
37
+ rules.coerce("2010-07-20", Date).should be_a(Date)
38
+ rules.coerce("2010-07-20", Date).should eq(Date.parse("2010-07-20"))
39
+
40
+ # Going further? See the set of rules in Myrrha::Ruby
@@ -0,0 +1,26 @@
1
+ require 'myrrha/with_core_ext'
2
+ require 'myrrha/coerce'
3
+
4
+ # it works on numerics
5
+ coerce("12", Integer) # => 12
6
+ coerce("12.0", Float) # => 12.0
7
+
8
+ # but also on regexp (through Regexp.compile)
9
+ coerce("[a-z]+", Regexp) # => /[a-z]+/
10
+
11
+ # and, yes, on Boolean (sorry Matz!)
12
+ coerce("true", Boolean) # => true
13
+ coerce("false", Boolean) # => false
14
+
15
+ # and on date and time (through Date/Time.parse)
16
+ require 'date'
17
+ require 'time'
18
+ coerce("2011-07-20", Date) # => #<Date: 2011-07-20 (4911525/2,0,2299161)>
19
+ coerce("2011-07-20 10:57", Time) # => 2011-07-20 10:57:00 +0200
20
+
21
+ # why not on URI?
22
+ require 'uri'
23
+ coerce('http://google.com', URI) # => #<URI::HTTP:0x8281ce0 URL:http://google.com>
24
+
25
+ # on nil, it always returns nil
26
+ coerce(nil, Integer) # => nil
@@ -0,0 +1,12 @@
1
+ require 'myrrha/coerce'
2
+
3
+ class Foo
4
+ def initialize(arg)
5
+ @arg = arg
6
+ end
7
+ def self.coerce(arg)
8
+ Foo.new(arg)
9
+ end
10
+ end
11
+
12
+ Myrrha.coerce(:hello, Foo)
@@ -0,0 +1,17 @@
1
+ require 'myrrha/coerce'
2
+
3
+ class Foo
4
+ def initialize(arg)
5
+ @arg = arg
6
+ end
7
+ end
8
+
9
+ Myrrha::Coerce.append do |r|
10
+ r.coercion(Symbol, Foo) do |value, _|
11
+ Foo.new(value)
12
+ end
13
+ end
14
+
15
+ Myrrha.coerce(:hello, Foo)
16
+ # => #<Foo:0x8866f84 @arg=:hello>
17
+
@@ -0,0 +1,23 @@
1
+ require 'myrrha/coerce'
2
+
3
+ class Foo
4
+ def initialize(arg)
5
+ @arg = arg
6
+ end
7
+ end
8
+
9
+ MyRules = Myrrha::Coerce.dup.append do |r|
10
+ r.coercion(Symbol, Foo) do |value, _|
11
+ Foo.new(value)
12
+ end
13
+ end
14
+
15
+ begin
16
+ Myrrha::Coerce.apply(:hello, Foo)
17
+ raise "Unexpected"
18
+ rescue Myrrha::Error
19
+ # => Myrrha::Error: Unable to coerce `hello` to Foo
20
+ end
21
+
22
+ MyRules.apply(:hello, Foo)
23
+ # => #<Foo:0x8b7d254 @arg=:hello>
@@ -0,0 +1,10 @@
1
+ require 'myrrha/with_core_ext'
2
+ require 'myrrha/coerce'
3
+ require 'date'
4
+
5
+ values = ["12", "true", "2011-07-20"]
6
+ types = [Integer, Boolean, Date]
7
+ values.zip(types).collect do |value,domain|
8
+ coerce(value, domain)
9
+ end
10
+ # => [12, true, #<Date: 2011-07-20 (...)>]
@@ -0,0 +1,6 @@
1
+ require 'myrrha/coerce'
2
+
3
+ Myrrha.coerce("12", Integer) # => 12
4
+ Myrrha.coerce("12.0", Float) # => 12.0
5
+
6
+ Myrrha.coerce("true", Myrrha::Boolean) # => true
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require "myrrha"
3
+ class Object
4
+
5
+ def eq(x)
6
+ lambda{|y| raise("Expected #{x} but was #{y} ") unless x == y}
7
+ end
8
+
9
+ def be_a(clazz)
10
+ lambda{|y| raise("Expected #{y.inspect} to be a #{clazz}") unless y.is_a?(clazz)}
11
+ end
12
+
13
+ def should(matcher)
14
+ matcher.call(self)
15
+ end
16
+
17
+ end
@@ -0,0 +1,73 @@
1
+ require File.expand_path('../examples_helper', __FILE__)
2
+
3
+ #
4
+ # Sometimes building friendly public APIs requires some type flexibility
5
+ # at the public interface, that is coercion.
6
+ #
7
+ # In the example below, the API class defines a public interface. The Foo
8
+ # class is a pseudo-internal class: it is not intended to be instantiated
9
+ # by users, but can be manipulated once obtained (typically reused as an
10
+ # argument for a later call to the API). An array of symbol is a good kind
11
+ # of Foo literals. Therefore, the API should allow both Foo instance and
12
+ # arrays of symbols in a 'flexible but safe' way.
13
+ #
14
+ class API
15
+
16
+ # Foo is an internal class, that users should not instantiate directly.
17
+ # Instances of Foo can are sometimes returned by internals, are reused
18
+ # later as API arguments (see below).
19
+ class Foo
20
+
21
+ attr_reader :elements
22
+
23
+ def initialize(elements)
24
+ @elements = elements
25
+ end
26
+
27
+ def reverse
28
+ Foo.new(elements.reverse)
29
+ end
30
+
31
+ def inspect
32
+ "Foo(#{elements.inspect})"
33
+ end
34
+ alias :to_s :inspect
35
+
36
+ end
37
+
38
+ # We define a simple rule for coercing arrays of symvols
39
+ # as Foo instances
40
+ Coercions = Myrrha.coercions do |r|
41
+ FriendlyFoo = lambda{|v| v.is_a?(Array) and v.all?{|s| s.is_a?(Symbol)}}
42
+ r.coercion FriendlyFoo, Foo, lambda{|v,t| Foo.new(v)}
43
+ end
44
+
45
+ #
46
+ # Reverses a Foo.
47
+ #
48
+ # This method may be used with a Foo instance. It also accepts an array of
49
+ # symbols as a friendly Foo literal.
50
+ #
51
+ def self.reverse(foo)
52
+ Coercions.coerce(foo, Foo).reverse
53
+ end
54
+
55
+ end # class API
56
+
57
+ # An initial call with an array of symbols work
58
+ puts(x = API.reverse([:a, :b]))
59
+
60
+ # And so is a call with a Foo instance
61
+ puts(y = API.reverse(x))
62
+
63
+ # An invalid call will simply fail
64
+ begin
65
+ API.reverse("hello")
66
+ true.should eq(false)
67
+ rescue Myrrha::Error => ex
68
+ puts ex.message
69
+ end
70
+
71
+ # this is for testing purposes
72
+ API.reverse([:a, :b]).should be_a(API::Foo)
73
+ API.reverse(API.reverse([:a, :b])).should be_a(API::Foo)