myrrha 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,75 @@
1
+ # 1.1.0 / 2011-07-28
2
+
3
+ ## Enhancements to coerce()
4
+
5
+ * Added coercion rules from Symbol/String to Module/Class
6
+
7
+ coerce("Integer", Class) # => Integer
8
+ coerce(:Integer, Class) # => Integer
9
+ coerce("Myrrha::Version", Module) # => Myrrha::Version
10
+ [... and so on ...]
11
+
12
+ * Added following coercion rules for Booleans
13
+
14
+ coerce("true", TrueClass) # => true
15
+ coerce("false", FalseClass) # => false
16
+
17
+ * Added coercion rule from any Object to String through ruby's String(). Note
18
+ that even with this coercion rule, coerce(nil, String) returns nil as that
19
+ rule has higher priority.
20
+
21
+ * require('time') is automatically issued when trying to coerce a String to
22
+ a Time. Time.parse is obviously needed.
23
+
24
+ * Myrrha::Boolean (Boolean with core extensions) is now a factored domain (see
25
+ below). Therefore, it is now a true Class instance.
26
+
27
+ ## Enhancements to the general coercion mechanism
28
+
29
+ * An optimistic coercion is tried when a rule is encountered whose target
30
+ domain is a super domain of the requested one. Coercion only succeeds if
31
+ the coerced value correctly belongs to the latter domain. Example:
32
+
33
+ rules = Myrrha.coercions do |r|
34
+ r.coercion String, Numeric, lambda{|s,t| Integer(s)}
35
+ end
36
+ rules.coerce("12", Integer) # => 12 in 1.1.0 while it failed in 1.0.0
37
+ rules.coerce("12", Float) # => Myrrha::Error
38
+
39
+ * You can now specify a coercion path, through an array of domains. For
40
+ example (completely contrived, of course):
41
+
42
+ rules = Myrrha.coercions do |r|
43
+ r.coercion String, Symbol, lambda{|s,t| s.to_sym }
44
+ r.coercion Float, String, lambda{|s,t| s.to_s }
45
+ r.coercion Integer, Float, lambda{|s,t| Float(s) }
46
+ r.coercion Integer, Symbol, [Float, String]
47
+ end
48
+ rules.coerce(12, Symbol) # => :"12.0" as Symbol(String(Float(12)))
49
+
50
+ * You can now define domains through specialization by constraint (sbyc) on ruby
51
+ classes, using Myrrha.domain:
52
+
53
+ # Create a positive integer domain, as ... positive integers
54
+ PosInt = Myrrha.domain(Integer){|i| i > 0 }
55
+
56
+ Created domain is a real Class instance, that correctly responds to :===
57
+ and :superclass. The feature is mainly introduced for supporting the following
58
+ kind of coercion scenarios (see README for more about this):
59
+
60
+ rules = Myrrha.coercions do |r|
61
+ r.coercion String, Integer, lambda{|s,t| Integer(s)}
62
+ end
63
+ rules.coerce("12", PosInt) # => 12
64
+ rules.coerce("-12", PosInt) # => ArgumentError, "Invalid value -12 for PosInt"
65
+
66
+ ## Bug fixes
67
+
68
+ * Fixed Coercions#dup when a set of rules has a main target domain. This fixes
69
+ the duplication of ToRubyLiteral rules, among others.
70
+
1
71
  # 1.0.0 / 2011-07-22
2
72
 
3
- * Enhancements
73
+ ## Enhancements
4
74
 
5
75
  * Birthday!
