lib/sprockets/processing.rb



# frozen_string_literal: true
require 'sprockets/file_reader'
require 'sprockets/mime'
require 'sprockets/processor_utils'
require 'sprockets/uri_utils'
require 'sprockets/utils'

module Sprockets
  # `Processing` is an internal mixin whose public methods are exposed on
  # the `Environment` and `CachedEnvironment` classes.
  module Processing
    include ProcessorUtils, URIUtils, Utils

    def pipelines
      config[:pipelines]
    end

    # Registers a pipeline that will be called by `call_processor` method.
    def register_pipeline(name, proc = nil, &block)
      proc ||= block

      self.config = hash_reassoc(config, :pipeline_exts) do |pipeline_exts|
        pipeline_exts.merge(".#{name}".freeze => name.to_sym)
      end

      self.config = hash_reassoc(config, :pipelines) do |pipelines|
        pipelines.merge(name.to_sym => proc)
      end
    end

    # Preprocessors are ran before Postprocessors and Engine
    # processors.
    def preprocessors
      config[:preprocessors]
    end
    alias_method :processors, :preprocessors

    # Postprocessors are ran after Preprocessors and Engine processors.
    def postprocessors
      config[:postprocessors]
    end

    # Registers a new Preprocessor `klass` for `mime_type`.
    #
    #     register_preprocessor 'text/css', Sprockets::DirectiveProcessor
    #
    # A block can be passed for to create a shorthand processor.
    #
    #     register_preprocessor 'text/css' do |input|
    #       input[:data].gsub(...)
    #     end
    #
    def register_preprocessor(*args, &block)
      register_config_processor(:preprocessors, *args, &block)
      compute_transformers!(self.config[:registered_transformers])
    end
    alias_method :register_processor, :register_preprocessor

    # Registers a new Postprocessor `klass` for `mime_type`.
    #
    #     register_postprocessor 'application/javascript', Sprockets::DirectiveProcessor
    #
    # A block can be passed for to create a shorthand processor.
    #
    #     register_postprocessor 'application/javascript' do |input|
    #       input[:data].gsub(...)
    #     end
    #
    def register_postprocessor(*args, &block)
      register_config_processor(:postprocessors, *args, &block)
      compute_transformers!(self.config[:registered_transformers])
    end

    # Remove Preprocessor `klass` for `mime_type`.
    #
    #     unregister_preprocessor 'text/css', Sprockets::DirectiveProcessor
    #
    def unregister_preprocessor(*args)
      unregister_config_processor(:preprocessors, *args)
      compute_transformers!(self.config[:registered_transformers])
    end
    alias_method :unregister_processor, :unregister_preprocessor

    # Remove Postprocessor `klass` for `mime_type`.
    #
    #     unregister_postprocessor 'text/css', Sprockets::DirectiveProcessor
    #
    def unregister_postprocessor(*args)
      unregister_config_processor(:postprocessors, *args)
      compute_transformers!(self.config[:registered_transformers])
    end

    # Bundle Processors are ran on concatenated assets rather than
    # individual files.
    def bundle_processors
      config[:bundle_processors]
    end

    # Registers a new Bundle Processor `klass` for `mime_type`.
    #
    #     register_bundle_processor  'application/javascript', Sprockets::DirectiveProcessor
    #
    # A block can be passed for to create a shorthand processor.
    #
    #     register_bundle_processor 'application/javascript' do |input|
    #       input[:data].gsub(...)
    #     end
    #
    def register_bundle_processor(*args, &block)
      register_config_processor(:bundle_processors, *args, &block)
    end

    # Remove Bundle Processor `klass` for `mime_type`.
    #
    #     unregister_bundle_processor 'application/javascript', Sprockets::DirectiveProcessor
    #
    def unregister_bundle_processor(*args)
      unregister_config_processor(:bundle_processors, *args)
    end

    # Public: Register bundle metadata reducer function.
    #
    # Examples
    #
    #   Sprockets.register_bundle_metadata_reducer 'application/javascript', :jshint_errors, [], :+
    #
    #   Sprockets.register_bundle_metadata_reducer 'text/css', :selector_count, 0 { |total, count|
    #     total + count
    #   }
    #
    # mime_type - String MIME Type. Use '*/*' applies to all types.
    # key       - Symbol metadata key
    # initial   - Initial memo to pass to the reduce funciton (default: nil)
    # block     - Proc accepting the memo accumulator and current value
    #
    # Returns nothing.
    def register_bundle_metadata_reducer(mime_type, key, *args, &block)
      case args.size
      when 0
        reducer = block
      when 1
        if block_given?
          initial = args[0]
          reducer = block
        else
          initial = nil
          reducer = args[0].to_proc
        end
      when 2
        initial = args[0]
        reducer = args[1].to_proc
      else
        raise ArgumentError, "wrong number of arguments (#{args.size} for 0..2)"
      end

      self.config = hash_reassoc(config, :bundle_reducers, mime_type) do |reducers|
        reducers.merge(key => [initial, reducer])
      end
    end

    protected
      def resolve_processors_cache_key_uri(uri)
        params = parse_uri_query_params(uri[11..-1])
        processors = processors_for(params[:type], params[:file_type], params[:pipeline])
        processors_cache_keys(processors)
      end

      def build_processors_uri(type, file_type, pipeline)
        query = encode_uri_query_params(
          type: type,
          file_type: file_type,
          pipeline: pipeline
        )
        "processors:#{query}"
      end

      def processors_for(type, file_type, pipeline)
        pipeline ||= :default
        if fn = config[:pipelines][pipeline.to_sym]
          fn.call(self, type, file_type)
        else
          raise Error, "no pipeline: #{pipeline}"
        end
      end

      def default_processors_for(type, file_type)
        bundled_processors = config[:bundle_processors][type]
        if bundled_processors.any?
          bundled_processors
        else
          self_processors_for(type, file_type)
        end
      end

      def self_processors_for(type, file_type)
        processors = []

        processors.concat config[:postprocessors][type]
        if type != file_type && processor = config[:transformers][file_type][type]
          processors << processor
        end
        processors.concat config[:preprocessors][file_type]

        if processors.any? || mime_type_charset_detecter(type)
          processors << FileReader
        end

        processors
      end

    private
      def register_config_processor(type, mime_type, processor = nil, &block)
        processor ||= block

        self.config = hash_reassoc(config, type, mime_type) do |processors|
          processors.unshift(processor)
          processors
        end
      end

      def unregister_config_processor(type, mime_type, processor)
        self.config = hash_reassoc(config, type, mime_type) do |processors|
          processors.delete_if { |p| p == processor || p.class == processor }
          processors
        end
      end
  end
end