<commit_before>require 'erb'

module Aspector
  class Base

    attr :options

    def initialize target, options = {}
      @target = target

      default_options = self.class.default_options
      if default_options and not default_options.empty?
        @options = default_options.merge(options)
      else
        @options = options
      end

      # @context is where advices will be applied (i.e. where methods are modified), can be different from target
      @context = get_context

      after_initialize
    end

    def apply
      invoke_deferred_logics
      define_methods_for_advice_blocks
      add_to_instances
      apply_to_methods
      add_method_hooks
      after_apply
    end

    def deferred_logic_results logic
      @deferred_logic_results[logic]
    end

    def apply_to_methods
      @context.public_instance_methods.each do |method|
        apply_to_method(method, :public)
      end

      @context.protected_instance_methods.each do |method|
        apply_to_method(method, :protected)
      end

      if @options[:private_methods]
        @context.private_instance_methods.each do |method|
          apply_to_method(method, :private)
        end
      end
    end

    def apply_to_method method, scope = nil
      advices = advices_for_method method
      return if advices.empty?

      scope ||=
        if @context.private_instance_methods.include?(method.to_s)
          :private
        elsif @context.protected_instance_methods.include?(method.to_s)
          :protected
        else
          :public
        end

      recreate_method method, advices, scope
    end

    protected

    # Hook method that runs after an aspect is instantiated
    def after_initialize
    end

    # Hook method that runs after an aspect is applied
    def after_apply
    end

    private

    def get_context
      return @target if @target.is_a?(Module) and not @options[:eigen_class]

      class << @target
        self
      end
    end

    def invoke_deferred_logics
      return unless self.class.deferred_logics

      @deferred_logic_results ||= {}
      self.class.deferred_logics.each do |logic|
        @deferred_logic_results[logic] = logic.apply @context, self
      end
    end

    def define_methods_for_advice_blocks
      self.class.advices.each do |advice|
        next unless advice.advice_block
        @context.send :define_method, advice.with_method, advice.advice_block
        @context.send :private, advice.with_method
      end
    end

    def add_to_instances
      aspect_instances = @context.instance_variable_get(:@aspector_instances)
      unless aspect_instances
        aspect_instances = AspectInstances.new
        @context.instance_variable_set(:@aspector_instances, aspect_instances)
      end
      aspect_instances << self
    end

    def add_method_hooks
      if @options[:eigen_class]
        return unless @target.is_a?(Module)

        eigen_class = class << @target; self; end
        orig_singleton_method_added = @target.method(:singleton_method_added)

        eigen_class.send :define_method, :singleton_method_added do |method|
          singleton_method_added_aspector(method) do
            orig_singleton_method_added.call(method)
          end
        end
      else
        eigen_class = class << @target; self; end

        if @target.is_a? Module
          orig_method_added = @target.method(:method_added)
        else
          orig_method_added = eigen_class.method(:method_added)
        end

        eigen_class.send :define_method, :method_added do |method|
          method_added_aspector(method) do
            orig_method_added.call(method)
          end
        end
      end
    end

    def advices_for_method method
      self.class.advices.select do |advice|
        advice.match?(method, self)
      end
    end

    def recreate_method method, advices, scope
      @context.instance_variable_set(:@aspector_creating_method, true)

      before_advices = advices.select {|advice| advice.before? }
      after_advices  = advices.select {|advice| advice.after?  }
      around_advices = advices.select {|advice| advice.around? }

      if around_advices.size > 1
        (around_advices.size - 1).downto(1) do |i|
          advice = around_advices[i]
          recreate_method_with_advices method, [], [], advice
        end
      end

      recreate_method_with_advices method, before_advices, after_advices, around_advices.first

      @context.send scope, method if scope != :public
    ensure
      @context.instance_variable_set(:@aspector_creating_method, nil)
    end

    def recreate_method_with_advices method, before_advices, after_advices, around_advice
      code = METHOD_TEMPLATE.result(binding)
      puts code
      @context.class_eval code, __FILE__, __LINE__ + 4<commit_msg>Comment out print generated code<commit_after>require 'erb'

