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.
- checksums.yaml +5 -5
- data/LICENSE +2 -2
- data/README.md +61 -34
- data/lib/rake/sprocketstask.rb +5 -4
- data/lib/sprockets.rb +123 -85
- data/lib/sprockets/asset.rb +161 -200
- data/lib/sprockets/asset_uri.rb +64 -0
- data/lib/sprockets/base.rb +138 -373
- data/lib/sprockets/bower.rb +56 -0
- data/lib/sprockets/bundle.rb +32 -0
- data/lib/sprockets/cache.rb +220 -0
- data/lib/sprockets/cache/file_store.rb +145 -13
- data/lib/sprockets/cache/memory_store.rb +66 -0
- data/lib/sprockets/cache/null_store.rb +46 -0
- data/lib/sprockets/cached_environment.rb +103 -0
- data/lib/sprockets/closure_compressor.rb +30 -12
- data/lib/sprockets/coffee_script_template.rb +23 -0
- data/lib/sprockets/compressing.rb +20 -25
- data/lib/sprockets/configuration.rb +95 -0
- data/lib/sprockets/context.rb +68 -131
- data/lib/sprockets/directive_processor.rb +138 -179
- data/lib/sprockets/eco_template.rb +10 -19
- data/lib/sprockets/ejs_template.rb +10 -19
- data/lib/sprockets/encoding_utils.rb +246 -0
- data/lib/sprockets/engines.rb +40 -29
- data/lib/sprockets/environment.rb +10 -66
- data/lib/sprockets/erb_template.rb +23 -0
- data/lib/sprockets/errors.rb +5 -13
- data/lib/sprockets/http_utils.rb +97 -0
- data/lib/sprockets/jst_processor.rb +28 -15
- data/lib/sprockets/lazy_processor.rb +15 -0
- data/lib/sprockets/legacy.rb +23 -0
- data/lib/sprockets/legacy_proc_processor.rb +35 -0
- data/lib/sprockets/legacy_tilt_processor.rb +29 -0
- data/lib/sprockets/manifest.rb +128 -99
- data/lib/sprockets/mime.rb +114 -33
- data/lib/sprockets/path_utils.rb +179 -0
- data/lib/sprockets/paths.rb +13 -26
- data/lib/sprockets/processing.rb +198 -107
- data/lib/sprockets/resolve.rb +289 -0
- data/lib/sprockets/sass_compressor.rb +36 -17
- data/lib/sprockets/sass_template.rb +269 -46
- data/lib/sprockets/server.rb +113 -83
- data/lib/sprockets/transformers.rb +69 -0
- data/lib/sprockets/uglifier_compressor.rb +36 -15
- data/lib/sprockets/utils.rb +161 -44
- data/lib/sprockets/version.rb +1 -1
- data/lib/sprockets/yui_compressor.rb +37 -12
- metadata +64 -106
- data/lib/sprockets/asset_attributes.rb +0 -137
- data/lib/sprockets/bundled_asset.rb +0 -78
- data/lib/sprockets/caching.rb +0 -96
- data/lib/sprockets/charset_normalizer.rb +0 -41
- data/lib/sprockets/index.rb +0 -100
- 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/sass_cache_store.rb +0 -29
- data/lib/sprockets/sass_functions.rb +0 -70
- data/lib/sprockets/sass_importer.rb +0 -30
- data/lib/sprockets/scss_template.rb +0 -13
- data/lib/sprockets/static_asset.rb +0 -60
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
class ERBTemplate
|
5
|
+
def self.call(input)
|
6
|
+
new.call(input)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(&block)
|
10
|
+
@block = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(input)
|
14
|
+
engine = ::ERB.new(input[:data], nil, '<>')
|
15
|
+
context = input[:environment].context_class.new(input)
|
16
|
+
klass = (class << context; self; end)
|
17
|
+
klass.class_eval(&@block) if @block
|
18
|
+
engine.def_method(klass, :_evaluate_template, input[:filename])
|
19
|
+
data = context._evaluate_template
|
20
|
+
context.metadata.merge(data: data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/sprockets/errors.rb
CHANGED
@@ -2,19 +2,11 @@
|
|
2
2
|
module Sprockets
|
3
3
|
class Error < StandardError; end
|
4
4
|
class ArgumentError < Error; end
|
5
|
-
class CircularDependencyError < Error; end
|
6
5
|
class ContentTypeMismatch < Error; end
|
7
|
-
class EncodingError < Error; end
|
8
|
-
class FileNotFound < Error; end
|
9
|
-
class FileOutsidePaths < Error; end
|
10
6
|
class NotImplementedError < Error; end
|
11
|
-
class
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def message
|
17
|
-
[super, sprockets_annotation].compact.join("\n")
|
18
|
-
end
|
19
|
-
end
|
7
|
+
class NotFound < Error; end
|
8
|
+
class ConversionError < NotFound; end
|
9
|
+
class FileNotFound < NotFound; end
|
10
|
+
class FileOutsidePaths < NotFound; end
|
11
|
+
class VersionNotFound < NotFound; end
|
20
12
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Sprockets
|
2
|
+
module HTTPUtils
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# Internal: Test mime type against mime range.
|
6
|
+
#
|
7
|
+
# match_mime_type?('text/html', 'text/*') => true
|
8
|
+
# match_mime_type?('text/plain', '*') => true
|
9
|
+
# match_mime_type?('text/html', 'application/json') => false
|
10
|
+
#
|
11
|
+
# Returns true if the given value is a mime match for the given mime match
|
12
|
+
# specification, false otherwise.
|
13
|
+
def match_mime_type?(value, matcher)
|
14
|
+
v1, v2 = value.split('/', 2)
|
15
|
+
m1, m2 = matcher.split('/', 2)
|
16
|
+
(m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Internal: Parse Accept header quality values.
|
20
|
+
#
|
21
|
+
# Adapted from Rack::Utils#q_values.
|
22
|
+
#
|
23
|
+
# Returns an Array of [String, Float].
|
24
|
+
def parse_q_values(values)
|
25
|
+
values.to_s.split(/\s*,\s*/).map do |part|
|
26
|
+
value, parameters = part.split(/\s*;\s*/, 2)
|
27
|
+
quality = 1.0
|
28
|
+
if md = /\Aq=([\d.]+)/.match(parameters)
|
29
|
+
quality = md[1].to_f
|
30
|
+
end
|
31
|
+
[value, quality]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Internal: Find all qvalue matches from an Array of available options.
|
36
|
+
#
|
37
|
+
# Adapted from Rack::Utils#q_values.
|
38
|
+
#
|
39
|
+
# Returns Array of matched Strings from available Array or [].
|
40
|
+
def find_q_matches(q_values, available, &matcher)
|
41
|
+
matcher ||= lambda { |a, b| a == b }
|
42
|
+
|
43
|
+
matches = []
|
44
|
+
|
45
|
+
case q_values
|
46
|
+
when Array
|
47
|
+
when String
|
48
|
+
q_values = parse_q_values(q_values)
|
49
|
+
when NilClass
|
50
|
+
q_values = []
|
51
|
+
else
|
52
|
+
raise TypeError, "unknown q_values type: #{q_values.class}"
|
53
|
+
end
|
54
|
+
|
55
|
+
q_values.each do |accepted, quality|
|
56
|
+
if match = available.find { |option| matcher.call(option, accepted) }
|
57
|
+
matches << [match, quality]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
matches.sort_by { |match, quality| -quality }.map { |match, quality| match }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Internal: Find the best qvalue match from an Array of available options.
|
65
|
+
#
|
66
|
+
# Adapted from Rack::Utils#q_values.
|
67
|
+
#
|
68
|
+
# Returns the matched String from available Array or nil.
|
69
|
+
def find_best_q_match(q_values, available, &matcher)
|
70
|
+
find_q_matches(q_values, available, &matcher).first
|
71
|
+
end
|
72
|
+
|
73
|
+
# Internal: Find the all qvalue match from an Array of available mime type
|
74
|
+
# options.
|
75
|
+
#
|
76
|
+
# Adapted from Rack::Utils#q_values.
|
77
|
+
#
|
78
|
+
# Returns Array of matched mime type Strings from available Array or [].
|
79
|
+
def find_mime_type_matches(q_value_header, available)
|
80
|
+
find_q_matches(q_value_header, available) do |a, b|
|
81
|
+
match_mime_type?(a, b)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Internal: Find the best qvalue match from an Array of available mime type
|
86
|
+
# options.
|
87
|
+
#
|
88
|
+
# Adapted from Rack::Utils#q_values.
|
89
|
+
#
|
90
|
+
# Returns the matched mime type String from available Array or nil.
|
91
|
+
def find_best_mime_type_match(q_value_header, available)
|
92
|
+
find_best_q_match(q_value_header, available) do |a, b|
|
93
|
+
match_mime_type?(a, b)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -1,29 +1,42 @@
|
|
1
|
-
require 'tilt'
|
2
|
-
|
3
1
|
module Sprockets
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
# Public: .jst engine.
|
3
|
+
#
|
4
|
+
# Exports server side compiled templates to an object.
|
5
|
+
#
|
6
|
+
# Name your template "users/show.jst.ejs", "users/new.jst.eco", etc.
|
7
|
+
#
|
8
|
+
# To accept the default options
|
9
|
+
#
|
10
|
+
# environment.register_engine '.jst',
|
11
|
+
# JstProcessor,
|
12
|
+
# mime_type: 'application/javascript'
|
13
|
+
#
|
14
|
+
# Change the default namespace.
|
15
|
+
#
|
16
|
+
# environment.register_engine '.jst',
|
17
|
+
# JstProcessor.new(namespace: 'App.templates'),
|
18
|
+
# mime_type: 'application/javascript'
|
19
|
+
#
|
20
|
+
class JstProcessor
|
7
21
|
def self.default_namespace
|
8
22
|
'this.JST'
|
9
23
|
end
|
10
24
|
|
11
|
-
def
|
12
|
-
|
25
|
+
def self.call(*args)
|
26
|
+
new.call(*args)
|
13
27
|
end
|
14
28
|
|
15
|
-
|
29
|
+
def initialize(options = {})
|
30
|
+
@namespace = options[:namespace] || self.class.default_namespace
|
31
|
+
end
|
16
32
|
|
17
|
-
def
|
33
|
+
def call(input)
|
34
|
+
data = input[:data].gsub(/$(.)/m, "\\1 ").strip
|
35
|
+
key = input[:name]
|
18
36
|
<<-JST
|
19
|
-
(function() { #{namespace} || (#{namespace} = {}); #{namespace}[#{
|
37
|
+
(function() { #{@namespace} || (#{@namespace} = {}); #{@namespace}[#{key.inspect}] = #{data};
|
20
38
|
}).call(this);
|
21
39
|
JST
|
22
40
|
end
|
23
|
-
|
24
|
-
private
|
25
|
-
def indent(string)
|
26
|
-
string.gsub(/$(.)/m, "\\1 ").strip
|
27
|
-
end
|
28
41
|
end
|
29
42
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Sprockets
|
2
|
+
# Internal: Used for lazy loading processors.
|
3
|
+
#
|
4
|
+
# LazyProcessor.new { CoffeeScriptTemplate }
|
5
|
+
#
|
6
|
+
class LazyProcessor
|
7
|
+
def initialize(&block)
|
8
|
+
@block = block
|
9
|
+
end
|
10
|
+
|
11
|
+
def unwrap
|
12
|
+
@obj ||= @block.call
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Sprockets
|
2
|
+
module Legacy
|
3
|
+
private
|
4
|
+
# Deprecated: Seriously.
|
5
|
+
def matches_filter(filters, logical_path, filename)
|
6
|
+
return true if filters.empty?
|
7
|
+
|
8
|
+
filters.any? do |filter|
|
9
|
+
if filter.is_a?(Regexp)
|
10
|
+
filter.match(logical_path)
|
11
|
+
elsif filter.respond_to?(:call)
|
12
|
+
if filter.arity == 1
|
13
|
+
filter.call(logical_path)
|
14
|
+
else
|
15
|
+
filter.call(logical_path, filename.to_s)
|
16
|
+
end
|
17
|
+
else
|
18
|
+
File.fnmatch(filter.to_s, logical_path)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
# Deprecated: Wraps legacy process Procs with new processor call signature.
|
5
|
+
#
|
6
|
+
# Will be removed in Sprockets 4.x.
|
7
|
+
#
|
8
|
+
# LegacyProcProcessor.new(:compress,
|
9
|
+
# proc { |context, data| data.gsub(...) })
|
10
|
+
#
|
11
|
+
class LegacyProcProcessor < Delegator
|
12
|
+
def initialize(name, proc)
|
13
|
+
@name = name
|
14
|
+
@proc = proc
|
15
|
+
end
|
16
|
+
|
17
|
+
def __getobj__
|
18
|
+
@proc
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
"Sprockets::LegacyProcProcessor (#{@name})"
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
name
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(input)
|
30
|
+
context = input[:environment].context_class.new(input)
|
31
|
+
data = @proc.call(context, input[:data])
|
32
|
+
context.metadata.merge(data: data)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
# Deprecated: Wraps legacy engine and process Tilt templates with new
|
5
|
+
# processor call signature.
|
6
|
+
#
|
7
|
+
# Will be removed in Sprockets 4.x.
|
8
|
+
#
|
9
|
+
# LegacyTiltProcessor.new(Tilt::CoffeeScriptTemplate)
|
10
|
+
#
|
11
|
+
class LegacyTiltProcessor < Delegator
|
12
|
+
def initialize(klass)
|
13
|
+
@klass = klass
|
14
|
+
end
|
15
|
+
|
16
|
+
def __getobj__
|
17
|
+
@klass
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(input)
|
21
|
+
filename = input[:filename]
|
22
|
+
data = input[:data]
|
23
|
+
context = input[:environment].context_class.new(input)
|
24
|
+
|
25
|
+
data = @klass.new(filename) { data }.render(context)
|
26
|
+
context.metadata.merge(data: data)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/sprockets/manifest.rb
CHANGED
@@ -1,26 +1,25 @@
|
|
1
|
-
require '
|
1
|
+
require 'json'
|
2
2
|
require 'securerandom'
|
3
3
|
require 'time'
|
4
4
|
|
5
5
|
module Sprockets
|
6
|
-
# The Manifest logs the contents of assets compiled to a single
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
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.
|
10
10
|
#
|
11
|
-
# The JSON is part of the public API and should be considered
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
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.
|
15
15
|
class Manifest
|
16
|
-
attr_reader :environment
|
17
|
-
|
18
|
-
# Create new Manifest associated with an `environment`. `
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
# directory.
|
16
|
+
attr_reader :environment
|
17
|
+
|
18
|
+
# Create new Manifest associated with an `environment`. `filename` is a full
|
19
|
+
# path to the manifest json file. The file may or may not already exist. The
|
20
|
+
# dirname of the `filename` will be used to write compiled assets to.
|
21
|
+
# Otherwise, if the path is a directory, the filename will default a random
|
22
|
+
# "manifest-123.json" file in that directory.
|
24
23
|
#
|
25
24
|
# Manifest.new(environment, "./public/assets/manifest.json")
|
26
25
|
#
|
@@ -29,48 +28,55 @@ module Sprockets
|
|
29
28
|
@environment = args.shift
|
30
29
|
end
|
31
30
|
|
32
|
-
@
|
31
|
+
@directory, @filename = args[0], args[1]
|
33
32
|
|
34
33
|
# Expand paths
|
35
|
-
@
|
36
|
-
@
|
34
|
+
@directory = File.expand_path(@directory) if @directory
|
35
|
+
@filename = File.expand_path(@filename) if @filename
|
37
36
|
|
38
|
-
# If
|
39
|
-
if @
|
40
|
-
@
|
37
|
+
# If filename is given as the second arg
|
38
|
+
if @directory && File.extname(@directory) != ""
|
39
|
+
@directory, @filename = nil, @directory
|
41
40
|
end
|
42
41
|
|
43
|
-
# Default dir to the directory of the
|
44
|
-
@
|
42
|
+
# Default dir to the directory of the filename
|
43
|
+
@directory ||= File.dirname(@filename) if @filename
|
45
44
|
|
46
|
-
# If directory is given w/o
|
47
|
-
if @
|
45
|
+
# If directory is given w/o filename, pick a random manifest.json location
|
46
|
+
if @directory && @filename.nil?
|
48
47
|
# Find the first manifest.json in the directory
|
49
|
-
|
50
|
-
if
|
51
|
-
@
|
48
|
+
filenames = Dir[File.join(@directory, "manifest*.json")]
|
49
|
+
if filenames.any?
|
50
|
+
@filename = filenames.first
|
52
51
|
else
|
53
|
-
@
|
52
|
+
@filename = File.join(@directory, "manifest-#{SecureRandom.hex(16)}.json")
|
54
53
|
end
|
55
54
|
end
|
56
55
|
|
57
|
-
unless @
|
58
|
-
raise ArgumentError, "manifest requires output
|
56
|
+
unless @directory && @filename
|
57
|
+
raise ArgumentError, "manifest requires output filename"
|
59
58
|
end
|
60
59
|
|
61
|
-
data =
|
60
|
+
data = {}
|
62
61
|
|
63
62
|
begin
|
64
|
-
if File.exist?(@
|
65
|
-
data = json_decode(File.read(@
|
63
|
+
if File.exist?(@filename)
|
64
|
+
data = json_decode(File.read(@filename))
|
66
65
|
end
|
67
|
-
rescue
|
68
|
-
logger.error "#{@
|
66
|
+
rescue JSON::ParserError => e
|
67
|
+
logger.error "#{@filename} is invalid: #{e.class} #{e.message}"
|
69
68
|
end
|
70
69
|
|
71
|
-
@data = data
|
70
|
+
@data = data
|
72
71
|
end
|
73
72
|
|
73
|
+
# Returns String path to manifest.json file.
|
74
|
+
attr_reader :filename
|
75
|
+
alias_method :path, :filename
|
76
|
+
|
77
|
+
attr_reader :directory
|
78
|
+
alias_method :dir, :directory
|
79
|
+
|
74
80
|
# Returns internal assets mapping. Keys are logical paths which
|
75
81
|
# map to the latest fingerprinted filename.
|
76
82
|
#
|
@@ -100,6 +106,53 @@ module Sprockets
|
|
100
106
|
@data['files'] ||= {}
|
101
107
|
end
|
102
108
|
|
109
|
+
# Internal: Compile logical path matching filter into a proc that can be
|
110
|
+
# passed to logical_paths.select(&proc).
|
111
|
+
#
|
112
|
+
# compile_match_filter(proc { |logical_path|
|
113
|
+
# File.extname(logical_path) == '.js'
|
114
|
+
# })
|
115
|
+
#
|
116
|
+
# compile_match_filter(/application.js/)
|
117
|
+
#
|
118
|
+
# compile_match_filter("foo/*.js")
|
119
|
+
#
|
120
|
+
# Returns a Proc or raise a TypeError.
|
121
|
+
def self.compile_match_filter(filter)
|
122
|
+
# If the filter is already a proc, great nothing to do.
|
123
|
+
if filter.respond_to?(:call)
|
124
|
+
filter
|
125
|
+
# If the filter is a regexp, wrap it in a proc that tests it against the
|
126
|
+
# logical path.
|
127
|
+
elsif filter.is_a?(Regexp)
|
128
|
+
proc { |logical_path| filter.match(logical_path) }
|
129
|
+
elsif filter.is_a?(String)
|
130
|
+
# If its an absolute path, detect the matching full filename
|
131
|
+
if PathUtils.absolute_path?(filter)
|
132
|
+
proc { |logical_path, filename| filename == filter.to_s }
|
133
|
+
else
|
134
|
+
# Otherwise do an fnmatch against the logical path.
|
135
|
+
proc { |logical_path| File.fnmatch(filter.to_s, logical_path) }
|
136
|
+
end
|
137
|
+
else
|
138
|
+
raise TypeError, "unknown filter type: #{filter.inspect}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Public: Filter logical paths in environment. Useful for selecting what
|
143
|
+
# files you want to compile.
|
144
|
+
#
|
145
|
+
# Returns an Enumerator.
|
146
|
+
def filter_logical_paths(*args)
|
147
|
+
filters = args.flatten.map { |arg| self.class.compile_match_filter(arg) }
|
148
|
+
environment.logical_paths.select do |a, b|
|
149
|
+
filters.any? { |f| f.call(a, b) }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Deprecated alias.
|
154
|
+
alias_method :find_logical_paths, :filter_logical_paths
|
155
|
+
|
103
156
|
# Compile and write asset to directory. The asset is written to a
|
104
157
|
# fingerprinted filename like
|
105
158
|
# `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
|
@@ -112,11 +165,10 @@ module Sprockets
|
|
112
165
|
raise Error, "manifest requires environment for compilation"
|
113
166
|
end
|
114
167
|
|
115
|
-
|
116
|
-
args.flatten.select { |fn| Pathname.new(fn).absolute? if fn.is_a?(String)}
|
168
|
+
filenames = []
|
117
169
|
|
118
|
-
|
119
|
-
|
170
|
+
filter_logical_paths(*args).each do |_, filename|
|
171
|
+
find_assets(filename) do |asset|
|
120
172
|
files[asset.digest_path] = {
|
121
173
|
'logical_path' => asset.logical_path,
|
122
174
|
'mtime' => asset.mtime.iso8601,
|
@@ -132,13 +184,14 @@ module Sprockets
|
|
132
184
|
else
|
133
185
|
logger.info "Writing #{target}"
|
134
186
|
asset.write_to target
|
135
|
-
asset.write_to "#{target}.gz" if asset.is_a?(BundledAsset)
|
136
187
|
end
|
137
188
|
|
189
|
+
filenames << filename
|
138
190
|
end
|
139
191
|
end
|
140
192
|
save
|
141
|
-
|
193
|
+
|
194
|
+
filenames
|
142
195
|
end
|
143
196
|
|
144
197
|
# Removes file from directory and from manifest. `filename` must
|
@@ -148,7 +201,6 @@ module Sprockets
|
|
148
201
|
#
|
149
202
|
def remove(filename)
|
150
203
|
path = File.join(dir, filename)
|
151
|
-
gzip = "#{path}.gz"
|
152
204
|
logical_path = files[filename]['logical_path']
|
153
205
|
|
154
206
|
if assets[logical_path] == filename
|
@@ -157,7 +209,6 @@ module Sprockets
|
|
157
209
|
|
158
210
|
files.delete(filename)
|
159
211
|
FileUtils.rm(path) if File.exist?(path)
|
160
|
-
FileUtils.rm(gzip) if File.exist?(gzip)
|
161
212
|
|
162
213
|
save
|
163
214
|
|
@@ -169,77 +220,61 @@ module Sprockets
|
|
169
220
|
# Cleanup old assets in the compile directory. By default it will
|
170
221
|
# keep the latest version plus 2 backups.
|
171
222
|
def clean(keep = 2)
|
172
|
-
|
173
|
-
|
174
|
-
|
223
|
+
asset_versions = files.group_by { |_, attrs| attrs['logical_path'] }
|
224
|
+
|
225
|
+
asset_versions.each do |logical_path, versions|
|
226
|
+
current = assets[logical_path]
|
227
|
+
|
228
|
+
backups = versions.reject { |path, _|
|
229
|
+
path == current
|
230
|
+
}.sort_by { |_, attrs|
|
231
|
+
# Sort by timestamp
|
232
|
+
Time.parse(attrs['mtime'])
|
233
|
+
}.reverse
|
175
234
|
|
176
235
|
# Keep the last N backups
|
177
|
-
|
236
|
+
backups = backups[keep..-1] || []
|
178
237
|
|
179
238
|
# Remove old assets
|
180
|
-
|
239
|
+
backups.each { |path, _| remove(path) }
|
181
240
|
end
|
182
241
|
end
|
183
242
|
|
184
243
|
# Wipe directive
|
185
244
|
def clobber
|
186
|
-
FileUtils.rm_r(
|
187
|
-
logger.info "Removed #{
|
245
|
+
FileUtils.rm_r(directory) if File.exist?(directory)
|
246
|
+
logger.info "Removed #{directory}"
|
188
247
|
nil
|
189
248
|
end
|
190
249
|
|
191
250
|
protected
|
192
|
-
# Finds all the backup assets for a logical path. The latest
|
193
|
-
# version is always excluded. The return array is sorted by the
|
194
|
-
# assets mtime in descending order (Newest to oldest).
|
195
|
-
def backups_for(logical_path)
|
196
|
-
files.select { |filename, attrs|
|
197
|
-
# Matching logical paths
|
198
|
-
attrs['logical_path'] == logical_path &&
|
199
|
-
# Excluding whatever asset is the current
|
200
|
-
assets[logical_path] != filename
|
201
|
-
}.sort_by { |filename, attrs|
|
202
|
-
# Sort by timestamp
|
203
|
-
Time.parse(attrs['mtime'])
|
204
|
-
}.reverse
|
205
|
-
end
|
206
|
-
|
207
251
|
# Basic wrapper around Environment#find_asset. Logs compile time.
|
208
|
-
def
|
209
|
-
|
210
|
-
|
211
|
-
|
252
|
+
def find_assets(path)
|
253
|
+
start = Utils.benchmark_start
|
254
|
+
environment.find_all_linked_assets(path) do |asset|
|
255
|
+
logger.debug do
|
256
|
+
"Compiled #{asset.logical_path} (#{Utils.benchmark_end(start)}ms)"
|
257
|
+
end
|
258
|
+
yield asset
|
259
|
+
start = Utils.benchmark_start
|
212
260
|
end
|
213
|
-
logger.debug "Compiled #{logical_path} (#{ms}ms)"
|
214
|
-
asset
|
215
261
|
end
|
216
262
|
|
217
263
|
# Persist manfiest back to FS
|
218
264
|
def save
|
219
|
-
FileUtils.mkdir_p File.dirname(
|
220
|
-
File.open(
|
265
|
+
FileUtils.mkdir_p File.dirname(filename)
|
266
|
+
File.open(filename, 'w') do |f|
|
221
267
|
f.write json_encode(@data)
|
222
268
|
end
|
223
269
|
end
|
224
270
|
|
225
271
|
private
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
MultiJson.load(obj)
|
230
|
-
end
|
231
|
-
|
232
|
-
def json_encode(obj)
|
233
|
-
MultiJson.dump(obj)
|
234
|
-
end
|
235
|
-
else
|
236
|
-
def json_decode(obj)
|
237
|
-
MultiJson.decode(obj)
|
238
|
-
end
|
272
|
+
def json_decode(obj)
|
273
|
+
JSON.parse(obj, create_additions: false)
|
274
|
+
end
|
239
275
|
|
240
|
-
|
241
|
-
|
242
|
-
end
|
276
|
+
def json_encode(obj)
|
277
|
+
JSON.generate(obj)
|
243
278
|
end
|
244
279
|
|
245
280
|
def logger
|
@@ -251,11 +286,5 @@ module Sprockets
|
|
251
286
|
logger
|
252
287
|
end
|
253
288
|
end
|
254
|
-
|
255
|
-
def benchmark
|
256
|
-
start_time = Time.now.to_f
|
257
|
-
yield
|
258
|
-
((Time.now.to_f - start_time) * 1000).to_i
|
259
|
-
end
|
260
289
|
end
|
261
290
|
end
|