sprockets 2.12.5 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sprockets might be problematic. Click here for more details.

Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +2 -2
  3. data/README.md +61 -34
  4. data/lib/rake/sprocketstask.rb +5 -4
  5. data/lib/sprockets.rb +123 -85
  6. data/lib/sprockets/asset.rb +161 -200
  7. data/lib/sprockets/asset_uri.rb +64 -0
  8. data/lib/sprockets/base.rb +138 -373
  9. data/lib/sprockets/bower.rb +56 -0
  10. data/lib/sprockets/bundle.rb +32 -0
  11. data/lib/sprockets/cache.rb +220 -0
  12. data/lib/sprockets/cache/file_store.rb +145 -13
  13. data/lib/sprockets/cache/memory_store.rb +66 -0
  14. data/lib/sprockets/cache/null_store.rb +46 -0
  15. data/lib/sprockets/cached_environment.rb +103 -0
  16. data/lib/sprockets/closure_compressor.rb +30 -12
  17. data/lib/sprockets/coffee_script_template.rb +23 -0
  18. data/lib/sprockets/compressing.rb +20 -25
  19. data/lib/sprockets/configuration.rb +95 -0
  20. data/lib/sprockets/context.rb +68 -131
  21. data/lib/sprockets/directive_processor.rb +138 -179
  22. data/lib/sprockets/eco_template.rb +10 -19
  23. data/lib/sprockets/ejs_template.rb +10 -19
  24. data/lib/sprockets/encoding_utils.rb +246 -0
  25. data/lib/sprockets/engines.rb +40 -29
  26. data/lib/sprockets/environment.rb +10 -66
  27. data/lib/sprockets/erb_template.rb +23 -0
  28. data/lib/sprockets/errors.rb +5 -13
  29. data/lib/sprockets/http_utils.rb +97 -0
  30. data/lib/sprockets/jst_processor.rb +28 -15
  31. data/lib/sprockets/lazy_processor.rb +15 -0
  32. data/lib/sprockets/legacy.rb +23 -0
  33. data/lib/sprockets/legacy_proc_processor.rb +35 -0
  34. data/lib/sprockets/legacy_tilt_processor.rb +29 -0
  35. data/lib/sprockets/manifest.rb +128 -99
  36. data/lib/sprockets/mime.rb +114 -33
  37. data/lib/sprockets/path_utils.rb +179 -0
  38. data/lib/sprockets/paths.rb +13 -26
  39. data/lib/sprockets/processing.rb +198 -107
  40. data/lib/sprockets/resolve.rb +289 -0
  41. data/lib/sprockets/sass_compressor.rb +36 -17
  42. data/lib/sprockets/sass_template.rb +269 -46
  43. data/lib/sprockets/server.rb +113 -83
  44. data/lib/sprockets/transformers.rb +69 -0
  45. data/lib/sprockets/uglifier_compressor.rb +36 -15
  46. data/lib/sprockets/utils.rb +161 -44
  47. data/lib/sprockets/version.rb +1 -1
  48. data/lib/sprockets/yui_compressor.rb +37 -12
  49. metadata +64 -106
  50. data/lib/sprockets/asset_attributes.rb +0 -137
  51. data/lib/sprockets/bundled_asset.rb +0 -78
  52. data/lib/sprockets/caching.rb +0 -96
  53. data/lib/sprockets/charset_normalizer.rb +0 -41
  54. data/lib/sprockets/index.rb +0 -100
  55. data/lib/sprockets/processed_asset.rb +0 -152
  56. data/lib/sprockets/processor.rb +0 -32
  57. data/lib/sprockets/safety_colons.rb +0 -28
  58. data/lib/sprockets/sass_cache_store.rb +0 -29
  59. data/lib/sprockets/sass_functions.rb +0 -70
  60. data/lib/sprockets/sass_importer.rb +0 -30
  61. data/lib/sprockets/scss_template.rb +0 -13
  62. data/lib/sprockets/static_asset.rb +0 -60
@@ -1,49 +1,130 @@
1
- require 'rack/mime'
1
+ require 'sprockets/encoding_utils'
2
2
 
3
3
  module Sprockets
