sprockets 3.0.3 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +101 -0
  3. data/{LICENSE → MIT-LICENSE} +2 -2
  4. data/README.md +531 -276
  5. data/bin/sprockets +12 -7
  6. data/lib/rake/sprocketstask.rb +9 -4
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +41 -28
  9. data/lib/sprockets/autoload/babel.rb +8 -0
  10. data/lib/sprockets/autoload/closure.rb +1 -0
  11. data/lib/sprockets/autoload/coffee_script.rb +1 -0
  12. data/lib/sprockets/autoload/eco.rb +1 -0
  13. data/lib/sprockets/autoload/ejs.rb +1 -0
  14. data/lib/sprockets/autoload/jsminc.rb +8 -0
  15. data/lib/sprockets/autoload/sass.rb +1 -0
  16. data/lib/sprockets/autoload/sassc.rb +8 -0
  17. data/lib/sprockets/autoload/uglifier.rb +1 -0
  18. data/lib/sprockets/autoload/yui.rb +1 -0
  19. data/lib/sprockets/autoload/zopfli.rb +7 -0
  20. data/lib/sprockets/autoload.rb +5 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +61 -13
  23. data/lib/sprockets/bower.rb +6 -3
  24. data/lib/sprockets/bundle.rb +41 -5
  25. data/lib/sprockets/cache/file_store.rb +32 -7
  26. data/lib/sprockets/cache/memory_store.rb +28 -10
  27. data/lib/sprockets/cache/null_store.rb +8 -0
  28. data/lib/sprockets/cache.rb +43 -6
  29. data/lib/sprockets/cached_environment.rb +15 -20
  30. data/lib/sprockets/closure_compressor.rb +6 -11
  31. data/lib/sprockets/coffee_script_processor.rb +20 -6
  32. data/lib/sprockets/compressing.rb +62 -2
  33. data/lib/sprockets/configuration.rb +5 -9
  34. data/lib/sprockets/context.rb +99 -25
  35. data/lib/sprockets/dependencies.rb +10 -9
  36. data/lib/sprockets/digest_utils.rb +103 -62
  37. data/lib/sprockets/directive_processor.rb +64 -36
  38. data/lib/sprockets/eco_processor.rb +4 -3
  39. data/lib/sprockets/ejs_processor.rb +4 -3
  40. data/lib/sprockets/encoding_utils.rb +1 -0
  41. data/lib/sprockets/environment.rb +9 -4
  42. data/lib/sprockets/erb_processor.rb +34 -21
  43. data/lib/sprockets/errors.rb +1 -0
  44. data/lib/sprockets/exporters/base.rb +71 -0
  45. data/lib/sprockets/exporters/file_exporter.rb +24 -0
  46. data/lib/sprockets/exporters/zlib_exporter.rb +33 -0
  47. data/lib/sprockets/exporters/zopfli_exporter.rb +14 -0
  48. data/lib/sprockets/exporting.rb +73 -0
  49. data/lib/sprockets/file_reader.rb +1 -0
  50. data/lib/sprockets/http_utils.rb +25 -7
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +11 -10
  53. data/lib/sprockets/loader.rb +244 -62
  54. data/lib/sprockets/manifest.rb +100 -46
  55. data/lib/sprockets/manifest_utils.rb +9 -6
  56. data/lib/sprockets/mime.rb +8 -42
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +3 -11
  59. data/lib/sprockets/path_digest_utils.rb +2 -1
  60. data/lib/sprockets/path_utils.rb +107 -22
  61. data/lib/sprockets/paths.rb +1 -0
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +32 -52
  64. data/lib/sprockets/processor_utils.rb +38 -39
  65. data/lib/sprockets/resolve.rb +177 -97
  66. data/lib/sprockets/sass_cache_store.rb +1 -0
  67. data/lib/sprockets/sass_compressor.rb +21 -17
  68. data/lib/sprockets/sass_functions.rb +1 -0
  69. data/lib/sprockets/sass_importer.rb +1 -0
  70. data/lib/sprockets/sass_processor.rb +46 -18
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +77 -44
  74. data/lib/sprockets/source_map_processor.rb +66 -0
  75. data/lib/sprockets/source_map_utils.rb +483 -0
  76. data/lib/sprockets/transformers.rb +63 -35
  77. data/lib/sprockets/uglifier_compressor.rb +23 -20
  78. data/lib/sprockets/unloaded_asset.rb +139 -0
  79. data/lib/sprockets/uri_tar.rb +99 -0
  80. data/lib/sprockets/uri_utils.rb +14 -14
  81. data/lib/sprockets/utils/gzip.rb +99 -0
  82. data/lib/sprockets/utils.rb +63 -71
  83. data/lib/sprockets/version.rb +2 -1
  84. data/lib/sprockets/yui_compressor.rb +5 -14
  85. data/lib/sprockets.rb +105 -33
  86. metadata +157 -27
  87. data/lib/sprockets/coffee_script_template.rb +0 -6
  88. data/lib/sprockets/eco_template.rb +0 -6
  89. data/lib/sprockets/ejs_template.rb +0 -6
  90. data/lib/sprockets/engines.rb +0 -81
  91. data/lib/sprockets/erb_template.rb +0 -6
  92. data/lib/sprockets/legacy.rb +0 -314
  93. data/lib/sprockets/legacy_proc_processor.rb +0 -35
  94. data/lib/sprockets/legacy_tilt_processor.rb +0 -29
  95. data/lib/sprockets/sass_template.rb +0 -7
