sinatra-assetpack 0.0.5
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/.gitignore +1 -0
- data/Gemfile +2 -0
- data/HISTORY.md +45 -0
- data/README.md +248 -0
- data/Rakefile +3 -0
- data/example/.gitignore +1 -0
- data/example/Rakefile +7 -0
- data/example/app.rb +28 -0
- data/example/app/css/test.sass +11 -0
- data/example/app/images/icon.png +0 -0
- data/example/app/js/app.js +3 -0
- data/example/app/js/vendor/jquery.js +2 -0
- data/example/app/js/vendor/jquery.plugin.js +2 -0
- data/example/app/js/vendor/underscore.js +2 -0
- data/example/views/index.erb +26 -0
- data/lib/sinatra/assetpack.rb +55 -0
- data/lib/sinatra/assetpack/buster_helpers.rb +21 -0
- data/lib/sinatra/assetpack/class_methods.rb +68 -0
- data/lib/sinatra/assetpack/compressor.rb +109 -0
- data/lib/sinatra/assetpack/configurator.rb +15 -0
- data/lib/sinatra/assetpack/css.rb +35 -0
- data/lib/sinatra/assetpack/hasharray.rb +70 -0
- data/lib/sinatra/assetpack/helpers.rb +48 -0
- data/lib/sinatra/assetpack/html_helpers.rb +17 -0
- data/lib/sinatra/assetpack/image.rb +37 -0
- data/lib/sinatra/assetpack/options.rb +223 -0
- data/lib/sinatra/assetpack/package.rb +111 -0
- data/lib/sinatra/assetpack/rake.rb +23 -0
- data/lib/sinatra/assetpack/version.rb +7 -0
- data/sinatra-assetpack.gemspec +23 -0
- data/test/app/.gitignore +1 -0
- data/test/app/Rakefile +7 -0
- data/test/app/app.rb +51 -0
- data/test/app/app/css/js2c.css +494 -0
- data/test/app/app/css/screen.sass +9 -0
- data/test/app/app/css/sqwishable.css +7 -0
- data/test/app/app/css/style.css +2 -0
- data/test/app/app/images/background.jpg +1 -0
- data/test/app/app/images/email.png +0 -0
- data/test/app/app/js/hello.js +1 -0
- data/test/app/app/js/hi.coffee +2 -0
- data/test/app/app/views/index.haml +1 -0
- data/test/build_test.rb +20 -0
- data/test/cache_test.rb +10 -0
- data/test/helpers_test.rb +23 -0
- data/test/img_test.rb +13 -0
- data/test/options_test.rb +17 -0
- data/test/order_test.rb +21 -0
- data/test/preproc_test.rb +23 -0
- data/test/simplecss_test.rb +16 -0
- data/test/sqwish_test.rb +29 -0
- data/test/test_helper.rb +38 -0
- data/test/unit_test.rb +96 -0
- data/test/yui_test.rb +22 -0
- metadata +200 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module AssetPack
|
3
|
+
module BusterHelpers
|
4
|
+
extend self
|
5
|
+
# Returns the cache buster suffix for given file(s).
|
6
|
+
# This implementation somewhat obfuscates the mtime to not reveal deployment dates.
|
7
|
+
def cache_buster_hash(*files)
|
8
|
+
i = files.map { |f| File.mtime(f).to_i }.max
|
9
|
+
(i * 4567).to_s.reverse[0...6]
|
10
|
+
end
|
11
|
+
|
12
|
+
# Adds a cache buster for the given path.
|
13
|
+
#
|
14
|
+
# add_cache_buster('/images/email.png', '/var/www/x/public/images/email.png')
|
15
|
+
#
|
16
|
+
def add_cache_buster(path, *files)
|
17
|
+
path.gsub(/(\.[^.]+)$/) { |ext| ".#{cache_buster_hash *files}#{ext}" }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module AssetPack
|
3
|
+
# Class methods that will be given to the Sinatra application.
|
4
|
+
module ClassMethods
|
5
|
+
# Sets asset options, or gets them
|
6
|
+
def assets(&blk)
|
7
|
+
@options ||= Options.new(self, &blk)
|
8
|
+
self.assets_initialize! if block_given?
|
9
|
+
|
10
|
+
@options
|
11
|
+
end
|
12
|
+
|
13
|
+
def assets_initialize!
|
14
|
+
add_compressed_routes!
|
15
|
+
add_individual_routes!
|
16
|
+
end
|
17
|
+
|
18
|
+
# Add routes for the compressed versions
|
19
|
+
def add_compressed_routes!
|
20
|
+
assets.packages.each do |name, package|
|
21
|
+
get package.route_regex do
|
22
|
+
content_type package.type
|
23
|
+
last_modified package.mtime
|
24
|
+
|
25
|
+
settings.assets.cache[package.hash] ||= package.minify
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add the routes for the individual files.
|
31
|
+
def add_individual_routes!
|
32
|
+
assets.served.each do |path, from|
|
33
|
+
get "/#{path}/*".squeeze('/') do |file|
|
34
|
+
fmt = File.extname(file)[1..-1]
|
35
|
+
|
36
|
+
# Sanity checks
|
37
|
+
pass unless AssetPack.supported_formats.include?(fmt)
|
38
|
+
fn = asset_path_for(file, from) or pass
|
39
|
+
|
40
|
+
# Send headers
|
41
|
+
content_type fmt.to_sym
|
42
|
+
last_modified File.mtime(fn).to_i
|
43
|
+
|
44
|
+
format = File.extname(fn)[1..-1]
|
45
|
+
|
46
|
+
if AssetPack.supported_formats.include?(format)
|
47
|
+
# It's a raw file, just send it
|
48
|
+
not_found unless format == fmt
|
49
|
+
|
50
|
+
if fmt == 'css'
|
51
|
+
asset_filter_css File.read(fn)
|
52
|
+
else
|
53
|
+
send_file fn
|
54
|
+
end
|
55
|
+
else
|
56
|
+
# Dynamic file
|
57
|
+
not_found unless AssetPack.tilt_formats[format] == fmt
|
58
|
+
out = render format.to_sym, File.read(fn)
|
59
|
+
out = asset_filter_css(out) if fmt == 'css'
|
60
|
+
out
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module AssetPack
|
3
|
+
module Compressor
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# Compresses a given string.
|
7
|
+
#
|
8
|
+
# compress File.read('x.js'), :js, :jsmin
|
9
|
+
#
|
10
|
+
def compress(str, type, engine=nil, options={})
|
11
|
+
engine ||= 'jsmin' if type == :js
|
12
|
+
engine ||= 'simple' if type == :css
|
13
|
+
|
14
|
+
key = :"#{type}/#{engine}"
|
15
|
+
meth = compressors[key]
|
16
|
+
return str unless meth
|
17
|
+
|
18
|
+
meth[str, options]
|
19
|
+
end
|
20
|
+
|
21
|
+
def compressors
|
22
|
+
@compressors ||= {
|
23
|
+
:'js/jsmin' => method(:jsmin),
|
24
|
+
:'js/yui' => method(:yui_js),
|
25
|
+
:'js/closure' => method(:closure_js),
|
26
|
+
:'css/sass' => method(:sass),
|
27
|
+
:'css/yui' => method(:yui_css),
|
28
|
+
:'css/simple' => method(:simple_css),
|
29
|
+
:'css/sqwish' => method(:sqwish_css)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# =====================================================================
|
34
|
+
# Compressors
|
35
|
+
|
36
|
+
def jsmin(str, options={})
|
37
|
+
require 'jsmin'
|
38
|
+
JSMin.minify str
|
39
|
+
end
|
40
|
+
|
41
|
+
def sass(str, options={})
|
42
|
+
Tilt.new("scss", {:style => :compressed}) { str }.render
|
43
|
+
rescue LoadError
|
44
|
+
simple_css str
|
45
|
+
end
|
46
|
+
|
47
|
+
def yui_css(str, options={})
|
48
|
+
require 'yui/compressor'
|
49
|
+
YUI::CssCompressor.new.compress(str)
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
sass str
|
52
|
+
end
|
53
|
+
|
54
|
+
def yui_js(str, options={})
|
55
|
+
require 'yui/compressor'
|
56
|
+
YUI::JavaScriptCompressor.new(options).compress(str)
|
57
|
+
rescue LoadError
|
58
|
+
jsmin str
|
59
|
+
end
|
60
|
+
|
61
|
+
def simple_css(str, options={})
|
62
|
+
str.gsub! /[ \r\n\t]+/m, ' '
|
63
|
+
str.gsub! %r{ *([;\{\},:]) *}, '\1'
|
64
|
+
end
|
65
|
+
|
66
|
+
def sqwish_css(str, options={})
|
67
|
+
cmd = "#{sqwish_bin} %f "
|
68
|
+
cmd += "--strict" if options[:strict]
|
69
|
+
|
70
|
+
_, input = sys :css, str, cmd
|
71
|
+
output = input.gsub(/\.css/, '.min.css')
|
72
|
+
|
73
|
+
File.read(output)
|
74
|
+
rescue => e
|
75
|
+
simple_css str
|
76
|
+
end
|
77
|
+
|
78
|
+
def sqwish_bin
|
79
|
+
ENV['SQWISH_PATH'] || "sqwish"
|
80
|
+
end
|
81
|
+
|
82
|
+
def closure_js(str, options={})
|
83
|
+
require 'net/http'
|
84
|
+
require 'uri'
|
85
|
+
|
86
|
+
response = Net::HTTP.post_form(URI.parse('http://closure-compiler.appspot.com/compile'), {
|
87
|
+
'js_code' => str,
|
88
|
+
'compilation_level' => options[:level] || "ADVANCED_OPTIMIZATIONS",
|
89
|
+
'output_format' => 'text',
|
90
|
+
'output_info' => 'compiled_code'
|
91
|
+
})
|
92
|
+
|
93
|
+
response.body
|
94
|
+
end
|
95
|
+
|
96
|
+
# For others
|
97
|
+
def sys(type, str, cmd)
|
98
|
+
t = Tempfile.new ['', ".#{type}"]
|
99
|
+
t.write(str)
|
100
|
+
t.close
|
101
|
+
|
102
|
+
output = `#{cmd.gsub('%f', t.path)}`
|
103
|
+
FileUtils.rm t
|
104
|
+
|
105
|
+
[output, t.path]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module AssetPack
|
3
|
+
module Configurator
|
4
|
+
def attrib(name)
|
5
|
+
define_method(:"#{name}") { |*a|
|
6
|
+
value = a.first
|
7
|
+
self.instance_variable_set :"@#{name}", value unless value.nil?
|
8
|
+
self.instance_variable_get :"@#{name}"
|
9
|
+
}
|
10
|
+
|
11
|
+
alias_method(:"#{name}=", :"#{name}")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module AssetPack
|
3
|
+
module Css
|
4
|
+
def self.preproc(str, assets)
|
5
|
+
str.gsub(/url\(["']?(.*?)["']?\)/) { |url|
|
6
|
+
file, options = $1.split('?')
|
7
|
+
local = assets.local_file_for file
|
8
|
+
|
9
|
+
url = if local
|
10
|
+
if options.to_s.include?('embed')
|
11
|
+
to_data_uri(local)
|
12
|
+
else
|
13
|
+
BusterHelpers.add_cache_buster(file, local)
|
14
|
+
end
|
15
|
+
else
|
16
|
+
url
|
17
|
+
end
|
18
|
+
|
19
|
+
"url(#{url})"
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.to_data_uri(file)
|
24
|
+
require 'base64'
|
25
|
+
|
26
|
+
data = File.read(file)
|
27
|
+
ext = File.extname(file)
|
28
|
+
mime = Sinatra::Base.mime_type(ext)
|
29
|
+
b64 = Base64.encode64(data).gsub("\n", '')
|
30
|
+
|
31
|
+
"data:#{mime};base64,#{b64}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module AssetPack
|
3
|
+
# Class: HashArray
|
4
|
+
# A stopgap solution to Ruby 1.8's lack of ordered hashes.
|
5
|
+
#
|
6
|
+
# A HashArray, for all intents and purposes, acts like an array. However, the
|
7
|
+
# common stuff are overloaded to work with hashes.
|
8
|
+
#
|
9
|
+
# ## Basic usage
|
10
|
+
#
|
11
|
+
# #### Creating
|
12
|
+
# You can create a HashArray by passing it an array.
|
13
|
+
#
|
14
|
+
# dict = HashArray.new([
|
15
|
+
# { :good_morning => "Bonjour" },
|
16
|
+
# { :goodbye => "Au revoir" },
|
17
|
+
# { :good_evening => "Bon nuit" }
|
18
|
+
# ])
|
19
|
+
#
|
20
|
+
# #### Converting
|
21
|
+
# You may also use it like so:
|
22
|
+
#
|
23
|
+
# letters = [ { :a => "Aye"}, { :b => "Bee" } ].to_hash_array
|
24
|
+
#
|
25
|
+
# #### Iterating
|
26
|
+
# Now you can use the typical enumerator functions:
|
27
|
+
#
|
28
|
+
# dict.each do |(key, value)|
|
29
|
+
# puts "#{key} is #{value}"
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# #=> :good_morning is "Bonjour"
|
33
|
+
# # :goodbye is "Au revoir"
|
34
|
+
# # :good_evening is "Bon nuit"
|
35
|
+
#
|
36
|
+
class HashArray < Array
|
37
|
+
def self.[](*arr)
|
38
|
+
new arr.each_slice(2).map { |(k, v)| Hash[k, v] }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Works like Hash#each.
|
42
|
+
#
|
43
|
+
# By extension, methods that rely on #each (like #inject) will work
|
44
|
+
# as intended.
|
45
|
+
#
|
46
|
+
def each(&block)
|
47
|
+
super { |hash| yield hash.to_a.flatten }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Works like Hash#map.
|
51
|
+
def map(&block)
|
52
|
+
super { |hash| yield hash.to_a.flatten }
|
53
|
+
end
|
54
|
+
|
55
|
+
# Works like Hash#values.
|
56
|
+
def values
|
57
|
+
inject([]) { |a, (k, v)| a << v; a }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns everything as a hash.
|
61
|
+
def to_hash
|
62
|
+
inject({}) { |hash, (k, v)| hash[k] = v; hash }
|
63
|
+
end
|
64
|
+
|
65
|
+
def keys
|
66
|
+
inject([]) { |a, (k, v)| a << k; a }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module AssetPack
|
3
|
+
module Helpers
|
4
|
+
def css(name, options={})
|
5
|
+
show_asset_pack :css, name, options
|
6
|
+
end
|
7
|
+
|
8
|
+
def js(name, options={})
|
9
|
+
show_asset_pack :js, name, options
|
10
|
+
end
|
11
|
+
|
12
|
+
def img(src, options={})
|
13
|
+
attrs = { :src => src }.merge(options)
|
14
|
+
|
15
|
+
local = settings.assets.local_file_for src
|
16
|
+
if local
|
17
|
+
i = Image.new(local)
|
18
|
+
attrs[:src] = BusterHelpers.add_cache_buster(src, local)
|
19
|
+
if i.dimensions?
|
20
|
+
attrs[:width] ||= i.width
|
21
|
+
attrs[:height] ||= i.height
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
"<img#{HtmlHelpers.kv attrs} />"
|
26
|
+
end
|
27
|
+
|
28
|
+
def show_asset_pack(type, name, options={})
|
29
|
+
pack = settings.assets.packages["#{name}.#{type}"]
|
30
|
+
return "" unless pack
|
31
|
+
|
32
|
+
if settings.production?
|
33
|
+
pack.to_production_html options
|
34
|
+
else
|
35
|
+
pack.to_development_html options
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def asset_filter_css(str)
|
40
|
+
Css.preproc str, settings.assets
|
41
|
+
end
|
42
|
+
|
43
|
+
def asset_path_for(file, from)
|
44
|
+
settings.assets.dyn_local_file_for file, from
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module AssetPack
|
3
|
+
module HtmlHelpers
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def e(str)
|
7
|
+
re = Rack::Utils.escape_html str
|
8
|
+
re = re.gsub("/", '/') # Rack sometimes insists on munging slashes in Ruby 1.8.
|
9
|
+
re
|
10
|
+
end
|
11
|
+
|
12
|
+
def kv(hash)
|
13
|
+
hash.map { |k, v| " #{e k}='#{e v}'" }.join('')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module AssetPack
|
3
|
+
class Image
|
4
|
+
def initialize(file)
|
5
|
+
@file = file
|
6
|
+
end
|
7
|
+
|
8
|
+
def dimensions
|
9
|
+
return @dimensions unless @dimensions.nil?
|
10
|
+
|
11
|
+
_, _, dim = `identify "#{@file}"`.split(' ')
|
12
|
+
w, h = dim.split('x')
|
13
|
+
|
14
|
+
if w.to_i != 0 && h.to_i != 0
|
15
|
+
@dimensions = [w.to_i, h.to_i]
|
16
|
+
else
|
17
|
+
@dimensions = false
|
18
|
+
end
|
19
|
+
|
20
|
+
rescue => e
|
21
|
+
@dimensions = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def dimensions?
|
25
|
+
!! dimensions
|
26
|
+
end
|
27
|
+
|
28
|
+
def width
|
29
|
+
dimensions? && dimensions[0]
|
30
|
+
end
|
31
|
+
|
32
|
+
def height
|
33
|
+
dimensions? && dimensions[1]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module Sinatra
|
2
|
+
module AssetPack
|
3
|
+
# Assets.
|
4
|
+
#
|
5
|
+
# == Common usage
|
6
|
+
#
|
7
|
+
# SinatraApp.assets {
|
8
|
+
# # dsl stuff here
|
9
|
+
# }
|
10
|
+
#
|
11
|
+
# a = SinatraApp.assets
|
12
|
+
#
|
13
|
+
# Getting options:
|
14
|
+
#
|
15
|
+
# a.js_compression
|
16
|
+
# a.output_path
|
17
|
+
#
|
18
|
+
# Served:
|
19
|
+
#
|
20
|
+
# a.served # { '/js' => '/var/www/project/app/js', ... }
|
21
|
+
# # (URL path => local path)
|
22
|
+
#
|
23
|
+
# Packages:
|
24
|
+
#
|
25
|
+
# a.packages # { 'app.css' => #<Package>, ... }
|
26
|
+
# # (name.type => package instance)
|
27
|
+
#
|
28
|
+
# Build:
|
29
|
+
#
|
30
|
+
# a.build! { |path| puts "Building #{path}" }
|
31
|
+
#
|
32
|
+
# Lookup:
|
33
|
+
#
|
34
|
+
# a.local_path_for('/images/bg.gif')
|
35
|
+
# a.served?('/images/bg.gif')
|
36
|
+
#
|
37
|
+
# a.glob('/js/*.js', '/js/vendor/**/*.js')
|
38
|
+
# # Returns a HashArray of (local => remote)
|
39
|
+
#
|
40
|
+
class Options
|
41
|
+
extend Configurator
|
42
|
+
|
43
|
+
def initialize(app, &blk)
|
44
|
+
@app = app
|
45
|
+
@js_compression = :jsmin
|
46
|
+
@css_compression = :simple
|
47
|
+
@output_path = app.public
|
48
|
+
|
49
|
+
@js_compression_options = Hash.new
|
50
|
+
@css_compression_options = Hash.new
|
51
|
+
|
52
|
+
reset!
|
53
|
+
|
54
|
+
# Defaults!
|
55
|
+
serve '/css', :from => 'app/css'
|
56
|
+
serve '/js', :from => 'app/js'
|
57
|
+
serve '/images', :from => 'app/images'
|
58
|
+
|
59
|
+
instance_eval &blk if block_given?
|
60
|
+
end
|
61
|
+
|
62
|
+
# =====================================================================
|
63
|
+
# DSL methods
|
64
|
+
|
65
|
+
def serve(path, options={})
|
66
|
+
raise Error unless options[:from]
|
67
|
+
return unless File.directory?(File.join(app.root, options[:from]))
|
68
|
+
|
69
|
+
@served[path] = options[:from]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Undo defaults.
|
73
|
+
def reset!
|
74
|
+
@served = Hash.new
|
75
|
+
@packages = Hash.new
|
76
|
+
end
|
77
|
+
|
78
|
+
# Adds some JS packages.
|
79
|
+
#
|
80
|
+
# js :foo, '/js', [ '/js/vendor/jquery.*.js' ]
|
81
|
+
#
|
82
|
+
def js(name, path, files=[])
|
83
|
+
@packages["#{name}.js"] = Package.new(self, name, :js, path, files)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Adds some CSS packages.
|
87
|
+
#
|
88
|
+
# css :app, '/css', [ '/css/screen.css' ]
|
89
|
+
#
|
90
|
+
def css(name, path, files=[])
|
91
|
+
@packages["#{name}.css"] = Package.new(self, name, :css, path, files)
|
92
|
+
end
|
93
|
+
|
94
|
+
attr_reader :app # Sinatra::Base instance
|
95
|
+
attr_reader :packages # Hash, keys are "foo.js", values are Packages
|
96
|
+
attr_reader :served # Hash, paths to be served.
|
97
|
+
# Key is URI path, value is local path
|
98
|
+
|
99
|
+
attrib :js_compression # Symbol, compression method for JS
|
100
|
+
attrib :css_compression # Symbol, compression method for CSS
|
101
|
+
attrib :output_path # '/public'
|
102
|
+
|
103
|
+
attrib :js_compression_options # Hash
|
104
|
+
attrib :css_compression_options # Hash
|
105
|
+
|
106
|
+
# =====================================================================
|
107
|
+
# Stuff
|
108
|
+
|
109
|
+
attr_reader :served
|
110
|
+
|
111
|
+
def build!(&blk)
|
112
|
+
session = Rack::Test::Session.new app
|
113
|
+
|
114
|
+
packages.each { |_, pack|
|
115
|
+
out = session.get(pack.path).body
|
116
|
+
|
117
|
+
write pack.path, out, &blk
|
118
|
+
write pack.production_path, out, &blk
|
119
|
+
}
|
120
|
+
|
121
|
+
files.each { |path, local|
|
122
|
+
out = session.get(path).body
|
123
|
+
write path, out, &blk
|
124
|
+
write BusterHelpers.add_cache_buster(path, local), out, &blk
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
def served?(path)
|
129
|
+
!! local_file_for(path)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns the local file for a given URI path.
|
133
|
+
# Returns nil if a file is not found.
|
134
|
+
def local_file_for(path)
|
135
|
+
path = path.squeeze('/')
|
136
|
+
|
137
|
+
uri, local = served.detect { |uri, local| path[0...uri.size] == uri }
|
138
|
+
|
139
|
+
if local
|
140
|
+
path = path[uri.size..-1]
|
141
|
+
path = File.join app.root, local, path
|
142
|
+
|
143
|
+
path if File.exists?(path)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Returns the local file for a given URI path. (for dynamic files)
|
148
|
+
# Returns nil if a file is not found.
|
149
|
+
# TODO: consolidate with local_file_for
|
150
|
+
def dyn_local_file_for(file, from)
|
151
|
+
# Remove extension
|
152
|
+
file = $1 if file =~ /^(.*)(\.[^\.]+)$/
|
153
|
+
|
154
|
+
# Remove cache-buster (/js/app.28389.js => /js/app)
|
155
|
+
file = $1 if file =~ /^(.*)\.[0-9]+$/
|
156
|
+
|
157
|
+
Dir[File.join(app.root, from, "#{file}.*")].first
|
158
|
+
end
|
159
|
+
|
160
|
+
# Writes `public/#{path}` based on contents of `output`.
|
161
|
+
def write(path, output)
|
162
|
+
require 'fileutils'
|
163
|
+
|
164
|
+
path = File.join(@output_path, path)
|
165
|
+
yield path if block_given?
|
166
|
+
|
167
|
+
FileUtils.mkdir_p File.dirname(path)
|
168
|
+
File.open(path, 'w') { |f| f.write output }
|
169
|
+
end
|
170
|
+
|
171
|
+
# Returns the files as a hash.
|
172
|
+
def files(match=nil)
|
173
|
+
# All
|
174
|
+
# A buncha tuples
|
175
|
+
tuples = @served.map { |prefix, local_path|
|
176
|
+
path = File.expand_path(File.join(@app.root, local_path))
|
177
|
+
spec = File.join(path, '**', '*')
|
178
|
+
|
179
|
+
Dir[spec].map { |f|
|
180
|
+
[ to_uri(f, prefix, path), f ] unless File.directory?(f)
|
181
|
+
}
|
182
|
+
}.flatten.compact
|
183
|
+
|
184
|
+
Hash[*tuples]
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns an array of URI paths of those matching given globs.
|
188
|
+
def glob(*match)
|
189
|
+
tuples = match.map { |spec|
|
190
|
+
paths = files.keys.select { |f| File.fnmatch?(spec, f) }.sort
|
191
|
+
paths.map { |key| [key, files[key]] }
|
192
|
+
}
|
193
|
+
|
194
|
+
HashArray[*tuples.flatten]
|
195
|
+
end
|
196
|
+
|
197
|
+
def cache
|
198
|
+
@cache ||= Hash.new
|
199
|
+
end
|
200
|
+
|
201
|
+
def reset_cache
|
202
|
+
@cache = nil && cache
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
# Returns a URI for a given file
|
207
|
+
# path = '/projects/x/app/css'
|
208
|
+
# to_uri('/projects/x/app/css/file.sass', '/styles', path) => '/styles/file.css'
|
209
|
+
#
|
210
|
+
def to_uri(f, prefix, path)
|
211
|
+
fn = (prefix + f.gsub(path, '')).squeeze('/')
|
212
|
+
|
213
|
+
# Switch the extension ('x.sass' => 'x.css')
|
214
|
+
file_ext = File.extname(fn).to_s[1..-1]
|
215
|
+
out_ext = AssetPack.tilt_formats[file_ext]
|
216
|
+
|
217
|
+
fn = fn.gsub(/\.#{file_ext}$/, ".#{out_ext}") if file_ext && out_ext
|
218
|
+
|
219
|
+
fn
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|