lib/rspec/core/metadata.rb



module Rspec
  module Core
    class Metadata < Hash

      def self.process(superclass_metadata, *args)
        new(superclass_metadata) do |metadata|
          metadata.process(*args)
        end
      end
      
      attr_reader :superclass_metadata

      def initialize(superclass_metadata=nil)
        @superclass_metadata = superclass_metadata
        update(@superclass_metadata) if @superclass_metadata
        store(:example_group, {})
        store(:behaviour, self[:example_group])
        yield self if block_given?
      end

      def process(*args)
        extra_metadata = args.last.is_a?(Hash) ? args.pop : {}
        extra_metadata.delete(:example_group) # Remove it when present to prevent it clobbering the one we setup
        extra_metadata.delete(:behaviour)     # Remove it when present to prevent it clobbering the one we setup

        self[:example_group][:describes] = args.shift unless args.first.is_a?(String)
        self[:example_group][:describes] ||= self.superclass_metadata && self.superclass_metadata[:example_group][:describes]
        self[:example_group][:description] = args.shift || ''

        self[:example_group][:name] = determine_name
        self[:example_group][:block] = extra_metadata.delete(:example_group_block)
        self[:example_group][:caller] = extra_metadata.delete(:caller)
        self[:example_group][:file_path] = file_path_from(self[:example_group], extra_metadata.delete(:file_path))
        self[:example_group][:line_number] = line_number_from(self[:example_group], extra_metadata.delete(:line_number))
        self[:example_group][:location] = location_from(self[:example_group])

        update(extra_metadata)
      end

      def for_example(description, options)
        dup.configure_for_example(description,options)
      end

      def configure_for_example(description, options)
        store(:description, description.to_s)
        store(:full_description, "#{self[:example_group][:name]} #{self[:description]}")
        store(:execution_result, {})
        store(:caller, options.delete(:caller))
        if self[:caller]
          store(:file_path, file_path_from(self))
          store(:line_number, line_number_from(self))
        end
        self[:location] = location_from(self)
        update(options)
        self
      end

      def apply_condition(filter_on, filter, metadata=nil)
        metadata ||= self
        case filter
        when Hash
          filter.all? { |k, v| apply_condition(k, v, metadata[filter_on]) }
        when Regexp
          metadata[filter_on] =~ filter
        when Proc
          filter.call(metadata[filter_on]) rescue false
        when Fixnum
          if filter_on == :line_number
            [metadata[:line_number],metadata[:example_group][:line_number]].include?(filter)
          else
            metadata[filter_on] == filter
          end
        else
          metadata[filter_on] == filter
        end
      end

      def all_apply?(filters)
        filters.all? do |filter_on, filter|
          apply_condition(filter_on, filter)
        end
      end

    private

      def file_path_from(metadata, given_file_path=nil)
        given_file_path || file_and_line_number(metadata)[0].strip
      end

      def line_number_from(metadata, given_line_number=nil)
        given_line_number || file_and_line_number(metadata)[1].to_i
      end

      def location_from(metadata)
        "#{metadata[:file_path]}:#{metadata[:line_number]}"
      end

      def file_and_line_number(metadata)
        candidate_entries_from_caller(metadata).first.split(':')
      end

      def candidate_entries_from_caller(metadata)
        metadata[:caller].grep(/\_spec\.rb:/i)
      end

      def determine_name
        if superclass_metadata && superclass_metadata[:example_group][:name]
          self[:example_group][:name] = "#{superclass_metadata[:example_group][:name]} #{self[:example_group][:description]}".strip 
        else
          self[:example_group][:name] = "#{self[:example_group][:describes]} #{self[:example_group][:description]}".strip
        end
      end

    end
  end
end