data/Gemfile.lock CHANGED
@@ -1,12 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- myrrha (1.0.0)
4
+ myrrha (1.1.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
8
8
  specs:
9
- bluecloth (2.0.11)
9
+ bluecloth (2.1.0)
10
10
  diff-lcs (1.1.2)
11
11
  highline (1.6.2)
12
12
  noe (1.3.0)
@@ -31,7 +31,7 @@ PLATFORMS
31
31
  ruby
32
32
 
33
33
  DEPENDENCIES
34
- bluecloth (~> 2.0.9)
34
+ bluecloth (~> 2.1.0)
35
35
  bundler (~> 1.0)
36
36
  myrrha!
37
37
  noe (~> 1.3.0)
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Myrrha
1
+ # Myrrha (v1.1.0)
2
2
 
3
3
  ## Description
4
4
 
@@ -17,15 +17,15 @@ a numeric, a boolean, a date, a time, an URI, and so on.
17
17
  # Bug fixes (tiny) do not even add new default rules to coerce and
18
18
  # to\_ruby\_literal. Minor version can, which could break your code.
19
19
  # Therefore, please always use:
20
- gem "alf", "~> 1.0.0"
20
+ gem "myrrha", "~> 1.0.0"
21
21
 
22
22
  ## Links
23
23
 
24
- * http://rubydoc.info/github/blambeau/myrrha/master/frames (read this file there!)
24
+ * http://www.rubydoc.info/gems/myrrha/1.1.0/file/README.md (read this file there!)
25
25
  * http://github.com/blambeau/myrrha (source code)
26
26
  * http://rubygems.org/gems/myrrha (download)
27
27
 
28
- ## The missing <code>coerce()</code>
28
+ ## The <code>coerce()</code> feature
29
29
 
30
30
  Myrrha.coerce(:anything, Domain)
31
31
  coerce(:anything, Domain) # with core extensions
@@ -48,34 +48,71 @@ possible and even straightforward:
48
48
  end
49
49
  # => [12, true, #<Date: 2011-07-20 (...)>]
50
50
 
51
- ### Example
51
+ ### Implemented coercions
52
+
53
+ Implemented coercions are somewhat conservative, and only use a subset of what
54
+ ruby provides here and there. This is to avoid strangeness ala PHP... The
55
+ general philosophy is to provide the natural coercions we apply everyday.
56
+
57
+ The master rules are
58
+
59
+ * <code>coerce(value, Domain)</code> return <code>value</code> if
60
+ <code>belongs_to?(value, Domain)</code> is true (see last section below)
61
+ * <code>coerce(value, Domain)</code> returns <code>Domain.coerce(value)</code>
62
+ if the latter method exists.
63
+ * <code>coerce("any string", Domain)</code> returns <code>Domain.parse(value)</code>
64
+ if the latter method exists.
65
+
66
+ The specific implemented rules are
52
67
 
53
68
  require 'myrrha/with_core_ext'
54
69
  require 'myrrha/coerce'
55
70
 
56
- # it works on numerics
71
+ # NilClass -> _Anything_ returns nil, always
72
+ coerce(nil, Integer) # => nil
73
+
74
+ # Object -> String, via ruby's String()
75
+ coerce("hello", String) # => "hello"
76
+ coerce(:hello, String) # => "hello"
77
+
78
+ # String -> Numeric, through ruby's Integer() and Float()
57
79
  coerce("12", Integer) # => 12
58
80
  coerce("12.0", Float) # => 12.0
59
81
 
60
- # but also on regexp (through Regexp.compile)
82
+ # String -> Numeric is smart enough:
83
+ coerce("12", Numeric) # => 12 (Integer)
84
+ coerce("12.0", Numeric) # => 12.0 (Float)
85
+
86
+ # String -> Regexp, through Regexp.compile
61
87
  coerce("[a-z]+", Regexp) # => /[a-z]+/
62
88
 
63
- # and, yes, on Boolean (sorry Matz!)
89
+ # String -> Symbol, through to_sym
90
+ coerce("hello", Symbol) # => :hello
91
+
92
+ # String -> Boolean (hum, sorry Matz!)
64
93
  coerce("true", Boolean) # => true
65
94
  coerce("false", Boolean) # => false
95
+ coerce("true", TrueClass) # => true
96
+ coerce("false", FalseClass) # => false
66
97
 
67
- # and on date and time (through Date/Time.parse)
98
+ # String -> Date, through Date.parse
68
99
  require 'date'
69
- require 'time'
70
100
  coerce("2011-07-20", Date) # => #<Date: 2011-07-20 (4911525/2,0,2299161)>
101
+
102
+ # String -> Time, through Time.parse (just in time issuing of require('time'))
71
103
  coerce("2011-07-20 10:57", Time) # => 2011-07-20 10:57:00 +0200
72
104
 
73
- # why not on URI?
105
+ # String -> URI, through URI.parse
74
106
  require 'uri'
75
107
  coerce('http://google.com', URI) # => #<URI::HTTP:0x8281ce0 URL:http://google.com>
76
108
 
77
- # on nil, it always returns nil
78
- coerce(nil, Integer) # => nil
109
+ # String -> Class and Module through constant lookup
110
+ coerce("Integer", Class) # => Integer
111
+ coerce("Myrrha::Version", Module) # => Myrrha::Version
112
+
113
+ # Symbol -> Class and Module through constant lookup
114
+ coerce(:Integer, Class) # => Integer
115
+ coerce(:Enumerable, Module) # => Enumerable
79
116
 
80
117
  ### No core extension? no problem!
81
118
 
@@ -133,7 +170,7 @@ might be intrusive. Why not using your own set of coercion rules?
133
170
  MyRules.apply(:hello, Foo)
134
171
  # => #<Foo:0x8b7d254 @arg=:hello>
135
172
 
136
- ## The missing <code>to\_ruby\_literal()</code>
173
+ ## The <code>to\_ruby\_literal()</code> feature
137
174
 
138
175
  Myrrha.to_ruby_literal([:anything])
139
176
  [:anything].to_ruby_literal # with core extensions
@@ -291,7 +328,41 @@ which PRE holds are executed in order, until one succeed (chain of
291
328
  responsibility design pattern). This means that coercions always execute in
292
329
  <code>O(number of rules)</code>.
293
330
 
294
- ### <code>belongs\_to?</code> and <code>subdomain?</code>
331
+ ### Specifying converters
332
+
333
+ A converter is the third (resp. second) element specified in a coercion rules
334
+ (resp. an upon or fallback rule). A converter is generally a Proc of arity 2,
335
+ which is passed the source value and requested target domain.
336
+
337
+ Myrrha.coercions do |r|
338
+ r.coercion String, Numeric, lambda{|value,requested_domain|
339
+ # this is converter code
340
+ }
341
+ end
342
+ convert("12", Integer)
343
+
344
+ A converter may also be specified as an array of domains. In this case, it is
345
+ assumed that they for a path inside the convertion graph. Consider for example
346
+ the following coercion rules (contrived example)
347
+
348
+ rules = Myrrha.coercions do |r|
349
+ r.coercion String, Symbol, lambda{|s,t| s.to_sym } # 1
350
+ r.coercion Float, String, lambda{|s,t| s.to_s } # 2
351
+ r.coercion Integer, Float, lambda{|s,t| Float(s) } # 3
352
+ r.coercion Integer, Symbol, [Float, String] # 4
353
+ end
354
+
355
+ The last rule specifies a convertion path, through intermediate domains. The
356
+ complete rule specifies that applying the following path will work
357
+
358
+ Integer -> Float -> String -> Symbol
359
+ #3 #2 #1
360
+
361
+ Indeed,
362
+
363
+ rules.coerce(12, Symbol) # => :"12.0"
364
+
365
+ ### Semantics of <code>belongs\_to?</code> and <code>subdomain?</code>
295
366
 
296
367
  The pseudo-code given above relies on two main abstractions. Suppose the user
297
368
  makes a call to <code>coerce(value, requested_domain)</code>:
@@ -305,6 +376,8 @@ makes a call to <code>coerce(value, requested_domain)</code>:
305
376
 
306
377
  * <code>subdomain?(SourceDomain,TargetDomain)</code> is true iif
307
378
  * <code>SourceDomain == TargetDomain</code> yields true
379
+ * TargetDomain respond to <code>:superdomain_of?</code> and answers true on
380
+ SourceDomain
308
381
  * SourceDomain and TargetDomain are both classes and the latter is a super
309
382
  class of the former
310
383
 
@@ -334,4 +407,55 @@ makes a call to <code>coerce(value, requested_domain)</code>:
334
407
  # this is your last change, an Myrrha::Error will be raised if you fail
335
408
  end
336
409
 
337
- end
410
+ end
411
+
412
+ ### Factoring domains through specialization by constraint
413
+
414
+ Specialization by constraint (SByC) is a theory of types for which the following
415
+ rules hold:
416
+
417
+ * A type (aka domain) is a set of values
418
+ * A sub-type is a subset
419
+ * A sub-type can therefore be specified through a predicate on the super domain
420
+
421
+ For example, "positive integers" is a sub type of "integers" where the predicate
422
+ is "value > 0".
423
+
424
+ Myrrha comes with a small feature allowing you to create types 'ala' SByC:
425
+
426
+ PosInt = Myrrha.domain(Integer){|i| i > 0}
427
+ PosInt.name # => "PosInt"
428
+ PosInt.class # => Class
429
+ PosInt.superclass # => Integer
430
+ PosInt.ancestors # => [PosInt, Integer, Numeric, Comparable, Object, Kernel, BasicObject]
431
+ PosInt === 10 # => true
432
+ PosInt === -1 # => false
433
+ PosInt.new(10) # => 10
434
+ PosInt.new(-10) # => ArgumentError, "Invalid value -10 for PosInt"
435
+
436
+ Note that the feature is very limited, and is not intended to provide a truly
437
+ coherent typing framework. For example:
438
+
439
+ 10.is_a?(PosInt) # => false
440
+ 10.kind_of?(PosInt) # => false
441
+
442
+ Instead, Myrrha domains are only provided as an helper to build sound coercions
443
+ rules easily while 1) keeping a Class-based approach to source and target
444
+ domains and 2) having friendly error messages 3) really supporting true
445
+ reasoning on types and value:
446
+
447
+ # Only a rule that converts String to Integer
448
+ rules = Myrrha.coercions do |r|
449
+ r.coercion String, Integer, lambda{|s,t| Integer(s)}
450
+ end
451
+
452
+ # it succeeds on both integers and positive integers
453
+ rules.coerce("12", Integer) # => 12
454
+ rules.coerce("12", PosInt) # => 12
455
+
456
+ # and correctly fails in each case!
457
+ rules.coerce("-12", Integer) # => -12
458
+ rules.coerce("-12", PosInt) # => ArgumentError, "Invalid value -12 for PosInt"
459
+
460
+
461
+
data/examples/coerce.rb CHANGED
@@ -1,26 +1,46 @@
1
1
  require 'myrrha/with_core_ext'
