thorero-assets 0.9.4
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/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
|