4
4
  module Mime
5
- # Returns a `Hash` of registered mime types registered on the
6
- # environment and those part of `Rack::Mime`.
7
- #
8
- # If an `ext` is given, it will lookup the mime type for that extension.
9
- def mime_types(ext = nil)
10
- if ext.nil?
11
- Rack::Mime::MIME_TYPES.merge(@mime_types)
12
- else
13
- ext = Sprockets::Utils.normalize_extension(ext)
14
- @mime_types[ext] || Rack::Mime::MIME_TYPES[ext]
5
+ include HTTPUtils
6
+
7
+ # Public: Mapping of MIME type Strings to properties Hash.
8
+ #
9
+ # key - MIME Type String
10
+ # value - Hash
11
+ # extensions - Array of extnames
12
+ # charset - Default Encoding or function to detect encoding
13
+ #
14
+ # Returns Hash.
15
+ attr_reader :mime_types
16
+
17
+ # Internal: Mapping of MIME extension Strings to MIME type Strings.
18
+ #
19
+ # Used for internal fast lookup purposes.
20
+ #
21
+ # Examples:
22
+ #
23
+ # mime_exts['.js'] #=> 'application/javascript'
24
+ #
25
+ # key - MIME extension String
26
+ # value - MIME Type String
27
+ #
28
+ # Returns Hash.
29
+ attr_reader :mime_exts
30
+
31
+ # Public: Register a new mime type.
32
+ #
33
+ # mime_type - String MIME Type
34
+ # options - Hash
35
+ # extensions: Array of String extnames
36
+ # charset: Proc/Method that detects the charset of a file.
37
+ # See EncodingUtils.
38
+ #
39
+ # Returns nothing.
40
+ def register_mime_type(mime_type, options = {})
41
+ # Legacy extension argument, will be removed from 4.x
42
+ if options.is_a?(String)
43
+ options = { extensions: [options] }
15
44
  end
16
- end
17
45
 
18
- # Returns a `Hash` of explicitly registered mime types.
19
- def registered_mime_types
20
- @mime_types.dup
21
- end
46
+ extnames = Array(options[:extensions]).map { |extname|
47
+ Sprockets::Utils.normalize_extension(extname)
48
+ }
22
49
 
23
- if {}.respond_to?(:key)
24
- def extension_for_mime_type(type)
25
- mime_types.key(type)
50
+ charset = options[:charset]
51
+ charset ||= EncodingUtils::DETECT if mime_type.start_with?('text/')
52
+
53
+ mutate_config(:mime_exts) do |mime_exts|
54
+ extnames.each do |extname|
55
+ mime_exts[extname] = mime_type
56
+ end
57
+ mime_exts
26
58
  end
27
- else
28
- def extension_for_mime_type(type)
29
- mime_types.index(type)
59
+
60
+ mutate_config(:mime_types) do |mime_types|
61
+ type = { extensions: extnames }
62
+ type[:charset] = charset if charset
63
+ mime_types.merge(mime_type => type)
30
64
  end
31
65
  end
32
66
 
33
- # Register a new mime type.
34
- def register_mime_type(mime_type, ext)
35
- ext = Sprockets::Utils.normalize_extension(ext)
36
- @mime_types[ext] = mime_type
67
+ # Public: Read file on disk with MIME type specific encoding.
68
+ #
69
+ # filename - String path
70
+ # content_type - String MIME type
71
+ #
72
+ # Returns String file contents transcoded to UTF-8 or in its external
73
+ # encoding.
74
+ def read_file(filename, content_type = nil)
75
+ data = File.open(filename, 'rb') { |f| f.read }
76
+
77
+ if type = mime_types[content_type]
78
+ if charset = type[:charset]
79
+ data = charset.call(data).encode(Encoding::UTF_8)
80
+ end
81
+ end
82
+
83
+ data
37
84
  end
38
85
 