2
2
  require 'myrrha/coerce'
3
3
 
4
- # it works on numerics
4
+ # NilClass -> _Anything_ returns nil, always
5
+ coerce(nil, Integer) # => nil
6
+
7
+ # Object -> String, via ruby's String()
8
+ coerce("hello", String) # => "hello"
9
+ coerce(:hello, String) # => "hello"
10
+
11
+ # String -> Numeric, through ruby's Integer() and Float()
5
12
  coerce("12", Integer) # => 12
6
13
  coerce("12.0", Float) # => 12.0
7
14
 
8
- # but also on regexp (through Regexp.compile)
15
+ # String -> Numeric is smart enough:
16
+ coerce("12", Numeric) # => 12 (Integer)
17
+ coerce("12.0", Numeric) # => 12.0 (Float)
18
+
19
+ # String -> Regexp, through Regexp.compile
9
20
  coerce("[a-z]+", Regexp) # => /[a-z]+/
10
21
 
11
- # and, yes, on Boolean (sorry Matz!)
22
+ # String -> Symbol, through to_sym
23
+ coerce("hello", Symbol) # => :hello
24
+
25
+ # String -> Boolean (hum, sorry Matz!)
12
26
  coerce("true", Boolean) # => true
13
27
  coerce("false", Boolean) # => false
14
28
 
15
- # and on date and time (through Date/Time.parse)
29
+ # String -> Date, through Date.parse
16
30
  require 'date'
17
- require 'time'
18
31
  coerce("2011-07-20", Date) # => #<Date: 2011-07-20 (4911525/2,0,2299161)>
