module Cube::Interface

Public Instance Methods

impotent() click to toggle source
# File lib/cube/interfaces.rb, line 194
def impotent
  cl = Cube.interface {}.extend(self)
  cl.module_exec do
    def extend_object(mod)
      super
    end
    def append_features(mod)
      super
    end
  end
  cl
end
proto(meth, *args) { || ... } click to toggle source
# File lib/cube/interfaces.rb, line 179
def proto(meth, *args)
  out_spec = yield if block_given?
  validate_spec(args)
  validate_spec(out_spec) if out_spec
  @ids.merge!({ meth.to_sym => { in: args, out: out_spec } })
end
public_visible(*ids) click to toggle source
# File lib/cube/interfaces.rb, line 186
def public_visible(*ids)
  unless ids.all? { |id| id.is_a?(Symbol) || id.is_a?(String) }
    raise ArgumentError, "Arguments should be strings or symbols"
  end
  spec = map_spec(ids)
  @ids.merge!(spec)
end
required_public_methods() click to toggle source

Accepts an array of method names that define the interface. When this module is included/implemented, those method names must have already been defined.

# File lib/cube/interfaces.rb, line 166
def required_public_methods
  @ids.keys
end
shell() click to toggle source

creates a shell object for testing

# File lib/cube/interfaces.rb, line 208
def shell
  ids = @ids
  unreq = @unreq
  cls = Class.new(Object) do
    (ids.keys - unreq).each do |m|
      define_method(m) { |*args| }
    end
  end
  Cube[cls].send(:shell_implements, self)
end
to_spec() click to toggle source
# File lib/cube/interfaces.rb, line 170
def to_spec
  inherited = (self.ancestors-[self]).select{ |x| Interface === x }
  inherited_ids = inherited.map{ |x| x.instance_variable_get('@ids') }

  # Store required method ids
  inherited_specs = map_spec(inherited_ids.flatten)
  @ids.merge(inherited_specs)
end

Private Instance Methods

append_features(mod) click to toggle source

This is called before `included`

Calls superclass method
# File lib/cube/interfaces.rb, line 39
def append_features(mod)
  return super if Interface === mod

  # Is this a sub-interface?
  # Get specs from super interfaces
  inherited = (self.ancestors-[self]).select{ |x| Interface === x }
  inherited_ids = inherited.map{ |x| x.instance_variable_get('@ids') }

  # Store required method ids
  specs = to_spec
  ids = @ids.keys + map_spec(inherited_ids.flatten).keys
  @unreq ||= []

  # Iterate over the methods, minus the unrequired methods, and raise
  # an error if the method has not been defined.
  mod_public_instance_methods = mod.public_instance_methods(true)
  (ids - @unreq).uniq.each do |id|
    id = id.to_s if RUBY_VERSION.to_f < 1.9
    unless mod_public_instance_methods.include?(id)
      raise Interface::PublicVisibleMethodMissing, "#{mod}: #{self}##{id}"
    end
    spec = specs[id]
    if spec.is_a?(Hash) && spec.key?(:in) && spec[:in].is_a?(Array)
      # Check arity and replace method with type checking method
      replace_check_method(mod, id, spec[:in], spec[:out])
    end
  end

  super mod
end
convert_to_lambda(&block) click to toggle source

convert a proc to lambda

# File lib/cube/interfaces.rb, line 26
def convert_to_lambda &block
  obj = Object.new
  obj.define_singleton_method(:_, &block)
  return obj.method(:_).to_proc
end
extend_object(obj) click to toggle source
# File lib/cube/interfaces.rb, line 32
def extend_object(obj)
  return append_features(obj) if Interface === obj
  append_features(class << obj; self end)
  included(obj)
end
map_spec(ids) click to toggle source

massage array spec and hash spec into hash spec

# File lib/cube/interfaces.rb, line 141
def map_spec(ids)
  ids.reduce({}) do |res, m|
    if m.is_a?(Hash)
      res.merge(m)
    elsif m.is_a?(Symbol) || m.is_a?(String)
      res.merge({ m.to_sym => nil })
    end
  end
end
replace_check_method(mod, id, inchecks, outcheck) click to toggle source

Check arity Replace with type_checking method if demanded

# File lib/cube/interfaces.rb, line 86
def replace_check_method(mod, id, inchecks, outcheck)
  # Get the previously stashed method if it exists
  stashed_meth = stashed_method(mod, id)
  orig_method = stashed_meth || mod.instance_method(id)
  unless mod.instance_variable_defined?("@__interface_arity_skip") \
    && mod.instance_variable_get("@__interface_arity_skip")
    orig_arity = orig_method.parameters.size
    check_arity = inchecks.size
    if orig_arity != check_arity
      raise Interface::MethodArityError,
            "#{mod}: #{self}##{id} arity mismatch: #{orig_arity} instead of #{check_arity}"
    end
  end

  # return if we are not doing runtime checks for this class
  unless ENV['RUBY_CUBE_TYPECHECK'].to_i > 0 \
    && mod.instance_variable_defined?("@__interface_runtime_check") \
    && mod.instance_variable_get("@__interface_runtime_check")
    return
  end
  # if the stashed method exists, it already exists. return
  return if stashed_meth
  iface = self
  stash_method(mod, id)
  # replace method with a type checking wrapper
  mod.class_exec do
    # random alias name to avoid conflicts
    ns_meth_name = "#{id}_#{SecureRandom.hex(3)}".to_sym
    alias_method ns_meth_name, id
    # The type checking wrapper
    define_method(id) do |*args|
      new_args = args.zip(inchecks).map do |v, t|
        begin
          Cube.check_type(t,v)
        rescue Dry::Types::ConstraintError => e
          raise Interface::TypeMismatchError,
                "#{mod}: #{iface}##{id} : #{e.message}"
        end
      end
      # types look good, call the original method
      ret = send(ns_meth_name, *new_args)
      # check return type if it exists
      begin
        Cube.check_type(outcheck, ret) if outcheck
      rescue Dry::Types::ConstraintError => e
        raise Interface::TypeMismatchError,
              "#{mod}: #{iface}##{id} (return): #{e.message}"
      end
      # looks good, return
      ret
    end
  end
end
stash_method(mod, id) click to toggle source

Stash a method in the module for future checks

# File lib/cube/interfaces.rb, line 71
def stash_method(mod, id)
  unless mod.instance_variable_defined?('@__interface_stashed_methods')
    mod.instance_variable_set('@__interface_stashed_methods', {})
  end
  mod.instance_variable_get('@__interface_stashed_methods')[id] = mod.instance_method(id)
end
stashed_method(mod, id) click to toggle source

Get a stashed method for the module

# File lib/cube/interfaces.rb, line 79
def stashed_method(mod, id)
  return nil unless mod.instance_variable_defined?('@__interface_stashed_methods')
  mod.instance_variable_get('@__interface_stashed_methods')[id]
end
validate_spec(spec) click to toggle source

validate the interface spec is valid

# File lib/cube/interfaces.rb, line 152
def validate_spec(spec)
  [*spec].each do |t|
    unless t.is_a?(Dry::Types::Type) || t.is_a?(Module)
      raise ArgumentError, "#{t} is not a Dry::Types::Type nor a Module"
    end
  end
end