39
- if defined? Encoding
40
- # Returns the correct encoding for a given mime type, while falling
41
- # back on the default external encoding, if it exists.
42
- def encoding_for_mime_type(type)
43
- encoding = Encoding::BINARY if type =~ %r{^(image|audio|video)/}
44
- encoding ||= default_external_encoding if respond_to?(:default_external_encoding)
45
- encoding
86
+ # Public: Mapping of supported HTTP Content/Transfer encodings
87
+ #
88
+ # key - String name
89
+ # value - Method/Proc to encode data
90
+ #
91
+ # Returns Hash.
92
+ attr_reader :encodings
93
+
94
+ # Public: Register a new encoding.
95
+ #
96
+ # Examples
97
+ #
98
+ # register_encoding :gzip, EncodingUtils::GZIP
99
+ #
100
+ # key - String name
101
+ # encode - Method/Proc to encode data
102
+ #
103
+ # Returns nothing.
104
+ def register_encoding(name, encode)
105
+ mutate_config(:encodings) do |encodings|
106
+ encodings.merge(name.to_s => encode)
46
107
  end
47
108
  end
109
+
110
+ private
111
+ def read_input(input)
112
+ read_file(input[:filename], input[:content_type])
113
+ end
114
+
115
+ # Internal: Get a postprocessor to perform the encoding.
116
+ #
117
+ # encoding - String encoding.
118
+ #
119
+ # Returns an Array of Processors.
120
+ def unwrap_encoding_processors(encoding)
121
+ processors = []
122
+ if encoder = self.encodings[encoding]
123
+ processors << lambda do |input|
124
+ { data: encoder.call(input[:data]), encoding: encoding }
125
+ end
126
+ end
127
+ processors
128
+ end
48
129
  end
49
130
  end