@@ -1,10 +1,10 @@
1
- require 'pathname'
1
+ # frozen_string_literal: true
2
2
  require 'rack/utils'
3
3
  require 'set'
4
4
  require 'sprockets/errors'
5
+ require 'delegate'
5
6
 
6
7
  module Sprockets
7
- # Deprecated: `Context` provides helper methods to all processors.
8
8
  # They are typically accessed by ERB templates. You can mix in custom helpers
9
9
  # by injecting them into `Environment#context_class`. Do not mix them into
10
10
  # `Context` directly.
@@ -19,10 +19,25 @@ module Sprockets
19
19
  # The `Context` also collects dependencies declared by
20
20
  # assets. See `DirectiveProcessor` for an example of this.
21
21
  class Context
22
- attr_reader :environment, :filename, :pathname
22
+ # Internal: Proxy for ENV that keeps track of the environment variables used
23
+ class ENVProxy < SimpleDelegator
24
+ def initialize(context)
25
+ @context = context
26
+ super(ENV)
27
+ end
23
28
 
24
- # Deprecated
25
- attr_accessor :__LINE__
29
+ def [](key)
30
+ @context.depend_on_env(key)
31
+ super
32
+ end
33
+
34
+ def fetch(key, *)
35
+ @context.depend_on_env(key)
36
+ super
37
+ end
38
+ end
39
+
40
+ attr_reader :environment, :filename
26
41
 
27
42
  def initialize(input)
28
43
  @environment = input[:environment]
@@ -31,7 +46,6 @@ module Sprockets
31
46
  @logical_path = input[:name]
32
47
  @filename = input[:filename]
33
48
  @dirname = File.dirname(@filename)
34
- @pathname = Pathname.new(@filename)
35
49
  @content_type = input[:content_type]
36
50
 
37
51
  @required = Set.new(@metadata[:required])
@@ -47,6 +61,10 @@ module Sprockets
47
61
  dependencies: @dependencies }
48
62
  end
49
63
 
64
+ def env_proxy
65
+ ENVProxy.new(self)
66
+ end
67
+
50
68
  # Returns the environment path that contains the file.
51
69
  #
52
70
  # If `app/javascripts` and `app/stylesheets` are in your path, and
@@ -79,13 +97,13 @@ module Sprockets
79
97
  # resolve("./bar.js")
80
98
  # # => "file:///path/to/app/javascripts/bar.js?type=application/javascript"
81
99
  #
82
- # path - String logical or absolute path
83
- # options
84
- # accept - String content accept type
100
+ # path - String logical or absolute path
101
+ # accept - String content accept type
85
102
  #
86
103
  # Returns an Asset URI String.