32
+
33
+ # String -> Time, through Time.parse (just in time issuing of require('time'))
19
34
  coerce("2011-07-20 10:57", Time) # => 2011-07-20 10:57:00 +0200
20
35
 
21
- # why not on URI?
36
+ # String -> URI, through URI.parse
22
37
  require 'uri'
23
- coerce('http://google.com', URI) # => #<URI::HTTP:0x8281ce0 URL:http://google.com>
38
+ coerce('http://google.com', URI) # => #<URI::HTTP:0x8281ce0 URL:http://google.com>
39
+
40
+ # String -> Class and Module through constant lookup
41
+ coerce("Integer", Class) # => Integer
42
+ coerce("Myrrha::Version", Module) # => Myrrha::Version
24
43
 
25
- # on nil, it always returns nil
26
- coerce(nil, Integer) # => nil
44
+ # Symbol -> Class and Module through constant lookup
45
+ coerce(:Integer, Class) # => Integer
46
+ coerce(:Enumerable, Module) # => Enumerable
@@ -0,0 +1,38 @@
1
+ require 'myrrha'
2
+
3
+ PosInt = Myrrha.domain(Integer){|i| i>0}
4
+
5
+ ###
6
+
7
+ PosInt.class
8
+ PosInt.superclass
9
+ PosInt.ancestors
10
+ PosInt === 10
11
+ PosInt === -1
12
+ PosInt.new(10)
13
+ begin
14
+ PosInt.new(-10)
15
+ raise "Unexpected case: PosInt.new(-10) succeeds"
16
+ rescue ArgumentError => ex
17
+ puts ex.message
18
+ end
19
+
20
+ ###
21
+
22
+ 10.is_a?(PosInt)
23
+ 10.kind_of?(PosInt)
24
+
25
+ ###
26
+
27
+ rules = Myrrha.coercions do |r|
28
+ r.coercion String, Integer, lambda{|s,t| Integer(s)}
29
+ end
30
+ rules.coerce("12", Integer)
31
+ rules.coerce("12", PosInt)
32
+ rules.coerce("-12", Integer)
33
+ begin
34
+ rules.coerce("-12", PosInt)
35
+ raise "Unexpected case: rules.coerce('-12', PosInt) succeeds"
36
+ rescue Myrrha::Error => ex
37
+ puts ex.message
38
+ end
data/lib/myrrha.rb CHANGED
@@ -8,6 +8,23 @@ module Myrrha
8
8
  #
9
9
  class Error < StandardError; end
10
10
 
11
+ #
12
+ # Creates a domain instance by specialization by constraint
13
+ #
14
+ # @param [Class] superdom the superdomain of the created domain
15
+ # @param [Proc] pred the domain predicate
16
+ # @return [Class] the created domain
17
+ #
18
+ def self.domain(superdom = Object, subdoms=nil, &pred)
19
+ dom = Class.new(superdom).extend(Domain)
20
+ dom.instance_eval {
21
+ @sub_domains = subdoms
22
+ @super_domain = superdom
23
+ @predicate = pred
24
+ }
25
+ dom
26
+ end
27
+
11
28
  #
12
29
  # Builds a set of coercions rules.
13
30
  #
@@ -26,6 +43,58 @@ module Myrrha
26
43
  end
27
44
 
28
45
  #
46
+ # Encapsulates class methods of created domains
47
+ #
48
+ module Domain
49
+
50
+ #
51
+ # Creates a new instance of this domain
52
+ #
53
+ def new(*args)
54
+ if (args.size == 1) && (superclass === args.first)
55
+ if self === args.first
56
+ args.first
57
+ else
58
+ raise ArgumentError, "Invalid value #{args.join(' ')} for #{self}"
59
+ end
60
+ elsif superclass.respond_to?(:new)
61
+ new(super(*args))
62
+ else
63
+ raise ArgumentError, "Invalid value #{args.join(' ')} for #{self}"
64
+ end
65
+ end
66
+
67
+ # (see Class.superclass)
68
+ def superclass
69
+ @super_domain
70
+ end
71
+
72
+ #
73
+ # Checks if `value` belongs to this domain
74
+ #
75
+ def ===(value)
76
+ (superclass === value) && @predicate.call(value)
77
+ end
78
+
79
+ #
80
+ # Returns true if clazz if an explicit sub domain of self or if it's the
81
+ # case in Ruby.
82
+ #
83
+ def superdomain_of?(child)
84
+ Array(@sub_domains).include?(child)
85
+ end
86
+
87
+ #
88
+ # Returns the specialization by constraint predicate
89
+ #
90
+ # @return [Proc] the domain predicate
91
+ #
92
+ def predicate
93
+ @predicate
94
+ end
95
+
96
+ end # module Domain
97
+
29
98
  # Defines a set of coercion rules
30
99
  #
31
100
  class Coercions
@@ -36,11 +105,12 @@ module Myrrha
36
105
  #
37
106
  # Creates an empty list of coercion rules
38
107
  #
39
- def initialize(upons = [], rules = [], fallbacks = [])
108
+ def initialize(upons = [], rules = [], fallbacks = [], main_target_domain = nil)
40
109
  @upons = upons
41
110
  @rules = rules
42
111
  @fallbacks = fallbacks
43
112
  @appender = :<<
113
+ @main_target_domain = main_target_domain
44
114
  yield(self) if block_given?
45
115
  end
46
116
 
@@ -183,11 +253,16 @@ module Myrrha
183
253
  error = nil
184
254
  each_rule do |from,to,converter|
