sprockets 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +72 -0
  3. data/README.md +665 -0
  4. data/bin/sprockets +93 -0
  5. data/lib/rake/sprocketstask.rb +153 -0
  6. data/lib/sprockets.rb +229 -0
  7. data/lib/sprockets/add_source_map_comment_to_asset_processor.rb +60 -0
  8. data/lib/sprockets/asset.rb +202 -0
  9. data/lib/sprockets/autoload.rb +16 -0
  10. data/lib/sprockets/autoload/babel.rb +8 -0
  11. data/lib/sprockets/autoload/closure.rb +8 -0
  12. data/lib/sprockets/autoload/coffee_script.rb +8 -0
  13. data/lib/sprockets/autoload/eco.rb +8 -0
  14. data/lib/sprockets/autoload/ejs.rb +8 -0
  15. data/lib/sprockets/autoload/jsminc.rb +8 -0
  16. data/lib/sprockets/autoload/sass.rb +8 -0
  17. data/lib/sprockets/autoload/sassc.rb +8 -0
  18. data/lib/sprockets/autoload/uglifier.rb +8 -0
  19. data/lib/sprockets/autoload/yui.rb +8 -0
  20. data/lib/sprockets/autoload/zopfli.rb +7 -0
  21. data/lib/sprockets/babel_processor.rb +66 -0
  22. data/lib/sprockets/base.rb +147 -0
  23. data/lib/sprockets/bower.rb +61 -0
  24. data/lib/sprockets/bundle.rb +105 -0
  25. data/lib/sprockets/cache.rb +271 -0
  26. data/lib/sprockets/cache/file_store.rb +208 -0
  27. data/lib/sprockets/cache/memory_store.rb +75 -0
  28. data/lib/sprockets/cache/null_store.rb +54 -0
  29. data/lib/sprockets/cached_environment.rb +64 -0
  30. data/lib/sprockets/closure_compressor.rb +48 -0
  31. data/lib/sprockets/coffee_script_processor.rb +39 -0
  32. data/lib/sprockets/compressing.rb +134 -0
  33. data/lib/sprockets/configuration.rb +79 -0
  34. data/lib/sprockets/context.rb +304 -0
  35. data/lib/sprockets/dependencies.rb +74 -0
  36. data/lib/sprockets/digest_utils.rb +200 -0
  37. data/lib/sprockets/directive_processor.rb +414 -0
  38. data/lib/sprockets/eco_processor.rb +33 -0
  39. data/lib/sprockets/ejs_processor.rb +32 -0
  40. data/lib/sprockets/encoding_utils.rb +262 -0
  41. data/lib/sprockets/environment.rb +46 -0
  42. data/lib/sprockets/erb_processor.rb +37 -0
  43. data/lib/sprockets/errors.rb +12 -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 +16 -0
  50. data/lib/sprockets/http_utils.rb +135 -0
  51. data/lib/sprockets/jsminc_compressor.rb +32 -0
  52. data/lib/sprockets/jst_processor.rb +50 -0
  53. data/lib/sprockets/loader.rb +345 -0
  54. data/lib/sprockets/manifest.rb +338 -0
  55. data/lib/sprockets/manifest_utils.rb +48 -0
  56. data/lib/sprockets/mime.rb +96 -0
  57. data/lib/sprockets/npm.rb +52 -0
  58. data/lib/sprockets/path_dependency_utils.rb +77 -0
  59. data/lib/sprockets/path_digest_utils.rb +48 -0
  60. data/lib/sprockets/path_utils.rb +367 -0
  61. data/lib/sprockets/paths.rb +82 -0
  62. data/lib/sprockets/preprocessors/default_source_map.rb +49 -0
  63. data/lib/sprockets/processing.rb +228 -0
  64. data/lib/sprockets/processor_utils.rb +169 -0
  65. data/lib/sprockets/resolve.rb +295 -0
  66. data/lib/sprockets/sass_cache_store.rb +30 -0
  67. data/lib/sprockets/sass_compressor.rb +63 -0
  68. data/lib/sprockets/sass_functions.rb +3 -0
  69. data/lib/sprockets/sass_importer.rb +3 -0
  70. data/lib/sprockets/sass_processor.rb +313 -0
  71. data/lib/sprockets/sassc_compressor.rb +56 -0
  72. data/lib/sprockets/sassc_processor.rb +297 -0
  73. data/lib/sprockets/server.rb +295 -0
  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 +173 -0
  77. data/lib/sprockets/uglifier_compressor.rb +66 -0
  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 +191 -0
  81. data/lib/sprockets/utils.rb +202 -0
  82. data/lib/sprockets/utils/gzip.rb +99 -0
  83. data/lib/sprockets/version.rb +4 -0
  84. data/lib/sprockets/yui_compressor.rb +56 -0
  85. metadata +444 -0
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/autoload'
3
+ require 'sprockets/source_map_utils'
4
+
5
+ module Sprockets
6
+ # Processor engine class for the CoffeeScript compiler.
7
+ # Depends on the `coffee-script` and `coffee-script-source` gems.
8
+ #
9
+ # For more infomation see:
10
+ #
11
+ # https://github.com/rails/ruby-coffee-script
12
+ #
13
+ module CoffeeScriptProcessor
14
+ VERSION = '2'
15
+
16
+ def self.cache_key
17
+ @cache_key ||= "#{name}:#{Autoload::CoffeeScript::Source.version}:#{VERSION}".freeze
18
+ end
19
+
20
+ def self.call(input)
21
+ data = input[:data]
22
+
23
+ js, map = input[:cache].fetch([self.cache_key, data]) do
24
+ result = Autoload::CoffeeScript.compile(
25
+ data,
26
+ sourceMap: "v3",
27
+ sourceFiles: [File.basename(input[:filename])],
28
+ generatedFile: input[:filename]
29
+ )
30
+ [result['js'], JSON.parse(result['v3SourceMap'])]
31
+ end
32
+
33
+ map = SourceMapUtils.format_source_map(map, input)
34
+ map = SourceMapUtils.combine_source_maps(input[:metadata][:map], map)
35
+
36
+ { data: js, map: map }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/utils'
3
+
4
+ module Sprockets
5
+ # `Compressing` is an internal mixin whose public methods are exposed on
6
+ # the `Environment` and `CachedEnvironment` classes.
7
+ module Compressing
8
+ include Utils
9
+
10
+ def compressors
11
+ config[:compressors]
12
+ end
13
+
14
+ # Public: Register a new compressor `klass` at `sym` for `mime_type`.
15
+ #
16
+ # Registering a processor allows it to be looked up by `sym` later when
17
+ # assigning a JavaScript or CSS compressor.
18
+ #
19
+ # Compressors only operate on JavaScript and CSS. If you want to compress a
20
+ # different type of asset, use a processor instead.
21
+ #
22
+ # Examples
23
+ #
24
+ # register_compressor 'text/css', :my_sass, MySassCompressor
25
+ # css_compressor = :my_sass
26
+ #
27
+ # mime_type - String MIME Type (one of: 'test/css' or 'application/javascript').
28
+ # sym - Symbol registration address.
29
+ # klass - The compressor class.
30
+ #
31
+ # Returns nothing.
32
+ def register_compressor(mime_type, sym, klass)
33
+ self.config = hash_reassoc(config, :compressors, mime_type) do |compressors|
34
+ compressors[sym] = klass
35
+ compressors
36
+ end
37
+ end
38
+
39
+ # Return CSS compressor or nil if none is set
40
+ def css_compressor
41
+ if defined? @css_compressor
42
+ @css_compressor
43
+ end
44
+ end
45
+
46
+ # Assign a compressor to run on `text/css` assets.
47
+ #
48
+ # The compressor object must respond to `compress`.
49
+ def css_compressor=(compressor)
50
+ unregister_bundle_processor 'text/css', @css_compressor if defined? @css_compressor
51
+ @css_compressor = nil
52
+ return unless compressor
53
+
54
+ if compressor.is_a?(Symbol)
55
+ @css_compressor = klass = config[:compressors]['text/css'][compressor] || raise(Error, "unknown compressor: #{compressor}")
56
+ elsif compressor.respond_to?(:compress)
57
+ klass = proc { |input| compressor.compress(input[:data]) }
58
+ @css_compressor = :css_compressor
59
+ else
60
+ @css_compressor = klass = compressor
61
+ end
62
+
63
+ register_bundle_processor 'text/css', klass
64
+ end
65
+
66
+ # Return JS compressor or nil if none is set
67
+ def js_compressor
68
+ if defined? @js_compressor
69
+ @js_compressor
70
+ end
71
+ end
72
+
73
+ # Assign a compressor to run on `application/javascript` assets.
74
+ #
75
+ # The compressor object must respond to `compress`.
76
+ def js_compressor=(compressor)
77
+ unregister_bundle_processor 'application/javascript', @js_compressor if defined? @js_compressor
78
+ @js_compressor = nil
79
+ return unless compressor
80
+
81
+ if compressor.is_a?(Symbol)
82
+ @js_compressor = klass = config[:compressors]['application/javascript'][compressor] || raise(Error, "unknown compressor: #{compressor}")
83
+ elsif compressor.respond_to?(:compress)
84
+ klass = proc { |input| compressor.compress(input[:data]) }
85
+ @js_compressor = :js_compressor
86
+ else
87
+ @js_compressor = klass = compressor
88
+ end
89
+
90
+ register_bundle_processor 'application/javascript', klass
91
+ end
92
+
93
+ # Public: Checks if Gzip is enabled.
94
+ def gzip?
95
+ config[:gzip_enabled]
96
+ end
97
+
98
+ # Public: Checks if Gzip is disabled.
99
+ def skip_gzip?
100
+ !gzip?
101
+ end
102
+
103
+ # Public: Enable or disable the creation of Gzip files.
104
+ #
105
+ # To disable gzip generation set to a falsey value:
106
+ #
107
+ # environment.gzip = false
108
+ #
109
+ # To enable set to a truthy value. By default zlib wil
110
+ # be used to gzip assets. If you have the Zopfli gem
111
+ # installed you can specify the zopfli algorithm to be used
112
+ # instead:
113
+ #
114
+ # environment.gzip = :zopfli
115
+ #
116
+ def gzip=(gzip)
117
+ self.config = config.merge(gzip_enabled: gzip).freeze
118
+
119
+ case gzip
120
+ when false, nil
121
+ self.unregister_exporter Exporters::ZlibExporter
122
+ self.unregister_exporter Exporters::ZopfliExporter
123
+ when :zopfli
124
+ self.unregister_exporter Exporters::ZlibExporter
125
+ self.register_exporter '*/*', Exporters::ZopfliExporter
126
+ else
127
+ self.unregister_exporter Exporters::ZopfliExporter
128
+ self.register_exporter '*/*', Exporters::ZlibExporter
129
+ end
130
+
131
+ gzip
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ require 'sprockets/compressing'
3
+ require 'sprockets/dependencies'
4
+ require 'sprockets/mime'
5
+ require 'sprockets/paths'
6
+ require 'sprockets/processing'
7
+ require 'sprockets/exporting'
8
+ require 'sprockets/transformers'
9
+ require 'sprockets/utils'
10
+
11
+ module Sprockets
12
+ module Configuration
13
+ include Paths, Mime, Transformers, Processing, Exporting, Compressing, Dependencies, Utils
14
+
15
+ def initialize_configuration(parent)
16
+ @config = parent.config
17
+ @logger = parent.logger
18
+ @context_class = Class.new(parent.context_class)
19
+ end
20
+
21
+ attr_reader :config
22
+
23
+ def config=(config)
24
+ raise TypeError, "can't assign mutable config" unless config.frozen?
25
+ @config = config
26
+ end
27
+
28
+ # Get and set `Logger` instance.
29
+ attr_accessor :logger
30
+
31
+ # The `Environment#version` is a custom value used for manually
32
+ # expiring all asset caches.
33
+ #
34
+ # Sprockets is able to track most file and directory changes and
35
+ # will take care of expiring the cache for you. However, its
36
+ # impossible to know when any custom helpers change that you mix
37
+ # into the `Context`.
38
+ #
39
+ # It would be wise to increment this value anytime you make a
40
+ # configuration change to the `Environment` object.
41
+ def version
42
+ config[:version]
43
+ end
44
+
45
+ # Assign an environment version.
46
+ #
47
+ # environment.version = '2.0'
48
+ #
49
+ def version=(version)
50
+ self.config = hash_reassoc(config, :version) { version.dup }
51
+ end
52
+
53
+ # Public: Returns a `Digest` implementation class.
54
+ #
55
+ # Defaults to `Digest::SHA256`.
56
+ def digest_class
57
+ config[:digest_class]
58
+ end
59
+
60
+ # Deprecated: Assign a `Digest` implementation class. This maybe any Ruby
61
+ # `Digest::` implementation such as `Digest::SHA256` or
62
+ # `Digest::MD5`.
63
+ #
64
+ # environment.digest_class = Digest::MD5
65
+ #
66
+ def digest_class=(klass)
67
+ self.config = config.merge(digest_class: klass).freeze
68
+ end
69
+
70
+ # This class maybe mutated and mixed in with custom helpers.
71
+ #
72
+ # environment.context_class.instance_eval do
73
+ # include MyHelpers
74
+ # def asset_url; end
75
+ # end
76
+ #
77
+ attr_reader :context_class
78
+ end
79
+ end
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+ require 'rack/utils'
3
+ require 'set'
4
+ require 'sprockets/errors'
5
+
6
+ module Sprockets
7
+ # They are typically accessed by ERB templates. You can mix in custom helpers
8
+ # by injecting them into `Environment#context_class`. Do not mix them into
9
+ # `Context` directly.
10
+ #
11
+ # environment.context_class.class_eval do
12
+ # include MyHelper
13
+ # def asset_url; end
14
+ # end
15
+ #
16
+ # <%= asset_url "foo.png" %>
17
+ #
18
+ # The `Context` also collects dependencies declared by
19
+ # assets. See `DirectiveProcessor` for an example of this.
20
+ class Context
21
+ # Internal: Proxy for ENV that keeps track of the environment variables used
22
+ class ENVProxy < SimpleDelegator
23
+ def initialize(context)
24
+ @context = context
25
+ super(ENV)
26
+ end
27
+
28
+ def [](key)
29
+ @context.depend_on_env(key)
30
+ super
31
+ end
32
+
33
+ def fetch(key, *)
34
+ @context.depend_on_env(key)
35
+ super
36
+ end
37
+ end
38
+
39
+ attr_reader :environment, :filename
40
+
41
+ def initialize(input)
42
+ @environment = input[:environment]
43
+ @metadata = input[:metadata]
44
+ @load_path = input[:load_path]
45
+ @logical_path = input[:name]
46
+ @filename = input[:filename]
47
+ @dirname = File.dirname(@filename)
48
+ @content_type = input[:content_type]
49
+
50
+ @required = Set.new(@metadata[:required])
51
+ @stubbed = Set.new(@metadata[:stubbed])
52
+ @links = Set.new(@metadata[:links])
53
+ @dependencies = Set.new(input[:metadata][:dependencies])
54
+ end
55
+
56
+ def metadata
57
+ { required: @required,
58
+ stubbed: @stubbed,
59
+ links: @links,
60
+ dependencies: @dependencies }
61
+ end
62
+
63
+ def env_proxy
64
+ ENVProxy.new(self)
65
+ end
66
+
67
+ # Returns the environment path that contains the file.
68
+ #
69
+ # If `app/javascripts` and `app/stylesheets` are in your path, and
70
+ # current file is `app/javascripts/foo/bar.js`, `load_path` would
71
+ # return `app/javascripts`.
72
+ attr_reader :load_path
73
+ alias_method :root_path, :load_path
74
+
75
+ # Returns logical path without any file extensions.
76
+ #
77
+ # 'app/javascripts/application.js'
78
+ # # => 'application'
79
+ #
80
+ attr_reader :logical_path
81
+
82
+ # Returns content type of file
83
+ #
84
+ # 'application/javascript'
85
+ # 'text/css'
86
+ #
87
+ attr_reader :content_type
88
+
89
+ # Public: Given a logical path, `resolve` will find and return an Asset URI.
90
+ # Relative paths will also be resolved. An accept type maybe given to
91
+ # restrict the search.
92
+ #
93
+ # resolve("foo.js")
94
+ # # => "file:///path/to/app/javascripts/foo.js?type=application/javascript"
95
+ #
96
+ # resolve("./bar.js")
97
+ # # => "file:///path/to/app/javascripts/bar.js?type=application/javascript"
98
+ #
99
+ # path - String logical or absolute path
100
+ # accept - String content accept type
101
+ #
102
+ # Returns an Asset URI String.
103
+ def resolve(path, **kargs)
104
+ kargs[:base_path] = @dirname
105
+ uri, deps = environment.resolve!(path, **kargs)
106
+ @dependencies.merge(deps)
107
+ uri
108
+ end
109
+
110
+ # Public: Load Asset by AssetURI and track it as a dependency.
111
+ #
112
+ # uri - AssetURI
113
+ #
114
+ # Returns Asset.
115
+ def load(uri)
116
+ asset = environment.load(uri)
117
+ @dependencies.merge(asset.metadata[:dependencies])
118
+ asset
119
+ end
120
+
121
+ # `depend_on` allows you to state a dependency on a file without
122
+ # including it.
123
+ #
124
+ # This is used for caching purposes. Any changes made to
125
+ # the dependency file will invalidate the cache of the
126
+ # source file.
127
+ def depend_on(path)
128
+ if environment.absolute_path?(path) && environment.stat(path)
129
+ @dependencies << environment.build_file_digest_uri(path)
130
+ else
131
+ resolve(path)
132
+ end
133
+ nil
134
+ end
135
+
136
+ # `depend_on_asset` allows you to state an asset dependency
137
+ # without including it.
138
+ #
139
+ # This is used for caching purposes. Any changes that would
140
+ # invalidate the dependency asset will invalidate the source
141
+ # file. Unlike `depend_on`, this will recursively include
142
+ # the target asset's dependencies.
143
+ def depend_on_asset(path)
144
+ load(resolve(path))
145
+ end
146
+
147
+ # `depend_on_env` allows you to state a dependency on an environment
148
+ # variable.
149
+ #
150
+ # This is used for caching purposes. Any changes in the value of the
151
+ # environment variable will invalidate the cache of the source file.
152
+ def depend_on_env(key)
153
+ @dependencies << "env:#{key}"
154
+ end
155
+
156
+ # `require_asset` declares `path` as a dependency of the file. The
157
+ # dependency will be inserted before the file and will only be
158
+ # included once.
159
+ #
160
+ # If ERB processing is enabled, you can use it to dynamically
161
+ # require assets.
162
+ #
163
+ # <%= require_asset "#{framework}.js" %>
164
+ #
165
+ def require_asset(path)
166
+ @required << resolve(path, accept: @content_type, pipeline: :self)
167
+ nil
168
+ end
169
+
170
+ # `stub_asset` blacklists `path` from being included in the bundle.
171
+ # `path` must be an asset which may or may not already be included
172
+ # in the bundle.
173
+ def stub_asset(path)
174
+ @stubbed << resolve(path, accept: @content_type, pipeline: :self)
175
+ nil
176
+ end
177
+
178
+ # `link_asset` declares an external dependency on an asset without directly
179
+ # including it. The target asset is returned from this function making it
180
+ # easy to construct a link to it.
181
+ #
182
+ # Returns an Asset or nil.
183
+ def link_asset(path)
184
+ asset = depend_on_asset(path)
185
+ @links << asset.uri
186
+ asset
187
+ end
188
+
189
+ # Returns a `data:` URI with the contents of the asset at the specified
190
+ # path, and marks that path as a dependency of the current file.
191
+ #
192
+ # Uses URI encoding for SVG files, base64 encoding for all the other files.
193
+ #
194
+ # Use `asset_data_uri` from ERB with CSS or JavaScript assets:
195
+ #
196
+ # #logo { background: url(<%= asset_data_uri 'logo.png' %>) }
197
+ #
198
+ # $('<img>').attr('src', '<%= asset_data_uri 'avatar.jpg' %>')
199
+ #
200
+ def asset_data_uri(path)
201
+ asset = depend_on_asset(path)
202
+ if asset.content_type == 'image/svg+xml'
203
+ svg_asset_data_uri(asset)
204
+ else
205
+ base64_asset_data_uri(asset)
206
+ end
207
+ end
208
+
209
+ # Expands logical path to full url to asset.
210
+ #
211
+ # NOTE: This helper is currently not implemented and should be
212
+ # customized by the application. Though, in the future, some
213
+ # basics implemention may be provided with different methods that
214
+ # are required to be overridden.
215
+ def asset_path(path, options = {})
216
+ message = <<-EOS
217
+ Custom asset_path helper is not implemented
218
+
219
+ Extend your environment context with a custom method.
220
+
221
+ environment.context_class.class_eval do
222
+ def asset_path(path, options = {})
223
+ end
224
+ end
225
+ EOS
226
+ raise NotImplementedError, message
227
+ end
228
+
229
+ # Expand logical image asset path.
230
+ def image_path(path)
231
+ asset_path(path, type: :image)
232
+ end
233
+
234
+ # Expand logical video asset path.
235
+ def video_path(path)
236
+ asset_path(path, type: :video)
237
+ end
238
+
239
+ # Expand logical audio asset path.
240
+ def audio_path(path)
241
+ asset_path(path, type: :audio)
242
+ end
243
+
244
+ # Expand logical font asset path.
245
+ def font_path(path)
246
+ asset_path(path, type: :font)
247
+ end
248
+
249
+ # Expand logical javascript asset path.
250
+ def javascript_path(path)
251
+ asset_path(path, type: :javascript)
252
+ end
253
+
254
+ # Expand logical stylesheet asset path.
255
+ def stylesheet_path(path)
256
+ asset_path(path, type: :stylesheet)
257
+ end
258
+
259
+ protected
260
+
261
+ # Returns a URI-encoded data URI (always "-quoted).
262
+ def svg_asset_data_uri(asset)
263
+ svg = asset.source.dup
264
+ optimize_svg_for_uri_escaping!(svg)
265
+ data = Rack::Utils.escape(svg)
266
+ optimize_quoted_uri_escapes!(data)
267
+ "\"data:#{asset.content_type};charset=utf-8,#{data}\""
268
+ end
269
+
270
+ # Returns a Base64-encoded data URI.
271
+ def base64_asset_data_uri(asset)
272
+ data = Rack::Utils.escape(EncodingUtils.base64(asset.source))
273
+ "data:#{asset.content_type};base64,#{data}"
274
+ end
275
+
276
+ # Optimizes an SVG for being URI-escaped.
277
+ #
278
+ # This method only performs these basic but crucial optimizations:
279
+ # * Replaces " with ', because ' does not need escaping.
280
+ # * Removes comments, meta, doctype, and newlines.
281
+ # * Collapses whitespace.
282
+ def optimize_svg_for_uri_escaping!(svg)
283
+ # Remove comments, xml meta, and doctype
284
+ svg.gsub!(/<!--.*?-->|<\?.*?\?>|<!.*?>/m, '')
285
+ # Replace consecutive whitespace and newlines with a space
286
+ svg.gsub!(/\s+/, ' ')
287
+ # Collapse inter-tag whitespace
288
+ svg.gsub!('> <', '><')
289
+ # Replace " with '
290
+ svg.gsub!(/([\w:])="(.*?)"/, "\\1='\\2'")
291
+ svg.strip!
292
+ end
293
+
294
+ # Un-escapes characters in the given URI-escaped string that do not need
295
+ # escaping in "-quoted data URIs.
296
+ def optimize_quoted_uri_escapes!(escaped)
297
+ escaped.gsub!('%3D', '=')
298
+ escaped.gsub!('%3A', ':')
299
+ escaped.gsub!('%2F', '/')
300
+ escaped.gsub!('%27', "'")
301
+ escaped.tr!('+', ' ')
302
+ end
303
+ end
304
+ end