myrrha 1.2.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,15 +1,3 @@
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
1
  # We run tests by default
14
2
  task :default => :test
15
3
 
@@ -1,6 +1,6 @@
1
1
  require 'myrrha'
2
2
 
3
- PosInt = Myrrha.domain(Integer){|i| i>0}
3
+ PosInt = Myrrha::Domain.sbyc(Integer){|i| i>0}
4
4
 
5
5
  ###
6
6
 
data/lib/myrrha.rb CHANGED
@@ -1,32 +1,15 @@
1
+ require_relative "myrrha/version"
2
+ require_relative "myrrha/loader"
3
+ require_relative 'myrrha/errors'
1
4
  #
2
5
  # Myrrha -- the missing coercion framework for Ruby
3
6
  #
4
7
  module Myrrha
5
-
6
- #
7
- # Raised when a coercion fails
8
- #
9
- class Error < StandardError; end
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
- @subdomains = subdoms
22
- @superdomain = superdom
23
- @predicate = pred
24
- }
25
- dom
26
- end
27
-
28
- #
29
- # Builds a set of coercions rules.
8
+
9
+ require_relative 'myrrha/domain'
10
+ require_relative 'myrrha/coercions'
11
+
12
+ # Builds a set of coercions rules.
30
13
  #
31
14
  # Example:
32
15
  #
@@ -41,351 +24,15 @@ module Myrrha
41
24
  def self.coercions(&block)
42
25
  Coercions.new(&block)
43
26
  end