185
255
  next unless from.nil? or belongs_to?(value, from, target_domain)
186
- next unless to.nil? or subdomain?(to, target_domain)
187
256
  begin
188
- catch(:nextrule){
189
- return convert(value, target_domain, converter)
190
- }
257
+ catch(:nextrule) do
258
+ if to.nil? or subdomain?(to, target_domain)
259
+ got = convert(value, target_domain, converter)
260
+ return got
261
+ elsif subdomain?(target_domain, to)
262
+ got = convert(value, to, converter)
263
+ return got if belongs_to?(got, target_domain)
264
+ end
265
+ end
191
266
  rescue => ex
192
267
  error = ex.message unless error
193
268
  end
@@ -233,10 +308,15 @@ module Myrrha
233
308
  # otherwise.
234
309
  #
235
310
  def subdomain?(child, parent)
236
- return true if child == parent
237
- (child.respond_to?(:superclass) && child.superclass) ?
238
- subdomain?(child.superclass, parent) :
311
+ if child == parent
312
+ true
313
+ elsif parent.respond_to?(:superdomain_of?)
314
+ parent.superdomain_of?(child)
315
+ elsif child.respond_to?(:superclass) && child.superclass
316
+ subdomain?(child.superclass, parent)
317
+ else
239
318
  false
319
+ end
240
320
  end
241
321
 
242
322
  #
@@ -246,7 +326,7 @@ module Myrrha
246
326
  # @return [Coercions] a copy of this set of rules
247
327
  #
248
328
  def dup
249
- Coercions.new(@upons.dup, @rules.dup, @fallbacks.dup)
329
+ Coercions.new(@upons.dup, @rules.dup, @fallbacks.dup, main_target_domain)
250
330
  end
251
331
 
252
332
  private
@@ -273,6 +353,9 @@ module Myrrha
273
353
  def convert(value, target_domain, converter)
274
354
  if converter.respond_to?(:call)
275
355
  converter.call(value, target_domain)
356
+ elsif converter.is_a?(Array)
357
+ path = converter + [target_domain]
358
+ path.inject(value){|cur,ndom| coerce(cur, ndom)}
276
359
  else
277
360
  raise ArgumentError, "Unable to use #{converter} for coercing"
278
361
  end
data/lib/myrrha/coerce.rb CHANGED
@@ -2,29 +2,12 @@ require 'myrrha'
2
2
  module Myrrha
3
3
 
4
4
  #
5
- # Defines the missing Boolean type.
5
+ # Defines the missing Boolean as a Myrrha's domain
6
6
  #
7
- # This module mimics a Ruby missing Boolean type.
8
- #
9
- module Boolean
7
+ Boolean = Myrrha.domain(Object, [TrueClass, FalseClass]){|x|
8
+ (x==true) || (x==false)
9
+ }
10
10
 
11
- #
12
- # Returns Object, as the superclass of Boolean
13
- #
14
- # @return [Class] Object
15
- #
16
- def self.superclass; Object; end
17
-
18
- #
19
- # Returns true if `val` is <code>true</code> or <code>false</code>, false
20
- # otherwise.
21
- #
22
- def self.===(val)
23
- (val == true) || (val == false)
24
- end
25
-
26
- end # module Boolean
27
-
28
11
  #
29
12
  # Coerces _s_ to a Boolean
30
13
  #
@@ -48,6 +31,14 @@ module Myrrha
48
31
  # Defines basic coercions for Ruby, mostly from String
49
32
  Coerce = coercions do |g|
50
33
 
34
+ # Returns a constant denoted by `s`
35
+ def g.constant_lookup(s, target_domain)
36
+ found = (s.split('::') - [""]).inject(Kernel){|cur,n|
37
+ cur.const_get(n.to_sym)
38
+ }
39
+ belongs_to?(found, target_domain) ? found : throw(:nextrule)
40
+ end
41
+
51
42
  # NilClass should return immediately
52
43
  g.upon(NilClass) do |s,t|
53
44
  nil
@@ -59,12 +50,18 @@ module Myrrha
59
50
  end
60
51
 
61
52
  # Specific basic rules
62
- g.coercion String, Integer, lambda{|s,t| Integer(s) }
63
- g.coercion String, Float, lambda{|s,t| Float(s) }
64
- g.coercion String, Boolean, lambda{|s,t| Boolean(s) }
65
- g.coercion Integer, Float, lambda{|s,t| Float(s) }
66
- g.coercion String, Symbol, lambda{|s,t| s.to_sym }
67
- g.coercion String, Regexp, lambda{|s,t| Regexp.compile(s) }
53
+ g.coercion Object, String, lambda{|s,t| String(s) }
54
+ g.coercion String, Integer, lambda{|s,t| Integer(s) }
55
+ g.coercion String, Float, lambda{|s,t| Float(s) }
56
+ g.coercion String, Boolean, lambda{|s,t| Boolean(s) }
57
+ g.coercion Integer, Float, lambda{|s,t| Float(s) }
58
+ g.coercion String, Symbol, lambda{|s,t| s.to_sym }
59
+ g.coercion String, Regexp, lambda{|s,t| Regexp.compile(s) }
60
+ g.coercion Symbol, Class, lambda{|s,t| g.constant_lookup(s.to_s, t) }
61
+ g.coercion Symbol, Module, lambda{|s,t| g.constant_lookup(s.to_s, t) }
62
+ g.coercion String, Class, lambda{|s,t| g.constant_lookup(s, t) }
63
+ g.coercion String, Module, lambda{|s,t| g.constant_lookup(s, t) }
64
+ g.coercion String, Time, lambda{|s,t| require 'time'; Time.parse(s) }
68
65
 
