slinky 0.7.3 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/.travis.yml +1 -1
- data/Gemfile +2 -0
- data/README.md +119 -8
- data/VERSION +1 -1
- data/lib/slinky.rb +2 -0
- data/lib/slinky/builder.rb +4 -2
- data/lib/slinky/compiled_file.rb +1 -1
- data/lib/slinky/compilers.rb +5 -2
- data/lib/slinky/compilers/clojurescript-compiler.rb +4 -4
- data/lib/slinky/compilers/coffee-compiler.rb +4 -3
- data/lib/slinky/compilers/haml-compiler.rb +4 -3
- data/lib/slinky/compilers/jsx-compiler.rb +13 -0
- data/lib/slinky/compilers/less-compiler.rb +4 -3
- data/lib/slinky/compilers/sass-compiler.rb +4 -3
- data/lib/slinky/config_reader.rb +18 -2
- data/lib/slinky/errors.rb +91 -0
- data/lib/slinky/graph.rb +113 -0
- data/lib/slinky/manifest.rb +290 -123
- data/lib/slinky/proxy_server.rb +1 -1
- data/lib/slinky/runner.rb +1 -1
- data/lib/slinky/server.rb +41 -5
- data/lib/slinky/templates/error.css +32 -0
- data/lib/slinky/templates/error.haml +13 -0
- data/lib/slinky/templates/error.js +26 -0
- data/lib/slinky/templates/inject.css +27 -0
- data/slinky.gemspec +17 -3
- data/spec/compilers_spec.rb +15 -0
- data/spec/manifest_spec.rb +750 -0
- data/spec/slinky_spec.rb +9 -421
- data/spec/spec_helper.rb +22 -7
- metadata +83 -47
data/lib/slinky/graph.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Slinky
|
3
|
+
# The Graph class describes a directed graph and provides various
|
4
|
+
# graph algorithms.
|
5
|
+
class Graph
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
attr_reader :nodes, :edges
|
9
|
+
|
10
|
+
# Creates a new Graph from an adjacency list
|
11
|
+
def initialize nodes, edges
|
12
|
+
@nodes = nodes
|
13
|
+
@edges = edges
|
14
|
+
end
|
15
|
+
|
16
|
+
# Builds an adjacency matrix representation of the graph
|
17
|
+
def adjacency_matrix
|
18
|
+
return @adjacency_matrix if @adjacency_matrix
|
19
|
+
|
20
|
+
# Convert from adjacency list to a map structure
|
21
|
+
g = Hash.new{|h,k| h[k] = []}
|
22
|
+
edges.each{|x|
|
23
|
+
g[x[1]] << x[0]
|
24
|
+
}
|
25
|
+
|
26
|
+
@adjacency_matrix = g
|
27
|
+
end
|
28
|
+
|
29
|
+
# Builds the transitive closure of the dependency graph using
|
30
|
+
# Floyd–Warshall
|
31
|
+
def transitive_closure
|
32
|
+
return @transitive_closure if @transitive_closure
|
33
|
+
|
34
|
+
g = adjacency_matrix
|
35
|
+
|
36
|
+
index_map = {}
|
37
|
+
nodes.each_with_index{|f, i| index_map[f] = i}
|
38
|
+
|
39
|
+
size = nodes.size
|
40
|
+
|
41
|
+
# Set up the distance matrix
|
42
|
+
dist = Array.new(size){|_| Array.new(size, Float::INFINITY)}
|
43
|
+
nodes.each_with_index{|fi, i|
|
44
|
+
dist[i][i] = 0
|
45
|
+
g[fi].each{|fj|
|
46
|
+
dist[i][index_map[fj]] = 1
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
# Compute the all-paths costs
|
51
|
+
size.times{|k|
|
52
|
+
size.times{|i|
|
53
|
+
size.times{|j|
|
54
|
+
if dist[i][j] > dist[i][k] + dist[k][j]
|
55
|
+
dist[i][j] = dist[i][k] + dist[k][j]
|
56
|
+
end
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
# Compute the transitive closure in map form
|
62
|
+
@transitive_closure = Hash.new{|h,k| h[k] = []}
|
63
|
+
size.times{|i|
|
64
|
+
size.times{|j|
|
65
|
+
if dist[i][j] < Float::INFINITY
|
66
|
+
@transitive_closure[nodes[i]] << nodes[j]
|
67
|
+
end
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
@transitive_closure
|
72
|
+
end
|
73
|
+
|
74
|
+
# Builds a list of files in topological order, so that when
|
75
|
+
# required in this order all dependencies are met. See
|
76
|
+
# http://en.wikipedia.org/wiki/Topological_sorting for more
|
77
|
+
# information.
|
78
|
+
def dependency_list
|
79
|
+
return @dependency_list if @dependency_list
|
80
|
+
|
81
|
+
graph = edges.clone
|
82
|
+
# will contain sorted elements
|
83
|
+
l = []
|
84
|
+
# start nodes, those with no incoming edges
|
85
|
+
s = nodes.reject{|mf| mf.directives[:slinky_require]}
|
86
|
+
while s.size > 0
|
87
|
+
n = s.delete s.first
|
88
|
+
l << n
|
89
|
+
nodes.each{|m|
|
90
|
+
e = graph.find{|e| e[0] == n && e[1] == m}
|
91
|
+
next unless e
|
92
|
+
graph.delete e
|
93
|
+
s << m unless graph.any?{|e| e[1] == m}
|
94
|
+
}
|
95
|
+
end
|
96
|
+
if graph != []
|
97
|
+
problems = graph.collect{|e| e.collect{|x| x.source}.join(" -> ")}
|
98
|
+
raise DependencyError.new("Dependencies #{problems.join(", ")} could not be satisfied")
|
99
|
+
end
|
100
|
+
@dependency_list = l
|
101
|
+
end
|
102
|
+
|
103
|
+
def each &block
|
104
|
+
edges.each do |e|
|
105
|
+
if block_given?
|
106
|
+
block.call e
|
107
|
+
else
|
108
|
+
yield e
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/slinky/manifest.rb
CHANGED
@@ -1,30 +1,26 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
require 'pathname'
|
3
3
|
require 'digest/md5'
|
4
|
+
require 'matrix'
|
5
|
+
require 'set'
|
4
6
|
|
5
7
|
module Slinky
|
6
|
-
# extensions of files that can contain build directives
|
7
|
-
DIRECTIVE_FILES = %w{js css html
|
8
|
+
# extensions of non-compiled files that can contain build directives
|
9
|
+
DIRECTIVE_FILES = %w{js css html}
|
8
10
|
DEPENDS_DIRECTIVE = /^[^\n\w]*(slinky_depends)\((".*"|'.+'|)\)[^\n\w]*$/
|
9
11
|
REQUIRE_DIRECTIVE = /^[^\n\w]*(slinky_require)\((".*"|'.+'|)\)[^\n\w]*$/
|
10
12
|
SCRIPTS_DIRECTIVE = /^[^\n\w]*(slinky_scripts)[^\n\w]*$/
|
11
13
|
STYLES_DIRECTIVE = /^[^\n\w]*(slinky_styles)[^\n\w]*$/
|
14
|
+
PRODUCT_DIRECTIVE = /^[^\n\w]*(slinky_product)\((".*"|'.+'|)\)[^\n\w]*$/
|
12
15
|
BUILD_DIRECTIVES = Regexp.union(DEPENDS_DIRECTIVE,
|
13
16
|
REQUIRE_DIRECTIVE,
|
14
17
|
SCRIPTS_DIRECTIVE,
|
15
|
-
STYLES_DIRECTIVE
|
18
|
+
STYLES_DIRECTIVE,
|
19
|
+
PRODUCT_DIRECTIVE)
|
16
20
|
CSS_URL_MATCHER = /url\(['"]?([^'"\/][^\s)]+\.[a-z]+)(\?\d+)?['"]?\)/
|
17
21
|
|
18
|
-
# Raised when a compilation fails for any reason
|
19
|
-
class BuildFailedError < StandardError; end
|
20
|
-
# Raised when a required file is not found.
|
21
|
-
class FileNotFoundError < StandardError; end
|
22
|
-
# Raised when there is a cycle in the dependency graph (i.e., file A
|
23
|
-
# requires file B which requires C which requires A)
|
24
|
-
class DependencyError < StandardError; end
|
25
|
-
|
26
22
|
class Manifest
|
27
|
-
attr_accessor :manifest_dir, :dir
|
23
|
+
attr_accessor :manifest_dir, :dir, :config
|
28
24
|
|
29
25
|
def initialize dir, config, options = {}
|
30
26
|
@dir = dir
|
@@ -87,59 +83,170 @@ module Slinky
|
|
87
83
|
@manifest_dir.find_by_path path, allow_multiple
|
88
84
|
end
|
89
85
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
86
|
+
# Finds all files that match the given pattern. The match rules
|
87
|
+
# are similar to those for .gitignore and given by
|
88
|
+
#
|
89
|
+
# 1. If the pattern ends with a slash, it will only match directories;
|
90
|
+
# e.g. `foo/` would match a directory `foo/` but not a file `foo`. In
|
91
|
+
# a file context, matching a directory is equivalent to matching all
|
92
|
+
# files under that directory, recursively.
|
93
|
+
# 2. If the pattern does not contain a slash, slinky treats it as a
|
94
|
+
# relative pathname which can match files in any directory. For
|
95
|
+
# example, the rule `test.js` will matching `/test.js` and
|
96
|
+
# `/component/test.js`.
|
97
|
+
# 3. If the pattern begins with a slash, it will be treated as an
|
98
|
+
# absolute path starting at the root of the source directory.
|
99
|
+
# 4. If the pattern does not begin with a slash, but does contain one or
|
100
|
+
# more slashes, it will be treated as a path relative to any
|
101
|
+
# directory. For example, `test/*.js` will match `/test/main.js`, and
|
102
|
+
# /component/test/component.js`, but not `main.js`.
|
103
|
+
# 5. A single star `*` in a pattern will match any number of characters within a
|
104
|
+
# single path component. For example, `/test/*.js` will match
|
105
|
+
# `/test/main_test.js` but not `/test/component/test.js`.
|
106
|
+
# 6. A double star `**` will match any number of characters including
|
107
|
+
# path separators. For example `/scripts/**/main.js` will match any
|
108
|
+
# file named `main.js` under the `/scripts` directory, including
|
109
|
+
# `/scripts/main.js` and `/scripts/component/main.js`.
|
110
|
+
def find_by_pattern pattern
|
111
|
+
# The strategy here is to convert the pattern into an equivalent
|
112
|
+
# regex and run that against the pathnames of all the files in
|
113
|
+
# the manifest.
|
114
|
+
regex_str = Regexp.escape(pattern)
|
115
|
+
.gsub('\*\*/', ".*")
|
116
|
+
.gsub('\*\*', ".*")
|
117
|
+
.gsub('\*', "[^/]*")
|
118
|
+
|
119
|
+
if regex_str[0] != '/'
|
120
|
+
regex_str = '.*/' + regex_str
|
121
|
+
end
|
122
|
+
|
123
|
+
if regex_str[-1] == '/'
|
124
|
+
regex_str += '.*'
|
97
125
|
end
|
126
|
+
|
127
|
+
regex_str = "^#{regex_str}$"
|
128
|
+
|
129
|
+
regex = Regexp.new(regex_str)
|
130
|
+
|
131
|
+
files(false).reject{|f|
|
132
|
+
!regex.match('/' + f.relative_source_path.to_s) &&
|
133
|
+
!regex.match('/' + f.relative_output_path.to_s)
|
134
|
+
}
|
98
135
|
end
|
99
136
|
|
100
|
-
|
101
|
-
|
137
|
+
# Finds all the matching manifest files for a particular product.
|
138
|
+
# This does not take into account dependencies.
|
139
|
+
def files_for_product product
|
140
|
+
if !p = @config.produce[product]
|
141
|
+
SlinkyError.raise NoSuchProductError,
|
142
|
+
"Product '#{product}' has not been configured"
|
143
|
+
end
|
102
144
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
}.join("\n")
|
145
|
+
type = type_for_product product
|
146
|
+
if type != ".js" && type != ".css"
|
147
|
+
SlinkyError.raise InvalidConfigError, "Only .js and .css products are supported"
|
148
|
+
end
|
108
149
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
150
|
+
g = dependency_graph.transitive_closure
|
151
|
+
|
152
|
+
# Topological indices for each file
|
153
|
+
indices = {}
|
154
|
+
dependency_list.each_with_index{|f, i| indices[f] = i}
|
155
|
+
|
156
|
+
# Compute the set of excluded files
|
157
|
+
excludes = Set.new((p["exclude"] || []).map{|p|
|
158
|
+
find_by_pattern(p)
|
159
|
+
}.flatten.uniq)
|
160
|
+
|
161
|
+
SlinkyError.batch_errors do
|
162
|
+
# First find the list of files that have been explictly
|
163
|
+
# included/excluded
|
164
|
+
p["include"].map{|f|
|
165
|
+
mfs = find_by_pattern(f)
|
166
|
+
.map{|mf| [mf] + g[f]}
|
167
|
+
.flatten
|
168
|
+
.reject{|f| f.output_path.extname != type}
|
169
|
+
if mfs.empty?
|
170
|
+
SlinkyError.raise FileNotFoundError,
|
171
|
+
"No files matched by include #{f} in product #{product}"
|
114
172
|
end
|
173
|
+
mfs.flatten
|
174
|
+
}.flatten.reject{|f|
|
175
|
+
excludes.include?(f)
|
176
|
+
# Then add all the files these require
|
177
|
+
}.map{|f|
|
178
|
+
# Find all of the downstream files
|
179
|
+
# check that we're not excluding any required files
|
180
|
+
g[f].each{|rf|
|
181
|
+
if p["exclude"] && r = p["exclude"].find{|ex| rf.matches_path?(ex, true)}
|
182
|
+
SlinkyError.raise DependencyError,
|
183
|
+
"File #{f} requires #{rf} which is excluded by exclusion rule #{r}"
|
184
|
+
end
|
185
|
+
}
|
186
|
+
[f] + g[f]
|
187
|
+
}.flatten.uniq.sort_by{|f|
|
188
|
+
# Sort by topological order
|
189
|
+
indices[f]
|
115
190
|
}
|
116
|
-
scripts.collect{|s| FileUtils.rm(s.build_to)}
|
117
191
|
end
|
118
192
|
end
|
119
193
|
|
120
|
-
def
|
121
|
-
|
122
|
-
|
194
|
+
def files_for_all_products
|
195
|
+
return @files_for_all_products if @files_for_all_products
|
196
|
+
SlinkyError.batch_errors do
|
197
|
+
@files_for_all_products = @config.produce.keys.map{|product|
|
198
|
+
files_for_product(product)
|
199
|
+
}.flatten.uniq
|
200
|
+
end
|
123
201
|
end
|
124
202
|
|
125
|
-
def
|
126
|
-
compressor =
|
203
|
+
def compress_product product
|
204
|
+
compressor = compressor_for_product product
|
205
|
+
post_processor = post_processor_for_product product
|
127
206
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
207
|
+
s = files_for_product(product).map{|mf|
|
208
|
+
f = File.open(mf.build_to.to_s, 'rb'){|f| f.read}
|
209
|
+
post_processor ? (post_processor.call(mf, f)) : f
|
210
|
+
}.join("\n")
|
211
|
+
|
212
|
+
# Make the directory the product is in
|
213
|
+
FileUtils.mkdir_p("#{@build_to}/#{Pathname.new(product).dirname}")
|
214
|
+
File.open("#{@build_to}/#{product}", "w+"){|f|
|
215
|
+
unless @no_minify
|
216
|
+
f.write(compressor.compress(s))
|
217
|
+
else
|
218
|
+
f.write(s)
|
219
|
+
end
|
133
220
|
}
|
134
221
|
end
|
135
222
|
|
223
|
+
# These are special cases for simplicity and backwards
|
224
|
+
# compatability. If no products are defined, we have two default
|
225
|
+
# products, one which includes are .js files in the repo and one
|
226
|
+
# that includes all .css files. This method produces an HTML include
|
227
|
+
# string for all of the .js files.
|
228
|
+
def scripts_string
|
229
|
+
product_string ConfigReader::DEFAULT_SCRIPT_PRODUCT
|
230
|
+
end
|
231
|
+
|
232
|
+
# These are special cases for simplicity and backwards
|
233
|
+
# compatability. If no products are defined, we have two default
|
234
|
+
# products, one which includes are .js files in the repo and one
|
235
|
+
# that includes all .css files. This method produces an HTML include
|
236
|
+
# string for all of the .css files.
|
136
237
|
def styles_string
|
238
|
+
product_string ConfigReader::DEFAULT_STYLE_PRODUCT
|
239
|
+
end
|
240
|
+
|
241
|
+
# Produces a string of HTML that includes all of the files for the
|
242
|
+
# given product.
|
243
|
+
def product_string product
|
137
244
|
if @devel
|
138
|
-
|
139
|
-
|
245
|
+
files_for_product(product).map{|f|
|
246
|
+
html_for_path("/#{f.relative_output_path}")
|
140
247
|
}.join("\n")
|
141
248
|
else
|
142
|
-
|
249
|
+
html_for_path("#{product}?#{rand(999999999)}")
|
143
250
|
end
|
144
251
|
end
|
145
252
|
|
@@ -149,59 +256,32 @@ module Slinky
|
|
149
256
|
# (required, by), each of which describes an edge.
|
150
257
|
#
|
151
258
|
# @return [[ManifestFile, ManifestFile]] the graph
|
152
|
-
def
|
259
|
+
def dependency_graph
|
260
|
+
return @dependency_graph if @dependency_graph
|
261
|
+
|
153
262
|
graph = []
|
154
263
|
files(false).each{|mf|
|
155
|
-
mf.
|
156
|
-
|
157
|
-
|
158
|
-
required.each{|x|
|
159
|
-
graph << [x, mf]
|
160
|
-
}
|
161
|
-
else
|
162
|
-
error = "Could not find file #{rf} required by #{mf.source}"
|
163
|
-
$stderr.puts error.foreground(:red)
|
164
|
-
raise FileNotFoundError.new(error)
|
165
|
-
end
|
166
|
-
} if mf.directives[:slinky_require]
|
264
|
+
mf.dependencies.each{|d|
|
265
|
+
graph << [d, mf]
|
266
|
+
}
|
167
267
|
}
|
168
|
-
|
268
|
+
|
269
|
+
@dependency_graph = Graph.new(files(false), graph)
|
169
270
|
end
|
170
271
|
|
171
|
-
# Builds a list of files in topological order, so that when
|
172
|
-
# required in this order all dependencies are met. See
|
173
|
-
# http://en.wikipedia.org/wiki/Topological_sorting for more
|
174
|
-
# information.
|
175
272
|
def dependency_list
|
176
|
-
|
177
|
-
graph = @dependency_graph.clone
|
178
|
-
# will contain sorted elements
|
179
|
-
l = []
|
180
|
-
# start nodes, those with no incoming edges
|
181
|
-
s = files(false).reject{|mf| mf.directives[:slinky_require]}
|
182
|
-
while s.size > 0
|
183
|
-
n = s.delete s.first
|
184
|
-
l << n
|
185
|
-
files(false).each{|m|
|
186
|
-
e = graph.find{|e| e[0] == n && e[1] == m}
|
187
|
-
next unless e
|
188
|
-
graph.delete e
|
189
|
-
s << m unless graph.any?{|e| e[1] == m}
|
190
|
-
}
|
191
|
-
end
|
192
|
-
if graph != []
|
193
|
-
problems = graph.collect{|e| e.collect{|x| x.source}.join(" -> ")}
|
194
|
-
$stderr.puts "Dependencies #{problems.join(", ")} could not be satisfied".foreground(:red)
|
195
|
-
raise DependencyError
|
196
|
-
end
|
197
|
-
l
|
273
|
+
dependency_graph.dependency_list
|
198
274
|
end
|
199
275
|
|
200
276
|
def build
|
201
277
|
@manifest_dir.build
|
202
278
|
unless @devel
|
203
|
-
|
204
|
-
|
279
|
+
@config.produce.keys.each{|product|
|
280
|
+
compress_product(product)
|
281
|
+
}
|
282
|
+
|
283
|
+
# clean up the files that have been processed
|
284
|
+
files_for_all_products.each{|mf| FileUtils.rm(mf.build_to, :force => true)}
|
205
285
|
end
|
206
286
|
end
|
207
287
|
|
@@ -225,10 +305,46 @@ module Slinky
|
|
225
305
|
end
|
226
306
|
end
|
227
307
|
|
308
|
+
def compressor_for_product product
|
309
|
+
case type_for_product(product)
|
310
|
+
when ".js"
|
311
|
+
YUI::JavaScriptCompressor.new(:munge => false)
|
312
|
+
when ".css"
|
313
|
+
YUI::CssCompressor.new()
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def post_processor_for_product product
|
318
|
+
case type_for_product(product)
|
319
|
+
when ".css"
|
320
|
+
lambda{|s, css| css.gsub(CSS_URL_MATCHER){|url|
|
321
|
+
p = s.relative_output_path.dirname.to_s + "/#{$1}"
|
322
|
+
"url('/#{p}')"
|
323
|
+
}}
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
228
327
|
def invalidate_cache
|
229
328
|
@files = nil
|
230
329
|
@dependency_graph = nil
|
231
330
|
@md5 = nil
|
331
|
+
@files_for_all_products = nil
|
332
|
+
end
|
333
|
+
|
334
|
+
def html_for_path path
|
335
|
+
ext = path.split("?").first.split(".").last
|
336
|
+
case ext
|
337
|
+
when "css"
|
338
|
+
%Q|<link rel="stylesheet" href="#{path}" />|
|
339
|
+
when "js"
|
340
|
+
%Q|<script type="text/javascript" src="#{path}"></script>|
|
341
|
+
else
|
342
|
+
raise InvalidConfigError.new("Unsupported file extension #{ext}")
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def type_for_product product
|
347
|
+
"." + product.split(".")[-1]
|
232
348
|
end
|
233
349
|
|
234
350
|
def manifest_update paths
|
@@ -278,6 +394,11 @@ module Slinky
|
|
278
394
|
#
|
279
395
|
# @return [ManifestFile] the manifest file at that path if one exists
|
280
396
|
def find_by_path path, allow_multiple = false
|
397
|
+
if path[0] == '/'
|
398
|
+
# refer absolute paths to the manifest
|
399
|
+
return @manifest.find_by_path(path[1..-1], allow_multiple)
|
400
|
+
end
|
401
|
+
|
281
402
|
components = path.to_s.split(File::SEPARATOR).reject{|x| x == ""}
|
282
403
|
case components.size
|
283
404
|
when 0
|
@@ -351,9 +472,13 @@ module Slinky
|
|
351
472
|
unless File.directory?(@build_dir.to_s)
|
352
473
|
FileUtils.mkdir(@build_dir.to_s)
|
353
474
|
end
|
354
|
-
|
355
|
-
|
356
|
-
|
475
|
+
|
476
|
+
if (@files + @children).map {|m| m.build}.any?
|
477
|
+
@build_dir
|
478
|
+
else
|
479
|
+
FileUtils.rmdir(@build_dir.to_s)
|
480
|
+
nil
|
481
|
+
end
|
357
482
|
end
|
358
483
|
|
359
484
|
def to_s
|
@@ -383,6 +508,26 @@ module Slinky
|
|
383
508
|
@last_md5 = nil
|
384
509
|
end
|
385
510
|
|
511
|
+
# Gets the list of manifest files that this one depends on
|
512
|
+
# according to its directive list and the dependencies config
|
513
|
+
# option.
|
514
|
+
#
|
515
|
+
# Throws a FileNotFoundError if a dependency doesn't exist in the
|
516
|
+
# tree.
|
517
|
+
def dependencies
|
518
|
+
SlinkyError.batch_errors do
|
519
|
+
(@directives[:slinky_require].to_a +
|
520
|
+
@manifest.config.dependencies["/" + relative_source_path.to_s].to_a).map{|rf|
|
521
|
+
required = parent.find_by_path(rf, true).flatten
|
522
|
+
if required.empty?
|
523
|
+
error = "Could not find file #{rf} required by /#{relative_source_path}"
|
524
|
+
SlinkyError.raise FileNotFoundError, error
|
525
|
+
end
|
526
|
+
required
|
527
|
+
}.flatten
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
386
531
|
# Predicate which determines whether the supplied name is the same
|
387
532
|
# as the file's name, taking into account compiled file
|
388
533
|
# extensions. For example, if mf refers to "/tmp/test/hello.sass",
|
@@ -415,6 +560,16 @@ module Slinky
|
|
415
560
|
end
|
416
561
|
end
|
417
562
|
|
563
|
+
# Predicate which determines whether the file matches (see
|
564
|
+
# `ManifestFile#matches?`) the full path relative to the manifest
|
565
|
+
# root.
|
566
|
+
def matches_path? s, match_glob = false
|
567
|
+
p = Pathname.new(s)
|
568
|
+
dir = Pathname.new("/" + relative_source_path.to_s).dirname
|
569
|
+
matches?(p.basename.to_s, match_glob) &&
|
570
|
+
dir == p.dirname
|
571
|
+
end
|
572
|
+
|
418
573
|
# Predicate which determines whether the file is the supplied path
|
419
574
|
# or lies on supplied tree
|
420
575
|
def in_tree? path
|
@@ -440,12 +595,12 @@ module Slinky
|
|
440
595
|
|
441
596
|
# returns the source path relative to the manifest directory
|
442
597
|
def relative_source_path
|
443
|
-
Pathname.new(@source).relative_path_from
|
598
|
+
Pathname.new(@source).relative_path_from(Pathname.new(@manifest.dir))
|
444
599
|
end
|
445
600
|
|
446
601
|
# Returns the output path relative to the manifest directory
|
447
602
|
def relative_output_path
|
448
|
-
output_path.relative_path_from
|
603
|
+
output_path.relative_path_from(Pathname.new(@manifest.dir))
|
449
604
|
end
|
450
605
|
|
451
606
|
# Looks through the file for directives
|
@@ -482,8 +637,11 @@ module Slinky
|
|
482
637
|
out = File.read(path)
|
483
638
|
out.gsub!(DEPENDS_DIRECTIVE, "")
|
484
639
|
out.gsub!(REQUIRE_DIRECTIVE, "")
|
485
|
-
out.gsub!(SCRIPTS_DIRECTIVE
|
486
|
-
out.gsub!(STYLES_DIRECTIVE
|
640
|
+
out.gsub!(SCRIPTS_DIRECTIVE){ @manifest.scripts_string }
|
641
|
+
out.gsub!(STYLES_DIRECTIVE){ @manifest.styles_string }
|
642
|
+
out.gsub!(PRODUCT_DIRECTIVE){
|
643
|
+
@manifest.product_string($2[1..-2])
|
644
|
+
}
|
487
645
|
to = to || Tempfile.new("slinky").path + ".cache"
|
488
646
|
File.open(to, "w+"){|f|
|
489
647
|
f.write(out)
|
@@ -528,27 +686,32 @@ module Slinky
|
|
528
686
|
find_directives
|
529
687
|
end
|
530
688
|
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
@
|
546
|
-
|
547
|
-
|
548
|
-
@updated
|
549
|
-
|
550
|
-
|
551
|
-
|
689
|
+
SlinkyError.batch_errors do
|
690
|
+
depends = @directives[:slinky_depends].map{|f|
|
691
|
+
p = parent.find_by_path(f, true)
|
692
|
+
unless p.size > 0
|
693
|
+
SlinkyError.raise DependencyError,
|
694
|
+
"File #{f} dependedon by #{@source} not found"
|
695
|
+
end
|
696
|
+
p
|
697
|
+
}.flatten.compact if @directives[:slinky_depends]
|
698
|
+
depends ||= []
|
699
|
+
@processing = true
|
700
|
+
# process each file on which we're dependent, watching out for
|
701
|
+
# infinite loops
|
702
|
+
depends.each{|f| f.process }
|
703
|
+
@processing = false
|
704
|
+
|
705
|
+
# get hash of source file
|
706
|
+
if @last_path && hash == @last_md5 && depends.all?{|f| f.updated < start_time}
|
707
|
+
@last_path
|
708
|
+
else
|
709
|
+
@last_md5 = hash
|
710
|
+
@updated = Time.now
|
711
|
+
# mangle file appropriately
|
712
|
+
f = should_compile ? (compile @source) : @source
|
713
|
+
@last_path = handle_directives(f, to)
|
714
|
+
end
|
552
715
|
end
|
553
716
|
end
|
554
717
|
|
@@ -560,25 +723,29 @@ module Slinky
|
|
560
723
|
# Builds the file by handling and compiling it and then copying it
|
561
724
|
# to the build path
|
562
725
|
def build
|
726
|
+
return nil unless should_build
|
727
|
+
|
563
728
|
if !File.exists? @build_path
|
564
729
|
FileUtils.mkdir_p(@build_path)
|
565
730
|
end
|
566
731
|
to = build_to
|
567
|
-
|
568
|
-
path = process to
|
569
|
-
rescue
|
570
|
-
raise BuildFailedError
|
571
|
-
end
|
732
|
+
path = process to
|
572
733
|
|
573
|
-
if
|
574
|
-
raise BuildFailedError
|
575
|
-
elsif path != to
|
734
|
+
if path != to
|
576
735
|
FileUtils.cp(path.to_s, to.to_s)
|
577
736
|
@last_built = Time.now
|
578
737
|
end
|
579
738
|
to
|
580
739
|
end
|
581
740
|
|
741
|
+
def should_build
|
742
|
+
@manifest.files_for_all_products.include?(self) || ![".js", ".css"].include?(output_path.extname)
|
743
|
+
end
|
744
|
+
|
745
|
+
def inspect
|
746
|
+
to_s
|
747
|
+
end
|
748
|
+
|
582
749
|
def to_s
|
583
750
|
"<Slinky::ManifestFile '#{@source}'>"
|
584
751
|
end
|