87
- def resolve(path, options = {})
88
- uri, deps = environment.resolve!(path, options.merge(base_path: @dirname))
104
+ def resolve(path, **kargs)
105
+ kargs[:base_path] = @dirname
106
+ uri, deps = environment.resolve!(path, **kargs)
89
107
  @dependencies.merge(deps)
90
108
  uri
91
109
  end
@@ -105,15 +123,13 @@ module Sprockets
105
123
  # including it.
106
124
  #
107
125
  # This is used for caching purposes. Any changes made to
108
- # the dependency file with invalidate the cache of the
126
+ # the dependency file will invalidate the cache of the
109
127
  # source file.
110
128
  def depend_on(path)
111
- path = path.to_s if path.is_a?(Pathname)
112
-
113
129
  if environment.absolute_path?(path) && environment.stat(path)
114
130
  @dependencies << environment.build_file_digest_uri(path)
115
131
  else
116
- resolve(path, compat: false)
132
+ resolve(path)
117
133
  end
118
134
  nil
119
135
  end
@@ -123,10 +139,19 @@ module Sprockets
123
139
  #
124
140
  # This is used for caching purposes. Any changes that would
125
141
  # invalidate the dependency asset will invalidate the source
126
- # file. Unlike `depend_on`, this will include recursively include
142
+ # file. Unlike `depend_on`, this will recursively include
127
143
  # the target asset's dependencies.
128
144
  def depend_on_asset(path)
129
- load(resolve(path, compat: false))
145
+ load(resolve(path))
146
+ end
147
+
148
+ # `depend_on_env` allows you to state a dependency on an environment
149
+ # variable.
150
+ #
151
+ # This is used for caching purposes. Any changes in the value of the
152
+ # environment variable will invalidate the cache of the source file.
153
+ def depend_on_env(key)
154
+ @dependencies << "env:#{key}"
130
155
  end
131
156
 
132
157
  # `require_asset` declares `path` as a dependency of the file. The
@@ -139,7 +164,7 @@ module Sprockets
139
164
  # <%= require_asset "#{framework}.js" %>
140
165
  #
141
166
  def require_asset(path)
142
- @required << resolve(path, accept: @content_type, pipeline: :self, compat: false)
167
+ @required << resolve(path, accept: @content_type, pipeline: :self)
143
168
  nil
144
169
  end
145
170
 
@@ -147,7 +172,7 @@ module Sprockets
147
172
  # `path` must be an asset which may or may not already be included
148
173
  # in the bundle.
149
174
  def stub_asset(path)
150
- @stubbed << resolve(path, accept: @content_type, pipeline: :self, compat: false)
175
+ @stubbed << resolve(path, accept: @content_type, pipeline: :self)
151
176
  nil
152
177
  end
153
178
 
@@ -162,9 +187,10 @@ module Sprockets
162
187
  asset
163
188
  end
164
189
 
165
- # Returns a Base64-encoded `data:` URI with the contents of the
166
- # asset at the specified path, and marks that path as a dependency
167
- # of the current file.
190
+ # Returns a `data:` URI with the contents of the asset at the specified
191
+ # path, and marks that path as a dependency of the current file.
192
+ #
193
+ # Uses URI encoding for SVG files, base64 encoding for all the other files.
168
194
  #
169
195
  # Use `asset_data_uri` from ERB with CSS or JavaScript assets:
170
196
  #
@@ -174,15 +200,18 @@ module Sprockets
174
200
  #
175
201
  def asset_data_uri(path)
176
202
  asset = depend_on_asset(path)
177
- data = EncodingUtils.base64(asset.source)
178
- "data:#{asset.content_type};base64,#{Rack::Utils.escape(data)}"
203
+ if asset.content_type == 'image/svg+xml'
204
+ svg_asset_data_uri(asset)
205
+ else
206
+ base64_asset_data_uri(asset)
207
+ end
179
208
  end
180
209
 
181
210
  # Expands logical path to full url to asset.
182
211
  #
183
212
  # NOTE: This helper is currently not implemented and should be
184
213
  # customized by the application. Though, in the future, some
185
- # basics implemention may be provided with different methods that
214
+ # basic implementation may be provided with different methods that
186
215
  # are required to be overridden.
187
216
  def asset_path(path, options = {})
188
217
  message = <<-EOS