44
-
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
- superdomain || super
70
- end
71
-
72
- #
73
- # Returns the super domain if installed
74
- #
75
- def superdomain
76
- @superdomain
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(@subdomains).include?(child)
85
- end
86
-
87
- #
88
- # Checks if `value` belongs to this domain
89
- #
90
- def ===(value)
91
- (superclass === value) && predicate.call(value)
92
- end
93
-
94
- #
95
- # Returns the specialization by constraint predicate
96
- #
97
- # @return [Proc] the domain predicate
98
- #
99
- def predicate
100
- @predicate
101
- end
102
-
103
- end # module Domain
104
-
105
- # Defines a set of coercion rules
106
- #
107
- class Coercions
108
-
109
- # @return [Domain] The main target domain, if any
110
- attr_accessor :main_target_domain
111
-
112
- #
113
- # Creates an empty list of coercion rules
114
- #
115
- def initialize(&defn)
116
- @definitions = []
117
- @upons = []
118
- @rules = []
119
- @fallbacks = []
120
- @appender = :<<
121
- @main_target_domain = nil
122
- extend_rules(:<<, defn) if defn
123
- end
124
-
125
- #
126
- # Appends the list of rules with new ones.
127
- #
128
- # New upon, coercion and fallback rules will be put after the already
129
- # existing ones, in each case.
130
- #
131
- # Example:
132
- #
133
- # rules = Myrrha.coercions do ... end
134
- # rules.append do |r|
135
- #
136
- # # [previous coercion rules would come here]
137
- #
138
- # # install new rules
139
- # r.coercion String, Float, lambda{|v,t| Float(t)}
140
- # end
141
- #
142
- def append(&proc)
143
- extend_rules(:<<, proc)
144
- end
145
-
146
- #
147
- # Prepends the list of rules with new ones.
148
- #
149
- # New upon, coercion and fallback rules will be put before the already
150
- # existing ones, in each case.
151
- #
152
- # Example:
153
- #
154
- # rules = Myrrha.coercions do ... end
155
- # rules.prepend do |r|
156
- #
157
- # # install new rules
158
- # r.coercion String, Float, lambda{|v,t| Float(t)}
159
- #
160
- # # [previous coercion rules would come here]
161
- #
162
- # end
163
- #
164
- def prepend(&proc)
165
- extend_rules(:unshift, proc)
166
- end
167
-
168
- #
169
- # Adds an upon rule for a source domain.
170
- #
171
- # Example:
172
- #
173
- # Myrrha.coercions do |r|
174
- #
175
- # # Don't even try something else on nil
176
- # r.upon(NilClass){|s,t| nil}
177
- # [...]
178
- #
179
- # end
180
- #
181
- # @param source [Domain] a source domain (mimic Domain)
182
- # @param converter [Converter] an optional converter (mimic Converter)
183
- # @param convproc [Proc] used when converter is not specified
184
- # @return self
185
- #
186
- def upon(source, converter = nil, &convproc)
187
- @upons.send(@appender, [source, nil, converter || convproc])
188
- self
189
- end
190
-
191
- #
192
- # Adds a coercion rule from a source to a target domain.
193
- #
194
- # The conversion can be provided through `converter` or via a block
195
- # directly. See main documentation about recognized converters.
196
- #
197
- # Example:
198
- #
199
- # Myrrha.coercions do |r|
200
- #
201
- # # With an explicit proc
202
- # r.coercion String, Integer, lambda{|v,t|
203
- # Integer(v)
204
- # }
205
- #
206
- # # With an implicit proc
207
- # r.coercion(String, Float) do |v,t|
208
- # Float(v)
209
- # end
210
- #
211
- # end
212
- #
213
- # @param source [Domain] a source domain (mimicing Domain)
214
- # @param target [Domain] a target domain (mimicing Domain)
215
- # @param converter [Converter] an optional converter (mimic Converter)
216
- # @param convproc [Proc] used when converter is not specified
217
- # @return self
218
- #
219
- def coercion(source, target = main_target_domain, converter = nil, &convproc)
220
- @rules.send(@appender, [source, target, converter || convproc])
221
- self
222
- end
223
-
224
- #
225
- # Adds a fallback rule for a source domain.
226
- #
227
- # Example:
228
- #
229
- # Myrrha.coercions do |r|
230
- #
231
- # # Add a 'last chance' rule for Strings
232
- # r.fallback(String) do |v,t|
233
- # # the user wants _v_ to be converted to a value of domain _t_
234
- # end
235
- #
236
- # end
237
- #
238
- # @param source [Domain] a source domain (mimic Domain)
239
- # @param converter [Converter] an optional converter (mimic Converter)
240
- # @param convproc [Proc] used when converter is not specified
241
- # @return self
242
- #
243
- def fallback(source, converter = nil, &convproc)
244
- @fallbacks.send(@appender, [source, nil, converter || convproc])
245
- self
246
- end
247
-
248
- #
249
- # Coerces `value` to an element of `target_domain`
250
- #
251
- # This method tries each coercion rule, then each fallback in turn. Rules
252
- # for which source and target domain match are executed until one succeeds.
253
- # A Myrrha::Error is raised if no rule matches or executes successfuly.
254
- #
255
- # @param [Object] value any ruby value
256
- # @param [Domain] target_domain a target domain to convert to (mimic Domain)
257
- # @return self
258
- #
259
- def coerce(value, target_domain = main_target_domain)
260
- return value if belongs_to?(value, target_domain)
261
- error = nil
262
- each_rule do |from,to,converter|
263
- next unless from.nil? or belongs_to?(value, from, target_domain)
264
- begin
265
- catch(:nextrule) do
266
- if to.nil? or subdomain?(to, target_domain)
267
- got = convert(value, target_domain, converter)
268
- return got
269
- elsif subdomain?(target_domain, to)
270
- got = convert(value, to, converter)
271
- return got if belongs_to?(got, target_domain)
272
- end
273
- end
274
- rescue => ex
275
- error = ex.message unless error
276
- end
277
- end
278
- msg = "Unable to coerce `#{value}` to #{target_domain}"
279
- msg += " (#{error})" if error
280
- raise Error, msg
281
- end
282
- alias :apply :coerce
283
-
284
- #
285
- # Returns true if `value` can be considered as a valid element of the
286
- # domain `domain`, false otherwise.
287
- #
288
- # @param [Object] value any ruby value
289
- # @param [Domain] domain a domain (mimic Domain)
290
- # @return [Boolean] true if `value` belongs to `domain`, false otherwise
291
- #
292
- def belongs_to?(value, domain, target_domain = domain)
293
- case domain
294
- when Proc
295
- if domain.arity == 2
296
- domain.call(value, target_domain)
297
- elsif RUBY_VERSION < "1.9"
298
- domain.call(value)
299
- elsif domain
300
- domain === value
301
- end
302
- else
303
- domain.respond_to?(:===) ?
304
- domain === value :
305
- false
306
- end
307
- end
308
-
309
- #
310
- # Returns `true` if `child` can be considered a valid sub domain of
311
- # `parent`, false otherwise.
312
- #
313
- # @param [Domain] child a domain (mimic Domain)
314
- # @param [Domain] parent another domain (mimic Domain)
315
- # @return [Boolean] true if `child` is a subdomain of `parent`, false
316
- # otherwise.
317
- #
318
- def subdomain?(child, parent)
319
- if child == parent
320
- true
321
- elsif parent.respond_to?(:superdomain_of?)
322
- parent.superdomain_of?(child)
323
- elsif child.respond_to?(:superclass) && child.superclass
324
- subdomain?(child.superclass, parent)
325
- else
326
- false
327
- end
328
- end
329
-
330
- #
331
- # Duplicates this set of rules in such a way that the original will not
332
- # be affected by any change made to the copy.
333
- #
334
- # @return [Coercions] a copy of this set of rules
335
- #
336
- def dup
337
- c = Coercions.new
338
- @definitions.each do |defn|
339
- c.extend_rules(*defn)
340
- end
341
- c
342
- end
343
-
344
- protected
345
-
346
- # Extends existing rules
347
- def extend_rules(appender, block)
348
- @definitions << [appender, block]
349
- @appender = appender
350
- block.call(self)
351
- self
352
- end
353
-
354
- #
355
- # Yields each rule in turn (upons, coercions then fallbacks)
356
- #
357
- def each_rule(&proc)
358
- @upons.each(&proc)
359
- @rules.each(&proc)
360
- @fallbacks.each(&proc)
361
- end
362
-
363
- #
364
- # Calls converter on a (value,target_domain) pair.
365
- #
366
- def convert(value, target_domain, converter)
367
- if converter.respond_to?(:call)
368
- converter.call(value, target_domain)
369
- elsif converter.is_a?(Array)
370
- path = converter + [target_domain]
371
- path.inject(value){|cur,ndom| coerce(cur, ndom)}
372
- else
373
- raise ArgumentError, "Unable to use #{converter} for coercing"
374
- end
375
- end
376
-
377
- end # class Coercions
378
-
27
+
379
28
  # Myrrha main options
