File: processor_utils.rb

package info (click to toggle)
ruby-sprockets 4.2.1-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,964 kB
  • sloc: ruby: 13,014; javascript: 157; makefile: 4
file content (170 lines) | stat: -rw-r--r-- 5,450 bytes parent folder | download | duplicates (2)
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