@@ -227,5 +256,50 @@ Extend your environment context with a custom method.
227
256
  def stylesheet_path(path)
228
257
  asset_path(path, type: :stylesheet)
229
258
  end
259
+
260
+ protected
261
+
262
+ # Returns a URI-encoded data URI (always "-quoted).
263
+ def svg_asset_data_uri(asset)
264
+ svg = asset.source.dup
265
+ optimize_svg_for_uri_escaping!(svg)
266
+ data = Rack::Utils.escape(svg)
267
+ optimize_quoted_uri_escapes!(data)
268
+ "\"data:#{asset.content_type};charset=utf-8,#{data}\""
269
+ end
270
+
271
+ # Returns a Base64-encoded data URI.
272
+ def base64_asset_data_uri(asset)
273
+ data = Rack::Utils.escape(EncodingUtils.base64(asset.source))
274
+ "data:#{asset.content_type};base64,#{data}"
275
+ end
276
+
277
+ # Optimizes an SVG for being URI-escaped.
278
+ #
279
+ # This method only performs these basic but crucial optimizations:
280
+ # * Replaces " with ', because ' does not need escaping.
281
+ # * Removes comments, meta, doctype, and newlines.
282
+ # * Collapses whitespace.
283
+ def optimize_svg_for_uri_escaping!(svg)
284
+ # Remove comments, xml meta, and doctype
285
+ svg.gsub!(/<!--.*?-->|<\?.*?\?>|<!.*?>/m, '')
286
+ # Replace consecutive whitespace and newlines with a space
287
+ svg.gsub!(/\s+/, ' ')
288
+ # Collapse inter-tag whitespace
289
+ svg.gsub!('> <', '><')
290
+ # Replace " with '
291
+ svg.gsub!(/([\w:])="(.*?)"/, "\\1='\\2'")
292
+ svg.strip!
293
+ end
294
+
295
+ # Un-escapes characters in the given URI-escaped string that do not need
296
+ # escaping in "-quoted data URIs.
297
+ def optimize_quoted_uri_escapes!(escaped)
298
+ escaped.gsub!('%3D', '=')
299
+ escaped.gsub!('%3A', ':')
300
+ escaped.gsub!('%2F', '/')
301
+ escaped.gsub!('%27', "'")
302
+ escaped.tr!('+', ' ')
303
+ end
230
304
  end
231
305
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'sprockets/digest_utils'
2
3
  require 'sprockets/path_digest_utils'
3
4
  require 'sprockets/uri_utils'
@@ -39,7 +40,7 @@ module Sprockets
39
40
  end
40
41
  end
41
42
 
42
- # Public: Add environmental dependency inheirted by all assets.
43
+ # Public: Add environmental dependency inherited by all assets.
43
44
  #
44
45
  # uri - String dependency URI
45
46
  #
@@ -51,18 +52,18 @@ module Sprockets
51
52
  end
52
53
  alias_method :depend_on, :add_dependency
53
54
 
54
- # Internal: Resolve set of dependency URIs.
55
- #
56
- # Returns Array of resolved Objects.
57
- def resolve_dependencies(uris)
58
- uris.map { |uri| resolve_dependency(uri) }
59
- end
60
-
61
55
  # Internal: Resolve dependency URIs.
62
56
  #
63
57
  # Returns resolved Object.
64
58
  def resolve_dependency(str)
65
- scheme = str[/([^:]+)/, 1]
59
+ # Optimize for the most common scheme to
60
+ # save 22k allocations on an average Spree app.
61
+ scheme = if str.start_with?('file-digest:'.freeze)
62
+ 'file-digest'.freeze
63
+ else
64
+ str[/([^:]+)/, 1]
65
+ end
66
+
66
67
  if resolver = config[:dependency_resolvers][scheme]
67
68
  resolver.call(self, str)
68
69
  else
@@ -1,4 +1,4 @@
1
- require 'digest/md5'
1
+ # frozen_string_literal: true
2
2
  require 'digest/sha1'
3
3
  require 'digest/sha2'
4
4
  require 'set'
@@ -18,7 +18,6 @@ module Sprockets
18
18
 
19
19
  # Internal: Maps digest bytesize to the digest class.
