module Dry::BlackTie

rubocop:disable Style/MultilineBlockChain rubocop:disable Metrics/MethodLength rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/AbcSize rubocop:disable Style/MethodName

Constants

DELEGATE_METHOD
IMPLICIT_DELEGATE_DEPRECATION
IMPLICIT_RECEIVER_DECLARATION
NORMALIZE_KEYS
PARAM_TYPES
POSTPONE_EXTEND
UNKNOWN_TYPE_DECLARATION
WRONG_PARAMETER_DECLARATION

Public Class Methods

Logger() click to toggle source
# File lib/dry/behaviour/black_tie.rb, line 15
def Logger
  @logger ||=
    if Kernel.const_defined?('::Rails')
      Rails.logger
    else
      require 'logger'
      Logger.new($stdout)
    end
  @logger ? @logger : Class.new { def warn(*); end }.new
end
defimpl(protocol = nil, target: nil, delegate: [], map: {}, &λ) click to toggle source
Calls superclass method
# File lib/dry/behaviour/black_tie.rb, line 97
def defimpl(protocol = nil, target: nil, delegate: [], map: {}, &λ)
  raise if target.nil? || !block_given? && delegate.empty? && map.empty?

  mds = normalize_map_delegates(delegate, map)

  Module.new do
    mds.each(&DELEGATE_METHOD.curry[singleton_class])
    singleton_class.class_eval(&λ) if block_given? # block takes precedence
  end.tap do |mod|
    protocol ? mod.extend(protocol) : POSTPONE_EXTEND.(mod, protocol = self)

    mod.methods(false).tap do |meths|
      (NORMALIZE_KEYS.(protocol) - meths).each_with_object(meths) do |m, acc|
        if BlackTie.protocols[protocol][:__implicit_inheritance__]
          mod.singleton_class.class_eval do
            define_method m do |this, *♿_args, &♿_λ|
              super(this, *♿_args, &♿_λ)
            end
          end
        else
          BlackTie.Logger.warn(
            IMPLICIT_DELEGATE_DEPRECATION % [Dry::BlackTie.proto_caller, protocol.inspect, m, target]
          )
          DELEGATE_METHOD.(mod.singleton_class, [m] * 2)
        end
        acc << m
      end
    end.each do |m|
      target = [target] unless target.is_a?(Array)
      target.each do |tgt|
        ok =
          mds.map(&:first).include?(m) ||
          [
            BlackTie.protocols[protocol][m],
            mod.method(m).parameters.reject { |_, v| v.to_s[/\A♿_/] }
          ].map(&:first).reduce(:==)

        # TODO[1.0] raise NotImplemented(:arity)
        BlackTie.Logger.warn(
          WRONG_PARAMETER_DECLARATION % [Dry::BlackTie.proto_caller, protocol.inspect, m, target, BlackTie.protocols[protocol][m].map(&:first)]
        ) unless ok

        BlackTie.implementations[protocol][tgt][m] = mod.method(m).to_proc
      end
    end
  end
end
implementations() click to toggle source
# File lib/dry/behaviour/black_tie.rb, line 30
def implementations
  @implementations ||= Hash.new { |h, k| h[k] = h.dup.clear }
end
normalize_map_delegates(delegate, map) click to toggle source
# File lib/dry/behaviour/black_tie.rb, line 196
def normalize_map_delegates(delegate, map)
  [*delegate, *map].map do |e|
    case e
    when Symbol, String then [e.to_sym] * 2
    when Array then e.map(&:to_sym) if e.size == 2
    end
  end.compact
end
proto_caller() click to toggle source
# File lib/dry/behaviour/black_tie.rb, line 9
def proto_caller
  caller.drop_while do |line|
    line =~ %r[dry-behaviour/lib/dry/behaviour]
  end.first
end
protocols() click to toggle source
# File lib/dry/behaviour/black_tie.rb, line 26
def protocols
  @protocols ||= Hash.new { |h, k| h[k] = h.dup.clear }
end
warn(*) click to toggle source
# File lib/dry/behaviour/black_tie.rb, line 23
def warn(*); end

Public Instance Methods

defmethod(name, *params) click to toggle source
# File lib/dry/behaviour/black_tie.rb, line 81
def defmethod(name, *params)
  if params.size.zero? || params.first.is_a?(Array) && params.first.last != :req
    BlackTie.Logger.warn(IMPLICIT_RECEIVER_DECLARATION % [Dry::BlackTie.proto_caller, self.inspect, name])
    params.unshift(:this)
  end
  params =
    params.map do |p, type|
      if type && !PARAM_TYPES.include?(type)
        BlackTie.Logger.warn(UNKNOWN_TYPE_DECLARATION % [Dry::BlackTie.proto_caller, type, self.inspect, name])
        type = nil
      end
      [type || PARAM_TYPES.include?(p) ? p : :req, p]
    end
  BlackTie.protocols[self][name] = params
