front-end-blender 0.8.3 → 0.14
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +81 -0
- data/bin/blend +23 -10
- data/lib/front_end_architect/blender.rb +379 -0
- data/lib/front_end_architect/hash.rb +26 -0
- data/lib/yui/LICENSE +30 -0
- data/lib/yui/yuicompressor.jar +0 -0
- metadata +9 -6
- data/README +0 -65
data/README.rdoc
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
= Front-End Blender
|
2
|
+
|
3
|
+
== What is Blender?
|
4
|
+
|
5
|
+
Blender is like ant or make for the front-end. It aggregates and compresses
|
6
|
+
CSS and/or JavaScript assets for a site into efficient, production-ready files.
|
7
|
+
|
8
|
+
== The Blendfile
|
9
|
+
|
10
|
+
The Blendfile, named Blendfile.yaml by default, is the configuration file
|
11
|
+
that tells Blender which source files are combined into which output files.
|
12
|
+
The file uses the YAML format. The output file is listed as hash key and
|
13
|
+
source files are the hash values as an array. Here is a sample Blendfile:
|
14
|
+
|
15
|
+
# Blendfile.yaml for www.boldpx.com
|
16
|
+
_behavior:
|
17
|
+
_global-min.js:
|
18
|
+
- ../_vendor/jquery/jquery.js
|
19
|
+
- ../_vendor/shadowbox/src/js/adapter/shadowbox-jquery.js
|
20
|
+
- ../_vendor/shadowbox/src/js/shadowbox.js
|
21
|
+
- _global.js
|
22
|
+
- _analytics.js
|
23
|
+
- ../vendor/google-analytics/ga.js
|
24
|
+
_style:
|
25
|
+
_global:
|
26
|
+
min.css:
|
27
|
+
- ../../_vendor/shadowbox/src/css/shadowbox.css
|
28
|
+
- typography.css
|
29
|
+
- typography-print.css
|
30
|
+
- colors.css
|
31
|
+
- colors-print.css
|
32
|
+
- layout-screen.css
|
33
|
+
- layout-print.css
|
34
|
+
|
35
|
+
== Usage
|
36
|
+
|
37
|
+
Usage: blend [options]
|
38
|
+
-h, --help Show this message
|
39
|
+
-V, --version Prints Blender's version number
|
40
|
+
-g, --generate Generate a stub Blendfile
|
41
|
+
-f, --file FILE Use given Blendfile
|
42
|
+
-r, --root ROOT Specify the path to the web root directory
|
43
|
+
-t, --type TYPE Select file type to blend (css, js)
|
44
|
+
-c, --cache-buster [BUSTER] Append cache busters to URLs in CSS, defaults to timestamps
|
45
|
+
--force Force blending when source files aren't newer than output files
|
46
|
+
--yui=YUIOPTS Pass arguments to YUI Compressor
|
47
|
+
-d, --data EXPERIMENTAL Convert url(file.ext) to url(data:) in CSS files
|
48
|
+
-z, --gzip EXPERIMENTAL Also create gzip output files
|
49
|
+
|
50
|
+
== Examples
|
51
|
+
|
52
|
+
In your site directory run 'blend' to minify CSS and JavaScript.
|
53
|
+
blend
|
54
|
+
|
55
|
+
Other examples:
|
56
|
+
blend --generate
|
57
|
+
blend --yui='--preserve-semi'
|
58
|
+
blend -t css
|
59
|
+
blend -t css -d
|
60
|
+
blend -f public/Blendfile.yaml
|
61
|
+
|
62
|
+
== Installation
|
63
|
+
|
64
|
+
To install the RubyGem, run the following at the command line:
|
65
|
+
sudo gem sources --add http://gems.github.com
|
66
|
+
sudo gem install front-end-blender
|
67
|
+
|
68
|
+
* Java, v1.4 or greater is required
|
69
|
+
* RubyGems, v1.2 or greater is recommended
|
70
|
+
|
71
|
+
Ruby installation instructions for your platform can be found here: http://www.ruby-lang.org/en/downloads/
|
72
|
+
Instructions for downloading & installing RubyGems can be found here: http://rubygems.org/read/chapter/3
|
73
|
+
Download and install Java here: http://java.com/en/
|
74
|
+
|
75
|
+
== License
|
76
|
+
|
77
|
+
Copyright (c) 2008 Chris Griego
|
78
|
+
(c) 2008 Blake Elshire
|
79
|
+
|
80
|
+
Blender is freely distributable under the terms of an MIT-style license.
|
81
|
+
For details, see http://www.opensource.org/licenses/mit-license.php
|
data/bin/blend
CHANGED
@@ -13,17 +13,30 @@ require 'front_end_architect/blender'
|
|
13
13
|
options = {}
|
14
14
|
|
15
15
|
opts = OptionParser.new do |opts|
|
16
|
-
opts.on('-h',
|
17
|
-
opts.on('-V',
|
18
|
-
opts.on('-
|
19
|
-
opts.on('-
|
20
|
-
opts.on('-
|
21
|
-
opts.on(
|
22
|
-
opts.on(
|
23
|
-
opts.on('
|
16
|
+
opts.on('-h', '--help', "Show this message") { puts opts; exit 0 }
|
17
|
+
opts.on('-V', '--version', "Show the version number") { puts "Front-End Blender v#{Blender::VERSION}"; exit 0 }
|
18
|
+
opts.on('-g', '--generate', String, "Generate a stub Blendfile") { options[:generate] = true }
|
19
|
+
opts.on('-f FILE', '--file FILE', String, "Use given Blendfile") {|f| options[:blendfile] = f }
|
20
|
+
opts.on('-r ROOT', '--root ROOT', String, "Set the path to the web root directory") {|r| options[:root] = r }
|
21
|
+
opts.on('-t TYPE', '--type TYPE', [:css, :js], "Select file type to blend (css, js)") {|t| options[:file_type] = t }
|
22
|
+
opts.on('-c [BUSTER]', '--cache-buster [BUSTER]', String, "Append cache busters to URLs in CSS, defaults to timestamps") {|b| options[:cache_buster] = b.nil? ? :mtime : b }
|
23
|
+
opts.on( '--force', String, "Force blending when source files aren't newer than output files") { options[:force] = true }
|
24
|
+
opts.on( '--yui=YUIOPTS', String, "Pass arguments to YUI Compressor") {|o| options[:yuiopts] = o }
|
25
|
+
opts.on('-d', '--data', String, "EXPERIMENTAL Convert url(file.ext) to url(data:) in CSS files") { options[:data] = true }
|
26
|
+
opts.on('-z', '--gzip', String, "EXPERIMENTAL Also create gzip output files") { options[:gzip] = true }
|
24
27
|
|
25
28
|
opts.parse!(ARGV) rescue return false
|
26
29
|
end
|
27
30
|
|
28
|
-
|
29
|
-
blender.
|
31
|
+
begin
|
32
|
+
blender = FrontEndArchitect::Blender.new(options)
|
33
|
+
|
34
|
+
if (options[:generate])
|
35
|
+
blender.generate
|
36
|
+
else
|
37
|
+
blender.blend
|
38
|
+
end
|
39
|
+
rescue Exception => e
|
40
|
+
puts e
|
41
|
+
exit 1
|
42
|
+
end
|
@@ -0,0 +1,379 @@
|
|
1
|
+
# Copyright (c) 2008 Chris Griego
|
2
|
+
# (c) 2008 Blake Elshire
|
3
|
+
#
|
4
|
+
# Blender is freely distributable under the terms of an MIT-style license.
|
5
|
+
# For details, see http://www.opensource.org/licenses/mit-license.php
|
6
|
+
|
7
|
+
# TODO Nearly all file name comparisons should be case-insensitive
|
8
|
+
|
9
|
+
$:.unshift File.join(File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__), *%w[..])
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'yaml'
|
13
|
+
require 'base64'
|
14
|
+
require 'benchmark'
|
15
|
+
require 'mime/types'
|
16
|
+
require 'find'
|
17
|
+
require 'pathname'
|
18
|
+
require 'zlib'
|
19
|
+
require 'front_end_architect/hash'
|
20
|
+
|
21
|
+
module FrontEndArchitect
|
22
|
+
class Blender
|
23
|
+
VERSION = '0.14'
|
24
|
+
|
25
|
+
FILTER_REGEX = /filter: ?[^?]+\(src=(['"])([^\?'"]+)(\?(?:[^'"]+)?)?\1,[^?]+\1\);/im
|
26
|
+
IMPORT_REGEX = /@import(?: url\(| )(['"]?)([^\?'"\)\s]+)(\?(?:[^'"\)]+)?)?\1\)?(?:[^?;]+)?;/im
|
27
|
+
URL_REGEX = /url\((['"]?)([^\?'"\)]+)(\?(?:[^'"\)]+)?)?\1?\)/im
|
28
|
+
|
29
|
+
DEFAULT_OPTIONS = {
|
30
|
+
:blendfile => 'Blendfile.yaml',
|
31
|
+
:data => false,
|
32
|
+
:force => false,
|
33
|
+
:root => File.dirname(:blendfile.to_s),
|
34
|
+
}
|
35
|
+
|
36
|
+
def initialize(opts)
|
37
|
+
@options = DEFAULT_OPTIONS.merge(opts)
|
38
|
+
end
|
39
|
+
|
40
|
+
def blend
|
41
|
+
elapsed = Benchmark.realtime do
|
42
|
+
unless File.exists? @options[:blendfile]
|
43
|
+
raise "Couldn't find '#{@options[:blendfile]}'"
|
44
|
+
end
|
45
|
+
|
46
|
+
blender = YAML::load_file @options[:blendfile]
|
47
|
+
|
48
|
+
Dir.chdir(File.dirname(@options[:blendfile]))
|
49
|
+
|
50
|
+
blender = flatten_blendfile(blender)
|
51
|
+
|
52
|
+
blender.each do |output_name, sources|
|
53
|
+
output_name = Pathname.new(output_name).cleanpath.to_s
|
54
|
+
|
55
|
+
output_new = false
|
56
|
+
gzip_output_name = output_name + '.gz'
|
57
|
+
|
58
|
+
# Checks the type flag and if the current file meets the type requirements continues
|
59
|
+
if output_name.match '.' + @options[:file_type].to_s
|
60
|
+
file_type = output_name.match(/\.css/) ? 'css' : 'js'
|
61
|
+
|
62
|
+
# Checks if output file exists and checks the mtimes of the source files to the output file if new creates a new file
|
63
|
+
if File.exists?(output_name) && (!@options[:gzip] || File.exists?(gzip_output_name))
|
64
|
+
output_files = []
|
65
|
+
output_files << File.mtime(output_name)
|
66
|
+
output_files << File.mtime(gzip_output_name) if @options[:gzip] && File.exists?(gzip_output_name)
|
67
|
+
|
68
|
+
oldest_output = output_files.sort.first
|
69
|
+
|
70
|
+
sources.each do |i|
|
71
|
+
if File.mtime(i) > oldest_output
|
72
|
+
output_new = true
|
73
|
+
break
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if output_new || @options[:force]
|
78
|
+
create_output(output_name, sources, file_type)
|
79
|
+
else
|
80
|
+
puts "Skipping: #{output_name}"
|
81
|
+
puts "Skipping: #{gzip_output_name}" if @options[:gzip]
|
82
|
+
end
|
83
|
+
else
|
84
|
+
create_output(output_name, sources, file_type)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
puts sprintf('%.5f', elapsed) + ' seconds'
|
91
|
+
end
|
92
|
+
|
93
|
+
def generate
|
94
|
+
if File.exists?(@options[:blendfile]) && !@options[:force]
|
95
|
+
raise "'#{@options[:blendfile]}' already exists"
|
96
|
+
end
|
97
|
+
|
98
|
+
blend_files = Hash.new
|
99
|
+
|
100
|
+
Find.find(Dir.getwd) do |f|
|
101
|
+
basename = File.basename(f)
|
102
|
+
|
103
|
+
if FileTest.directory?(f) && (basename[0] == ?. || basename.match(/^(yui|tinymce|dojo|wp-includes|wp-admin|mint)$/) || (File.basename(f) == 'rails' && File.basename(File.dirname(f)) == 'vendor'))
|
104
|
+
Find.prune
|
105
|
+
elsif !(basename.match(/[-.](pack|min)\.(css|js)$/) || basename.match(/^(sifr\.js|ext\.js|mootools.*\.js)$/))
|
106
|
+
# TODO Test for 'pack.js' and 'min.css' where the folder name serves as the identifier
|
107
|
+
# TODO Check file contents instead of name for minification (port YSlow's isMinified)
|
108
|
+
f.gsub!(Dir.getwd.to_s + '/', '')
|
109
|
+
|
110
|
+
if File.extname(f) == '.css' || File.extname(f) == '.js'
|
111
|
+
min_file = basename.sub(/\.(css|js)$/, '-min.\1')
|
112
|
+
path = File.dirname(f).split('/') # File#dirname depends on /
|
113
|
+
|
114
|
+
path.push min_file
|
115
|
+
path.push [basename]
|
116
|
+
|
117
|
+
h = path.reverse.inject { |m,v| { v => m } }
|
118
|
+
|
119
|
+
blend_files.deep_merge!(h).inspect
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
File.open(@options[:blendfile], 'w') do |blendfile|
|
125
|
+
blendfile << blend_files.to_yaml
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
protected
|
130
|
+
|
131
|
+
def flatten_blendfile(value, key=nil, context=[])
|
132
|
+
if value.is_a? Hash
|
133
|
+
context << key unless key.nil?
|
134
|
+
|
135
|
+
new_hash = {}
|
136
|
+
|
137
|
+
value.each do |k, v|
|
138
|
+
new_hash.merge! flatten_blendfile(v, k, context.dup)
|
139
|
+
end
|
140
|
+
|
141
|
+
new_hash
|
142
|
+
else
|
143
|
+
prefix = context.join(File::SEPARATOR)
|
144
|
+
prefix += File::SEPARATOR unless context.empty?
|
145
|
+
|
146
|
+
value.each_index do |i|
|
147
|
+
value[i] = prefix + value[i]
|
148
|
+
end
|
149
|
+
|
150
|
+
return { (prefix + key) => value }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def create_output(output_name, sources, type)
|
155
|
+
output = ''
|
156
|
+
|
157
|
+
File.open(output_name, 'w') do |output_file|
|
158
|
+
# Determine full path of the output file
|
159
|
+
output_path = Pathname.new(File.expand_path(File.dirname(output_name)))
|
160
|
+
imports = ''
|
161
|
+
|
162
|
+
sources.each do |i|
|
163
|
+
if File.extname(i) == '.css'
|
164
|
+
processed_output, processed_imports = process_css(i, output_path)
|
165
|
+
|
166
|
+
output << processed_output
|
167
|
+
imports << processed_imports
|
168
|
+
else
|
169
|
+
output << IO.read(i)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
if File.extname(output_name) == '.css' && !imports.empty?
|
174
|
+
output.insert(0, imports)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Compress
|
178
|
+
libdir = File.join(File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__), *%w[.. .. lib])
|
179
|
+
|
180
|
+
IO.popen("java -jar #{libdir}/yui/yuicompressor.jar #{@options[:yuiopts]} --type #{type}", 'r+') do |io|
|
181
|
+
io.write output
|
182
|
+
io.close_write
|
183
|
+
|
184
|
+
output = io.read
|
185
|
+
|
186
|
+
if File.extname(output_name) == '.css'
|
187
|
+
output.gsub! ' and(', ' and (' # Workaround for YUI Compressor Bug #1938329
|
188
|
+
output.gsub! '*/;}', '*/}' # Workaround for YUI Compressor Bug #1961175
|
189
|
+
|
190
|
+
if @options[:data]
|
191
|
+
output = output.gsub(URL_REGEX) do
|
192
|
+
unless $2.include?('.css')
|
193
|
+
mime_type = MIME::Types.type_for($2)
|
194
|
+
url_contents = make_data_uri(IO.read($2), mime_type[0])
|
195
|
+
else
|
196
|
+
url_contents = $2
|
197
|
+
end
|
198
|
+
%Q!url(#{url_contents})!
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
output_file << output
|
204
|
+
end
|
205
|
+
|
206
|
+
if $? == 32512 # command not found
|
207
|
+
raise "\nBlender requires Java, v1.4 or greater, to be installed for YUI Compressor"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
puts output_name
|
212
|
+
|
213
|
+
if @options[:gzip]
|
214
|
+
output_gzip = output_name + '.gz'
|
215
|
+
|
216
|
+
Zlib::GzipWriter.open(output_gzip) do |gz|
|
217
|
+
gz.write(output)
|
218
|
+
end
|
219
|
+
|
220
|
+
puts output_gzip
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# TODO Move this to a seperate class and clean it up A LOT. For 1.1
|
225
|
+
def process_css(input_file, output_path)
|
226
|
+
# Determine full path of input file
|
227
|
+
input_path = Pathname.new(File.dirname(input_file))
|
228
|
+
input = IO.read(input_file)
|
229
|
+
found_imports = ''
|
230
|
+
|
231
|
+
# Find filter statements and append cache busters to URLs
|
232
|
+
if @options[:cache_buster]
|
233
|
+
input = input.gsub(FILTER_REGEX) do |filter|
|
234
|
+
uri = $2
|
235
|
+
unless uri.match(/^(https:\/\/|http:\/\/|\/\/)/)
|
236
|
+
full_path = File.expand_path($2, File.dirname(input_file))
|
237
|
+
buster = make_cache_buster(full_path, $3)
|
238
|
+
new_path = uri.to_s + buster
|
239
|
+
|
240
|
+
%Q!filter='#{new_path}'!
|
241
|
+
else
|
242
|
+
%Q!filter='#{$2}#{$3}'!
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Handle @import statements URL rewrite and adding cache busters
|
248
|
+
input = input.gsub(IMPORT_REGEX) do |import|
|
249
|
+
uri = $2
|
250
|
+
asset_path = Pathname.new(File.expand_path(uri, input_path))
|
251
|
+
|
252
|
+
if uri.match(/^(\/[^\/]+.+)$/)
|
253
|
+
asset_path = Pathname.new(File.join(File.expand_path(@options[:root]), uri))
|
254
|
+
end
|
255
|
+
|
256
|
+
unless uri.match(/^(https:\/\/|http:\/\/|\/\/)/)
|
257
|
+
if (output_path != input_path)
|
258
|
+
|
259
|
+
new_path = asset_path.relative_path_from(output_path)
|
260
|
+
|
261
|
+
if @options[:cache_buster]
|
262
|
+
buster = make_cache_buster(asset_path, $3)
|
263
|
+
import.gsub!(uri, new_path.to_s+buster)
|
264
|
+
else
|
265
|
+
import.gsub!(uri, new_path)
|
266
|
+
end
|
267
|
+
else
|
268
|
+
if @options[:cache_buster]
|
269
|
+
buster = make_cache_buster(asset_path, $3)
|
270
|
+
import.gsub!(uri, asset_path.to_s+buster)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
found_imports << import
|
276
|
+
|
277
|
+
%Q!!
|
278
|
+
end
|
279
|
+
|
280
|
+
if output_path == input_path
|
281
|
+
if @options[:data]
|
282
|
+
input = input.gsub(URL_REGEX) do
|
283
|
+
uri = $2
|
284
|
+
|
285
|
+
unless uri.match(/^(https:\/\/|http:\/\/|\/\/)/)
|
286
|
+
new_path = File.expand_path($2, File.dirname(input_file))
|
287
|
+
if uri.match(/^(\/[^\/]+.+)$/)
|
288
|
+
new_path = Pathname.new(File.join(File.expand_path(@options[:root]), uri))
|
289
|
+
end
|
290
|
+
|
291
|
+
%Q!url(#{new_path})!
|
292
|
+
else
|
293
|
+
%Q!url(#{$2}#{$3})!
|
294
|
+
end
|
295
|
+
end
|
296
|
+
elsif @options[:cache_buster]
|
297
|
+
input = input.gsub(URL_REGEX) do
|
298
|
+
unless uri.match(/^(https:\/\/|http:\/\/|\/\/)/)
|
299
|
+
uri = $2
|
300
|
+
|
301
|
+
if uri.match(/^(\/[^\/]+.+)$/)
|
302
|
+
uri = Pathname.new(File.join(File.expand_path(@options[:root]), uri))
|
303
|
+
end
|
304
|
+
|
305
|
+
if @options[:cache_buster]
|
306
|
+
buster = make_cache_buster(uri, $3)
|
307
|
+
new_path = uri.to_s+buster
|
308
|
+
end
|
309
|
+
|
310
|
+
%Q!url(#{new_path})!
|
311
|
+
else
|
312
|
+
%Q!url(#{$2}#{$3})!
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
return input, found_imports
|
318
|
+
else
|
319
|
+
# Find all url(.ext) in file and rewrite relative url from output directory.
|
320
|
+
input = input.gsub(URL_REGEX) do
|
321
|
+
uri = $2
|
322
|
+
|
323
|
+
unless uri.match(/^(https:\/\/|http:\/\/|\/\/)/)
|
324
|
+
if @options[:data]
|
325
|
+
# if doing data conversion rewrite url as an absolute path.
|
326
|
+
new_path = File.expand_path(uri, File.dirname(input_file))
|
327
|
+
|
328
|
+
if uri.match(/^(\/[^\/]+.+)$/)
|
329
|
+
new_path = Pathname.new(File.join(File.expand_path(@options[:root]), uri))
|
330
|
+
end
|
331
|
+
else
|
332
|
+
asset_path = Pathname.new(File.expand_path(uri, File.dirname(input_file)))
|
333
|
+
|
334
|
+
if uri.match(/^(\/[^\/]+.+)$/)
|
335
|
+
asset_path = Pathname.new(File.join(File.expand_path(@options[:root]), uri))
|
336
|
+
end
|
337
|
+
|
338
|
+
new_path = asset_path.relative_path_from(output_path)
|
339
|
+
|
340
|
+
if @options[:cache_buster]
|
341
|
+
buster = make_cache_buster(asset_path, $3)
|
342
|
+
new_path = new_path.to_s+buster
|
343
|
+
else
|
344
|
+
new_path = new_path.to_s+$3 unless $3.nil?
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
%Q!url(#{new_path})!
|
349
|
+
else
|
350
|
+
%Q!url(#{$2}#{$3})!
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
return input, found_imports
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def make_cache_buster(asset_path, query_string)
|
359
|
+
unless query_string.nil?
|
360
|
+
query_string += '&'
|
361
|
+
else
|
362
|
+
query_string = '?'
|
363
|
+
end
|
364
|
+
|
365
|
+
if @options[:cache_buster] == :mtime
|
366
|
+
file_mtime = File.mtime(asset_path).to_i
|
367
|
+
buster = query_string + file_mtime.to_s
|
368
|
+
else
|
369
|
+
buster = query_string + @options[:cache_buster]
|
370
|
+
end
|
371
|
+
|
372
|
+
return buster
|
373
|
+
end
|
374
|
+
|
375
|
+
def make_data_uri(content, content_type)
|
376
|
+
"data:#{content_type};base64,#{Base64.encode64(content)}".gsub("\n", '')
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Hash
|
2
|
+
def deep_merge(hash)
|
3
|
+
target = dup
|
4
|
+
|
5
|
+
hash.keys.each do |key|
|
6
|
+
if hash[key].is_a? Hash and self[key].is_a? Hash
|
7
|
+
target[key] = target[key].deep_merge(hash[key])
|
8
|
+
next
|
9
|
+
end
|
10
|
+
|
11
|
+
target[key] = hash[key]
|
12
|
+
end
|
13
|
+
|
14
|
+
target
|
15
|
+
end
|
16
|
+
|
17
|
+
def deep_merge!(second)
|
18
|
+
second.each_pair do |k,v|
|
19
|
+
if self[k].is_a?(Hash) and second[k].is_a? Hash
|
20
|
+
self[k].deep_merge!(second[k])
|
21
|
+
else
|
22
|
+
self[k] = second[k]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/yui/LICENSE
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Software License Agreement (BSD License)
|
2
|
+
|
3
|
+
Copyright (c) 2008, Yahoo! Inc.
|
4
|
+
All rights reserved.
|
5
|
+
|
6
|
+
Redistribution and use of this software in source and binary forms, with or without modification, are
|
7
|
+
permitted provided that the following conditions are met:
|
8
|
+
|
9
|
+
* Redistributions of source code must retain the above
|
10
|
+
copyright notice, this list of conditions and the
|
11
|
+
following disclaimer.
|
12
|
+
|
13
|
+
* Redistributions in binary form must reproduce the above
|
14
|
+
copyright notice, this list of conditions and the
|
15
|
+
following disclaimer in the documentation and/or other
|
16
|
+
materials provided with the distribution.
|
17
|
+
|
18
|
+
* Neither the name of Yahoo! Inc. nor the names of its
|
19
|
+
contributors may be used to endorse or promote products
|
20
|
+
derived from this software without specific prior
|
21
|
+
written permission of Yahoo! Inc.
|
22
|
+
|
23
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
24
|
+
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
25
|
+
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
26
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
27
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
28
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
29
|
+
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
30
|
+
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: front-end-blender
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: "0.14"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Blake Elshire & Chris Griego
|
@@ -9,7 +9,7 @@ autorequire: front_end_architect/blend
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-06-23 00:00:00 -07:00
|
13
13
|
default_executable: blend
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -30,10 +30,13 @@ extensions: []
|
|
30
30
|
extra_rdoc_files: []
|
31
31
|
|
32
32
|
files:
|
33
|
-
- README
|
33
|
+
- README.rdoc
|
34
34
|
- MIT-LICENSE
|
35
|
-
- bin
|
36
|
-
- lib
|
35
|
+
- bin/blend
|
36
|
+
- lib/front_end_architect/blender.rb
|
37
|
+
- lib/front_end_architect/hash.rb
|
38
|
+
- lib/yui/LICENSE
|
39
|
+
- lib/yui/yuicompressor.jar
|
37
40
|
has_rdoc: false
|
38
41
|
homepage: http://github.com/front-end/front-end-blender/tree/master
|
39
42
|
post_install_message:
|
@@ -55,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
58
|
version:
|
56
59
|
requirements:
|
57
60
|
- Java, v1.4 or greater
|
58
|
-
rubyforge_project:
|
61
|
+
rubyforge_project: frontendblender
|
59
62
|
rubygems_version: 1.0.1
|
60
63
|
signing_key:
|
61
64
|
specification_version: 2
|
data/README
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
== What is Blender?
|
2
|
-
|
3
|
-
Blender is like ant or make for the front-end. It aggregates and compresses
|
4
|
-
CSS and/or JavaScript assets for a site into efficient, production-ready files.
|
5
|
-
|
6
|
-
== Blendfile
|
7
|
-
|
8
|
-
The Blendfile, named blender.yaml by default, is the configuration file that
|
9
|
-
tells Blender which source files are combined into which output files. The
|
10
|
-
file uses the YAML format. The output file is listed as hash key and source
|
11
|
-
files are the hash values as an array. Here is a sample Blendfile:
|
12
|
-
|
13
|
-
# blender.yaml for boldpx.com
|
14
|
-
_behavior/_global-min.js:
|
15
|
-
- _vendor/jquery/jquery.js
|
16
|
-
- _vendor/shadowbox/src/js/adapter/shadowbox-jquery.js
|
17
|
-
- _vendor/shadowbox/src/js/shadowbox.js
|
18
|
-
- _behavior/_global.js
|
19
|
-
|
20
|
-
_style/_global-min.css:
|
21
|
-
- _vendor/shadowbox/src/css/shadowbox.css
|
22
|
-
- _style/_global/typography.css
|
23
|
-
- _style/_global/typography-print.css
|
24
|
-
- _style/_global/colors.css
|
25
|
-
- _style/_global/colors-print.css
|
26
|
-
- _style/_global/layout-screen.css
|
27
|
-
- _style/_global/layout-print.css
|
28
|
-
|
29
|
-
== Usage
|
30
|
-
|
31
|
-
Usage: blend [options]
|
32
|
-
-h, --help Show this message
|
33
|
-
-V, --version Prints Blender's version number
|
34
|
-
-f, --file FILE Use given Blendfile
|
35
|
-
-t, --type TYPE Select file type to blend (css, js)
|
36
|
-
-d, --data Convert url(image.ext) to url(data:) in CSS files EXPERIMENTAL
|
37
|
-
--force Force blending when source files aren't newer than output files
|
38
|
-
--yui=YUIOPTS Pass arguments to YUI Compressor
|
39
|
-
-g, --generate Generate a stub Blendfile
|
40
|
-
|
41
|
-
== Examples
|
42
|
-
|
43
|
-
In your site directory run 'blend' to minify CSS and JavaScript.
|
44
|
-
blend
|
45
|
-
|
46
|
-
Other examples:
|
47
|
-
blend -f site/blender.yaml
|
48
|
-
blend -t css
|
49
|
-
blend -t css -d
|
50
|
-
blend --yui='--preserve-semi'
|
51
|
-
|
52
|
-
== Installation
|
53
|
-
|
54
|
-
To install the rubygem, run the following at the command line:
|
55
|
-
sudo gem install blender
|
56
|
-
|
57
|
-
Java, v1.4 or greater is required
|
58
|
-
|
59
|
-
== License
|
60
|
-
|
61
|
-
Copyright (c) 2008 Chris Griego
|
62
|
-
(c) 2008 Blake Elshire
|
63
|
-
|
64
|
-
Blender is freely distributable under the terms of an MIT-style license.
|
65
|
-
For details, see http://www.opensource.org/licenses/mit-license.php
|