20
20
  DIGEST_SIZES = {
21
- 16 => Digest::MD5,
22
21
  20 => Digest::SHA1,
23
22
  32 => Digest::SHA256,
24
23
  48 => Digest::SHA384,
@@ -34,6 +33,49 @@ module Sprockets
34
33
  DIGEST_SIZES[bytes.bytesize]
35
34
  end
36
35
 
36
+ ADD_VALUE_TO_DIGEST = {
37
+ String => ->(val, digest) { digest << val },
38
+ FalseClass => ->(val, digest) { digest << 'FalseClass'.freeze },
39
+ TrueClass => ->(val, digest) { digest << 'TrueClass'.freeze },
40
+ NilClass => ->(val, digest) { digest << 'NilClass'.freeze },
41
+
42
+ Symbol => ->(val, digest) {
43
+ digest << 'Symbol'.freeze
44
+ digest << val.to_s
45
+ },
46
+ Integer => ->(val, digest) {
47
+ digest << 'Integer'.freeze
48
+ digest << val.to_s
49
+ },
50
+ Array => ->(val, digest) {
51
+ digest << 'Array'.freeze
52
+ val.each do |element|
53
+ ADD_VALUE_TO_DIGEST[element.class].call(element, digest)
54
+ end
55
+ },
56
+ Hash => ->(val, digest) {
57
+ digest << 'Hash'.freeze
58
+ val.sort.each do |array|
59
+ ADD_VALUE_TO_DIGEST[Array].call(array, digest)
60
+ end
61
+ },
62
+ Set => ->(val, digest) {
63
+ digest << 'Set'.freeze
64
+ ADD_VALUE_TO_DIGEST[Array].call(val, digest)
65
+ },
66
+ Encoding => ->(val, digest) {
67
+ digest << 'Encoding'.freeze
68
+ digest << val.name
69
+ }
70
+ }
71
+
72
+ ADD_VALUE_TO_DIGEST.compare_by_identity.rehash
73
+
74
+ ADD_VALUE_TO_DIGEST.default_proc = ->(_, val) {
75
+ raise TypeError, "couldn't digest #{ val }"
76
+ }
77
+ private_constant :ADD_VALUE_TO_DIGEST
78
+
37
79
  # Internal: Generate a hexdigest for a nested JSON serializable object.
38
80
  #
39
81
  # This is used for generating cache keys, so its pretty important its
@@ -43,48 +85,18 @@ module Sprockets
43
85
  #
44
86
  # Returns a String digest of the object.
45
87
  def digest(obj)
46
- digest = digest_class.new
47
- queue = [obj]
48
-
49
- while queue.length > 0
50
- obj = queue.shift
51
- klass = obj.class
52
-
53
- if klass == String
54
- digest << obj
55
- elsif klass == Symbol
56
- digest << 'Symbol'
57
- digest << obj.to_s
58
- elsif klass == Fixnum
59
- digest << 'Fixnum'
60
- digest << obj.to_s
61
- elsif klass == Bignum
62
- digest << 'Bignum'
63
- digest << obj.to_s
64
- elsif klass == TrueClass
65
- digest << 'TrueClass'
66
- elsif klass == FalseClass
67
- digest << 'FalseClass'
68
- elsif klass == NilClass
69
- digest << 'NilClass'
70
- elsif klass == Array
71
- digest << 'Array'
72
- queue.concat(obj)
73
- elsif klass == Hash
74
- digest << 'Hash'
75
- queue.concat(obj.sort)
76
- elsif klass == Set
77
- digest << 'Set'
78
- queue.concat(obj.to_a)
79
- elsif klass == Encoding
80
- digest << 'Encoding'
81
- digest << obj.name
82
- else
83
- raise TypeError, "couldn't digest #{klass}"
84
- end
85
- end
88
+ build_digest(obj).digest
89
+ end
86
90
 
87
- digest.digest
91
+ # Internal: Generate a hexdigest for a nested JSON serializable object.
92
+ #
93
+ # The same as `pack_hexdigest(digest(obj))`.
94
+ #
95
+ # obj - A JSON serializable object.
96
+ #
97
+ # Returns a String digest of the object.
98
+ def hexdigest(obj)
99
+ build_digest(obj).hexdigest!
88
100
  end
89
101
 
90
102
  # Internal: Pack a binary digest to a hex encoded string.
@@ -93,7 +105,16 @@ module Sprockets
93
105
  #
94
106
  # Returns hex String.
95
107
  def pack_hexdigest(bin)
96
- bin.unpack('H*').first
108
+ bin.unpack('H*'.freeze).first
109
+ end
110
+
111
+ # Internal: Unpack a hex encoded digest string into binary bytes.
112
+ #
113
+ # hex - String hex
114
+ #
115
+ # Returns binary String.
116
+ def unpack_hexdigest(hex)
117
+ [hex].pack('H*')
97
118
  end
98
119
 
99
120
  # Internal: Pack a binary digest to a base64 encoded string.
@@ -117,25 +138,20 @@ module Sprockets
117
138
  str
118
139
  end
119
140
 
120
- # Internal: Maps digest class to the named information hash algorithm name.
121
- #
122
- # http://www.iana.org/assignments/named-information/named-information.xhtml
123
- NI_HASH_ALGORITHMS = {
124
- Digest::SHA256 => 'sha-256'.freeze,
125
- Digest::SHA384 => 'sha-384'.freeze,
126
- Digest::SHA512 => 'sha-512'.freeze
141
+ # Internal: Maps digest class to the CSP hash algorithm name.
142
+ HASH_ALGORITHMS = {
143
+ Digest::SHA256 => 'sha256'.freeze,
144
+ Digest::SHA384 => 'sha384'.freeze,
145
+ Digest::SHA512 => 'sha512'.freeze
127
146
  }
128
147
 
129
- # Internal: Generate a "named information" URI for use in the `integrity`
130
- # attribute of an asset tag as per the subresource integrity specification.
148
+ # Public: Generate hash for use in the `integrity` attribute of an asset tag
149
+ # as per the subresource integrity specification.
131
150
  #
132
- # digest - The String byte digest of the asset content.
133
- # content_type - The content-type the asset will be served with. This *must*
134
- # be accurate if provided. Otherwise, subresource integrity
135
- # will block the loading of the asset.
151
+ # digest - The String byte digest of the asset content.
136
152
  #
137
153
  # Returns a String or nil if hash algorithm is incompatible.
138
- def integrity_uri(digest, content_type = nil)
154
+ def integrity_uri(digest)
139
155
  case digest
140
156
  when Digest::Base
141
157
  digest_class = digest.class
@@ -146,11 +162,36 @@ module Sprockets
146
162
  raise TypeError, "unknown digest: #{digest.inspect}"
147
163
  end
148
164
 
149
- if hash_name = NI_HASH_ALGORITHMS[digest_class]
150
- uri = "ni:///#{hash_name};#{pack_urlsafe_base64digest(digest)}"
151
- uri << "?ct=#{content_type}" if content_type
152
- uri
165
+ if hash_name = HASH_ALGORITHMS[digest_class]
166
+ "#{hash_name}-#{pack_base64digest(digest)}"
153
167
  end
154
168
  end
169
+
170
+ # Public: Generate hash for use in the `integrity` attribute of an asset tag
171
+ # as per the subresource integrity specification.
172
+ #
173
+ # digest - The String hexbyte digest of the asset content.
174
+ #
175
+ # Returns a String or nil if hash algorithm is incompatible.
176
+ def hexdigest_integrity_uri(hexdigest)
177
+ integrity_uri(unpack_hexdigest(hexdigest))
178
+ end
179
+
180
+ # Internal: Checks an asset name for a valid digest
181
+ #
182
+ # name - The name of the asset
183
+ #
184
+ # Returns true if the name contains a digest like string and .digested before the extension
185
+ def already_digested?(name)
186
+ return name =~ /-([0-9a-zA-Z]{7,128})\.digested/
187
+ end
188
+
189
+ private
190
+ def build_digest(obj)
191
+ digest = digest_class.new
192
+
193
+ ADD_VALUE_TO_DIGEST[obj.class].call(obj, digest)
194
+ digest
195
+ end
155
196
  end
156
197
  end