69
66
  # By default, we try to invoke :parse on the class
70
67
  g.fallback(String) do |s,t|
@@ -2,7 +2,7 @@ module Myrrha
2
2
  module Version
3
3
 
4
4
  MAJOR = 1
5
- MINOR = 0
5
+ MINOR = 1
6
6
  TINY = 0
7
7
 
8
8
  def self.to_s
data/myrrha.gemspec CHANGED
@@ -127,7 +127,7 @@ Gem::Specification.new do |s|
127
127
  s.add_development_dependency("bundler", "~> 1.0")
128
128
  s.add_development_dependency("rspec", "~> 2.6.0")
129
129
  s.add_development_dependency("yard", "~> 0.7.2")
130
- s.add_development_dependency("bluecloth", "~> 2.0.9")
130
+ s.add_development_dependency("bluecloth", "~> 2.1.0")
131
131
  s.add_development_dependency("wlang", "~> 0.10.1")
132
132
  s.add_development_dependency("noe", "~> 1.3.0")
133
133
 
data/myrrha.noespec CHANGED
@@ -9,7 +9,7 @@ variables:
9
9
  upper:
10
10
  Myrrha
11
11
  version:
12
- 1.0.0
12
+ 1.1.0
13
13
  summary: |-
14
14
  Myrrha provides the coercion framework which is missing to Ruby, IMHO.
15
15
  description: |-
@@ -29,7 +29,7 @@ variables:
29
29
  - {name: bundler, version: "~> 1.0", groups: [development]}
30
30
  - {name: rspec, version: "~> 2.6.0", groups: [development]}
31
31
  - {name: yard, version: "~> 0.7.2", groups: [development]}
32
- - {name: bluecloth, version: "~> 2.0.9", groups: [development]}
32
+ - {name: bluecloth, version: "~> 2.1.0", groups: [development]}
33
33
  - {name: wlang, version: "~> 0.10.1", groups: [development]}
34
34
  - {name: noe, version: "~> 1.3.0", groups: [development]}
35
35
  rake_tasks:
@@ -16,6 +16,13 @@ module Myrrha
16
16
  dupped.coerce("12", Float).should eql(12.0)
17
17
  lambda{ rules.coerce("12", Float) }.should raise_error(Myrrha::Error)
18
18
  end
19
+
20
+ it "should not forget main_target_domain" do
21
+ rules = Coercions.new do |r|
22
+ r.main_target_domain = Integer
23
+ end
24
+ rules.dup.main_target_domain.should eql(Integer)
25
+ end
19
26
 
20
27
  end
21
28
  end
@@ -1,11 +1,12 @@
1
1
  require 'spec_helper'
2
2
  module Myrrha
3
3
  describe "Coercions#subdomain?" do
4
- let(:graph){ Coercions.new }
4
+ let(:r){ Coercions.new }
5
5
 
6
6
  specify {
7
- graph.subdomain?(Symbol, Object).should be_true
7
+ r.subdomain?(Symbol, Object).should be_true
8
+ r.subdomain?(Class, Module).should be_true
8
9
  }
9
-
10
+
10
11
  end
11
12
  end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+ describe "Myrrha.coercions" do
3
+
4
+ it "should support using user-defined domains" do
5
+ name = Myrrha.domain{|s| s.is_a?(Symbol)}
6
+ rules = Myrrha.coercions do |r|
7
+ r.coercion String, name, lambda{|s,t| s.to_sym}
8
+ r.coercion name, String, lambda{|s,t| s.to_s}
9
+ end
10
+ rules.coerce("hello", name).should eq(:hello)
11
+ rules.coerce(:hello, String).should eq("hello")
12
+ end
13
+
14
+ end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+ module Myrrha
3
+ describe "#domain" do
4
+
5
+ specify "the basic contract" do
6
+ subject = Myrrha.domain{|s| s == 12}
7
+ subject.should be_a(Class)
8
+ subject.superclass.should eq(Object)
9
+ (subject === 12).should be_true
10
+ (subject === 13).should be_false
11
+ end
12
+
13
+ specify "with a ruby superclass" do
14
+ subject = Myrrha.domain(Integer){|i| i > 0}
15
+ subject.should be_a(Class)
16
+ subject.superclass.should eq(Integer)
17
+ (subject === 12).should be_true
18
+ (subject === 0).should be_false
19
+ end
20
+
21
+ describe "A factored sub domain of Integer" do
22
+ PosInt = Myrrha.domain(Integer){|i| i > 0}
23
+ specify("#name") {
24
+ PosInt.name.should eq("Myrrha::PosInt")
25
+ }
26
+ specify("#new") {
27
+ PosInt.new(12).should eq(12)
28
+ lambda {
29
+ PosInt.new(0)
30
+ }.should raise_error(ArgumentError)
31
+ }
32
+ specify("#superclass"){
33
+ PosInt.superclass.should eql(Integer)
34
+ }
35
+ specify("#superdomain_of?"){
36
+ PosInt.superdomain_of?(Object).should be_false
37
+ PosInt.superdomain_of?(Integer).should be_false
38
+ }
39
+ it "should be usable in a case" do
40
+ [-12, 12].collect{|i|
41
+ case i
42
+ when PosInt
43
+ :posint
44
+ when Integer
45
+ :integer
46
+ end
47
+ }.should eq([:integer, :posint])
48
+ end
49
+ end
50
+
51
+ describe "A factored sub domain of a user-defined Class" do
52
+ class Color
53
+ attr_reader :r
54
+ attr_reader :g
55
+ attr_reader :b
56
+ def initialize(r,g,b)
57
+ raise ArgumentError unless [r,g,b].all?{|i| i.is_a?(Integer)}
58
+ @r, @g, @b = r, g, b
59
+ end
60
+ end
61
+ RedToZero = Myrrha.domain(Color){|c| c.r == 0}
62
+ specify("#===") {
63
+ (RedToZero === Color.new(0,1,1)).should be_true
64
+ (RedToZero === Color.new(1,1,1)).should be_false
65
+ }
66
+ specify("#new") {
67
+ RedToZero.new(Color.new(0,1,1)).should be_a(Color)
68
+ RedToZero.new(0, 1, 1).should be_a(Color)
69
+ lambda{
70
+ RedToZero.new(Color.new(1,1,1))
71
+ }.should raise_error(ArgumentError)
72
+ lambda{
73
+ RedToZero.new(1, 1, 1)
74
+ }.should raise_error(ArgumentError)
75
+ }
76
+ end
77
+
78
+ end
79
+ end
data/spec/spec_helper.rb CHANGED
@@ -3,7 +3,6 @@ require 'myrrha/with_core_ext'
3
3
  require 'myrrha/coerce'
