blender 0.20
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +3 -0
- data/MIT-LICENSE +20 -0
- data/Manifest.txt +10 -0
- data/README.txt +83 -0
- data/Rakefile +37 -0
- data/bin/blend +43 -0
- data/lib/front_end_architect/blender.rb +402 -0
- data/lib/front_end_architect/hash.rb +26 -0
- data/lib/yui/LICENSE +30 -0
- data/lib/yui/yuicompressor.jar +0 -0
- metadata +95 -0
data/History.txt
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Chris Griego
|
2
|
+
(c) 2008 Blake Elshire
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
6
|
+
in the Software without restriction, including without limitation the rights
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
12
|
+
all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
THE SOFTWARE.
|
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,83 @@
|
|
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
|
+
-m, --min [MINIFIER] Select minifier to use (yui, none)
|
45
|
+
-c, --cache-buster [BUSTER] Append cache busters to URLs in CSS, defaults to timestamps
|
46
|
+
--force Force blending when source files aren't newer than output files
|
47
|
+
--yui=YUIOPTS Pass arguments to YUI Compressor
|
48
|
+
-d, --data EXPERIMENTAL Convert url(file.ext) to url(data:) in CSS files
|
49
|
+
-z, --gzip EXPERIMENTAL Also create gzip output files
|
50
|
+
|
51
|
+
== Examples
|
52
|
+
|
53
|
+
In your site directory run 'blend' to minify CSS and JavaScript.
|
54
|
+
blend
|
55
|
+
|
56
|
+
Other examples:
|
57
|
+
blend --generate
|
58
|
+
blend --yui='--preserve-semi'
|
59
|
+
blend -t css
|
60
|
+
blend -t css -d
|
61
|
+
blend -f public/Blendfile.yaml
|
62
|
+
|
63
|
+
== Installation
|
64
|
+
|
65
|
+
To install the RubyGem, run the following at the command line (you may need to use a command such as su or sudo):
|
66
|
+
gem install mime-types
|
67
|
+
gem install colored
|
68
|
+
gem install front-end-blender --source http://gems.github.com
|
69
|
+
|
70
|
+
If you're using Windows, you'll also need to run the following:
|
71
|
+
gem install win32console
|
72
|
+
|
73
|
+
* Java v1.4 or greater is required, http://java.com/en/
|
74
|
+
* Ruby v1.8.6 or greater is required, http://www.ruby-lang.org/en/downloads/
|
75
|
+
* RubyGems v1.2 or greater is recommended, http://rubygems.org/read/chapter/3
|
76
|
+
|
77
|
+
== License
|
78
|
+
|
79
|
+
Copyright (c) 2008 Chris Griego
|
80
|
+
(c) 2008 Blake Elshire
|
81
|
+
|
82
|
+
Blender is freely distributable under the terms of an MIT-style license.
|
83
|
+
For details, see http://www.opensource.org/licenses/mit-license.php
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
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
|
+
$:.unshift File.join(File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__), *%w[lib])
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'hoe'
|
11
|
+
require 'front_end_architect/blender.rb'
|
12
|
+
|
13
|
+
hoe = Hoe.new('blender', FrontEndArchitect::Blender::VERSION) do |p|
|
14
|
+
p.author = ['Blake Elshire', 'Chris Griego']
|
15
|
+
p.email = 'blender@front-end-architect.com'
|
16
|
+
p.summary = 'Blender outputs efficient, production-ready CSS and/or JavaScript assets.'
|
17
|
+
p.description = 'Blender is like ant or make for the front-end. It aggregates and compresses CSS and/or JavaScript assets for a site into efficient, production-ready files.'
|
18
|
+
|
19
|
+
p.url = 'http://www.front-end-architect.com/blender/'
|
20
|
+
|
21
|
+
p.extra_deps << ['mime-types', '>= 1.15']
|
22
|
+
p.extra_deps << ['colored', '>= 1.1']
|
23
|
+
|
24
|
+
p.spec_extras[:requirements] = 'Java, v1.4 or greater'
|
25
|
+
|
26
|
+
p.remote_rdoc_dir = '' # Release to root
|
27
|
+
end
|
28
|
+
|
29
|
+
task :update_gemspec do
|
30
|
+
File.open("#{hoe.name}.gemspec", 'w') do |gemspec|
|
31
|
+
gemspec << hoe.spec.to_ruby
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
task :debug_changes do
|
36
|
+
puts hoe.changes
|
37
|
+
end
|
data/bin/blend
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Copyright (c) 2008 Chris Griego
|
4
|
+
# (c) 2008 Blake Elshire
|
5
|
+
#
|
6
|
+
# Blender is freely distributable under the terms of an MIT-style license.
|
7
|
+
# For details, see http://www.opensource.org/licenses/mit-license.php
|
8
|
+
|
9
|
+
$:.unshift File.join(File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__), *%w[.. lib])
|
10
|
+
require 'optparse'
|
11
|
+
require 'front_end_architect/blender'
|
12
|
+
|
13
|
+
options = {}
|
14
|
+
|
15
|
+
opts = OptionParser.new do |opts|
|
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('-m [MINIFIER]', '--min [MINIFIER]', [:yui, :none], "Select minifier to use (yui, none)") {|m| options[:min] = m.nil? ? :none : m.to_sym }
|
23
|
+
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 }
|
24
|
+
opts.on( '--force', String, "Force blending when source files aren't newer than output files") { options[:force] = true }
|
25
|
+
opts.on( '--yui=YUIOPTS', String, "Pass arguments to YUI Compressor") {|o| options[:yuiopts] = o }
|
26
|
+
opts.on('-d', '--data', String, "EXPERIMENTAL Convert url(file.ext) to url(data:) in CSS files") { options[:data] = true }
|
27
|
+
opts.on('-z', '--gzip', String, "EXPERIMENTAL Also create gzip output files") { options[:gzip] = true }
|
28
|
+
|
29
|
+
opts.parse!(ARGV) rescue return false
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
blender = FrontEndArchitect::Blender.new(options)
|
34
|
+
|
35
|
+
if (options[:generate])
|
36
|
+
blender.generate
|
37
|
+
else
|
38
|
+
blender.blend
|
39
|
+
end
|
40
|
+
rescue Exception => e
|
41
|
+
puts e
|
42
|
+
exit 1
|
43
|
+
end
|
@@ -0,0 +1,402 @@
|
|
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
|
+
$:.unshift File.join(File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__), *%w[..])
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'yaml'
|
11
|
+
require 'base64'
|
12
|
+
require 'benchmark'
|
13
|
+
require 'colored'
|
14
|
+
require 'mime/types'
|
15
|
+
require 'find'
|
16
|
+
require 'pathname'
|
17
|
+
require 'zlib'
|
18
|
+
require 'front_end_architect/hash'
|
19
|
+
|
20
|
+
module FrontEndArchitect
|
21
|
+
class Blender
|
22
|
+
VERSION = '0.20'
|
23
|
+
|
24
|
+
FILTER_REGEX = /filter: ?[^?]+\(src=(['"])([^\?'"]+)(\?(?:[^'"]+)?)?\1,[^?]+\1\);/im
|
25
|
+
IMPORT_REGEX = /@import(?: url\(| )(['"]?)([^\?'"\)\s]+)(\?(?:[^'"\)]+)?)?\1\)?(?:[^?;]+)?;/im
|
26
|
+
URL_REGEX = /url\((['"]?)([^\?'"\)]+)(\?(?:[^'"\)]+)?)?\1?\)/im
|
27
|
+
|
28
|
+
DEFAULT_OPTIONS = {
|
29
|
+
:blendfile => 'Blendfile.yaml',
|
30
|
+
:data => false,
|
31
|
+
:force => false,
|
32
|
+
:root => File.dirname(:blendfile.to_s),
|
33
|
+
:min => :yui,
|
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
|
+
blendfile_mtime = File.mtime(@options[:blendfile])
|
47
|
+
blender = YAML::load_file @options[:blendfile]
|
48
|
+
|
49
|
+
Dir.chdir(File.dirname(@options[:blendfile]))
|
50
|
+
|
51
|
+
blender = flatten_blendfile(blender)
|
52
|
+
|
53
|
+
blender.each do |output_name, sources|
|
54
|
+
output_name = Pathname.new(output_name).cleanpath.to_s
|
55
|
+
|
56
|
+
output_new = false
|
57
|
+
gzip_output_name = output_name + '.gz'
|
58
|
+
|
59
|
+
# Checks the type flag and if the current file meets the type requirements continues
|
60
|
+
if output_name.match '.' + @options[:file_type].to_s
|
61
|
+
file_type = output_name.match(/\.css/) ? 'css' : 'js'
|
62
|
+
|
63
|
+
# Checks if output file exists and checks the mtimes of the source files to the output file if new creates a new file
|
64
|
+
if File.exists?(output_name) && (!@options[:gzip] || File.exists?(gzip_output_name))
|
65
|
+
output_files = []
|
66
|
+
output_files << File.mtime(output_name)
|
67
|
+
output_files << File.mtime(gzip_output_name) if @options[:gzip] && File.exists?(gzip_output_name)
|
68
|
+
|
69
|
+
oldest_output = output_files.sort.first
|
70
|
+
|
71
|
+
if blendfile_mtime > oldest_output
|
72
|
+
output_new = true
|
73
|
+
else
|
74
|
+
sources.each do |i|
|
75
|
+
if File.mtime(i) > oldest_output
|
76
|
+
output_new = true
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if output_new || @options[:force]
|
83
|
+
if File.writable?(output_name) && !(@options[:gzip] && !File.writable?(gzip_output_name))
|
84
|
+
create_output(output_name, sources, file_type)
|
85
|
+
else
|
86
|
+
puts 'Permission Denied:'.white_on_red + ' ' + output_name.red
|
87
|
+
puts 'Permission Denied:'.white_on_red + ' ' + gzip_output_name.red if @options[:gzip]
|
88
|
+
end
|
89
|
+
else
|
90
|
+
puts 'Skipping: '.yellow + output_name.yellow
|
91
|
+
puts 'Skipping: '.yellow + gzip_output_name.yellow if @options[:gzip]
|
92
|
+
end
|
93
|
+
else
|
94
|
+
create_output(output_name, sources, file_type)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
puts sprintf('%.5f', elapsed) + ' seconds'
|
101
|
+
end
|
102
|
+
|
103
|
+
def generate
|
104
|
+
if File.exists?(@options[:blendfile]) && !@options[:force]
|
105
|
+
raise "'#{@options[:blendfile]}' already exists"
|
106
|
+
end
|
107
|
+
|
108
|
+
blend_files = Hash.new
|
109
|
+
|
110
|
+
Find.find(Dir.getwd) do |f|
|
111
|
+
basename = File.basename(f)
|
112
|
+
|
113
|
+
if FileTest.directory?(f) && (basename[0] == ?. || basename.match(/^(yui|tinymce|dojo|wp-includes|wp-admin|mint)$/i) || (File.basename(f).downcase == 'rails' && File.basename(File.dirname(f)).downcase == 'vendor'))
|
114
|
+
Find.prune
|
115
|
+
elsif !(basename.match(/(^|[-.])(pack|min)\.(css|js)$/i) || basename.match(/^(sifr\.js|ext\.js|mootools.*\.js)$/i))
|
116
|
+
# TODO Check file contents instead of name for minification (port YSlow's isMinified)
|
117
|
+
f.gsub!(Dir.getwd.to_s + '/', '')
|
118
|
+
|
119
|
+
if File.extname(f).downcase == '.css' || File.extname(f).downcase == '.js'
|
120
|
+
min_file = basename.sub(/\.(css|js)$/i, '-min.\1')
|
121
|
+
path = File.dirname(f).split('/') # File::dirname depends on /
|
122
|
+
|
123
|
+
path.push min_file
|
124
|
+
path.push [basename]
|
125
|
+
|
126
|
+
h = path.reverse.inject { |m,v| { v => m } }
|
127
|
+
|
128
|
+
blend_files.deep_merge!(h).inspect
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
File.open(@options[:blendfile], 'w') do |blendfile|
|
134
|
+
blendfile << blend_files.to_yaml
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
protected
|
139
|
+
|
140
|
+
def flatten_blendfile(value, key=nil, context=[])
|
141
|
+
if value.is_a? Hash
|
142
|
+
context << key unless key.nil?
|
143
|
+
|
144
|
+
new_hash = {}
|
145
|
+
|
146
|
+
value.each do |k, v|
|
147
|
+
new_hash.merge! flatten_blendfile(v, k, context.dup)
|
148
|
+
end
|
149
|
+
|
150
|
+
new_hash
|
151
|
+
else
|
152
|
+
prefix = context.join(File::SEPARATOR)
|
153
|
+
prefix += File::SEPARATOR unless context.empty?
|
154
|
+
|
155
|
+
value.each_index do |i|
|
156
|
+
unless value[i].match(/^(\/[^\/]+.+)$/)
|
157
|
+
value[i] = prefix + value[i]
|
158
|
+
else
|
159
|
+
value[i] = @options[:root] + value[i]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
return { (prefix + key) => value }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def create_output(output_name, sources, type)
|
168
|
+
output = ''
|
169
|
+
|
170
|
+
File.open(output_name, 'w') do |output_file|
|
171
|
+
# Determine full path of the output file
|
172
|
+
output_path = Pathname.new(File.expand_path(File.dirname(output_name)))
|
173
|
+
imports = ''
|
174
|
+
|
175
|
+
sources.each do |i|
|
176
|
+
if File.extname(i).downcase == '.css'
|
177
|
+
processed_output, processed_imports = process_css(i, output_path)
|
178
|
+
|
179
|
+
output << processed_output
|
180
|
+
imports << processed_imports
|
181
|
+
else
|
182
|
+
output << IO.read(i)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
if File.extname(output_name).downcase == '.css' && !imports.empty?
|
187
|
+
output.insert(0, imports)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Compress
|
191
|
+
if @options[:min] == :yui
|
192
|
+
libdir = File.join(File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__), *%w[.. .. lib])
|
193
|
+
|
194
|
+
IO.popen("java -jar #{libdir}/yui/yuicompressor.jar #{@options[:yuiopts]} --type #{type}", 'r+') do |io|
|
195
|
+
io.write output
|
196
|
+
io.close_write
|
197
|
+
|
198
|
+
output = io.read
|
199
|
+
|
200
|
+
if File.extname(output_name) == '.css'
|
201
|
+
output.gsub! ' and(', ' and (' # Workaround for YUI Compressor Bug #1938329
|
202
|
+
output.gsub! '*/;}', '*/}' # Workaround for YUI Compressor Bug #1961175
|
203
|
+
end
|
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
|
+
# Data
|
212
|
+
if @options[:data]
|
213
|
+
if File.extname(output_name).downcase == '.css'
|
214
|
+
output = output.gsub(URL_REGEX) do
|
215
|
+
unless $2.downcase.include?('.css')
|
216
|
+
mime_type = MIME::Types.type_for($2)
|
217
|
+
url_contents = make_data_uri(IO.read($2), mime_type[0])
|
218
|
+
else
|
219
|
+
url_contents = $2
|
220
|
+
end
|
221
|
+
%Q!url(#{url_contents})!
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
output_file << output
|
227
|
+
end
|
228
|
+
|
229
|
+
puts output_name.green
|
230
|
+
|
231
|
+
if @options[:gzip]
|
232
|
+
output_gzip = output_name + '.gz'
|
233
|
+
|
234
|
+
Zlib::GzipWriter.open(output_gzip) do |gz|
|
235
|
+
gz.write(output)
|
236
|
+
end
|
237
|
+
|
238
|
+
puts output_gzip.green
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# TODO Move this to a seperate class and clean it up A LOT. For 1.1
|
243
|
+
def process_css(input_file, output_path)
|
244
|
+
# Determine full path of input file
|
245
|
+
input_path = Pathname.new(File.dirname(input_file))
|
246
|
+
input = IO.read(input_file)
|
247
|
+
found_imports = ''
|
248
|
+
|
249
|
+
# Find filter statements and append cache busters to URLs
|
250
|
+
if @options[:cache_buster]
|
251
|
+
input = input.gsub(FILTER_REGEX) do |filter|
|
252
|
+
uri = $2
|
253
|
+
cbuster = $3
|
254
|
+
unless uri.match(/^(https:\/\/|http:\/\/|\/\/)/i)
|
255
|
+
full_path = File.expand_path($2, File.dirname(input_file))
|
256
|
+
buster = make_cache_buster(full_path, $3)
|
257
|
+
new_path = uri.to_s + buster
|
258
|
+
|
259
|
+
%Q!filter='#{new_path}'!
|
260
|
+
else
|
261
|
+
%Q!filter='#{uri}#{cbuster}'!
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Handle @import statements URL rewrite and adding cache busters
|
267
|
+
input = input.gsub(IMPORT_REGEX) do |import|
|
268
|
+
uri = $2
|
269
|
+
asset_path = Pathname.new(File.expand_path(uri, input_path))
|
270
|
+
|
271
|
+
if uri.match(/^(\/[^\/]+.+)$/)
|
272
|
+
asset_path = Pathname.new(File.join(File.expand_path(@options[:root]), uri))
|
273
|
+
end
|
274
|
+
|
275
|
+
unless uri.match(/^(https:\/\/|http:\/\/|\/\/)/i)
|
276
|
+
if (output_path != input_path)
|
277
|
+
|
278
|
+
new_path = asset_path.relative_path_from(output_path)
|
279
|
+
|
280
|
+
if @options[:cache_buster]
|
281
|
+
buster = make_cache_buster(asset_path, $3)
|
282
|
+
import.gsub!(uri, new_path.to_s+buster)
|
283
|
+
else
|
284
|
+
import.gsub!(uri, new_path)
|
285
|
+
end
|
286
|
+
else
|
287
|
+
if @options[:cache_buster]
|
288
|
+
buster = make_cache_buster(asset_path, $3)
|
289
|
+
import.gsub!(uri, asset_path.to_s+buster)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
found_imports << import
|
295
|
+
|
296
|
+
%Q!!
|
297
|
+
end
|
298
|
+
|
299
|
+
if output_path == input_path
|
300
|
+
if @options[:data]
|
301
|
+
input = input.gsub(URL_REGEX) do
|
302
|
+
uri = $2
|
303
|
+
cbuster = $3
|
304
|
+
|
305
|
+
unless uri.match(/^(https:\/\/|http:\/\/|\/\/)/i)
|
306
|
+
new_path = File.expand_path($2, File.dirname(input_file))
|
307
|
+
|
308
|
+
if uri.match(/^(\/[^\/]+.+)$/)
|
309
|
+
new_path = Pathname.new(File.join(File.expand_path(@options[:root]), uri))
|
310
|
+
end
|
311
|
+
|
312
|
+
%Q!url(#{new_path})!
|
313
|
+
else
|
314
|
+
%Q!url(#{uri}#{cbuster})!
|
315
|
+
end
|
316
|
+
end
|
317
|
+
elsif @options[:cache_buster]
|
318
|
+
input = input.gsub(URL_REGEX) do
|
319
|
+
unless uri.match(/^(https:\/\/|http:\/\/|\/\/)/i)
|
320
|
+
uri = $2
|
321
|
+
cbuster = $3
|
322
|
+
|
323
|
+
if uri.match(/^(\/[^\/]+.+)$/)
|
324
|
+
uri = Pathname.new(File.join(File.expand_path(@options[:root]), uri))
|
325
|
+
end
|
326
|
+
|
327
|
+
if @options[:cache_buster]
|
328
|
+
buster = make_cache_buster(uri, $3)
|
329
|
+
new_path = uri.to_s+buster
|
330
|
+
end
|
331
|
+
|
332
|
+
%Q!url(#{new_path})!
|
333
|
+
else
|
334
|
+
%Q!url(#{uri}#{cbuster})!
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
return input, found_imports
|
340
|
+
else
|
341
|
+
# Find all url(.ext) in file and rewrite relative url from output directory.
|
342
|
+
input = input.gsub(URL_REGEX) do
|
343
|
+
uri = $2
|
344
|
+
cbuster = $3
|
345
|
+
|
346
|
+
unless uri.match(/^(https:\/\/|http:\/\/|\/\/)/i)
|
347
|
+
if @options[:data]
|
348
|
+
# if doing data conversion rewrite url as an absolute path
|
349
|
+
new_path = File.expand_path(uri, File.dirname(input_file))
|
350
|
+
|
351
|
+
if uri.match(/^(\/[^\/]+.+)$/)
|
352
|
+
new_path = Pathname.new(File.join(File.expand_path(@options[:root]), uri))
|
353
|
+
end
|
354
|
+
else
|
355
|
+
asset_path = Pathname.new(File.expand_path(uri, File.dirname(input_file)))
|
356
|
+
|
357
|
+
if uri.match(/^(\/[^\/]+.+)$/)
|
358
|
+
asset_path = Pathname.new(File.join(File.expand_path(@options[:root]), uri))
|
359
|
+
end
|
360
|
+
|
361
|
+
new_path = asset_path.relative_path_from(output_path)
|
362
|
+
|
363
|
+
if @options[:cache_buster]
|
364
|
+
buster = make_cache_buster(asset_path, $3)
|
365
|
+
new_path = new_path.to_s+buster
|
366
|
+
else
|
367
|
+
new_path = new_path.to_s+$3 unless $3.nil?
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
%Q!url(#{new_path})!
|
372
|
+
else
|
373
|
+
%Q!url(#{uri}#{cbuster})!
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
return input, found_imports
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def make_cache_buster(asset_path, query_string)
|
382
|
+
unless query_string.nil?
|
383
|
+
query_string += '&'
|
384
|
+
else
|
385
|
+
query_string = '?'
|
386
|
+
end
|
387
|
+
|
388
|
+
if @options[:cache_buster] == :mtime
|
389
|
+
file_mtime = File.mtime(asset_path).to_i
|
390
|
+
buster = query_string + file_mtime.to_s
|
391
|
+
else
|
392
|
+
buster = query_string + @options[:cache_buster]
|
393
|
+
end
|
394
|
+
|
395
|
+
return buster
|
396
|
+
end
|
397
|
+
|
398
|
+
def make_data_uri(content, content_type)
|
399
|
+
"data:#{content_type};base64,#{Base64.encode64(content)}".gsub("\n", '')
|
400
|
+
end
|
401
|
+
end
|
402
|
+
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
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blender
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.20"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Blake Elshire
|
8
|
+
- Chris Griego
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2008-10-13 00:00:00 -05:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: mime-types
|
18
|
+
type: :runtime
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "1.15"
|
25
|
+
version:
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: colored
|
28
|
+
type: :runtime
|
29
|
+
version_requirement:
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "1.1"
|
35
|
+
version:
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: hoe
|
38
|
+
type: :development
|
39
|
+
version_requirement:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 1.7.0
|
45
|
+
version:
|
46
|
+
description: Blender is like ant or make for the front-end. It aggregates and compresses CSS and/or JavaScript assets for a site into efficient, production-ready files.
|
47
|
+
email: blender@front-end-architect.com
|
48
|
+
executables:
|
49
|
+
- blend
|
50
|
+
extensions: []
|
51
|
+
|
52
|
+
extra_rdoc_files:
|
53
|
+
- History.txt
|
54
|
+
- Manifest.txt
|
55
|
+
- README.txt
|
56
|
+
files:
|
57
|
+
- History.txt
|
58
|
+
- MIT-LICENSE
|
59
|
+
- Manifest.txt
|
60
|
+
- README.txt
|
61
|
+
- Rakefile
|
62
|
+
- bin/blend
|
63
|
+
- lib/front_end_architect/blender.rb
|
64
|
+
- lib/front_end_architect/hash.rb
|
65
|
+
- lib/yui/LICENSE
|
66
|
+
- lib/yui/yuicompressor.jar
|
67
|
+
has_rdoc: true
|
68
|
+
homepage: http://www.front-end-architect.com/blender/
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options:
|
71
|
+
- --main
|
72
|
+
- README.txt
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
version:
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
version:
|
87
|
+
requirements:
|
88
|
+
- Java, v1.4 or greater
|
89
|
+
rubyforge_project: blender
|
90
|
+
rubygems_version: 1.2.0
|
91
|
+
signing_key:
|
92
|
+
specification_version: 2
|
93
|
+
summary: Blender outputs efficient, production-ready CSS and/or JavaScript assets.
|
94
|
+
test_files: []
|
95
|
+
|