module Sunspot
  # 
  # DataExtractors present an internal API for the indexer to use to extract
  # field values from models for indexing. They must implement the #value_for
  # method, which takes an object and returns the value extracted from it.
  #
  module DataExtractor #:nodoc: all
    #
    # Abstract extractor to perform common actions on extracted values
    #
    class AbstractExtractor
      BLACKLIST_REGEXP = /[\x0-\x8\xB\xC\xE-\x1F\x7f]/

      def value_for(object)
        extract_value_from(object)
      end

      private

      def extract_value_from(object)
        case object
          when String
            remove_blacklisted_chars(object)
          when Array
            object.map { |o| extract_value_from(o) }
          when Hash
            object.inject({}) { |h, (k, v)| h.merge(extract_value_from(k) => extract_value_from(v)) }
          else
            object
        end
      end

      def remove_blacklisted_chars(object)
        object.gsub(BLACKLIST_REGEXP, '')
      end
    end

    # 
    # AttributeExtractors extract data by simply calling a method on the block.
    #
    class AttributeExtractor < AbstractExtractor
      def initialize(attribute_name)
        @attribute_name = attribute_name
      end

      def value_for(object)
        super object.send(@attribute_name)
      end
    end

    # 
    # BlockExtractors extract data by evaluating a block in the context of the
    # object instance, or if the block takes an argument, by passing the object
    # as the argument to the block. Either way, the return value of the block is
    # the value returned by the extractor.
    #
    class BlockExtractor < AbstractExtractor
      def initialize(&block)
        @block = block
      end

      def value_for(object)
        super Util.instance_eval_or_call(object, &@block)
      end
    end

    # 
    # Constant data extractors simply return the same value for every object.
    #
    class Constant < AbstractExtractor
      def initialize(value)
        @value = value
      end

      def value_for(object)
        super @value
      end
    end
  end
end
