sprockets 2.3.2 → 3.0.0
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.
- checksums.yaml +7 -0
- data/LICENSE +2 -2
- data/README.md +332 -115
- data/bin/sprockets +8 -0
- data/lib/rake/sprocketstask.rb +25 -13
- data/lib/sprockets/asset.rb +143 -205
- data/lib/sprockets/autoload/closure.rb +7 -0
- data/lib/sprockets/autoload/coffee_script.rb +7 -0
- data/lib/sprockets/autoload/eco.rb +7 -0
- data/lib/sprockets/autoload/ejs.rb +7 -0
- data/lib/sprockets/autoload/sass.rb +7 -0
- data/lib/sprockets/autoload/uglifier.rb +7 -0
- data/lib/sprockets/autoload/yui.rb +7 -0
- data/lib/sprockets/autoload.rb +11 -0
- data/lib/sprockets/base.rb +49 -257
- data/lib/sprockets/bower.rb +58 -0
- data/lib/sprockets/bundle.rb +65 -0
- data/lib/sprockets/cache/file_store.rb +165 -14
- data/lib/sprockets/cache/memory_store.rb +66 -0
- data/lib/sprockets/cache/null_store.rb +46 -0
- data/lib/sprockets/cache.rb +234 -0
- data/lib/sprockets/cached_environment.rb +69 -0
- data/lib/sprockets/closure_compressor.rb +53 -0
- data/lib/sprockets/coffee_script_processor.rb +25 -0
- data/lib/sprockets/coffee_script_template.rb +6 -0
- data/lib/sprockets/compressing.rb +74 -0
- data/lib/sprockets/configuration.rb +83 -0
- data/lib/sprockets/context.rb +125 -131
- data/lib/sprockets/dependencies.rb +73 -0
- data/lib/sprockets/digest_utils.rb +156 -0
- data/lib/sprockets/directive_processor.rb +209 -211
- data/lib/sprockets/eco_processor.rb +32 -0
- data/lib/sprockets/eco_template.rb +3 -35
- data/lib/sprockets/ejs_processor.rb +31 -0
- data/lib/sprockets/ejs_template.rb +3 -34
- data/lib/sprockets/encoding_utils.rb +258 -0
- data/lib/sprockets/engines.rb +45 -38
- data/lib/sprockets/environment.rb +17 -67
- data/lib/sprockets/erb_processor.rb +30 -0
- data/lib/sprockets/erb_template.rb +6 -0
- data/lib/sprockets/errors.rb +6 -13
- data/lib/sprockets/file_reader.rb +15 -0
- data/lib/sprockets/http_utils.rb +115 -0
- data/lib/sprockets/jst_processor.rb +35 -19
- data/lib/sprockets/legacy.rb +314 -0
- data/lib/sprockets/legacy_proc_processor.rb +35 -0
- data/lib/sprockets/legacy_tilt_processor.rb +29 -0
- data/lib/sprockets/loader.rb +176 -0
- data/lib/sprockets/manifest.rb +179 -98
- data/lib/sprockets/manifest_utils.rb +45 -0
- data/lib/sprockets/mime.rb +114 -32
- data/lib/sprockets/path_dependency_utils.rb +85 -0
- data/lib/sprockets/path_digest_utils.rb +47 -0
- data/lib/sprockets/path_utils.rb +282 -0
- data/lib/sprockets/paths.rb +81 -0
- data/lib/sprockets/processing.rb +157 -189
- data/lib/sprockets/processor_utils.rb +103 -0
- data/lib/sprockets/resolve.rb +208 -0
- data/lib/sprockets/sass_cache_store.rb +19 -15
- data/lib/sprockets/sass_compressor.rb +59 -0
- data/lib/sprockets/sass_functions.rb +2 -0
- data/lib/sprockets/sass_importer.rb +2 -29
- data/lib/sprockets/sass_processor.rb +285 -0
- data/lib/sprockets/sass_template.rb +4 -44
- data/lib/sprockets/server.rb +109 -84
- data/lib/sprockets/transformers.rb +145 -0
- data/lib/sprockets/uglifier_compressor.rb +63 -0
- data/lib/sprockets/uri_utils.rb +190 -0
- data/lib/sprockets/utils.rb +193 -44
- data/lib/sprockets/version.rb +1 -1
- data/lib/sprockets/yui_compressor.rb +65 -0
- data/lib/sprockets.rb +144 -53
- metadata +248 -238
- data/lib/sprockets/asset_attributes.rb +0 -126
- data/lib/sprockets/bundled_asset.rb +0 -79
- data/lib/sprockets/caching.rb +0 -96
- data/lib/sprockets/charset_normalizer.rb +0 -41
- data/lib/sprockets/index.rb +0 -99
- data/lib/sprockets/processed_asset.rb +0 -152
- data/lib/sprockets/processor.rb +0 -32
- data/lib/sprockets/safety_colons.rb +0 -28
- data/lib/sprockets/scss_template.rb +0 -13
- data/lib/sprockets/static_asset.rb +0 -57
- data/lib/sprockets/trail.rb +0 -90
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'sprockets/asset'
|
2
|
+
require 'sprockets/digest_utils'
|
3
|
+
require 'sprockets/engines'
|
4
|
+
require 'sprockets/errors'
|
5
|
+
require 'sprockets/file_reader'
|
6
|
+
require 'sprockets/mime'
|
7
|
+
require 'sprockets/path_utils'
|
8
|
+
require 'sprockets/processing'
|
9
|
+
require 'sprockets/processor_utils'
|
10
|
+
require 'sprockets/resolve'
|
11
|
+
require 'sprockets/transformers'
|
12
|
+
require 'sprockets/uri_utils'
|
13
|
+
|
14
|
+
module Sprockets
|
15
|
+
# The loader phase takes a asset URI location and returns a constructed Asset
|
16
|
+
# object.
|
17
|
+
module Loader
|
18
|
+
include DigestUtils, PathUtils, ProcessorUtils, URIUtils
|
19
|
+
include Engines, Mime, Processing, Resolve, Transformers
|
20
|
+
|
21
|
+
# Public: Load Asset by AssetURI.
|
22
|
+
#
|
23
|
+
# uri - AssetURI
|
24
|
+
#
|
25
|
+
# Returns Asset.
|
26
|
+
def load(uri)
|
27
|
+
filename, params = parse_asset_uri(uri)
|
28
|
+
if params.key?(:id)
|
29
|
+
asset = cache.fetch("asset-uri:#{VERSION}#{uri}") do
|
30
|
+
load_asset_by_id_uri(uri, filename, params)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
asset = fetch_asset_from_dependency_cache(uri, filename) do |paths|
|
34
|
+
if paths
|
35
|
+
digest = digest(resolve_dependencies(paths))
|
36
|
+
if id_uri = cache.get("asset-uri-digest:#{VERSION}:#{uri}:#{digest}", true)
|
37
|
+
cache.get("asset-uri:#{VERSION}:#{id_uri}", true)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
load_asset_by_uri(uri, filename, params)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
Asset.new(self, asset)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def load_asset_by_id_uri(uri, filename, params)
|
49
|
+
# Internal assertion, should be routed through load_asset_by_uri
|
50
|
+
unless id = params.delete(:id)
|
51
|
+
raise ArgumentError, "expected uri to have an id: #{uri}"
|
52
|
+
end
|
53
|
+
|
54
|
+
uri = build_asset_uri(filename, params)
|
55
|
+
asset = load_asset_by_uri(uri, filename, params)
|
56
|
+
|
57
|
+
if id && asset[:id] != id
|
58
|
+
raise VersionNotFound, "could not find specified id: #{uri}##{id}"
|
59
|
+
end
|
60
|
+
|
61
|
+
asset
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_asset_by_uri(uri, filename, params)
|
65
|
+
# Internal assertion, should be routed through load_asset_by_id_uri
|
66
|
+
if params.key?(:id)
|
67
|
+
raise ArgumentError, "expected uri to have no id: #{uri}"
|
68
|
+
end
|
69
|
+
|
70
|
+
unless file?(filename)
|
71
|
+
raise FileNotFound, "could not find file: #{filename}"
|
72
|
+
end
|
73
|
+
|
74
|
+
load_path, logical_path = paths_split(config[:paths], filename)
|
75
|
+
|
76
|
+
unless load_path
|
77
|
+
raise FileOutsidePaths, "#{filename} is no longer under a load path: #{self.paths.join(', ')}"
|
78
|
+
end
|
79
|
+
|
80
|
+
logical_path, file_type, engine_extnames, _ = parse_path_extnames(logical_path)
|
81
|
+
logical_path = normalize_logical_path(logical_path)
|
82
|
+
name = logical_path
|
83
|
+
|
84
|
+
if pipeline = params[:pipeline]
|
85
|
+
logical_path += ".#{pipeline}"
|
86
|
+
end
|
87
|
+
|
88
|
+
if type = params[:type]
|
89
|
+
logical_path += config[:mime_types][type][:extensions].first
|
90
|
+
end
|
91
|
+
|
92
|
+
if type != file_type && !config[:transformers][file_type][type]
|
93
|
+
raise ConversionError, "could not convert #{file_type.inspect} to #{type.inspect}"
|
94
|
+
end
|
95
|
+
|
96
|
+
processors = processors_for(type, file_type, engine_extnames, pipeline)
|
97
|
+
|
98
|
+
processors_dep_uri = build_processors_uri(type, file_type, engine_extnames, pipeline)
|
99
|
+
dependencies = config[:dependencies] + [processors_dep_uri]
|
100
|
+
|
101
|
+
# Read into memory and process if theres a processor pipeline
|
102
|
+
if processors.any?
|
103
|
+
result = call_processors(processors, {
|
104
|
+
environment: self,
|
105
|
+
cache: self.cache,
|
106
|
+
uri: uri,
|
107
|
+
filename: filename,
|
108
|
+
load_path: load_path,
|
109
|
+
name: name,
|
110
|
+
content_type: type,
|
111
|
+
metadata: { dependencies: dependencies }
|
112
|
+
})
|
113
|
+
source = result.delete(:data)
|
114
|
+
metadata = result.merge!(
|
115
|
+
charset: source.encoding.name.downcase,
|
116
|
+
digest: digest(source),
|
117
|
+
length: source.bytesize
|
118
|
+
)
|
119
|
+
else
|
120
|
+
metadata = {
|
121
|
+
digest: file_digest(filename),
|
122
|
+
length: self.stat(filename).size,
|
123
|
+
dependencies: dependencies
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
asset = {
|
128
|
+
uri: uri,
|
129
|
+
load_path: load_path,
|
130
|
+
filename: filename,
|
131
|
+
name: name,
|
132
|
+
logical_path: logical_path,
|
133
|
+
content_type: type,
|
134
|
+
source: source,
|
135
|
+
metadata: metadata,
|
136
|
+
integrity: integrity_uri(metadata[:digest], type),
|
137
|
+
dependencies_digest: digest(resolve_dependencies(metadata[:dependencies]))
|
138
|
+
}
|
139
|
+
|
140
|
+
asset[:id] = pack_hexdigest(digest(asset))
|
141
|
+
asset[:uri] = build_asset_uri(filename, params.merge(id: asset[:id]))
|
142
|
+
|
143
|
+
# Deprecated: Avoid tracking Asset mtime
|
144
|
+
asset[:mtime] = metadata[:dependencies].map { |u|
|
145
|
+
if u.start_with?("file-digest:")
|
146
|
+
s = self.stat(parse_file_digest_uri(u))
|
147
|
+
s ? s.mtime.to_i : 0
|
148
|
+
else
|
149
|
+
0
|
150
|
+
end
|
151
|
+
}.max
|
152
|
+
|
153
|
+
cache.set("asset-uri:#{VERSION}:#{asset[:uri]}", asset, true)
|
154
|
+
cache.set("asset-uri-digest:#{VERSION}:#{uri}:#{asset[:dependencies_digest]}", asset[:uri], true)
|
155
|
+
|
156
|
+
asset
|
157
|
+
end
|
158
|
+
|
159
|
+
def fetch_asset_from_dependency_cache(uri, filename, limit = 3)
|
160
|
+
key = "asset-uri-cache-dependencies:#{VERSION}:#{uri}:#{file_digest(filename)}"
|
161
|
+
history = cache.get(key) || []
|
162
|
+
|
163
|
+
history.each_with_index do |deps, index|
|
164
|
+
if asset = yield(deps)
|
165
|
+
cache.set(key, history.rotate!(index)) if index > 0
|
166
|
+
return asset
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
asset = yield
|
171
|
+
deps = asset[:metadata][:dependencies]
|
172
|
+
cache.set(key, history.unshift(deps).take(limit))
|
173
|
+
asset
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/lib/sprockets/manifest.rb
CHANGED
@@ -1,51 +1,87 @@
|
|
1
|
-
require '
|
1
|
+
require 'json'
|
2
2
|
require 'time'
|
3
|
+
require 'sprockets/manifest_utils'
|
3
4
|
|
4
5
|
module Sprockets
|
5
|
-
# The Manifest logs the contents of assets compiled to a single
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
6
|
+
# The Manifest logs the contents of assets compiled to a single directory. It
|
7
|
+
# records basic attributes about the asset for fast lookup without having to
|
8
|
+
# compile. A pointer from each logical path indicates which fingerprinted
|
9
|
+
# asset is the current one.
|
9
10
|
#
|
10
|
-
# The JSON is part of the public API and should be considered
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
11
|
+
# The JSON is part of the public API and should be considered stable. This
|
12
|
+
# should make it easy to read from other programming languages and processes
|
13
|
+
# that don't have sprockets loaded. See `#assets` and `#files` for more
|
14
|
+
# infomation about the structure.
|
14
15
|
class Manifest
|
15
|
-
|
16
|
+
include ManifestUtils
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# filename will
|
18
|
+
attr_reader :environment
|
19
|
+
|
20
|
+
# Create new Manifest associated with an `environment`. `filename` is a full
|
21
|
+
# path to the manifest json file. The file may or may not already exist. The
|
22
|
+
# dirname of the `filename` will be used to write compiled assets to.
|
23
|
+
# Otherwise, if the path is a directory, the filename will default a random
|
24
|
+
# ".sprockets-manifest-*.json" file in that directory.
|
22
25
|
#
|
23
26
|
# Manifest.new(environment, "./public/assets/manifest.json")
|
24
27
|
#
|
25
|
-
def initialize(
|
26
|
-
|
28
|
+
def initialize(*args)
|
29
|
+
if args.first.is_a?(Base) || args.first.nil?
|
30
|
+
@environment = args.shift
|
31
|
+
end
|
32
|
+
|
33
|
+
@directory, @filename = args[0], args[1]
|
34
|
+
|
35
|
+
# Whether the manifest file is using the old manifest-*.json naming convention
|
36
|
+
@legacy_manifest = false
|
37
|
+
|
38
|
+
# Expand paths
|
39
|
+
@directory = File.expand_path(@directory) if @directory
|
40
|
+
@filename = File.expand_path(@filename) if @filename
|
41
|
+
|
42
|
+
# If filename is given as the second arg
|
43
|
+
if @directory && File.extname(@directory) != ""
|
44
|
+
@directory, @filename = nil, @directory
|
45
|
+
end
|
27
46
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
47
|
+
# Default dir to the directory of the filename
|
48
|
+
@directory ||= File.dirname(@filename) if @filename
|
49
|
+
|
50
|
+
# If directory is given w/o filename, pick a random manifest location
|
51
|
+
@rename_filename = nil
|
52
|
+
if @directory && @filename.nil?
|
53
|
+
@filename = find_directory_manifest(@directory)
|
54
|
+
|
55
|
+
# If legacy manifest name autodetected, mark to rename on save
|
56
|
+
if File.basename(@filename).start_with?("manifest")
|
57
|
+
@rename_filename = File.join(@directory, generate_manifest_path)
|
58
|
+
end
|
34
59
|
end
|
35
60
|
|
36
|
-
|
61
|
+
unless @directory && @filename
|
62
|
+
raise ArgumentError, "manifest requires output filename"
|
63
|
+
end
|
64
|
+
|
65
|
+
data = {}
|
37
66
|
|
38
67
|
begin
|
39
|
-
if File.exist?(@
|
40
|
-
data =
|
68
|
+
if File.exist?(@filename)
|
69
|
+
data = json_decode(File.read(@filename))
|
41
70
|
end
|
42
|
-
rescue
|
43
|
-
logger.error "#{@
|
71
|
+
rescue JSON::ParserError => e
|
72
|
+
logger.error "#{@filename} is invalid: #{e.class} #{e.message}"
|
44
73
|
end
|
45
74
|
|
46
|
-
@data = data
|
75
|
+
@data = data
|
47
76
|
end
|
48
77
|
|
78
|
+
# Returns String path to manifest.json file.
|
79
|
+
attr_reader :filename
|
80
|
+
alias_method :path, :filename
|
81
|
+
|
82
|
+
attr_reader :directory
|
83
|
+
alias_method :dir, :directory
|
84
|
+
|
49
85
|
# Returns internal assets mapping. Keys are logical paths which
|
50
86
|
# map to the latest fingerprinted filename.
|
51
87
|
#
|
@@ -75,6 +111,40 @@ module Sprockets
|
|
75
111
|
@data['files'] ||= {}
|
76
112
|
end
|
77
113
|
|
114
|
+
# Public: Find all assets matching pattern set in environment.
|
115
|
+
#
|
116
|
+
# Returns Enumerator of Assets.
|
117
|
+
def find(*args)
|
118
|
+
unless environment
|
119
|
+
raise Error, "manifest requires environment for compilation"
|
120
|
+
end
|
121
|
+
|
122
|
+
return to_enum(__method__, *args) unless block_given?
|
123
|
+
|
124
|
+
paths, filters = args.flatten.partition { |arg| self.class.simple_logical_path?(arg) }
|
125
|
+
filters = filters.map { |arg| self.class.compile_match_filter(arg) }
|
126
|
+
|
127
|
+
environment = self.environment.cached
|
128
|
+
|
129
|
+
paths.each do |path|
|
130
|
+
environment.find_all_linked_assets(path) do |asset|
|
131
|
+
yield asset
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
if filters.any?
|
136
|
+
environment.logical_paths do |logical_path, filename|
|
137
|
+
if filters.any? { |f| f.call(logical_path, filename) }
|
138
|
+
environment.find_all_linked_assets(filename) do |asset|
|
139
|
+
yield asset
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
|
78
148
|
# Compile and write asset to directory. The asset is written to a
|
79
149
|
# fingerprinted filename like
|
80
150
|
# `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
|
@@ -83,32 +153,36 @@ module Sprockets
|
|
83
153
|
# compile("application.js")
|
84
154
|
#
|
85
155
|
def compile(*args)
|
86
|
-
|
87
|
-
|
156
|
+
unless environment
|
157
|
+
raise Error, "manifest requires environment for compilation"
|
158
|
+
end
|
88
159
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
if File.exist?(target)
|
102
|
-
logger.debug "Skipping #{target}, already exists"
|
103
|
-
else
|
104
|
-
logger.info "Writing #{target}"
|
105
|
-
asset.write_to target
|
106
|
-
end
|
160
|
+
filenames = []
|
161
|
+
|
162
|
+
find(*args) do |asset|
|
163
|
+
files[asset.digest_path] = {
|
164
|
+
'logical_path' => asset.logical_path,
|
165
|
+
'mtime' => asset.mtime.iso8601,
|
166
|
+
'size' => asset.bytesize,
|
167
|
+
'digest' => asset.hexdigest,
|
168
|
+
'integrity' => asset.integrity
|
169
|
+
}
|
170
|
+
assets[asset.logical_path] = asset.digest_path
|
107
171
|
|
108
|
-
|
109
|
-
|
172
|
+
target = File.join(dir, asset.digest_path)
|
173
|
+
|
174
|
+
if File.exist?(target)
|
175
|
+
logger.debug "Skipping #{target}, already exists"
|
176
|
+
else
|
177
|
+
logger.info "Writing #{target}"
|
178
|
+
asset.write_to target
|
110
179
|
end
|
180
|
+
|
181
|
+
filenames << asset.filename
|
111
182
|
end
|
183
|
+
save
|
184
|
+
|
185
|
+
filenames
|
112
186
|
end
|
113
187
|
|
114
188
|
# Removes file from directory and from manifest. `filename` must
|
@@ -129,76 +203,83 @@ module Sprockets
|
|
129
203
|
|
130
204
|
save
|
131
205
|
|
132
|
-
logger.
|
206
|
+
logger.info "Removed #{filename}"
|
133
207
|
|
134
208
|
nil
|
135
209
|
end
|
136
210
|
|
137
211
|
# Cleanup old assets in the compile directory. By default it will
|
138
|
-
# keep the latest version
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
212
|
+
# keep the latest version, 2 backups and any created within the past hour.
|
213
|
+
#
|
214
|
+
# Examples
|
215
|
+
#
|
216
|
+
# To force only 1 backup to be kept, set count=1 and age=0.
|
217
|
+
#
|
218
|
+
# To only keep files created within the last 10 minutes, set count=0 and
|
219
|
+
# age=600.
|
220
|
+
#
|
221
|
+
def clean(count = 2, age = 3600)
|
222
|
+
asset_versions = files.group_by { |_, attrs| attrs['logical_path'] }
|
143
223
|
|
144
|
-
|
145
|
-
|
224
|
+
asset_versions.each do |logical_path, versions|
|
225
|
+
current = assets[logical_path]
|
146
226
|
|
147
|
-
|
148
|
-
|
227
|
+
versions.reject { |path, _|
|
228
|
+
path == current
|
229
|
+
}.sort_by { |_, attrs|
|
230
|
+
# Sort by timestamp
|
231
|
+
Time.parse(attrs['mtime'])
|
232
|
+
}.reverse.each_with_index.drop_while { |(_, attrs), index|
|
233
|
+
age = [0, Time.now - Time.parse(attrs['mtime'])].max
|
234
|
+
# Keep if under age or within the count limit
|
235
|
+
age < age || index < count
|
236
|
+
}.each { |(path, _), _|
|
237
|
+
# Remove old assets
|
238
|
+
remove(path)
|
239
|
+
}
|
149
240
|
end
|
150
241
|
end
|
151
242
|
|
152
243
|
# Wipe directive
|
153
244
|
def clobber
|
154
|
-
FileUtils.rm_r(
|
155
|
-
logger.
|
245
|
+
FileUtils.rm_r(directory) if File.exist?(directory)
|
246
|
+
logger.info "Removed #{directory}"
|
156
247
|
nil
|
157
248
|
end
|
158
249
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
attrs['logical_path'] == logical_path &&
|
167
|
-
# Excluding whatever asset is the current
|
168
|
-
assets[logical_path] != filename
|
169
|
-
}.sort_by { |filename, attrs|
|
170
|
-
# Sort by timestamp
|
171
|
-
Time.parse(attrs['mtime'])
|
172
|
-
}.reverse
|
250
|
+
# Persist manfiest back to FS
|
251
|
+
def save
|
252
|
+
if @rename_filename
|
253
|
+
logger.info "Renaming #{@filename} to #{@rename_filename}"
|
254
|
+
FileUtils.mv(@filename, @rename_filename)
|
255
|
+
@filename = @rename_filename
|
256
|
+
@rename_filename = nil
|
173
257
|
end
|
174
258
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
asset = environment.find_asset(logical_path)
|
180
|
-
end
|
181
|
-
logger.warn "Compiled #{logical_path} (#{ms}ms)"
|
182
|
-
asset
|
259
|
+
data = json_encode(@data)
|
260
|
+
FileUtils.mkdir_p File.dirname(@filename)
|
261
|
+
PathUtils.atomic_write(@filename) do |f|
|
262
|
+
f.write(data)
|
183
263
|
end
|
264
|
+
end
|
184
265
|
|
185
|
-
|
186
|
-
def
|
187
|
-
|
188
|
-
File.open(path, 'w') do |f|
|
189
|
-
f.write MultiJson.encode(@data)
|
190
|
-
end
|
266
|
+
private
|
267
|
+
def json_decode(obj)
|
268
|
+
JSON.parse(obj, create_additions: false)
|
191
269
|
end
|
192
270
|
|
193
|
-
|
194
|
-
|
195
|
-
environment.logger
|
271
|
+
def json_encode(obj)
|
272
|
+
JSON.generate(obj)
|
196
273
|
end
|
197
274
|
|
198
|
-
def
|
199
|
-
|
200
|
-
|
201
|
-
|
275
|
+
def logger
|
276
|
+
if environment
|
277
|
+
environment.logger
|
278
|
+
else
|
279
|
+
logger = Logger.new($stderr)
|
280
|
+
logger.level = Logger::FATAL
|
281
|
+
logger
|
282
|
+
end
|
202
283
|
end
|
203
284
|
end
|
204
285
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
# Public: Manifest utilities.
|
5
|
+
module ManifestUtils
|
6
|
+
extend self
|
7
|
+
|
8
|
+
MANIFEST_RE = /^\.sprockets-manifest-[0-9a-f]{32}.json$/
|
9
|
+
LEGACY_MANIFEST_RE = /^manifest(-[0-9a-f]{32})?.json$/
|
10
|
+
|
11
|
+
# Public: Generate a new random manifest path.
|
12
|
+
#
|
13
|
+
# Manifests are not intended to be accessed publicly, but typically live
|
14
|
+
# alongside public assets for convenience. To avoid being served, the
|
15
|
+
# filename is prefixed with a "." which is usually hidden by web servers
|
16
|
+
# like Apache. To help in other environments that may not control this,
|
17
|
+
# a random hex string is appended to the filename to prevent people from
|
18
|
+
# guessing the location. If directory indexes are enabled on the server,
|
19
|
+
# all bets are off.
|
20
|
+
#
|
21
|
+
# Return String path.
|
22
|
+
def generate_manifest_path
|
23
|
+
".sprockets-manifest-#{SecureRandom.hex(16)}.json"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: Find or pick a new manifest filename for target build directory.
|
27
|
+
#
|
28
|
+
# dirname - String dirname
|
29
|
+
#
|
30
|
+
# Examples
|
31
|
+
#
|
32
|
+
# find_directory_manifest("/app/public/assets")
|
33
|
+
# # => "/app/public/assets/.sprockets-manifest-abc123.json"
|
34
|
+
#
|
35
|
+
# Returns String filename.
|
36
|
+
def find_directory_manifest(dirname)
|
37
|
+
entries = File.directory?(dirname) ? Dir.entries(dirname) : []
|
38
|
+
entry = entries.find { |e| e =~ MANIFEST_RE } ||
|
39
|
+
# Deprecated: Will be removed in 4.x
|
40
|
+
entries.find { |e| e =~ LEGACY_MANIFEST_RE } ||
|
41
|
+
generate_manifest_path
|
42
|
+
File.join(dirname, entry)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|