4
4
  require 'myrrha/to_ruby_literal'
5
5
  require 'date'
6
- require 'time'
7
6
  require 'shared/a_value'
8
7
 
9
8
  unless defined?(SAFE_VALUES)
data/spec/test_coerce.rb CHANGED
@@ -8,6 +8,18 @@ describe "::Ruby's coercion " do
8
8
  end
9
9
  end
10
10
 
11
+ describe "to String" do
12
+ specify "from Integer" do
13
+ coerce(12, String).should eql("12")
14
+ end
15
+ specify "from String" do
16
+ coerce("12", String).should eql("12")
17
+ end
18
+ specify "from NilClass" do
19
+ coerce(nil, String).should be_nil
20
+ end
21
+ end
22
+
11
23
  describe "to Integer" do
12
24
  specify "from Integer" do
13
25
  coerce(12, Integer).should eql(12)
@@ -62,6 +74,20 @@ describe "::Ruby's coercion " do
62
74
  lambda{coerce("abc", Boolean)}.should raise_error(Myrrha::Error)
63
75
  end
64
76
  end
77
+
78
+ describe "to TrueClass" do
79
+ specify "from String" do
80
+ coerce("true", TrueClass).should eql(true)
81
+ lambda{ coerce("false", TrueClass) }.should raise_error(Myrrha::Error)
82
+ end
83
+ end
84
+
85
+ describe "to FalseClass" do
86
+ specify "from String" do
87
+ coerce("false", FalseClass).should eql(false)
88
+ lambda{ coerce("true", FalseClass) }.should raise_error(Myrrha::Error)
89
+ end
90
+ end
65
91
 
66
92
  describe "to Date" do
67
93
  let(:expected){ Date.parse("2011-07-20") }
@@ -102,6 +128,38 @@ describe "::Ruby's coercion " do
102
128
  coerce("http://www.google.com/", URI).should eql(URI.parse("http://www.google.com/"))
103
129
  end
104
130
  end
131
+
132
+ describe "to Module" do
133
+ specify "from String" do
134
+ coerce("Kernel", Module).should eql(Kernel)
135
+ coerce("::Kernel", Module).should eql(Kernel)
136
+ coerce("Myrrha::Version", Module).should eql(Myrrha::Version)
137
+ coerce("::Myrrha::Version", Module).should eql(Myrrha::Version)
138
+ coerce("Myrrha::Coercions", Module).should eql(Myrrha::Coercions)
139
+ end
140
+ specify "from Symbol" do
141
+ coerce(:Kernel, Module).should eql(Kernel)
142
+ end
143
+ it "should raise error if not a module" do
144
+ lambda{
145
+ coerce("Myrrha::VERSION", Module)
146
+ }.should raise_error(Myrrha::Error)
147
+ end
148
+ end
149
+
150
+ describe "to Class" do
151
+ specify "from String" do
152
+ coerce("Myrrha::Coercions", Class).should eql(Myrrha::Coercions)
153
+ end
154
+ specify "from Symbol" do
155
+ coerce(:Integer, Class).should eql(Integer)
156
+ end
157
+ it "should raise error if not a module" do
158
+ lambda{
159
+ coerce("Myrrha::Version", Class)
160
+ }.should raise_error(Myrrha::Error)
161
+ end
162
+ end
105
163
 
106
164
  specify "to a class that respond to coerce" do
107
165
  class Coerceable
data/spec/test_myrrha.rb CHANGED
@@ -84,4 +84,41 @@ describe Myrrha do
84
84
  rules.coerce("hello", Symbol).should eq(:HELLO)
85
85
  end
86
86
 