end
defprotocol(implicit_inheritance: false, &λ) click to toggle source
# File lib/dry/behaviour/black_tie.rb, line 35
def defprotocol(implicit_inheritance: false, &λ)
  raise ::Dry::Protocol::DuplicateDefinition.new(self) if BlackTie.protocols.key?(self)
  raise ::Dry::Protocol::MalformedDefinition.new(self) unless block_given?

  BlackTie.protocols[self][:__implicit_inheritance__] = !!implicit_inheritance

  ims = instance_methods(false)
  class_eval(&λ)
  (instance_methods(false) - ims).each { |m| class_eval { module_function m } }

  singleton_class.send :define_method, :method_missing do |method, *_args|
    raise Dry::Protocol::NotImplemented.new(
      :method, inspect, method: method, self: self
    )
  end

  singleton_class.send :define_method, :implementation_for do |receiver|
    receiver.class.ancestors.lazy.map do |c|
      BlackTie.implementations[self].fetch(c, nil)
    end.reject(&:nil?).first
  end

  BlackTie.protocols[self].each do |method, *_| # FIXME: CHECK ARITY HERE
    singleton_class.send :define_method, method do |receiver = nil, *args|
      impl = implementation_for(receiver)
      raise Dry::Protocol::NotImplemented.new(
        :protocol, inspect,
        method: method, receiver: receiver, args: args, self: self
      ) unless impl
      begin
        impl[method].(*args.unshift(receiver))
      rescue => e
        raise Dry::Protocol::NotImplemented.new(
            :nested, inspect,
            cause: e,
            method: method, receiver: receiver, args: args, impl: impl, self: self
          )
      end
    end
  end

  singleton_class.send :define_method, :respond_to? do |method|
    NORMALIZE_KEYS.(self).include? method
  end
end

Private Instance Methods

defimpl(protocol = nil, target: nil, delegate: [], map: {}, &λ) click to toggle source
Calls superclass method
# File lib/dry/behaviour/black_tie.rb, line 97
def defimpl(protocol = nil, target: nil, delegate: [], map: {}, &λ)
  raise if target.nil? || !block_given? && delegate.empty? && map.empty?

  mds = normalize_map_delegates(delegate, map)

  Module.new do
    mds.each(&DELEGATE_METHOD.curry[singleton_class])
    singleton_class.class_eval(&λ) if block_given? # block takes precedence
  end.tap do |mod|
    protocol ? mod.extend(protocol) : POSTPONE_EXTEND.(mod, protocol = self)

    mod.methods(false).tap do |meths|
      (NORMALIZE_KEYS.(protocol) - meths).each_with_object(meths) do |m, acc|
        if BlackTie.protocols[protocol][:__implicit_inheritance__]
          mod.singleton_class.class_eval do
            define_method m do |this, *♿_args, &♿_λ|
              super(this, *♿_args, &♿_λ)
            end
          end
        else
          BlackTie.Logger.warn(
            IMPLICIT_DELEGATE_DEPRECATION % [Dry::BlackTie.proto_caller, protocol.inspect, m, target]
          )
          DELEGATE_METHOD.(mod.singleton_class, [m] * 2)
        end
        acc << m
      end
    end.each do |m|
      target = [target] unless target.is_a?(Array)
      target.each do |tgt|
        ok =
          mds.map(&:first).include?(m) ||
          [
            BlackTie.protocols[protocol][m],
            mod.method(m).parameters.reject { |_, v| v.to_s[/\A♿_/] }
          ].map(&:first).reduce(:==)

        # TODO[1.0] raise NotImplemented(:arity)
        BlackTie.Logger.warn(
          WRONG_PARAMETER_DECLARATION % [Dry::BlackTie.proto_caller, protocol.inspect, m, target, BlackTie.protocols[protocol][m].map(&:first)]
        ) unless ok

        BlackTie.implementations[protocol][tgt][m] = mod.method(m).to_proc
      end
    end
  end
end
normalize_map_delegates(delegate, map) click to toggle source
# File lib/dry/behaviour/black_tie.rb, line 196
def normalize_map_delegates(delegate, map)
  [*delegate, *map].map do |e|
    case e
    when Symbol, String then [e.to_sym] * 2
    when Array then e.map(&:to_sym) if e.size == 2
    end
  end.compact
end