380
29
  OPTIONS = {
381
30
  :core_ext => false
382
31
  }
383
-
32
+
384
33
  # Install core extensions?
385
34
  def self.core_ext?
386
35
  OPTIONS[:core_ext]
387
36
  end
388
-
37
+
389
38
  end # module Myrrha
390
- require "myrrha/version"
391
- require "myrrha/loader"
data/lib/myrrha/coerce.rb CHANGED
@@ -1,22 +1,19 @@
1
1
  require 'myrrha'
2
2
  module Myrrha
3
-
4
- #
5
- # Defines the missing Boolean as a Myrrha's domain
6
- #
7
- Boolean = Myrrha.domain(Object, [TrueClass, FalseClass]){|x|
8
- (x==true) || (x==false)
9
- }
3
+
4
+ class Boolean < Object
5
+ extend Myrrha::Domain::SByC.new(Object, [TrueClass, FalseClass], lambda{|x| (x==true) || (x==false)})
6
+ end
10
7
 
11
8
  #
12
9
  # Coerces _s_ to a Boolean
13
10
  #
14
11
  # This method mimics Ruby's Integer(), Float(), etc. for Boolean values.
15
12
  #
16
- # @param [Object] s a Boolean or a String
13
+ # @param [Object] s a Boolean or a String
17
14
  # @return [Boolean] true if `s` is already true of the string 'true',
18
15
  # false if `s` is already false of the string 'false'.
19
- # @raise [ArgumentError] if `s` cannot be coerced to a boolean.
16
+ # @raise [ArgumentError] if `s` cannot be coerced to a boolean.
20
17
  #
21
18
  def self.Boolean(s)
22
19
  if (s==true || s.to_str.strip == "true")
@@ -27,28 +24,28 @@ module Myrrha
27
24
  raise ArgumentError, "invalid value for Boolean: \"#{s}\""
28
25
  end
29
26
  end
30
-
31
- # Defines basic coercions for Ruby, mostly from String
27
+
28
+ # Defines basic coercions for Ruby, mostly from String
32
29
  Coerce = coercions do |g|
33
-
30
+
34
31
  # Returns a constant denoted by `s`
35
32
  def g.constant_lookup(s, target_domain)
36
- found = (s.split('::') - [""]).inject(Kernel){|cur,n|
33
+ found = (s.split('::') - [""]).inject(Kernel){|cur,n|
37
34
  cur.const_get(n.to_sym)
38
35
  }
39
36
  belongs_to?(found, target_domain) ? found : throw(:nextrule)
40
37
  end
41
-
38
+
42
39
  # NilClass should return immediately
43
- g.upon(NilClass) do |s,t|
40
+ g.upon(NilClass) do |s,t|
44
41
  nil
45
42
  end
46
-
43
+
47
44
  # Use t.coerce if it exists
48
45
  g.upon(lambda{|s,t| t.respond_to?(:coerce)}) do |s,t|
49
46
  t.coerce(s)
50
47
  end
51
-
48
+
52
49
  # Specific basic rules
53
50
  g.coercion Object, String, lambda{|s,t| String(s) }
54
51
  g.coercion String, Integer, lambda{|s,t| Integer(s) }
@@ -62,18 +59,18 @@ module Myrrha
62
59
  g.coercion String, Class, lambda{|s,t| g.constant_lookup(s, t) }
63
60
  g.coercion String, Module, lambda{|s,t| g.constant_lookup(s, t) }
64
61
  g.coercion String, Time, lambda{|s,t| require 'time'; Time.parse(s) }
65
-
66
- # By default, we try to invoke :parse on the class
67
- g.fallback(String) do |s,t|
68
- t.respond_to?(:parse) ? t.parse(s.to_str) : throw(:nextrule)
62
+
63
+ # By default, we try to invoke :parse on the class
64
+ g.fallback(String) do |s,t|
65
+ t.respond_to?(:parse) ? t.parse(s.to_str) : throw(:nextrule)
69
66
  end
70
-
67
+
71
68
  end # Coerce
72
69
 
73
70
  def self.coerce(value, domain)
74
71
  Coerce.apply(value, domain)
75
72
  end
76
-
73
+
77
74
  end # module Myrrha
78
75
 
79
76
  if Myrrha.core_ext?
@@ -85,6 +82,6 @@ if Myrrha.core_ext?
85
82
  private
86
83
  def coerce(value, domain)
87
84
  Myrrha.coerce(value, domain)
88
- end
85
+ end
89
86
  end
90
- end
87
+ end