ramaze-asset 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gems +6 -0
- data/.gitignore +14 -0
- data/.rvmrc +1 -0
- data/LICENSE +19 -0
- data/README.md +64 -0
- data/Rakefile +15 -0
- data/example.rb +54 -0
- data/lib/.gitkeep +0 -0
- data/lib/ramaze/.gitkeep +0 -0
- data/lib/ramaze/asset.rb +15 -0
- data/lib/ramaze/asset/css.rb +44 -0
- data/lib/ramaze/asset/environment.rb +482 -0
- data/lib/ramaze/asset/error.rb +5 -0
- data/lib/ramaze/asset/file_group.rb +385 -0
- data/lib/ramaze/asset/javascript.rb +41 -0
- data/lib/ramaze/asset/spec/bacon/color_output.rb +39 -0
- data/lib/ramaze/asset/spec/helper.rb +18 -0
- data/lib/ramaze/asset/version.rb +5 -0
- data/lib/vendor/cssmin.rb +110 -0
- data/lib/vendor/jsmin.rb +280 -0
- data/pkg/.gitkeep +0 -0
- data/ramaze-asset.gemspec +24 -0
- data/spec/.gitkeep +0 -0
- data/spec/fixtures/public/css/github.css +126 -0
- data/spec/fixtures/public/css/reset.css +123 -0
- data/spec/fixtures/public/js/mootools_core.js +5515 -0
- data/spec/fixtures/public/js/mootools_more.js +13455 -0
- data/spec/helper.rb +4 -0
- data/spec/ramaze_asset/css.rb +69 -0
- data/spec/ramaze_asset/environment.rb +148 -0
- data/spec/ramaze_asset/file_group.rb +118 -0
- data/spec/ramaze_asset/javascript.rb +69 -0
- data/task/build.rake +35 -0
- data/task/clean.rake +6 -0
- data/task/test.rake +8 -0
- metadata +148 -0
@@ -0,0 +1,385 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'ramaze/gestalt'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module Ramaze
|
7
|
+
module Asset
|
8
|
+
##
|
9
|
+
# Ramaze::Asset::FileGroup is used to group a set of files of the same type,
|
10
|
+
# such as Javascript files, together. The HTML for these files can be
|
11
|
+
# generated as well as a minified version of all the files.
|
12
|
+
#
|
13
|
+
# ## Creating File Groups
|
14
|
+
#
|
15
|
+
# Ramaze::Asset comes with two file groups that are capable of processing
|
16
|
+
# Javascript and CSS files. If you need to have a file group for
|
17
|
+
# Coffeescript, Less or other files is quite easy to add these yourself.
|
18
|
+
# First you must create a class that extends Ramaze::Asset::FileGroup:
|
19
|
+
#
|
20
|
+
# class Less < Ramaze::Asset::FileGroup
|
21
|
+
#
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# It's important that you define the correct file extensions for your file
|
25
|
+
# group. Without this Ramaze::Asset will not be able to find the files for
|
26
|
+
# you (unless they have an extension specified) and the minified file will
|
27
|
+
# end up not having an extension. Setting an extension can be done by
|
28
|
+
# calling the class method ``extension()``. This method has two parameters,
|
29
|
+
# the first one is the extension of the source file, the second the
|
30
|
+
# extension for the minified file. In case of Less this would result in the
|
31
|
+
# following:
|
32
|
+
#
|
33
|
+
# class Less < Ramaze::Asset::FileGroup
|
34
|
+
# extension '.less', '.css'
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# The next step is to define your own minify() and html_tag() methods. If
|
38
|
+
# you don't define these methods they'll raise an error and most likely
|
39
|
+
# break things.
|
40
|
+
#
|
41
|
+
# class Less < Ramaze::Asset::FileGroup
|
42
|
+
# extension '.less', '.css'
|
43
|
+
#
|
44
|
+
# def minify(input)
|
45
|
+
#
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# def html_tag(gestalt, path)
|
49
|
+
#
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# The minify() method should return a string containing the minified data.
|
54
|
+
# The html_tag() method uses an instance of Ramaze::Gestalt to build a
|
55
|
+
# single tag for a given (relative) path.
|
56
|
+
#
|
57
|
+
# A full example of Less looks like the following:
|
58
|
+
#
|
59
|
+
# require 'tempfile'
|
60
|
+
#
|
61
|
+
# class Less < Ramaze::Asset::FileGroup
|
62
|
+
# extension '.less', '.css'
|
63
|
+
#
|
64
|
+
# # +input+ contains the raw Less data. The command line tool only
|
65
|
+
# # accepts files so this data has to be written to a temp file.
|
66
|
+
# def minify(input)
|
67
|
+
# file = Tempfile.new('less')
|
68
|
+
# file.write(input)
|
69
|
+
# file.rewind
|
70
|
+
#
|
71
|
+
# minified = `lessc #{file.path} -x`
|
72
|
+
#
|
73
|
+
# file.close(true)
|
74
|
+
#
|
75
|
+
# return minified
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# def html_tag(gestalt, path)
|
79
|
+
# gestalt.link(
|
80
|
+
# :rel => 'stylesheet',
|
81
|
+
# :href => path,
|
82
|
+
# :type => 'text/css'
|
83
|
+
# )
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# Note that it's important to remember that when dealing with files that
|
88
|
+
# have to be compiled, such as Less and Coffeescript files, setting :minify
|
89
|
+
# to false will not work. Without setting this option to true the minify()
|
90
|
+
# method will never be called and thus the raw Less/Coffeescript file would
|
91
|
+
# be served.
|
92
|
+
#
|
93
|
+
# @author Yorick Peterse
|
94
|
+
# @since 0.1
|
95
|
+
#
|
96
|
+
class FileGroup
|
97
|
+
# Array containing all the files that belong to this group, including
|
98
|
+
# their files extensions.
|
99
|
+
attr_accessor :files
|
100
|
+
|
101
|
+
# Hash containing all the options for the file group.
|
102
|
+
attr_reader :options
|
103
|
+
|
104
|
+
##
|
105
|
+
# Sets the file extensions for the current class. These extensions should
|
106
|
+
# start with a dot. If the minified extension is not specified it will be
|
107
|
+
# generated by adding a ".min" suffix followed by the source extension. If
|
108
|
+
# the source extension is ".css" the minified extension would in this case
|
109
|
+
# be ".min.css".
|
110
|
+
#
|
111
|
+
# @author Yorick Peterse
|
112
|
+
# @since 0.1
|
113
|
+
# @param [#to_s] source_ext The extension of the source file such as
|
114
|
+
# ".css" or ".js".
|
115
|
+
# @param [#to_s] minified_ext The extension to use for the minified file.
|
116
|
+
# Useful when the resulting extension is different than the source
|
117
|
+
# extension (such as with Less or Coffeescript).
|
118
|
+
#
|
119
|
+
def self.extension(source_ext, minified_ext = nil)
|
120
|
+
if minified_ext.nil?
|
121
|
+
minified_ext = '.min' + source_ext
|
122
|
+
end
|
123
|
+
|
124
|
+
if source_ext[0] != '.' or minified_ext[0] != '.'
|
125
|
+
raise(
|
126
|
+
Ramaze::Asset::AssetError,
|
127
|
+
'Extensions should start with a dot'
|
128
|
+
)
|
129
|
+
end
|
130
|
+
|
131
|
+
self.instance_variable_set(
|
132
|
+
:@extension,
|
133
|
+
{:source => source_ext, :minified => minified_ext}
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
##
|
138
|
+
# Creates a new instance of the file group and prepares it.
|
139
|
+
#
|
140
|
+
# @author Yorick Peterse
|
141
|
+
# @since 0.1
|
142
|
+
# @param [Array] files An array of files for this group.
|
143
|
+
# @param [Hash] options A hash containing various options to customize
|
144
|
+
# this file group.
|
145
|
+
# @option options :minify When set to true all the files in the group will
|
146
|
+
# be minified.
|
147
|
+
# @option options :name A name to use for the minified file. By default
|
148
|
+
# this is set to a hash of all the file names.
|
149
|
+
# @option options :paths An array of file paths to look for the files.
|
150
|
+
# @option options :cache_path The path to a directory where the minified
|
151
|
+
# files should be saved.
|
152
|
+
#
|
153
|
+
def initialize(files, options = {})
|
154
|
+
@minified = false
|
155
|
+
@files = files
|
156
|
+
@options = {
|
157
|
+
:minify => false,
|
158
|
+
:name => nil,
|
159
|
+
:paths => [],
|
160
|
+
:cache_path => []
|
161
|
+
}.merge(options)
|
162
|
+
|
163
|
+
if @options[:paths].empty?
|
164
|
+
raise(
|
165
|
+
Ramaze::Asset::AssetError,
|
166
|
+
'No public directories were specified'
|
167
|
+
)
|
168
|
+
end
|
169
|
+
|
170
|
+
if !File.directory?(@options[:cache_path])
|
171
|
+
raise(
|
172
|
+
Ramaze::Asset::AssetError,
|
173
|
+
"The directory #{@options[:cache_path]} does not exist"
|
174
|
+
)
|
175
|
+
end
|
176
|
+
|
177
|
+
if extension.nil?
|
178
|
+
raise(
|
179
|
+
Ramaze::Asset::AssetError,
|
180
|
+
'You need to specify an extension'
|
181
|
+
)
|
182
|
+
end
|
183
|
+
|
184
|
+
prepare_files
|
185
|
+
|
186
|
+
# When :minify is set :name should also be set.
|
187
|
+
if @options[:minify] === true and @options[:name].nil?
|
188
|
+
@options[:name] = @files.map { |file| file }.join()
|
189
|
+
@options[:name] = Digest::SHA1.new.hexdigest(@options[:name])
|
190
|
+
end
|
191
|
+
|
192
|
+
if !@options[:name].nil?
|
193
|
+
@options[:name] += extension[:minified]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
##
|
198
|
+
# Returns the extension of the current file group.
|
199
|
+
#
|
200
|
+
# @author Yorick Peterse
|
201
|
+
# @since 0.1.
|
202
|
+
# @return [String]
|
203
|
+
#
|
204
|
+
def extension
|
205
|
+
return self.class.instance_variable_get(:@extension)
|
206
|
+
end
|
207
|
+
|
208
|
+
##
|
209
|
+
# When the :minify option is set to true this method will merge all files,
|
210
|
+
# minify them and cache them in the :cache_path directory.
|
211
|
+
#
|
212
|
+
# @author Yorick Peterse
|
213
|
+
# @since 0.1
|
214
|
+
#
|
215
|
+
def build
|
216
|
+
return if @options[:minify] != true
|
217
|
+
|
218
|
+
cache_path = File.join(
|
219
|
+
@options[:cache_path],
|
220
|
+
@options[:name]
|
221
|
+
)
|
222
|
+
|
223
|
+
# Minify the file in a sub process so that memory leaks (or just general
|
224
|
+
# increases of memory usage) don't affect the master process.
|
225
|
+
pid = Process.fork do
|
226
|
+
processed = []
|
227
|
+
file_paths = []
|
228
|
+
minified = ''
|
229
|
+
write = true
|
230
|
+
|
231
|
+
# Try to find the paths to the files.
|
232
|
+
@options[:paths].each do |directory|
|
233
|
+
@files.each do |file|
|
234
|
+
path = File.join(directory, file)
|
235
|
+
|
236
|
+
# Only add the file to the list if it hasn't already been added.
|
237
|
+
if File.exist?(path) and !processed.include?(file)
|
238
|
+
file_paths.push(path)
|
239
|
+
processed.push(file)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
file_paths.each do |file|
|
245
|
+
minified += minify(File.read(file, File.size(file)))
|
246
|
+
end
|
247
|
+
|
248
|
+
# Check if the file already exists. If this is the cache a hash of
|
249
|
+
# both files is generated and compared. If it's different the file has
|
250
|
+
# to be re-created.
|
251
|
+
if File.exist?(cache_path)
|
252
|
+
old_hash = Digest::SHA1.new.hexdigest(minified)
|
253
|
+
new_hash = Digest::SHA1.new.hexdigest(
|
254
|
+
File.read(cache_path, File.size(cache_path))
|
255
|
+
)
|
256
|
+
|
257
|
+
if old_hash === new_hash
|
258
|
+
write = false
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
if write === true
|
263
|
+
File.open(cache_path, 'w') do |handle|
|
264
|
+
handle.write(minified)
|
265
|
+
handle.close
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Don't call any at_exit() hooks, they're not needed in this process.
|
270
|
+
Kernel.exit!
|
271
|
+
end
|
272
|
+
|
273
|
+
Process.waitpid(pid)
|
274
|
+
|
275
|
+
# Make sure the cache file is present
|
276
|
+
if !File.size?(cache_path)
|
277
|
+
raise(
|
278
|
+
Ramaze::Asset::AssetError,
|
279
|
+
"The cache file #{cache_path} could not be created"
|
280
|
+
)
|
281
|
+
end
|
282
|
+
|
283
|
+
@minified = true
|
284
|
+
end
|
285
|
+
|
286
|
+
##
|
287
|
+
# Builds the HTML tags for all the current files.
|
288
|
+
#
|
289
|
+
# @author Yorick Peterse
|
290
|
+
# @since 0.1
|
291
|
+
# @return [String]
|
292
|
+
#
|
293
|
+
def build_html
|
294
|
+
prefix = '/'
|
295
|
+
|
296
|
+
if @options[:minify] === true and @minified === true
|
297
|
+
# Get the relative path to the cache directory from one of the public
|
298
|
+
# directories.
|
299
|
+
@options[:paths].each do |path|
|
300
|
+
path = @options[:cache_path].gsub(path, '')
|
301
|
+
|
302
|
+
if path != @options[:cache_path] and !path.empty?
|
303
|
+
prefix += path
|
304
|
+
break
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
files = [('/' + @options[:name]).squeeze('/')]
|
309
|
+
else
|
310
|
+
files = @files
|
311
|
+
end
|
312
|
+
|
313
|
+
# Let's make sure the URLs are actually pointing to the cached
|
314
|
+
# directory.
|
315
|
+
files.each_with_index do |file, index|
|
316
|
+
files[index] = "#{prefix}/#{file}".squeeze('/')
|
317
|
+
end
|
318
|
+
|
319
|
+
g = Ramaze::Gestalt.new
|
320
|
+
|
321
|
+
files.each { |file| html_tag(g, file) }
|
322
|
+
|
323
|
+
return g.to_s
|
324
|
+
end
|
325
|
+
|
326
|
+
##
|
327
|
+
# Minifies a single file.
|
328
|
+
#
|
329
|
+
# @author Yorick Peterse
|
330
|
+
# @since 0.1
|
331
|
+
# @param [String] input The string to minify.
|
332
|
+
# @raise NotImplementedError Raised when the sub class didn't implement
|
333
|
+
# this method.
|
334
|
+
#
|
335
|
+
def minify(input)
|
336
|
+
raise(
|
337
|
+
NotImplementedError,
|
338
|
+
'You need to define your own minify() instance method'
|
339
|
+
)
|
340
|
+
end
|
341
|
+
|
342
|
+
##
|
343
|
+
# Builds the HTML tag for a single file using Ramaze::Gestalt.
|
344
|
+
#
|
345
|
+
# @author Yorick Peterse
|
346
|
+
# @since 0.1
|
347
|
+
# @param [Ramaze::Gestalt] gestalt An instance of Ramaze::Gestalt that's
|
348
|
+
# used to build all the tags.
|
349
|
+
# @param [String] path The relative path to the file.
|
350
|
+
# @raise NotImplementedError Raised when the sub class didn't implement
|
351
|
+
# this method.
|
352
|
+
#
|
353
|
+
def html_tag(gestalt, path)
|
354
|
+
raise(
|
355
|
+
NotImplementedError,
|
356
|
+
'You need to define your own build_html instance method'
|
357
|
+
)
|
358
|
+
end
|
359
|
+
|
360
|
+
private
|
361
|
+
|
362
|
+
##
|
363
|
+
# Loops through all the files and adds the required extensions to them and
|
364
|
+
# makes sure they're relative to the root rather than the current working
|
365
|
+
# directory.
|
366
|
+
#
|
367
|
+
# @author Yorick Peterse
|
368
|
+
# @since 0.1
|
369
|
+
#
|
370
|
+
def prepare_files
|
371
|
+
@files.each_with_index do |file, index|
|
372
|
+
file += extension[:source] if File.extname(file) != extension[:source]
|
373
|
+
|
374
|
+
if file[0] != '/'
|
375
|
+
file = '/' + file
|
376
|
+
end
|
377
|
+
|
378
|
+
file = file.squeeze('/')
|
379
|
+
|
380
|
+
@files[index] = file
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end # FileGroup
|
384
|
+
end # Asset
|
385
|
+
end # Ramaze
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require __DIR__('../../vendor/jsmin')
|
2
|
+
|
3
|
+
module Ramaze
|
4
|
+
module Asset
|
5
|
+
##
|
6
|
+
# File group for Javascript files. These Javascript files are minified using
|
7
|
+
# JSMin.
|
8
|
+
#
|
9
|
+
# @author Yorick Peterse
|
10
|
+
# @since 0.1
|
11
|
+
#
|
12
|
+
class Javascript < Ramaze::Asset::FileGroup
|
13
|
+
extension '.js'
|
14
|
+
|
15
|
+
##
|
16
|
+
# Minifies the output and returns the result as a string.
|
17
|
+
#
|
18
|
+
# @author Yorick Peterse
|
19
|
+
# @since 0.1
|
20
|
+
# @param [String] input The input to minify.
|
21
|
+
# @return [String]
|
22
|
+
#
|
23
|
+
def minify(input)
|
24
|
+
return JSMin.minify(input)
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Builds a ``<script>`` tag for a single Javascript file.
|
29
|
+
#
|
30
|
+
# @author Yorick Peterse
|
31
|
+
# @since 0.1
|
32
|
+
# @param [Ramaze::Gestalt] gestalt An instance of Ramaze::Gestalt used to
|
33
|
+
# build the tags.
|
34
|
+
# @param [String] path The relative path to the file for the tag.
|
35
|
+
#
|
36
|
+
def html_tag(gestalt, path)
|
37
|
+
gestalt.script(:src => path, :type => 'text/javascript') {}
|
38
|
+
end
|
39
|
+
end # Javascript
|
40
|
+
end # Asset
|
41
|
+
end # Ramaze
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#:nodoc:
|
2
|
+
module Bacon
|
3
|
+
#:nodoc:
|
4
|
+
module ColorOutput
|
5
|
+
#:nodoc:
|
6
|
+
def handle_specification(name)
|
7
|
+
puts spaces + name
|
8
|
+
yield
|
9
|
+
puts if Counter[:context_depth] == 1
|
10
|
+
end
|
11
|
+
|
12
|
+
#:nodoc:
|
13
|
+
def handle_requirement(description)
|
14
|
+
error = yield
|
15
|
+
|
16
|
+
if !error.empty?
|
17
|
+
puts "#{spaces} \e[31m- #{description} [FAILED]\e[0m"
|
18
|
+
else
|
19
|
+
puts "#{spaces} \e[32m- #{description}\e[0m"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
#:nodoc:
|
24
|
+
def handle_summary
|
25
|
+
print ErrorLog if Backtraces
|
26
|
+
puts "%d specifications (%d requirements), %d failures, %d errors" %
|
27
|
+
Counter.values_at(:specifications, :requirements, :failed, :errors)
|
28
|
+
end
|
29
|
+
|
30
|
+
#:nodoc:
|
31
|
+
def spaces
|
32
|
+
if Counter[:context_depth] === 0
|
33
|
+
Counter[:context_depth] = 1
|
34
|
+
end
|
35
|
+
|
36
|
+
return ' ' * (Counter[:context_depth] - 1)
|
37
|
+
end
|
38
|
+
end # ColorOutput
|
39
|
+
end # Bacon
|