lib/myrrha.rb in myrrha-1.0.0 vs lib/myrrha.rb in myrrha-1.1.0
- old
+ new
@@ -7,10 +7,27 @@
# Raised when a coercion fails
#
class Error < StandardError; end
#
+ # Creates a domain instance by specialization by constraint
+ #
+ # @param [Class] superdom the superdomain of the created domain
+ # @param [Proc] pred the domain predicate
+ # @return [Class] the created domain
+ #
+ def self.domain(superdom = Object, subdoms=nil, &pred)
+ dom = Class.new(superdom).extend(Domain)
+ dom.instance_eval {
+ @sub_domains = subdoms
+ @super_domain = superdom
+ @predicate = pred
+ }
+ dom
+ end
+
+ #
# Builds a set of coercions rules.
#
# Example:
#
# rules = Myrrha.coercions do |c|
@@ -24,25 +41,78 @@
def self.coercions(&block)
Coercions.new(&block)
end
#
+ # Encapsulates class methods of created domains
+ #
+ module Domain
+
+ #
+ # Creates a new instance of this domain
+ #
+ def new(*args)
+ if (args.size == 1) && (superclass === args.first)
+ if self === args.first
+ args.first
+ else
+ raise ArgumentError, "Invalid value #{args.join(' ')} for #{self}"
+ end
+ elsif superclass.respond_to?(:new)
+ new(super(*args))
+ else
+ raise ArgumentError, "Invalid value #{args.join(' ')} for #{self}"
+ end
+ end
+
+ # (see Class.superclass)
+ def superclass
+ @super_domain
+ end
+
+ #
+ # Checks if `value` belongs to this domain
+ #
+ def ===(value)
+ (superclass === value) && @predicate.call(value)
+ end
+
+ #
+ # Returns true if clazz if an explicit sub domain of self or if it's the
+ # case in Ruby.
+ #
+ def superdomain_of?(child)
+ Array(@sub_domains).include?(child)
+ end
+
+ #
+ # Returns the specialization by constraint predicate
+ #
+ # @return [Proc] the domain predicate
+ #
+ def predicate
+ @predicate
+ end
+
+ end # module Domain
+
# Defines a set of coercion rules
#
class Coercions
# @return [Domain] The main target domain, if any
attr_accessor :main_target_domain
#
# Creates an empty list of coercion rules
#
- def initialize(upons = [], rules = [], fallbacks = [])
+ def initialize(upons = [], rules = [], fallbacks = [], main_target_domain = nil)
@upons = upons
@rules = rules
@fallbacks = fallbacks
@appender = :<<
+ @main_target_domain = main_target_domain
yield(self) if block_given?
end
#
# Appends the list of rules with new ones.
@@ -181,15 +251,20 @@
def coerce(value, target_domain = main_target_domain)
return value if belongs_to?(value, target_domain)
error = nil
each_rule do |from,to,converter|
next unless from.nil? or belongs_to?(value, from, target_domain)
- next unless to.nil? or subdomain?(to, target_domain)
begin
- catch(:nextrule){
- return convert(value, target_domain, converter)
- }
+ catch(:nextrule) do
+ if to.nil? or subdomain?(to, target_domain)
+ got = convert(value, target_domain, converter)
+ return got
+ elsif subdomain?(target_domain, to)
+ got = convert(value, to, converter)
+ return got if belongs_to?(got, target_domain)
+ end
+ end
rescue => ex
error = ex.message unless error
end
end
msg = "Unable to coerce `#{value}` to #{target_domain}"
@@ -231,24 +306,29 @@
# @param [Domain] parent another domain (mimic Domain)
# @return [Boolean] true if `child` is a subdomain of `parent`, false
# otherwise.
#
def subdomain?(child, parent)
- return true if child == parent
- (child.respond_to?(:superclass) && child.superclass) ?
- subdomain?(child.superclass, parent) :
+ if child == parent
+ true
+ elsif parent.respond_to?(:superdomain_of?)
+ parent.superdomain_of?(child)
+ elsif child.respond_to?(:superclass) && child.superclass
+ subdomain?(child.superclass, parent)
+ else
false
+ end
end
#
# Duplicates this set of rules in such a way that the original will not
# be affected by any change made to the copy.
#
# @return [Coercions] a copy of this set of rules
#
def dup
- Coercions.new(@upons.dup, @rules.dup, @fallbacks.dup)
+ Coercions.new(@upons.dup, @rules.dup, @fallbacks.dup, main_target_domain)
end
private
# Extends existing rules
@@ -271,9 +351,12 @@
# Calls converter on a (value,target_domain) pair.
#
def convert(value, target_domain, converter)
if converter.respond_to?(:call)
converter.call(value, target_domain)
+ elsif converter.is_a?(Array)
+ path = converter + [target_domain]
+ path.inject(value){|cur,ndom| coerce(cur, ndom)}
else
raise ArgumentError, "Unable to use #{converter} for coercing"
end
end
\ No newline at end of file