-
Notifications
You must be signed in to change notification settings - Fork 791
/
Copy pathprocessor_utils.rb
170 lines (148 loc) · 5.32 KB
/
processor_utils.rb
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