thorero-assets 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README +250 -0
- data/Rakefile +63 -0
- data/TODO +5 -0
- data/lib/merb-assets.rb +14 -0
- data/lib/merb-assets/assets.rb +242 -0
- data/lib/merb-assets/assets_mixin.rb +635 -0
- data/spec/merb-assets_spec.rb +257 -0
- data/spec/spec_helper.rb +18 -0
- metadata +73 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 YOUR NAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
merb-assets
|
2
|
+
===========
|
3
|
+
|
4
|
+
Provides extra functionality related to assets:
|
5
|
+
|
6
|
+
# Assets bundling.
|
7
|
+
# Assets hosts.
|
8
|
+
# Helpers to add asset links to views.
|
9
|
+
# Deployment-time assets processing (for instance, with YUI Compressor).
|
10
|
+
|
11
|
+
Quick overview of the API
|
12
|
+
==============================
|
13
|
+
|
14
|
+
# asset_path generates path for asset taking type and name.
|
15
|
+
# UniqueAssetPath class handles generation of paths using subdomains.
|
16
|
+
# AbstractAssetBundler is the base asset bundlers class.
|
17
|
+
|
18
|
+
# auto_link generates conventional asset tags based on controller/action.
|
19
|
+
# link_to creates anchor tag (a tag).
|
20
|
+
# image_tag creates img tag.
|
21
|
+
|
22
|
+
# escape_js escapes JavaScript.
|
23
|
+
# js method translates object into JSON.
|
24
|
+
|
25
|
+
# require_js is smart(-er) way to do includes just once per page no matter
|
26
|
+
how many times partial use it.
|
27
|
+
# require_css is like require_js but for JavaScript.
|
28
|
+
|
29
|
+
# js_include_tag is used when you need to include script tag with bundling.
|
30
|
+
# css_include_tag works like js_include_tag but for stylesheets.
|
31
|
+
# include_required_js generates script tags for previously included files.
|
32
|
+
# include_required_css generates link tags for previously included files.
|
33
|
+
|
34
|
+
# uniq_js_path generates a script tag for path with asset subdomain.
|
35
|
+
# uniq_css_path generates a link tag for path with asset subdomain.
|
36
|
+
|
37
|
+
|
38
|
+
Examples
|
39
|
+
===========
|
40
|
+
|
41
|
+
auto_link to include asset tags using convention:
|
42
|
+
|
43
|
+
We want all possible matches in the FileSys up to the action name
|
44
|
+
Given: controller_name = "namespace/controller"
|
45
|
+
action_name = "action"
|
46
|
+
If all files are present should generate link/script tags for:
|
47
|
+
namespace.(css|js)
|
48
|
+
namespace/controller.(css|js)
|
49
|
+
namespace/controller/action.(css|js)
|
50
|
+
|
51
|
+
link_to and image_tag to make anchor and image tags:
|
52
|
+
|
53
|
+
image_tag('foo.gif')
|
54
|
+
# => <img src='/images/foo.gif' />
|
55
|
+
|
56
|
+
image_tag('foo.gif', :class => 'bar')
|
57
|
+
# => <img src='/images/foo.gif' class='bar' />
|
58
|
+
|
59
|
+
image_tag('foo.gif', :path => '/files/')
|
60
|
+
# => <img src='/files/foo.gif' />
|
61
|
+
|
62
|
+
image_tag('http://test.com/foo.gif')
|
63
|
+
# => <img src="http://test.com/foo.gif">
|
64
|
+
|
65
|
+
image_tag('charts', :path => '/dynamic/')
|
66
|
+
or
|
67
|
+
image_tag('/dynamic/charts')
|
68
|
+
# => <img src="/dynamic/charts">
|
69
|
+
|
70
|
+
link_to("The Merb home page", "http://www.merbivore.com/")
|
71
|
+
# => <a href="http://www.merbivore.com/">The Merb home page</a>
|
72
|
+
|
73
|
+
link_to("The Ruby home page", "http://www.ruby-lang.org", {'class' => 'special', 'target' => 'blank'})
|
74
|
+
# => <a href="http://www.ruby-lang.org" class="special" target="blank">The Ruby home page</a>
|
75
|
+
|
76
|
+
link_to p.title, "/blog/show/#{p.id}"
|
77
|
+
# => <a href="/blog/show/13">The Entry Title</a>
|
78
|
+
|
79
|
+
uniq_css_tag and uniq_js_tag for paths with asset subdomains:
|
80
|
+
|
81
|
+
uniq_css_tag("my")
|
82
|
+
#=> <link href="http://assets2.my-awesome-domain.com/stylesheets/my.css" type="text/css" />
|
83
|
+
|
84
|
+
uniq_js_tag("my")
|
85
|
+
#=> <script type="text/javascript" src="http://assets2.my-awesome-domain.com/javascripts/my.js"></script>
|
86
|
+
|
87
|
+
uniq_js_path("my")
|
88
|
+
#=> "http://assets2.my-awesome-domain.com/javascripts/my.js"
|
89
|
+
|
90
|
+
uniq_js_path(["admin/secrets","home/signup"])
|
91
|
+
#=> ["http://assets2.my-awesome-domain.com/javascripts/admin/secrets.js",
|
92
|
+
"http://assets1.my-awesome-domain.com/javascripts/home/signup.js"]
|
93
|
+
|
94
|
+
re_js and require_css, include_required_js and include_requried_css
|
95
|
+
quire assets in parts/partials just once:
|
96
|
+
|
97
|
+
|
98
|
+
In your application layout:
|
99
|
+
|
100
|
+
js_include_tag :prototype, :lowpro, :bundle => :base
|
101
|
+
|
102
|
+
In your controller layout:
|
103
|
+
|
104
|
+
require_js :bundle => :posts
|
105
|
+
|
106
|
+
The require_js method can be used to require any JavaScript file anywhere
|
107
|
+
in your templates. Regardless of how many times a single script is
|
108
|
+
included with require_js, Merb will only include it once in the header.
|
109
|
+
|
110
|
+
# File: app/views/layouts/application.html.erb
|
111
|
+
|
112
|
+
<html>
|
113
|
+
<head>
|
114
|
+
<%= include_required_js %>
|
115
|
+
<%= include_required_css %>
|
116
|
+
</head>
|
117
|
+
<body>
|
118
|
+
<%= catch_content :layout %>
|
119
|
+
</body>
|
120
|
+
</html>
|
121
|
+
|
122
|
+
# File: app/views/whatever/_part1.herb
|
123
|
+
|
124
|
+
<% require_js 'this' -%>
|
125
|
+
<% require_css 'that', 'another_one' -%>
|
126
|
+
|
127
|
+
# File: app/views/whatever/_part2.herb
|
128
|
+
|
129
|
+
<% require_js 'this', 'something_else' -%>
|
130
|
+
<% require_css 'that' -%>
|
131
|
+
|
132
|
+
# File: app/views/whatever/index.herb
|
133
|
+
|
134
|
+
<%= partial(:part1) %>
|
135
|
+
<%= partial(:part2) %>
|
136
|
+
|
137
|
+
# Will generate the following in the final page...
|
138
|
+
<html>
|
139
|
+
<head>
|
140
|
+
<script src="/javascripts/this.js" type="text/javascript"></script>
|
141
|
+
<script src="/javascripts/something_else.js" type="text/javascript"></script>
|
142
|
+
<link href="/stylesheets/that.css" media="all" rel="Stylesheet" type="text/css"/>
|
143
|
+
<link href="/stylesheets/another_one.css" media="all" rel="Stylesheet" type="text/css"/>
|
144
|
+
</head>
|
145
|
+
.
|
146
|
+
.
|
147
|
+
.
|
148
|
+
</html>
|
149
|
+
|
150
|
+
# my_action.herb has a call to require_css 'style'
|
151
|
+
# File: layout/application.html.erb
|
152
|
+
include_required_css
|
153
|
+
# => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
|
154
|
+
|
155
|
+
# my_action.herb has a call to require_css 'style', 'ie-specific'
|
156
|
+
# File: layout/application.html.erb
|
157
|
+
include_required_css
|
158
|
+
# => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
|
159
|
+
# <link href="/stylesheets/ie-specific.css" media="all" rel="Stylesheet" type="text/css"/>
|
160
|
+
|
161
|
+
# my_action.herb has a call to require_js 'jquery'
|
162
|
+
# File: layout/application.html.erb
|
163
|
+
include_required_js
|
164
|
+
# => <script src="/javascripts/jquery.js" type="text/javascript"></script>
|
165
|
+
|
166
|
+
# my_action.herb has a call to require_js 'jquery', 'effects', 'validation'
|
167
|
+
# File: layout/application.html.erb
|
168
|
+
include_required_js
|
169
|
+
# => <script src="/javascripts/jquery.js" type="text/javascript"></script>
|
170
|
+
# <script src="/javascripts/effects.js" type="text/javascript"></script>
|
171
|
+
# <script src="/javascripts/validation.js" type="text/javascript"></script>
|
172
|
+
|
173
|
+
<% require_css('style') %>
|
174
|
+
# A subsequent call to include_required_css will render...
|
175
|
+
# => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
|
176
|
+
|
177
|
+
<% require_css('style', 'ie-specific') %>
|
178
|
+
# A subsequent call to include_required_css will render...
|
179
|
+
# => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
|
180
|
+
# <link href="/stylesheets/ie-specific.css" media="all" rel="Stylesheet" type="text/css"/>
|
181
|
+
|
182
|
+
<% require_js 'jquery' %>
|
183
|
+
# A subsequent call to include_required_js will render...
|
184
|
+
# => <script src="/javascripts/jquery.js" type="text/javascript"></script>
|
185
|
+
|
186
|
+
<% require_js 'jquery', 'effects' %>
|
187
|
+
# A subsequent call to include_required_js will render...
|
188
|
+
# => <script src="/javascripts/jquery.js" type="text/javascript"></script>
|
189
|
+
# <script src="/javascripts/effects.js" type="text/javascript"></script>
|
190
|
+
|
191
|
+
css_include_tag and js_include_tag that do not use asset subdomains:
|
192
|
+
|
193
|
+
css_include_tag 'style'
|
194
|
+
# => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" charset="utf-8" />
|
195
|
+
|
196
|
+
css_include_tag 'style.css', 'layout'
|
197
|
+
# => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" charset="utf-8" />
|
198
|
+
# <link href="/stylesheets/layout.css" media="all" rel="Stylesheet" type="text/css" charset="utf-8" />
|
199
|
+
|
200
|
+
css_include_tag :menu
|
201
|
+
# => <link href="/stylesheets/menu.css" media="all" rel="Stylesheet" type="text/css" charset="utf-8" />
|
202
|
+
|
203
|
+
css_include_tag :style, :screen
|
204
|
+
# => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" charset="utf-8" />
|
205
|
+
# <link href="/stylesheets/screen.css" media="all" rel="Stylesheet" type="text/css" charset="utf-8" />
|
206
|
+
|
207
|
+
css_include_tag :style, :media => :print
|
208
|
+
# => <link href="/stylesheets/style.css" media="print" rel="Stylesheet" type="text/css" charset="utf-8" />
|
209
|
+
|
210
|
+
css_include_tag :style, :charset => 'iso-8859-1'
|
211
|
+
# => <link href="/stylesheets/style.css" media="print" rel="Stylesheet" type="text/css" charset="iso-8859-1" />
|
212
|
+
|
213
|
+
js_include_tag 'jquery'
|
214
|
+
# => <script src="/javascripts/jquery.js" type="text/javascript"></script>
|
215
|
+
|
216
|
+
js_include_tag 'moofx.js', 'upload'
|
217
|
+
# => <script src="/javascripts/moofx.js" type="text/javascript"></script>
|
218
|
+
# <script src="/javascripts/upload.js" type="text/javascript"></script>
|
219
|
+
|
220
|
+
js_include_tag :effects
|
221
|
+
# => <script src="/javascripts/effects.js" type="text/javascript"></script>
|
222
|
+
|
223
|
+
js_include_tag :jquery, :validation
|
224
|
+
# => <script src="/javascripts/jquery.js" type="text/javascript"></script>
|
225
|
+
# <script src="/javascripts/validation.js" type="text/javascript"></script>
|
226
|
+
|
227
|
+
Utility methods examples
|
228
|
+
==========================
|
229
|
+
|
230
|
+
uniq_css_path("my")
|
231
|
+
#=> "http://assets2.my-awesome-domain.com/stylesheets/my.css"
|
232
|
+
|
233
|
+
uniq_css_path(["admin/secrets","home/signup"])
|
234
|
+
#=> ["http://assets2.my-awesome-domain.com/stylesheets/admin/secrets.css",
|
235
|
+
"http://assets1.my-awesome-domain.com/stylesheets/home/signup.css"]
|
236
|
+
|
237
|
+
uniq_path("/javascripts/my.js","/javascripts/my.css")
|
238
|
+
#=> ["http://assets2.my-awesome-domain.com/javascripts/my.js", "http://assets1.my-awesome-domain.com/javascripts/my.css"]
|
239
|
+
|
240
|
+
uniq_path(["/javascripts/my.js","/stylesheets/my.css"])
|
241
|
+
#=> ["http://assets2.my-awesome-domain.com/javascripts/my.js", "http://assets1.my-awesome-domain.com/stylesheets/my.css"]
|
242
|
+
|
243
|
+
uniq_path(%w(/javascripts/my.js /stylesheets/my.css))
|
244
|
+
#=> ["http://assets2.my-awesome-domain.com/javascripts/my.js", "http://assets1.my-awesome-domain.com/stylesheets/my.css"]
|
245
|
+
|
246
|
+
uniq_path('/stylesheets/somearbitrary.css')
|
247
|
+
#=> "http://assets3.my-awesome-domain.com/stylesheets/somearbitrary.css"
|
248
|
+
|
249
|
+
uniq_path('/images/hostsexypicture.jpg')
|
250
|
+
#=>"http://assets1.my-awesome-domain.com/images/hostsexypicture.jpg"
|
data/Rakefile
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require "extlib"
|
4
|
+
require 'merb-core/tasks/merb_rake_helper'
|
5
|
+
|
6
|
+
##############################################################################
|
7
|
+
# Package && release
|
8
|
+
##############################################################################
|
9
|
+
RUBY_FORGE_PROJECT = "thorero"
|
10
|
+
PROJECT_URL = "http://merbivore.com"
|
11
|
+
PROJECT_SUMMARY = "Merb plugin that provides the helpers for assets and asset bundling"
|
12
|
+
PROJECT_DESCRIPTION = PROJECT_SUMMARY
|
13
|
+
|
14
|
+
GEM_AUTHOR = "Ezra Zygmuntowicz"
|
15
|
+
GEM_EMAIL = "ez@engineyard.com"
|
16
|
+
|
17
|
+
GEM_NAME = "thorero-assets"
|
18
|
+
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
19
|
+
GEM_VERSION = (Merb::MORE_VERSION rescue "0.9.4") + PKG_BUILD
|
20
|
+
|
21
|
+
RELEASE_NAME = "REL #{GEM_VERSION}"
|
22
|
+
|
23
|
+
require "extlib/tasks/release"
|
24
|
+
|
25
|
+
spec = Gem::Specification.new do |s|
|
26
|
+
s.rubyforge_project = RUBY_FORGE_PROJECT
|
27
|
+
s.name = GEM_NAME
|
28
|
+
s.version = GEM_VERSION
|
29
|
+
s.platform = Gem::Platform::RUBY
|
30
|
+
s.has_rdoc = true
|
31
|
+
s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
|
32
|
+
s.summary = PROJECT_SUMMARY
|
33
|
+
s.description = PROJECT_DESCRIPTION
|
34
|
+
s.author = GEM_AUTHOR
|
35
|
+
s.email = GEM_EMAIL
|
36
|
+
s.homepage = PROJECT_URL
|
37
|
+
s.add_dependency('merb-core', '>= 0.9.4')
|
38
|
+
s.require_path = 'lib'
|
39
|
+
s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
|
40
|
+
end
|
41
|
+
|
42
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
43
|
+
pkg.gem_spec = spec
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Install the gem"
|
47
|
+
task :install => [:package] do
|
48
|
+
sh %{#{sudo} gem install #{install_home} pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources}
|
49
|
+
end
|
50
|
+
|
51
|
+
namespace :jruby do
|
52
|
+
|
53
|
+
desc "Run :package and install the resulting .gem with jruby"
|
54
|
+
task :install => :package do
|
55
|
+
sh %{#{sudo} jruby -S gem install #{install_home} pkg/#{GEM_NAME}-#{GEM_VERSION}.gem --no-rdoc --no-ri}
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
desc "Run specs"
|
61
|
+
task :spec do
|
62
|
+
sh %{spec -r spec/spec_helper.rb spec/*_spec.rb}
|
63
|
+
end
|
data/TODO
ADDED
data/lib/merb-assets.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'merb-assets/assets'
|
2
|
+
require 'merb-assets/assets_mixin'
|
3
|
+
|
4
|
+
Merb::BootLoader.before_app_loads do
|
5
|
+
Merb::Controller.send(:include, Merb::AssetsMixin)
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
Merb::Plugins.config[:asset_helpers] = {
|
10
|
+
:max_hosts => 4,
|
11
|
+
:asset_domain => "assets%s",
|
12
|
+
:domain => "my-awesome-domain.com",
|
13
|
+
:use_ssl => false
|
14
|
+
} if Merb::Plugins.config[:asset_helpers].nil?
|
@@ -0,0 +1,242 @@
|
|
1
|
+
module Merb
|
2
|
+
module Assets
|
3
|
+
|
4
|
+
# Check whether the assets should be bundled.
|
5
|
+
#
|
6
|
+
# ==== Returns
|
7
|
+
# Boolean::
|
8
|
+
# True if the assets should be bundled (e.g., production mode or
|
9
|
+
# :bundle_assets is explicitly enabled).
|
10
|
+
def self.bundle?
|
11
|
+
(Merb.environment == 'production') ||
|
12
|
+
(!!Merb::Config[:bundle_assets])
|
13
|
+
end
|
14
|
+
|
15
|
+
# Helpers for handling asset files.
|
16
|
+
module AssetHelpers
|
17
|
+
ASSET_FILE_EXTENSIONS = {
|
18
|
+
:javascript => ".js",
|
19
|
+
:stylesheet => ".css"
|
20
|
+
}
|
21
|
+
|
22
|
+
# Returns the URI path to a particular asset file. If +local_path+ is
|
23
|
+
# true, returns the path relative to the Merb.root, not the public
|
24
|
+
# directory. Uses the path_prefix, if any is configured.
|
25
|
+
#
|
26
|
+
# ==== Parameters
|
27
|
+
# asset_type<Symbol>:: Type of the asset (e.g. :javascript).
|
28
|
+
# filename<~to_s>:: The path to the file.
|
29
|
+
# local_path<Boolean>::
|
30
|
+
# If true, the returned path will be relative to the Merb.root,
|
31
|
+
# otherwise it will be the public URI path. Defaults to false.
|
32
|
+
#
|
33
|
+
# ==== Returns
|
34
|
+
# String:: The path to the asset.
|
35
|
+
#
|
36
|
+
# ==== Examples
|
37
|
+
# asset_path(:javascript, :dingo)
|
38
|
+
# # => "/javascripts/dingo.js"
|
39
|
+
#
|
40
|
+
# asset_path(:javascript, :dingo, true)
|
41
|
+
# # => "public/javascripts/dingo.js"
|
42
|
+
def asset_path(asset_type, filename, local_path = false)
|
43
|
+
filename = filename.to_s
|
44
|
+
if filename !~ /#{'\\' + ASSET_FILE_EXTENSIONS[asset_type]}\Z/ && filename.index('?').nil?
|
45
|
+
filename = "#{filename}#{ASSET_FILE_EXTENSIONS[asset_type]}" # don't modify receiver
|
46
|
+
end
|
47
|
+
if filename !~ %r{^(/|https?://)}
|
48
|
+
filename = "/#{asset_type}s/#{filename}"
|
49
|
+
end
|
50
|
+
if local_path
|
51
|
+
return "public#{filename}"
|
52
|
+
else
|
53
|
+
return "#{Merb::Config[:path_prefix]}#{filename}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Helper for creating unique paths to a file name
|
59
|
+
# Can increase speend for browsers that are limited to a certain number of connections per host
|
60
|
+
# for downloading static files (css, js, images...)
|
61
|
+
class UniqueAssetPath
|
62
|
+
class << self
|
63
|
+
# Builds the path to the file based on the name
|
64
|
+
#
|
65
|
+
# ==== Parameters
|
66
|
+
# filename<String>:: Name of file to generate path for
|
67
|
+
#
|
68
|
+
# ==== Returns
|
69
|
+
# String:: The path to the asset.
|
70
|
+
#
|
71
|
+
# ==== Examples
|
72
|
+
# build("/javascripts/my_fancy_script.js")
|
73
|
+
# # => "https://assets5.my-awesome-domain.com/javascripts/my_fancy_script.js"
|
74
|
+
#
|
75
|
+
def build(filename)
|
76
|
+
config = Merb::Plugins.config[:asset_helpers]
|
77
|
+
#%{#{(USE_SSL ? 'https' : 'http')}://#{sprintf(config[:asset_domain],self.calculate_host_id(file))}.#{config[:domain]}/#{filename}}
|
78
|
+
path = config[:use_ssl] ? 'https://' : 'http://'
|
79
|
+
path << sprintf(config[:asset_domain],self.calculate_host_id(filename)) << ".#{config[:domain]}"
|
80
|
+
path << "/" if filename.index('/') != 0
|
81
|
+
path << filename
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
# Calculates the id for the host
|
87
|
+
def calculate_host_id(filename)
|
88
|
+
ascii_total = 0
|
89
|
+
filename.each_byte {|byte|
|
90
|
+
ascii_total += byte
|
91
|
+
}
|
92
|
+
(ascii_total % Merb::Plugins.config[:asset_helpers][:max_hosts] + 1)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# An abstract class for bundling text assets into single files.
|
98
|
+
class AbstractAssetBundler
|
99
|
+
|
100
|
+
class_inheritable_accessor :cached_bundles
|
101
|
+
self.cached_bundles ||= []
|
102
|
+
|
103
|
+
class << self
|
104
|
+
|
105
|
+
# Mark a bundle as cached.
|
106
|
+
#
|
107
|
+
# ==== Parameters
|
108
|
+
# name<~to_s>:: Name of the bundle
|
109
|
+
#
|
110
|
+
def cache_bundle(name)
|
111
|
+
cached_bundles.push(name.to_s)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Purge a bundle from the cache.
|
115
|
+
#
|
116
|
+
# ==== Parameters
|
117
|
+
# name<~to_s>:: Name of the bundle
|
118
|
+
#
|
119
|
+
def purge_bundle(name)
|
120
|
+
cached_bundles.delete(name.to_s)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Test if a bundle has been cached.
|
124
|
+
#
|
125
|
+
# ==== Parameters
|
126
|
+
# name<~to_s>:: Name of the bundle
|
127
|
+
#
|
128
|
+
# ==== Returns
|
129
|
+
# Boolean:: Whether the bundle has been cached or not.
|
130
|
+
def cached_bundle?(name)
|
131
|
+
cached_bundles.include?(name.to_s)
|
132
|
+
end
|
133
|
+
|
134
|
+
# ==== Parameters
|
135
|
+
# &block:: A block to add as a post-bundle callback.
|
136
|
+
#
|
137
|
+
# ==== Examples
|
138
|
+
# add_callback { |filename| `yuicompressor #{filename}` }
|
139
|
+
def add_callback(&block)
|
140
|
+
callbacks << block
|
141
|
+
end
|
142
|
+
alias_method :after_bundling, :add_callback
|
143
|
+
|
144
|
+
# Retrieve existing callbacks.
|
145
|
+
#
|
146
|
+
# ==== Returns
|
147
|
+
# Array[Proc]:: An array of existing callbacks.
|
148
|
+
def callbacks
|
149
|
+
@callbacks ||= []
|
150
|
+
return @callbacks
|
151
|
+
end
|
152
|
+
|
153
|
+
# The type of asset for which the bundler is responsible. Override
|
154
|
+
# this method in your bundler code.
|
155
|
+
#
|
156
|
+
# ==== Raises
|
157
|
+
# NotImplementedError:: This method is implemented by the bundler.
|
158
|
+
#
|
159
|
+
# ==== Returns
|
160
|
+
# Symbol:: The type of the asset
|
161
|
+
def asset_type
|
162
|
+
raise NotImplementedError, "should return a symbol for the first argument to be passed to asset_path"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# ==== Parameters
|
167
|
+
# name<~to_s>::
|
168
|
+
# Name of the bundle. If name is true, it will be converted to :all.
|
169
|
+
# *files<String>:: Names of the files to bundle.
|
170
|
+
def initialize(name, *files)
|
171
|
+
@bundle_name = name == true ? :all : name
|
172
|
+
@bundle_filename = asset_path(self.class.asset_type, @bundle_name, true)
|
173
|
+
@files = files.map { |f| asset_path(self.class.asset_type, f, true) }
|
174
|
+
end
|
175
|
+
|
176
|
+
# Creates the new bundled file, executing all the callbacks.
|
177
|
+
#
|
178
|
+
# ==== Returns
|
179
|
+
# Symbol:: Name of the bundle.
|
180
|
+
def bundle!
|
181
|
+
# TODO: push it out to the helper level so we don't have to create the helper object.
|
182
|
+
unless self.class.cached_bundle?(@bundle_name)
|
183
|
+
# skip regeneration of new bundled files - preventing multiple merb apps stepping on eachother
|
184
|
+
# file needs to be older than 60 seconds to be regenerated
|
185
|
+
if File.exist?(@bundle_filename) && File.mtime(@bundle_filename) >= Time.now - 60
|
186
|
+
return @bundle_name # serve the old file for now - to be regenerated later
|
187
|
+
end
|
188
|
+
bundle_files(@bundle_filename, *@files)
|
189
|
+
if File.exist?(@bundle_filename)
|
190
|
+
self.class.callbacks.each { |c| c.call(@bundle_filename) }
|
191
|
+
Merb.logger.info("Assets: bundled :#{@bundle_name} into #{File.basename(@bundle_filename)}")
|
192
|
+
self.class.cache_bundle(@bundle_name)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
return @bundle_name
|
196
|
+
end
|
197
|
+
|
198
|
+
protected
|
199
|
+
|
200
|
+
include Merb::Assets::AssetHelpers # for asset_path
|
201
|
+
|
202
|
+
# Bundle all the files into one.
|
203
|
+
#
|
204
|
+
# ==== Parameters
|
205
|
+
# filename<String>:: Name of the bundle file.
|
206
|
+
# *files<String>:: Filenames to be bundled.
|
207
|
+
def bundle_files(filename, *files)
|
208
|
+
File.open(filename, "w") do |f|
|
209
|
+
f.flock(File::LOCK_EX)
|
210
|
+
files.each { |file| f.puts(File.read(file)) }
|
211
|
+
f.flock(File::LOCK_UN)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
# Bundles javascripts into a single file:
|
218
|
+
#
|
219
|
+
# javascripts/#{name}.js
|
220
|
+
class JavascriptAssetBundler < AbstractAssetBundler
|
221
|
+
|
222
|
+
# ==== Returns
|
223
|
+
# Symbol:: The asset type, i.e. :javascript.
|
224
|
+
def self.asset_type
|
225
|
+
:javascript
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Bundles stylesheets into a single file:
|
230
|
+
#
|
231
|
+
# stylesheets/#{name}.css
|
232
|
+
class StylesheetAssetBundler < AbstractAssetBundler
|
233
|
+
|
234
|
+
# ==== Returns
|
235
|
+
# Symbol:: The asset type, i.e. :stylesheet.
|
236
|
+
def self.asset_type
|
237
|
+
:stylesheet
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
end
|