1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
# frozen_string_literal: true
require 'set'
module Sprockets
# Functional utilities for dealing with Processor functions.
#
# A Processor is a general function that may modify or transform an asset as
# part of the pipeline. CoffeeScript to JavaScript conversion, Minification
# or Concatenation are all implemented as separate Processor steps.
#
# Processors maybe any object that responds to call. So procs or a class that
# defines a self.call method.
#
# For ergonomics, processors may return a number of shorthand values.
# Unfortunately, this means that processors can not compose via ordinary
# function composition. The composition helpers here can help.
module ProcessorUtils
extend self
class CompositeProcessor < Struct.new(:processor_strategy, :param, :processors) # :nodoc:
SINGULAR = lambda { |param, input| ProcessorUtils.call_processor param, input }
PLURAL = lambda { |param, input| ProcessorUtils.call_processors param, input }
def self.create(processors)
if processors.length == 1
new SINGULAR, processors.first, processors
else
new PLURAL, processors, processors
end
end
def call(input)
processor_strategy.call param, input
end
def cache_key
ProcessorUtils.processors_cache_keys(processors)
end
end
# Public: Compose processors in right to left order.
#
# processors - Array of processors callables
#
# Returns a composed Proc.
def compose_processors(*processors)
CompositeProcessor.create processors
end
# Public: Invoke list of processors in right to left order.
#
# The right to left order processing mirrors standard function composition.
# Think about:
#
# bundle.call(uglify.call(coffee.call(input)))
#
# processors - Array of processor callables
# input - Hash of input data to pass to each processor
#
# Returns a Hash with :data and other processor metadata key/values.
def call_processors(processors, input)
data = input[:data] || ""
metadata = (input[:metadata] || {}).dup
processors.reverse_each do |processor|
result = call_processor(processor, input.merge(data: data, metadata: metadata))
data = result.delete(:data)
metadata.merge!(result)
end
metadata.merge(data: data)
end
# Public: Invoke processor.
#
# processor - Processor callables
# input - Hash of input data to pass to processor
#
# Returns a Hash with :data and other processor metadata key/values.
def call_processor(processor, input)
metadata = (input[:metadata] || {}).dup
metadata[:data] = input[:data]
case result = processor.call({data: "", metadata: {}}.merge(input))
when NilClass
metadata
when Hash
metadata.merge(result)
when String
metadata.merge(data: result)
else
raise TypeError, "invalid processor return type: #{result.class}"
end
end
# Internal: Get processor defined cached key.
#
# processor - Processor function
#
# Returns JSON serializable key or nil.
def processor_cache_key(processor)
processor.cache_key if processor.respond_to?(:cache_key)
end
# Internal: Get combined cache keys for set of processors.
#
# processors - Array of processor functions
#
# Returns Array of JSON serializable keys.
def processors_cache_keys(processors)
processors.map { |processor| processor_cache_key(processor) }
end
# Internal: Set of all "simple" value types allowed to be returned in
# processor metadata.
VALID_METADATA_VALUE_TYPES = Set.new([
String,
Symbol,
TrueClass,
FalseClass,
NilClass,
Integer
]).freeze
# Internal: Set of all nested compound metadata types that can nest values.
VALID_METADATA_COMPOUND_TYPES = Set.new([
Array,
Hash,
Set
]).freeze
# Internal: Hash of all "simple" value types allowed to be returned in
# processor metadata.
VALID_METADATA_VALUE_TYPES_HASH = VALID_METADATA_VALUE_TYPES.each_with_object({}) do |type, hash|
hash[type] = true
end.freeze
# Internal: Hash of all nested compound metadata types that can nest values.
VALID_METADATA_COMPOUND_TYPES_HASH = VALID_METADATA_COMPOUND_TYPES.each_with_object({}) do |type, hash|
hash[type] = true
end.freeze
# Internal: Set of all allowed metadata types.
VALID_METADATA_TYPES = (VALID_METADATA_VALUE_TYPES + VALID_METADATA_COMPOUND_TYPES).freeze
# Internal: Validate returned result of calling a processor pipeline and
# raise a friendly user error message.
#
# result - Metadata Hash returned from call_processors
#
# Returns result or raises a TypeError.
def validate_processor_result!(result)
if !result.instance_of?(Hash)
raise TypeError, "processor metadata result was expected to be a Hash, but was #{result.class}"
end
if !result[:data].instance_of?(String)
raise TypeError, "processor :data was expected to be a String, but as #{result[:data].class}"
end
result.each do |key, value|
if !key.instance_of?(Symbol)
raise TypeError, "processor metadata[#{key.inspect}] expected to be a Symbol"
end
end
result
end
end
end
|