87
+ it "should used superdomain rules in an optimistic strategy" do
88
+ rules = Myrrha.coercions do |c|
89
+ c.coercion String, Numeric, lambda{|s,t| Integer(s)}
90
+ end
91
+ rules.coerce("12", Integer).should eql(12)
92
+ lambda{ rules.coerce("12", Float) }.should raise_error(Myrrha::Error)
93
+ end
94
+
95
+ describe "path convertions" do
96
+ let(:rules){
97
+ Myrrha.coercions do |c|
98
+ c.coercion Integer, String, lambda{|s,t| s.to_s}
99
+ c.coercion String, Float, lambda{|s,t| Float(s)}
100
+ c.coercion Integer, Float, [String]
101
+ c.coercion Float, String, lambda{|s,t| s.to_s}
102
+ c.coercion String, Symbol, lambda{|s,t| s.to_sym}
103
+ c.coercion Integer, Symbol, [Float, String]
104
+ end
105
+ }
106
+ it "should work with a simple and single" do
107
+ rules.coerce(12, Float).should eql(12.0)
108
+ end
109
+ it "should work with a complex and multiple path" do
110
+ rules.coerce(12, Symbol).should eql(:"12.0")
111
+ end
112
+ end
113
+
114
+ specify "path convertions (from CHANGELOG)" do
115
+ rules = Myrrha.coercions do |r|
116
+ r.coercion String, Symbol, lambda{|s,t| s.to_sym }
117
+ r.coercion Float, String, lambda{|s,t| s.to_s }
118
+ r.coercion Integer, Float, lambda{|s,t| Float(s) }
119
+ r.coercion Integer, Symbol, [Float, String]
120
+ end
121
+ rules.coerce(12, Symbol).should eql(:"12.0")
122
+ end
123
+
87
124
  end
data/tasks/examples.rake CHANGED
@@ -6,6 +6,7 @@ task :examples do
6
6
  print '.'
7
7
  else
8
8
  print '*'
9
+ raise "Example #{file} failed."
9
10
  end
10
11
  end
11
12
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: myrrha
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-07-22 00:00:00.000000000Z
12
+ date: 2011-07-28 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &77746150 !ruby/object:Gem::Requirement
16
+ requirement: &85576310 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.9.2
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *77746150
24
+ version_requirements: *85576310
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: bundler
27
- requirement: &77745820 !ruby/object:Gem::Requirement
27
+ requirement: &85575520 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '1.0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *77745820
35
+ version_requirements: *85575520
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &77745530 !ruby/object:Gem::Requirement
38
+ requirement: &85574670 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 2.6.0
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *77745530
46
+ version_requirements: *85574670
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: yard
49
- requirement: &77745240 !ruby/object:Gem::Requirement
49
+ requirement: &85573700 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,21 +54,21 @@ dependencies:
54
54
  version: 0.7.2
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *77745240
57
+ version_requirements: *85573700
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: bluecloth
60
- requirement: &77744950 !ruby/object:Gem::Requirement
60
+ requirement: &85572820 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
64
64
  - !ruby/object:Gem::Version
65
- version: 2.0.9
65
+ version: 2.1.0
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *77744950
68
+ version_requirements: *85572820
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: wlang
71
- requirement: &77744660 !ruby/object:Gem::Requirement
71
+ requirement: &85550940 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 0.10.1
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *77744660
79
+ version_requirements: *85550940
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: noe
82
- requirement: &77744360 !ruby/object:Gem::Requirement
82
+ requirement: &85550220 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ~>
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: 1.3.0
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *77744360
90
+ version_requirements: *85550220
91
91
  description: ! "Myrrha provides the coercion framework which is missing to Ruby, IMHO.
92
92
  Coercions\nare simply defined as a set of rules for converting values from source
93
93
  to target\ndomains (in an abstract sense). As a typical and useful example, it comes
@@ -113,6 +113,7 @@ files:
113
113
  - examples/String#toXXX.rb
114
114
  - examples/examples_helper.rb
115
115
  - examples/coerce_foo2.rb
116
+ - examples/sbyc_domain.rb
116
117
  - examples/to_ruby_literal_foo3.rb
117
118
  - examples/to_ruby_literal_noext.rb
118
119
  - examples/coerce_foo3.rb
@@ -123,6 +124,8 @@ files:
123
124
  - lib/myrrha/version.rb
124
125
  - lib/myrrha.rb
125
126
  - spec/spec_helper.rb
127
+ - spec/myrrha/test_coercions.rb
128
+ - spec/myrrha/test_domain.rb
126
129
  - spec/test_myrrha.rb
127
130
  - spec/test_to_ruby_literal.rb
128
131
  - spec/test_value.rb
@@ -164,7 +167,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
164
167
  version: '0'
165
168
  segments:
166
169
  - 0
167
- hash: -508754947
170
+ hash: -900430961
168
171
  required_rubygems_version: !ruby/object:Gem::Requirement
169
172
  none: false
170
173
  requirements:
@@ -173,15 +176,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
176
  version: '0'
174
177
  segments:
175
178
  - 0
176
- hash: -508754947
179
+ hash: -900430961
177
180
  requirements: []
178
181
  rubyforge_project:
179
- rubygems_version: 1.8.5
182
+ rubygems_version: 1.8.6
180
183
  signing_key:
181
184
  specification_version: 3
182
185
  summary: Myrrha provides the coercion framework which is missing to Ruby, IMHO.
183
186
  test_files:
184
187
  - spec/spec_helper.rb
188
+ - spec/myrrha/test_coercions.rb
189
+ - spec/myrrha/test_domain.rb
185
190
  - spec/test_myrrha.rb
186
191
  - spec/test_to_ruby_literal.rb
187
192
  - spec/test_value.rb