<commit_before>require 'json'

module RSpecRcv
  module RSpec
    module Metadata
      extend self

      DEFAULTS = {
          exportable_proc: Proc.new { response.body },
          export_with: :to_json,
          fail_on_changed_output: true,
          export_fixture_to: nil,
          base_path: nil
      }

      def configure!
        ::RSpec.configure do |config|
          when_tagged_with_rcv = { rcv: lambda { |val| !!val } }

          config.after(:each, when_tagged_with_rcv) do |ex|
            example = ex.respond_to?(:metadata) ? ex : ex.example
            opts = DEFAULTS.merge(example.metadata[:rcv])
            path = if opts[:base_path]
                     File.join(opts[:base_path], opts[:export_fixture_to])
                   else
                     opts[:export_fixture_to]
                   end

            data = instance_eval(&opts[:exportable_proc])

            existing_data = nil
            if File.exists?(path)
              begin
                existing_data = JSON.parse(File.read(path))
              rescue JSON::ParserError
                # The file must not have been valid, so we will overwrite it
              end
            end

            if existing_data && opts[:fail_on_changed_output]
              if existing_data["data"] != data
                raise RSpecRcv::DataChangedError.new("Existing data will be overwritten. Turn off this feature with fail_on_changed_output=false")
              end
            end

            unless existing_data && existing_data["file"] == ex.file_path && existing_data["data"] == data
              File.open(path, 'w') do |file|
                output = { recorded_at: Time.now, file: ex.file_path, data: data }
                file.write(output.send(opts[:export_with]))
              end
            end
          end
        end
      end
    end
  end
end<commit_msg>Pull all of the non-rspec behavior into a handler<commit_after>require 'json'

module RSpecRcv
  module RSpec
    module Metadata
      extend self

      class Handler
        DEFAULTS = {
            exportable_proc: Proc.new { response.body },
            export_with: :to_json,
            fail_on_changed_output: true,
            export_fixture_to: nil,
            base_path: nil
        }

        def initialize(file_path, data, metadata: {})
          @file_path = file_path
          @opts = DEFAULTS.merge(metadata)
          @data = data.call(@opts)
        end

        def call
          return if existing_data && existing_data["file"] == file_path && existing_data["data"] == data

          if existing_data && opts[:fail_on_changed_output]
            if existing_data["data"] != data
              raise RSpecRcv::DataChangedError.new("Existing data will be overwritten. Turn off this feature with fail_on_changed_output=false")
            end
          end

          File.open(path, 'w') do |file|
            output = { recorded_at: Time.now, file: file_path, data: data }
            file.write(output.send(opts[:export_with]))
          end
        end

        private

        attr_reader :file_path, :data, :opts

        def path
          if opts[:base_path]
            File.join(opts[:base_path], opts[:export_fixture_to])
          else
            opts[:export_fixture_to]
          end
        end

        def existing_data
          @existing_data ||= if File.exists?(path)
            begin
              existing_data = JSON.parse(File.read(path))
            rescue JSON::ParserError
              # The file must not have been valid, so we will overwrite it
            end
          end
        end
      end

      def configure!
        ::RSpec.configure do |config|
          when_tagged_with_rcv = { rcv: lambda { |val| !!val } }

          config.after(:each, when_tagged_with_rcv) do |ex|
            example = ex.respond_to?(:metadata) ? ex : ex.example
            opts = example.metadata[:rcv]
            data_proc = lambda { |opts| instance_eval(&opts[:exportable_proc]) }

            Handler.new(ex.file_path, data_proc, metadata: opts).call
          end
        end
      end
    end
  end
end
{'fraction_non_alphanumeric': 0.08896969696969698, 'fraction_numerical': 0.0, 'mean_word_length': 1.7397078353253652, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, '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': '6800339', 'n_tokens_mistral': 1164, 'n_tokens_neox': 1133, 'n_words': 281}