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