@@ -0,0 +1,179 @@
1
+ require 'fileutils'
2
+
3
+ module Sprockets
4
+ # Internal: File and path related utilities. Mixed into Environment.
5
+ #
6
+ # Probably would be called FileUtils, but that causes namespace annoyances
7
+ # when code actually wants to reference ::FileUtils.
8
+ module PathUtils
9
+ extend self
10
+
11
+ # Internal: Like `File.stat`.
12
+ #
13
+ # path - String file or directory path
14
+ #
15
+ # Returns nil if the file does not exist.
16
+ def stat(path)
17
+ if File.exist?(path)
18
+ File.stat(path.to_s)
19
+ else
20
+ nil
21
+ end
22
+ end
23
+
24
+ # Internal: Like `File.file?`.
25
+ #
26
+ # path - String file path.
27
+ #
28
+ # Returns true path exists and is a file.
29
+ def file?(path)
30
+ if stat = self.stat(path)
31
+ stat.file?
32
+ else
33
+ false
34
+ end
35
+ end
36
+
37
+ # Internal: A version of `Dir.entries` that filters out `.` files and `~`
38
+ # swap files.
39
+ #
40
+ # path - String directory path
41
+ #
42
+ # Returns an empty `Array` if the directory does not exist.
43
+ def entries(path)
44
+ if File.directory?(path)
45
+ Dir.entries(path).reject { |entry| entry =~ /^\.|~$|^\#.*\#$/ }.sort
46
+ else
47
+ []
48
+ end
49
+ end
50
+
51
+ # Internal: Check if path is absolute or relative.
52
+ #
53
+ # path - String path.
54
+ #
55
+ # Returns true if path is absolute, otherwise false.
56
+ if File::ALT_SEPARATOR
57
+ # On Windows, ALT_SEPARATOR is \
58
+ def absolute_path?(path)
59
+ path[0] == File::SEPARATOR || path[0] == File::ALT_SEPARATOR
60
+ end
61
+ else
62
+ def absolute_path?(path)
63
+ path[0] == File::SEPARATOR
64
+ end
65
+ end
66
+
67
+ # Internal: Check if path is explicitly relative.
68
+ # Starts with "./" or "../".
69
+ #
70
+ # path - String path.
71
+ #
72
+ # Returns true if path is relative, otherwise false.
73
+ def relative_path?(path)
74
+ path =~ /^\.\.?($|\/)/ ? true : false
75
+ end
76
+
77
+ # Internal: Get relative path for root path and subpath.
78
+ #
79
+ # path - String path
80
+ # subpath - String subpath of path
81
+ #
82
+ # Returns relative String path if subpath is a subpath of path, or nil if
83
+ # subpath is outside of path.
84
+ def split_subpath(path, subpath)
85
+ path = File.join(path, '')
86
+ if subpath.start_with?(path)
87
+ subpath[path.length..-1]
88
+ else
89
+ nil
90
+ end
91
+ end
92
+
93
+ # Internal: Detect root path and base for file in a set of paths.
94
+ #
95
+ # paths - Array of String paths
96
+ # filename - String path of file expected to be in one of the paths.
97
+ #
98
+ # Returns [String root, String path]
99
+ def paths_split(paths, filename)
100
+ paths.each do |path|
101
+ if subpath = split_subpath(path, filename)
102
+ return path, subpath
103
+ end
104
+ end
105
+ nil
106
+ end
107
+
108
+ # Internal: Get path's extensions.
109
+ #
110
+ # path - String
111
+ #
112
+ # Returns an Array of String extnames.
113
+ def path_extnames(path)
114
+ File.basename(path).scan(/\.[^.]+/)
115
+ end
116
+
117
+ # Internal: Stat all the files under a directory.
118
+ #
119
+ # dir - A String directory
120
+ #
121
+ # Returns an Enumerator of [path, stat].
122
+ def stat_directory(dir)
123
+ return to_enum(__method__, dir) unless block_given?
124
+
125
+ self.entries(dir).each do |entry|
126
+ path = File.join(dir, entry)
127
+ if stat = self.stat(path)
128
+ yield path, stat
129
+ end
130
+ end
131
+
132
+ nil
133
+ end
134
+
135
+ # Internal: Recursive stat all the files under a directory.
136
+ #
137
+ # dir - A String directory
138
+ #
139
+ # Returns an Enumerator of [path, stat].
140
+ def stat_tree(dir, &block)
141
+ return to_enum(__method__, dir) unless block_given?
142
+
143
+ self.stat_directory(dir) do |path, stat|
144
+ yield path, stat
145
+
146
+ if stat.directory?
147
+ stat_tree(path, &block)
148
+ end
149
+ end
150
+
151
+ nil
152
+ end
153
+
154
+ # Internal: Write to a file atomically. Useful for situations where you
155
+ # don't want other processes or threads to see half-written files.
156
+ #
157
+ # Utils.atomic_write('important.file') do |file|
158
+ # file.write('hello')
159
+ # end
160
+ #
161
+ # Returns nothing.
162
+ def atomic_write(filename)
163
+ tmpname = [
164
+ File.dirname(filename),
165
+ Thread.current.object_id,
166
+ Process.pid,
167
+ rand(1000000)
168
+ ].join('.')
169
+
170
+ File.open(tmpname, 'wb+') do |f|
171
+ yield f
172
+ end
173
+
174
+ FileUtils.mv(tmpname, filename)
175
+ ensure
176
+ FileUtils.rm(tmpname) if File.exist?(tmpname)
177
+ end
178
+ end
179
+ end
@@ -4,33 +4,31 @@ module Sprockets
4
4
  #
5
5
  # All relative paths are expanded with root as its base. To be
6
6
  # useful set this to your applications root directory. (`Rails.root`)
7
- def root
8
- @trail.root.dup
9
- end
7
+ attr_reader :root
10
8
 
11
9
  # Returns an `Array` of path `String`s.
12
10
  #
13
11
  # These paths will be used for asset logical path lookups.
14
- #
15
- # Note that a copy of the `Array` is returned so mutating will
16
- # have no affect on the environment. See `append_path`,
17
- # `prepend_path`, and `clear_paths`.
18
- def paths
19
- @trail.paths.dup
20
- end
12
+ attr_reader :paths
21
13
 
22
14
  # Prepend a `path` to the `paths` list.
23
15
  #
24
16
  # Paths at the end of the `Array` have the least priority.
25
17
  def prepend_path(path)
26
- @trail.prepend_path(path)
18
+ mutate_config(:paths) do |paths|
19
+ path = File.expand_path(path, root).dup.freeze
20
+ paths.unshift(path)
21
+ end
27
22
  end
28
23
 
29
24
  # Append a `path` to the `paths` list.
30
25
  #
31
26
  # Paths at the beginning of the `Array` have a higher priority.
32
27
  def append_path(path)
33
- @trail.append_path(path)
28
+ mutate_config(:paths) do |paths|
29
+ path = File.expand_path(path, root).dup.freeze
30
+ paths.push(path)
31
+ end
34
32
  end
35
33
 
36
34
  # Clear all paths and start fresh.
@@ -39,20 +37,9 @@ module Sprockets
39
37
  # completely wipe the paths list and reappend them in the order
40
38
  # you want.
41
39
  def clear_paths
42
- @trail.paths.dup.each { |path| @trail.remove_path(path) }
40
+ mutate_config(:paths) do |paths|
41
+ paths.clear
42
+ end
43
43
  end
44
-
45
- # Returns an `Array` of extensions.
46
- #
47
- # These extensions maybe omitted from logical path searches.
48
- #
49
- # # => [".js", ".css", ".coffee", ".sass", ...]
50
- #
51
- def extensions
52
- @trail.extensions.dup
53
- end
54
-
55
- protected
56
- attr_reader :trail
57
44
  end
58
45
  end
@@ -1,64 +1,43 @@
1
1
  require 'sprockets/engines'
2
+ require 'sprockets/lazy_processor'
3
+ require 'sprockets/legacy_proc_processor'
4
+ require 'sprockets/legacy_tilt_processor'
2
5
  require 'sprockets/mime'
3
- require 'sprockets/processor'
4
6
  require 'sprockets/utils'
5
7
 
6
8
  module Sprockets
7
9
  # `Processing` is an internal mixin whose public methods are exposed on
8
- # the `Environment` and `Index` classes.
10
+ # the `Environment` and `CachedEnvironment` classes.
9
11
  module Processing
10
- # Returns an `Array` of format extension `String`s.
11
- #
12
- # format_extensions
13
- # # => ['.js', '.css']
14
- #
15
- def format_extensions
16
- @trail.extensions - @engines.keys
17
- end
18
-
19
- # Deprecated alias for `preprocessors`.
20
- def processors(*args)
21
- preprocessors(*args)
22
- end
23
-
24
- # Returns an `Array` of `Processor` classes. If a `mime_type`
25
- # argument is supplied, the processors registered under that
26
- # extension will be returned.
27
- #
28
12
  # Preprocessors are ran before Postprocessors and Engine
29
13
  # processors.
14
+ attr_reader :preprocessors
15
+
16
+ # Internal: Find and load preprocessors by mime type.
30
17
  #
31
- # All `Processor`s must follow the `Tilt::Template` interface. It is
32
- # recommended to subclass `Tilt::Template`.
33
- def preprocessors(mime_type = nil)
34
- if mime_type
35
- @preprocessors[mime_type].dup
36
- else
37
- deep_copy_hash(@preprocessors)
18
+ # mime_type - String MIME type.
19
+ #
20
+ # Returns Array of Procs.
21
+ def unwrap_preprocessors(mime_type)
22
+ preprocessors[mime_type].map do |processor|
23
+ unwrap_processor(processor)
38
24
  end
39
25
  end
40
26
 
41
- # Returns an `Array` of `Processor` classes. If a `mime_type`
42
- # argument is supplied, the processors registered under that
43
- # extension will be returned.
44
- #
45
27
  # Postprocessors are ran after Preprocessors and Engine processors.
28
+ attr_reader :postprocessors
29
+
30
+ # Internal: Find and load postprocessors by mime type.
46
31
  #
47
- # All `Processor`s must follow the `Tilt::Template` interface. It is
48
- # recommended to subclass `Tilt::Template`.
49
- def postprocessors(mime_type = nil)
50
- if mime_type
51
- @postprocessors[mime_type].dup
52
- else
53
- deep_copy_hash(@postprocessors)
32
+ # mime_type - String MIME type.
33
+ #
34
+ # Returns Array of Procs.
35
+ def unwrap_postprocessors(mime_type)
36
+ postprocessors[mime_type].map do |processor|
37
+ unwrap_processor(processor)
54
38
  end
55
39
  end
56
40
 
57
- # Deprecated alias for `register_preprocessor`.
58
- def register_processor(*args, &block)
59
- register_preprocessor(*args, &block)
60
- end
61
-
62
41
  # Registers a new Preprocessor `klass` for `mime_type`.
63
42
  #
64
43
  # register_preprocessor 'text/css', Sprockets::DirectiveProcessor
@@ -70,42 +49,28 @@ module Sprockets
70
49
  # end
71
50
  #
72
51
  def register_preprocessor(mime_type, klass, &block)
73
- if block_given?
74
- name = klass.to_s
75
- klass = Class.new(Processor) do
76
- @name = name
77
- @processor = block
78
- end
52
+ mutate_hash_config(:preprocessors, mime_type) do |processors|
53
+ processors.push(wrap_processor(klass, block))
54
+ processors
79
55
  end
80
-
81
- @preprocessors[mime_type].push(klass)
82
56
  end
83
57
 
84
58
  # Registers a new Postprocessor `klass` for `mime_type`.
85
59
  #
86
- # register_postprocessor 'text/css', Sprockets::CharsetNormalizer
60
+ # register_postprocessor 'application/javascript', Sprockets::DirectiveProcessor
87
61
  #
88
62
  # A block can be passed for to create a shorthand processor.
89
63
  #
90
- # register_postprocessor 'text/css', :my_processor do |context, data|
64
+ # register_postprocessor 'application/javascript', :my_processor do |context, data|
91
65
  # data.gsub(...)
92
66
  # end
93
67
  #
94
- def register_postprocessor(mime_type, klass, &block)
95
- if block_given?
96
- name = klass.to_s
97
- klass = Class.new(Processor) do
98
- @name = name
99
- @processor = block
100
- end
68
+ def register_postprocessor(mime_type, klass, proc = nil, &block)
69
+ proc ||= block
70
+ mutate_hash_config(:postprocessors, mime_type) do |processors|
71
+ processors.push(wrap_processor(klass, proc))
72
+ processors
101
73
  end
102
-
103
- @postprocessors[mime_type].push(klass)
104
- end
105
-
106
- # Deprecated alias for `unregister_preprocessor`.
107
- def unregister_processor(*args)
108
- unregister_preprocessor(*args)
109
74
  end
110
75
 
111
76
  # Remove Preprocessor `klass` for `mime_type`.
@@ -114,13 +79,15 @@ module Sprockets
114
79
  #
115
80
  def unregister_preprocessor(mime_type, klass)
116
81
  if klass.is_a?(String) || klass.is_a?(Symbol)
117
- klass = @preprocessors[mime_type].detect { |cls|
118
- cls.respond_to?(:name) &&
119
- cls.name == "Sprockets::Processor (#{klass})"
82
+ klass = preprocessors[mime_type].detect { |cls|
83
+ cls.respond_to?(:name) && cls.name == "Sprockets::LegacyProcProcessor (#{klass})"
120
84
  }
121
85
  end
122
86
 
123
- @preprocessors[mime_type].delete(klass)
87
+ mutate_hash_config(:preprocessors, mime_type) do |processors|
88
+ processors.delete(klass)
89
+ processors
90
+ end
124
91
  end
125
92
 
126
93
  # Remove Postprocessor `klass` for `mime_type`.
@@ -129,78 +96,202 @@ module Sprockets
129
96
  #
130
97
  def unregister_postprocessor(mime_type, klass)
131
98
  if klass.is_a?(String) || klass.is_a?(Symbol)
132
- klass = @postprocessors[mime_type].detect { |cls|
133
- cls.respond_to?(:name) &&
134
- cls.name == "Sprockets::Processor (#{klass})"
99
+ klass = postprocessors[mime_type].detect { |cls|
100
+ cls.respond_to?(:name) && cls.name == "Sprockets::LegacyProcProcessor (#{klass})"
135
101
  }
136
102
  end
137
103
 
138
- @postprocessors[mime_type].delete(klass)
104
+ mutate_hash_config(:postprocessors, mime_type) do |processors|
105
+ processors.delete(klass)
106
+ processors
107
+ end
139
108
  end
140
109
 
141
- # Returns an `Array` of `Processor` classes. If a `mime_type`
142
- # argument is supplied, the processors registered under that
143
- # extension will be returned.
144
- #
145
110
  # Bundle Processors are ran on concatenated assets rather than
146
111
  # individual files.
112
+ attr_reader :bundle_processors
113
+
114
+ # Internal: Find and load bundle processors by mime type.
147
115
  #
148
- # All `Processor`s must follow the `Tilt::Template` interface. It is
149
- # recommended to subclass `Tilt::Template`.
150
- def bundle_processors(mime_type = nil)
151
- if mime_type
152
- @bundle_processors[mime_type].dup
153
- else
154
- deep_copy_hash(@bundle_processors)
116
+ # mime_type - String MIME type.
117
+ #
118
+ # Returns Array of Procs.
119
+ def unwrap_bundle_processors(mime_type)
120
+ bundle_processors[mime_type].map do |processor|
121
+ unwrap_processor(processor)
155
122
  end
156
123
  end
157
124
 
158
125
  # Registers a new Bundle Processor `klass` for `mime_type`.
159
126
  #
160
- # register_bundle_processor 'text/css', Sprockets::CharsetNormalizer
127
+ # register_bundle_processor 'application/javascript', Sprockets::DirectiveProcessor
161
128
  #
162
129
  # A block can be passed for to create a shorthand processor.
163
130
  #
164
- # register_bundle_processor 'text/css', :my_processor do |context, data|
131
+ # register_bundle_processor 'application/javascript', :my_processor do |context, data|
165
132
  # data.gsub(...)
166
133
  # end
167
134
  #
168
135
  def register_bundle_processor(mime_type, klass, &block)
169
- if block_given?
170
- name = klass.to_s
171
- klass = Class.new(Processor) do
172
- @name = name
173
- @processor = block
174
- end
136
+ mutate_hash_config(:bundle_processors, mime_type) do |processors|
137
+ processors.push(wrap_processor(klass, block))
138
+ processors
175
139
  end
176
-
177
- @bundle_processors[mime_type].push(klass)
178
140
  end
179
141
 
180
142
  # Remove Bundle Processor `klass` for `mime_type`.
181
143
  #
182
- # unregister_bundle_processor 'text/css', Sprockets::CharsetNormalizer
144
+ # unregister_bundle_processor 'application/javascript', Sprockets::DirectiveProcessor
183
145
  #
184
146
  def unregister_bundle_processor(mime_type, klass)
185
147
  if klass.is_a?(String) || klass.is_a?(Symbol)
186
- klass = @bundle_processors[mime_type].detect { |cls|
187
- cls.respond_to?(:name) &&
188
- cls.name == "Sprockets::Processor (#{klass})"
148
+ klass = bundle_processors[mime_type].detect { |cls|
149
+ cls.respond_to?(:name) && cls.name == "Sprockets::LegacyProcProcessor (#{klass})"
189
150
  }
190
151
  end
191
152
 
192
- @bundle_processors[mime_type].delete(klass)
153
+ mutate_hash_config(:bundle_processors, mime_type) do |processors|
154
+ processors.delete(klass)
155
+ processors
156
+ end
193
157
  end
194
158
 
195
- private
196
- def add_engine_to_trail(ext, klass)
197
- @trail.append_extension(ext.to_s)
159
+ # Internal: Run processors on filename and data.
160
+ #
161
+ # Returns Hash.
162
+ def process(processors, uri, filename, load_path, name, content_type)
163
+ data, metadata = nil, {}
164
+
165
+ input = {
166
+ environment: self,
167
+ cache: cache,
168
+ uri: uri,
169
+ filename: filename,
170
+ load_path: load_path,
171
+ name: name,
172
+ content_type: content_type,
173
+ metadata: metadata
174
+ }
198
175
 
199
- if klass.respond_to?(:default_mime_type) && klass.default_mime_type
200
- if format_ext = extension_for_mime_type(klass.default_mime_type)
201
- @trail.alias_extension(ext.to_s, format_ext)
176
+ processors.each do |processor|
177
+ begin
178
+ result = processor.call(input.merge(data: data, metadata: metadata))
179
+ case result
180
+ when NilClass
181
+ # noop
182
+ when Hash
183
+ data = result[:data]
184
+ metadata = metadata.merge(result)
185
+ metadata.delete(:data)
186
+ when String
187
+ data = result
188
+ else
189
+ raise Error, "invalid processor return type: #{result.class}"
202
190
  end
203
191
  end
204
192
  end
193
+
194
+ {
195
+ source: data,
196
+ charset: data.encoding.name.downcase,
197
+ length: data.bytesize,
198
+ digest: digest_class.hexdigest(data),
199
+ metadata: metadata
200
+ }
201
+ end
202
+
203
+ # Internal: Two dimensional Hash of reducer functions for a given mime type
204
+ # and asset metadata key.
205
+ attr_reader :bundle_reducers
206
+
207
+ # Public: Register bundle reducer function.
208
+ #
209
+ # Examples
210
+ #
211
+ # Sprockets.register_bundle_reducer 'application/javascript', :jshint_errors, [], :+
212
+ #
213
+ # Sprockets.register_bundle_reducer 'text/css', :selector_count, 0 { |total, count|
214
+ # total + count
215
+ # }
216
+ #
217
+ # mime_type - String MIME Type. Use '*/*' applies to all types.
218
+ # key - Symbol metadata key
219
+ # initial - Initial memo to pass to the reduce funciton (default: nil)
220
+ # block - Proc accepting the memo accumulator and current value
221
+ #
222
+ # Returns nothing.
223
+ def register_bundle_reducer(mime_type, key, *args, &block)
224
+ case args.size
225
+ when 0
226
+ reducer = block
227
+ when 1
228
+ if block_given?
229
+ initial = args[0]
230
+ reducer = block
231
+ else
232
+ initial = nil
233
+ reducer = args[0].to_proc
234
+ end
235
+ when 2
236
+ initial = args[0]
237
+ reducer = args[1].to_proc
238
+ else
239
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 0..2)"
240
+ end
241
+
242
+ mutate_hash_config(:bundle_reducers, mime_type) do |reducers|
243
+ reducers.merge(key => [initial, reducer])
244
+ end
245
+ end
246
+
247
+ # Internal: Gather all bundle reducer functions for MIME type.
248
+ #
249
+ # mime_type - String MIME type
250
+ #
251
+ # Returns an Array of [initial, reducer_proc] pairs.
252
+ def unwrap_bundle_reducers(mime_type)
253
+ self.bundle_reducers['*/*'].merge(self.bundle_reducers[mime_type])
254
+ end
255
+
256
+ # Internal: Run bundle reducers on set of Assets producing a reduced
257
+ # metadata Hash.
258
+ #
259
+ # assets - Array of Assets
260
+ # reducers - Array of [initial, reducer_proc] pairs
261
+ #
262
+ # Returns reduced asset metadata Hash.
263
+ def process_bundle_reducers(assets, reducers)
264
+ initial = {}
265
+ reducers.each do |k, (v, _)|
266
+ initial[k] = v if v
267
+ end
268
+
269
+ assets.reduce(initial) do |h, asset|
270
+ reducers.each do |k, (_, block)|
271
+ value = k == :data ? asset.source : asset.metadata[k]
272
+ h[k] = h.key?(k) ? block.call(h[k], value) : value
273
+ end
274
+ h
275
+ end
276
+ end
277
+
278
+ private
279
+ def wrap_processor(klass, proc)
280
+ if !proc
281
+ if klass.class == Sprockets::LazyProcessor || klass.respond_to?(:call)
282
+ klass
283
+ else
284
+ LegacyTiltProcessor.new(klass)
285
+ end
286
+ elsif proc.respond_to?(:arity) && proc.arity == 2
287
+ LegacyProcProcessor.new(klass.to_s, proc)
288
+ else
289
+ proc
290
+ end
291
+ end
292
+
293
+ def unwrap_processor(processor)
294
+ processor.respond_to?(:unwrap) ? processor.unwrap : processor
295
+ end
205
296
  end
206
297
  end