lib/myrrha.rb in myrrha-1.2.2 vs lib/myrrha.rb in myrrha-2.0.0
- old
+ new
@@ -1,35 +1,18 @@
+require_relative "myrrha/version"
+require_relative "myrrha/loader"
+require_relative 'myrrha/errors'
#
# Myrrha -- the missing coercion framework for Ruby
#
module Myrrha
-
+
+ require_relative 'myrrha/domain'
+ require_relative 'myrrha/coercions'
+
+ # Builds a set of coercions rules.
#
- # 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 {
- @subdomains = subdoms
- @superdomain = superdom
- @predicate = pred
- }
- dom
- end
-
- #
- # Builds a set of coercions rules.
- #
# Example:
#
# rules = Myrrha.coercions do |c|
# c.coercion String, Integer, lambda{|s,t| Integer(s)}
# #
@@ -39,353 +22,17 @@
# end
#
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
- superdomain || super
- end
-
- #
- # Returns the super domain if installed
- #
- def superdomain
- @superdomain
- 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(@subdomains).include?(child)
- end
-
- #
- # Checks if `value` belongs to this domain
- #
- def ===(value)
- (superclass === value) && predicate.call(value)
- 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(&defn)
- @definitions = []
- @upons = []
- @rules = []
- @fallbacks = []
- @appender = :<<
- @main_target_domain = nil
- extend_rules(:<<, defn) if defn
- end
-
- #
- # Appends the list of rules with new ones.
- #
- # New upon, coercion and fallback rules will be put after the already
- # existing ones, in each case.
- #
- # Example:
- #
- # rules = Myrrha.coercions do ... end
- # rules.append do |r|
- #
- # # [previous coercion rules would come here]
- #
- # # install new rules
- # r.coercion String, Float, lambda{|v,t| Float(t)}
- # end
- #
- def append(&proc)
- extend_rules(:<<, proc)
- end
-
- #
- # Prepends the list of rules with new ones.
- #
- # New upon, coercion and fallback rules will be put before the already
- # existing ones, in each case.
- #
- # Example:
- #
- # rules = Myrrha.coercions do ... end
- # rules.prepend do |r|
- #
- # # install new rules
- # r.coercion String, Float, lambda{|v,t| Float(t)}
- #
- # # [previous coercion rules would come here]
- #
- # end
- #
- def prepend(&proc)
- extend_rules(:unshift, proc)
- end
-
- #
- # Adds an upon rule for a source domain.
- #
- # Example:
- #
- # Myrrha.coercions do |r|
- #
- # # Don't even try something else on nil
- # r.upon(NilClass){|s,t| nil}
- # [...]
- #
- # end
- #
- # @param source [Domain] a source domain (mimic Domain)
- # @param converter [Converter] an optional converter (mimic Converter)
- # @param convproc [Proc] used when converter is not specified
- # @return self
- #
- def upon(source, converter = nil, &convproc)
- @upons.send(@appender, [source, nil, converter || convproc])
- self
- end
-
- #
- # Adds a coercion rule from a source to a target domain.
- #
- # The conversion can be provided through `converter` or via a block
- # directly. See main documentation about recognized converters.
- #
- # Example:
- #
- # Myrrha.coercions do |r|
- #
- # # With an explicit proc
- # r.coercion String, Integer, lambda{|v,t|
- # Integer(v)
- # }
- #
- # # With an implicit proc
- # r.coercion(String, Float) do |v,t|
- # Float(v)
- # end
- #
- # end
- #
- # @param source [Domain] a source domain (mimicing Domain)
- # @param target [Domain] a target domain (mimicing Domain)
- # @param converter [Converter] an optional converter (mimic Converter)
- # @param convproc [Proc] used when converter is not specified
- # @return self
- #
- def coercion(source, target = main_target_domain, converter = nil, &convproc)
- @rules.send(@appender, [source, target, converter || convproc])
- self
- end
-
- #
- # Adds a fallback rule for a source domain.
- #
- # Example:
- #
- # Myrrha.coercions do |r|
- #
- # # Add a 'last chance' rule for Strings
- # r.fallback(String) do |v,t|
- # # the user wants _v_ to be converted to a value of domain _t_
- # end
- #
- # end
- #
- # @param source [Domain] a source domain (mimic Domain)
- # @param converter [Converter] an optional converter (mimic Converter)
- # @param convproc [Proc] used when converter is not specified
- # @return self
- #
- def fallback(source, converter = nil, &convproc)
- @fallbacks.send(@appender, [source, nil, converter || convproc])
- self
- end
-
- #
- # Coerces `value` to an element of `target_domain`
- #
- # This method tries each coercion rule, then each fallback in turn. Rules
- # for which source and target domain match are executed until one succeeds.
- # A Myrrha::Error is raised if no rule matches or executes successfuly.
- #
- # @param [Object] value any ruby value
- # @param [Domain] target_domain a target domain to convert to (mimic Domain)
- # @return self
- #
- 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)
- begin
- 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}"
- msg += " (#{error})" if error
- raise Error, msg
- end
- alias :apply :coerce
-
- #
- # Returns true if `value` can be considered as a valid element of the
- # domain `domain`, false otherwise.
- #
- # @param [Object] value any ruby value
- # @param [Domain] domain a domain (mimic Domain)
- # @return [Boolean] true if `value` belongs to `domain`, false otherwise
- #
- def belongs_to?(value, domain, target_domain = domain)
- case domain
- when Proc
- if domain.arity == 2
- domain.call(value, target_domain)
- elsif RUBY_VERSION < "1.9"
- domain.call(value)
- elsif domain
- domain === value
- end
- else
- domain.respond_to?(:===) ?
- domain === value :
- false
- end
- end
-
- #
- # Returns `true` if `child` can be considered a valid sub domain of
- # `parent`, false otherwise.
- #
- # @param [Domain] child a domain (mimic Domain)
- # @param [Domain] parent another domain (mimic Domain)
- # @return [Boolean] true if `child` is a subdomain of `parent`, false
- # otherwise.
- #
- def subdomain?(child, 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
- c = Coercions.new
- @definitions.each do |defn|
- c.extend_rules(*defn)
- end
- c
- end
-
- protected
-
- # Extends existing rules
- def extend_rules(appender, block)
- @definitions << [appender, block]
- @appender = appender
- block.call(self)
- self
- end
-
- #
- # Yields each rule in turn (upons, coercions then fallbacks)
- #
- def each_rule(&proc)
- @upons.each(&proc)
- @rules.each(&proc)
- @fallbacks.each(&proc)
- end
-
- #
- # 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
-
- end # class Coercions
-
+
# Myrrha main options
OPTIONS = {
:core_ext => false
}
-
+
# Install core extensions?
def self.core_ext?
OPTIONS[:core_ext]
end
-
+
end # module Myrrha
-require "myrrha/version"
-require "myrrha/loader"