module Aspector
  class Base

    attr :options

    def initialize target, options = {}
      @target = target

      default_options = self.class.default_options
      if default_options and not default_options.empty?
        @options = default_options.merge(options)
      else
        @options = options
      end

      # @context is where advices will be applied (i.e. where methods are modified), can be different from target
      @context = get_context

      after_initialize
    end

    def apply
      invoke_deferred_logics
      define_methods_for_advice_blocks
      add_to_instances
      apply_to_methods
      add_method_hooks
      after_apply
    end

    def deferred_logic_results logic
      @deferred_logic_results[logic]
    end

    def apply_to_methods
      @context.public_instance_methods.each do |method|
        apply_to_method(method, :public)
      end

      @context.protected_instance_methods.each do |method|
        apply_to_method(method, :protected)
      end

      if @options[:private_methods]
        @context.private_instance_methods.each do |method|
          apply_to_method(method, :private)
        end
      end
    end

    def apply_to_method method, scope = nil
      advices = advices_for_method method
      return if advices.empty?

      scope ||=
        if @context.private_instance_methods.include?(method.to_s)
          :private
        elsif @context.protected_instance_methods.include?(method.to_s)
          :protected
        else
          :public
        end

      recreate_method method, advices, scope
    end

    protected

    # Hook method that runs after an aspect is instantiated
    def after_initialize
    end

    # Hook method that runs after an aspect is applied
    def after_apply
    end

    private

    def get_context
      return @target if @target.is_a?(Module) and not @options[:eigen_class]

      class << @target
        self
      end
    end

    def invoke_deferred_logics
      return unless self.class.deferred_logics

      @deferred_logic_results ||= {}
      self.class.deferred_logics.each do |logic|
        @deferred_logic_results[logic] = logic.apply @context, self
      end
    end

    def define_methods_for_advice_blocks
      self.class.advices.each do |advice|
        next unless advice.advice_block
        @context.send :define_method, advice.with_method, advice.advice_block
        @context.send :private, advice.with_method
      end
    end

    def add_to_instances
      aspect_instances = @context.instance_variable_get(:@aspector_instances)
      unless aspect_instances
        aspect_instances = AspectInstances.new
        @context.instance_variable_set(:@aspector_instances, aspect_instances)
      end
      aspect_instances << self
    end

    def add_method_hooks
      if @options[:eigen_class]
        return unless @target.is_a?(Module)

        eigen_class = class << @target; self; end
        orig_singleton_method_added = @target.method(:singleton_method_added)

        eigen_class.send :define_method, :singleton_method_added do |method|
          singleton_method_added_aspector(method) do
            orig_singleton_method_added.call(method)
          end
        end
      else
        eigen_class = class << @target; self; end

        if @target.is_a? Module
          orig_method_added = @target.method(:method_added)
        else
          orig_method_added = eigen_class.method(:method_added)
        end

        eigen_class.send :define_method, :method_added do |method|
          method_added_aspector(method) do
            orig_method_added.call(method)
          end
        end
      end
    end

    def advices_for_method method
      self.class.advices.select do |advice|
        advice.match?(method, self)
      end
    end

    def recreate_method method, advices, scope
      @context.instance_variable_set(:@aspector_creating_method, true)

      before_advices = advices.select {|advice| advice.before? }
      after_advices  = advices.select {|advice| advice.after?  }
      around_advices = advices.select {|advice| advice.around? }

      if around_advices.size > 1
        (around_advices.size - 1).downto(1) do |i|
          advice = around_advices[i]
          recreate_method_with_advices method, [], [], advice
        end
      end

      recreate_method_with_advices method, before_advices, after_advices, around_advices.first

      @context.send scope, method if scope != :public
    ensure
      @context.instance_variable_set(:@aspector_creating_method, nil)
    end

    def recreate_method_with_advices method, before_advices, after_advices, around_advice
      code = METHOD_TEMPLATE.result(binding)
      #puts code
      @context.class_eval code, __FILE__, __LINE__ + 4
{'fraction_non_alphanumeric': 0.06686772046589018, 'fraction_numerical': 0.0008319467554076539, 'mean_word_length': 2.6059242594675665, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4366835', 'n_tokens_mistral': 2895, 'n_tokens_neox': 2807, 'n_words': 647}