sundawg-sinatra-assetpack-fork 0.0.12.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +2 -0
  3. data/HISTORY.md +131 -0
  4. data/README.md +656 -0
  5. data/Rakefile +31 -0
  6. data/docsrc/style.css +32 -0
  7. data/examples/basic/.gitignore +1 -0
  8. data/examples/basic/Rakefile +7 -0
  9. data/examples/basic/app/css/test.sass +11 -0
  10. data/examples/basic/app/images/icon.png +0 -0
  11. data/examples/basic/app/js/app.js +4 -0
  12. data/examples/basic/app/js/vendor/jquery.js +2 -0
  13. data/examples/basic/app/js/vendor/jquery.plugin.js +2 -0
  14. data/examples/basic/app/js/vendor/underscore.js +2 -0
  15. data/examples/basic/app.rb +39 -0
  16. data/examples/basic/views/index.erb +26 -0
  17. data/examples/classic/app.rb +24 -0
  18. data/examples/classic/css/screen.scss +1 -0
  19. data/examples/compass/.gitignore +1 -0
  20. data/examples/compass/Rakefile +7 -0
  21. data/examples/compass/app/css/main.scss +64 -0
  22. data/examples/compass/app/images/icon/mail.png +0 -0
  23. data/examples/compass/app/images/icon/refresh.png +0 -0
  24. data/examples/compass/app/images/icon-scfd8d7d404.png +0 -0
  25. data/examples/compass/app/images/junk/mail.png +0 -0
  26. data/examples/compass/app/images/junk/refresh.png +0 -0
  27. data/examples/compass/app/js/app.js +3 -0
  28. data/examples/compass/app/js/vendor/jquery.js +2 -0
  29. data/examples/compass/app/js/vendor/jquery.plugin.js +2 -0
  30. data/examples/compass/app/js/vendor/underscore.js +2 -0
  31. data/examples/compass/app.rb +45 -0
  32. data/examples/compass/config.ru +3 -0
  33. data/examples/compass/views/index.erb +15 -0
  34. data/lib/sinatra/assetpack/buster_helpers.rb +36 -0
  35. data/lib/sinatra/assetpack/class_methods.rb +82 -0
  36. data/lib/sinatra/assetpack/compressor.rb +54 -0
  37. data/lib/sinatra/assetpack/configurator.rb +21 -0
  38. data/lib/sinatra/assetpack/css.rb +36 -0
  39. data/lib/sinatra/assetpack/engine.rb +20 -0
  40. data/lib/sinatra/assetpack/engines/closure.rb +19 -0
  41. data/lib/sinatra/assetpack/engines/jsmin.rb +10 -0
  42. data/lib/sinatra/assetpack/engines/sass.rb +11 -0
  43. data/lib/sinatra/assetpack/engines/simple.rb +11 -0
  44. data/lib/sinatra/assetpack/engines/sqwish.rb +21 -0
  45. data/lib/sinatra/assetpack/engines/uglify.rb +12 -0
  46. data/lib/sinatra/assetpack/engines/yui.rb +22 -0
  47. data/lib/sinatra/assetpack/hasharray.rb +66 -0
  48. data/lib/sinatra/assetpack/helpers.rb +62 -0
  49. data/lib/sinatra/assetpack/html_helpers.rb +17 -0
  50. data/lib/sinatra/assetpack/image.rb +59 -0
  51. data/lib/sinatra/assetpack/options.rb +329 -0
  52. data/lib/sinatra/assetpack/package.rb +126 -0
  53. data/lib/sinatra/assetpack/rake.rb +29 -0
  54. data/lib/sinatra/assetpack/version.rb +7 -0
  55. data/lib/sinatra/assetpack.rb +61 -0
  56. data/sinatra-assetpack.gemspec +26 -0
  57. data/test/app/.gitignore +1 -0
  58. data/test/app/Rakefile +7 -0
  59. data/test/app/app/css/behavior.htc +1 -0
  60. data/test/app/app/css/js2c.css +494 -0
  61. data/test/app/app/css/screen.sass +9 -0
  62. data/test/app/app/css/sqwishable.css +7 -0
  63. data/test/app/app/css/style.css +3 -0
  64. data/test/app/app/css/stylus.styl +3 -0
  65. data/test/app/app/css_stylus/stylus.styl +3 -0
  66. data/test/app/app/images/background.jpg +1 -0
  67. data/test/app/app/images/email.png +0 -0
  68. data/test/app/app/js/_ignoreme.js +1 -0
  69. data/test/app/app/js/hello.2.js +1 -0
  70. data/test/app/app/js/hello.js +1 -0
  71. data/test/app/app/js/helloe.js +1 -0
  72. data/test/app/app/js/hi.coffee +2 -0
  73. data/test/app/app/js/ugly.js +7 -0
  74. data/test/app/app/js/yoe.coffee +2 -0
  75. data/test/app/app/js_glob/a/b/c1/hello.js +1 -0
  76. data/test/app/app/js_glob/a/b/c2/hi.js +1 -0
  77. data/test/app/app/js_glob/a/b/c2/hola.js +1 -0
  78. data/test/app/app/views/index.haml +1 -0
  79. data/test/app/app.rb +67 -0
  80. data/test/arity_test.rb +26 -0
  81. data/test/build_test.rb +31 -0
  82. data/test/cache_test.rb +9 -0
  83. data/test/combined_test.rb +8 -0
  84. data/test/compressed_test.rb +30 -0
  85. data/test/glob_test.rb +42 -0
  86. data/test/helpers_test.rb +30 -0
  87. data/test/ignore_test.rb +30 -0
  88. data/test/img_test.rb +31 -0
  89. data/test/local_file_test.rb +21 -0
  90. data/test/mime_type_test.rb +33 -0
  91. data/test/non_existent_test.rb +45 -0
  92. data/test/options_test.rb +21 -0
  93. data/test/order_test.rb +20 -0
  94. data/test/preproc_test.rb +28 -0
  95. data/test/redundant_test.rb +11 -0
  96. data/test/simplecss_test.rb +16 -0
  97. data/test/sqwish_test.rb +31 -0
  98. data/test/stylus_test.rb +23 -0
  99. data/test/template_cache_test.rb +29 -0
  100. data/test/test_helper.rb +46 -0
  101. data/test/tilt_test.rb +11 -0
  102. data/test/uglifier_test.rb +23 -0
  103. data/test/unit_test.rb +106 -0
  104. data/test/yui_test.rb +22 -0
  105. metadata +341 -0
@@ -0,0 +1,22 @@
1
+ module Sinatra::AssetPack
2
+ class YuiEngine < Engine
3
+ def initialize
4
+ require 'yui/compressor'
5
+ end
6
+
7
+ def js(str, options={})
8
+ YUI::JavaScriptCompressor.new(options).compress(str)
9
+ rescue LoadError
10
+ nil
11
+ end
12
+
13
+ def css(str, options={})
14
+ YUI::CssCompressor.new.compress(str)
15
+ rescue Errno::ENOENT
16
+ nil
17
+ end
18
+ end
19
+
20
+ Compressor.register :js, :yui, YuiEngine
21
+ Compressor.register :css, :yui, YuiEngine
22
+ end
@@ -0,0 +1,66 @@
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#values.
42
+ def values
43
+ inject([]) { |a, (k, v)| a << v; a }
44
+ end
45
+
46
+ # Returns everything as a hash.
47
+ def to_hash
48
+ inject({}) { |hash, (k, v)| hash[k] = v; hash }
49
+ end
50
+
51
+ def keys
52
+ inject([]) { |a, (k, v)| a << k; a }
53
+ end
54
+
55
+ [:each, :map, :map!, :reject, :reject!, :select, :select!].each do |meth|
56
+ send(:define_method, meth) { |*a, &block|
57
+ if block.respond_to?(:call)
58
+ super(*a) { |hash| block.call *hash.to_a }
59
+ else
60
+ super(*a)
61
+ end
62
+ }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,62 @@
1
+ module Sinatra
2
+ module AssetPack
3
+ module Helpers
4
+ def css(*args)
5
+ show_asset_pack :css, *args
6
+ end
7
+
8
+ def js(*args)
9
+ show_asset_pack :js, *args
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[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, *args)
29
+ names = Array.new
30
+ while args.first.is_a?(Symbol)
31
+ names << args.shift
32
+ end
33
+
34
+ options = args.shift if args.first.is_a?(Hash)
35
+
36
+ names.map { |name|
37
+ show_one_asset_pack type, name, (options || Hash.new)
38
+ }.join "\n"
39
+ end
40
+
41
+ def show_one_asset_pack(type, name, options={})
42
+ pack = settings.assets.packages["#{name}.#{type}"]
43
+ return "" unless pack
44
+
45
+ # force production packageing for override, stage, or production
46
+ if ENV['ASSETPACK_FORCE_PRODUCTION'] || settings.stage? || settings.production?
47
+ pack.to_production_html options
48
+ else
49
+ pack.to_development_html options
50
+ end
51
+ end
52
+
53
+ def asset_filter_css(str)
54
+ Css.preproc str, settings.assets
55
+ end
56
+
57
+ def asset_path_for(file, from)
58
+ settings.assets.dyn_local_file_for file, from
59
+ end
60
+ end
61
+ end
62
+ 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("&#x2F;", '/') # 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,59 @@
1
+ module Sinatra
2
+ module AssetPack
3
+ # An image.
4
+ #
5
+ # == Common usage
6
+ #
7
+ # i = Image['/app/images/background.png'] # Local file path
8
+ #
9
+ # i.dimensions # Tuple for [ width, height ]
10
+ # i.width
11
+ # i.height
12
+ #
13
+ # i.dimensions? # True if dimensions are available
14
+ # # (e.g., if ImageMagick is installed and working)
15
+ #
16
+ class Image
17
+ # Looks up an image.
18
+ # This makes each image only have one associated instance forever.
19
+ def self.[](fname)
20
+ fname = File.expand_path(fname) || fname
21
+
22
+ @cache ||= Hash.new
23
+ @cache[fname] ||= new fname
24
+ end
25
+
26
+ def initialize(file)
27
+ @file = file
28
+ end
29
+
30
+ def dimensions
31
+ return @dimensions unless @dimensions.nil?
32
+
33
+ _, _, dim = `identify "#{@file}"`.split(' ')
34
+ w, h = dim.split('x')
35
+
36
+ if w.to_i != 0 && h.to_i != 0
37
+ @dimensions = [w.to_i, h.to_i]
38
+ else
39
+ @dimensions = false
40
+ end
41
+
42
+ rescue => e
43
+ @dimensions = false
44
+ end
45
+
46
+ def dimensions?
47
+ !! dimensions
48
+ end
49
+
50
+ def width
51
+ dimensions? && dimensions[0]
52
+ end
53
+
54
+ def height
55
+ dimensions? && dimensions[1]
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,329 @@
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
+ include Configurator
42
+
43
+ def initialize(app, &blk)
44
+ unless app.root?
45
+ raise Error, "Please set :root in your Sinatra app."
46
+ end
47
+
48
+ @app = app
49
+ @js_compression = :jsmin
50
+ @css_compression = :simple
51
+
52
+ begin
53
+ @output_path = app.public
54
+ rescue NoMethodError
55
+ @output_path = app.public_folder
56
+ end
57
+
58
+ @js_compression_options = Hash.new
59
+ @css_compression_options = Hash.new
60
+
61
+ @ignored = Array.new
62
+
63
+ reset!
64
+
65
+ # Defaults!
66
+ serve '/css', :from => 'app/css'
67
+ serve '/js', :from => 'app/js'
68
+ serve '/images', :from => 'app/images'
69
+
70
+ ignore '.*'
71
+ ignore '_*'
72
+
73
+ blk.arity <= 0 ? instance_eval(&blk) : yield(self) if block_given?
74
+ end
75
+
76
+ # =====================================================================
77
+ # DSL methods
78
+
79
+ def serve(path, options={})
80
+ raise Error unless options[:from]
81
+ return unless File.directory?(File.join(app.root, options[:from]))
82
+
83
+ @served[path] = options[:from]
84
+ end
85
+
86
+ # Undo defaults.
87
+ def reset!
88
+ @served = Hash.new
89
+ @packages = Hash.new
90
+ end
91
+
92
+ # Ignores a given path spec.
93
+ def ignore(spec)
94
+ if spec[0] == '/'
95
+ @ignored << "#{spec}"
96
+ @ignored << "#{spec}/**"
97
+ else
98
+ @ignored << "**/#{spec}"
99
+ @ignored << "**/#{spec}/**"
100
+ end
101
+ end
102
+
103
+ # Makes nothing ignored. Use this if you don't want to ignore dotfiles and underscore files.
104
+ def clear_ignores!
105
+ @ignored = Array.new
106
+ end
107
+
108
+ # Checks if a given path is ignored.
109
+ def ignored?(fn)
110
+ @ignored.any? { |spec| File.fnmatch spec, fn }
111
+ end
112
+
113
+ # Adds some JS packages.
114
+ #
115
+ # js :foo, [ '/js/vendor/jquery.*.js', ... ]
116
+ # js :foo, '/js/foo.js', [ '/js/vendor/jquery.*.js', ... ]
117
+ #
118
+ def js(name, *args)
119
+ js_or_css :js, name, *args
120
+ end
121
+
122
+ # Adds some CSS packages.
123
+ #
124
+ # css :app, [ '/css/screen.css', ... ]
125
+ # css :app, '/css/app.css', [ '/css/screen.css', ... ]
126
+ #
127
+ def css(name, *args) #path, files=[])
128
+ js_or_css :css, name, *args
129
+ end
130
+
131
+ def js_or_css(type, name, *args)
132
+ # Account for "css :name, '/path/to/css', [ files ]"
133
+ if args[0].is_a?(String) && args[1].respond_to?(:each)
134
+ path, files = args
135
+
136
+ # Account for "css :name, [ files ]"
137
+ elsif args[0].respond_to?(:each)
138
+ path = "/assets/#{name}.#{type}" # /assets/foobar.css by default
139
+ files = args[0]
140
+
141
+ else
142
+ raise ArgumentError
143
+ end
144
+
145
+ @packages["#{name}.#{type}"] = Package.new(self, name, type, path, files)
146
+ end
147
+
148
+ attr_reader :app # Sinatra::Base instance
149
+ attr_reader :packages # Hash, keys are "foo.js", values are Packages
150
+ attr_reader :served # Hash, paths to be served.
151
+ # Key is URI path, value is local path
152
+
153
+ attrib :js_compression # Symbol, compression method for JS
154
+ attrib :css_compression # Symbol, compression method for CSS
155
+ attrib :output_path # '/public'
156
+
157
+ attrib :js_compression_options # Hash
158
+ attrib :css_compression_options # Hash
159
+
160
+ attrib :prebuild # Bool
161
+
162
+ def js_compression(name=nil, options=nil)
163
+ @js_compression = name unless name.nil?
164
+ @js_compression_options = options if options.is_a?(Hash)
165
+ @js_compression
166
+ end
167
+
168
+ def css_compression(name=nil, options=nil)
169
+ @css_compression = name unless name.nil?
170
+ @css_compression_options = options if options.is_a?(Hash)
171
+ @css_compression
172
+ end
173
+
174
+ # =====================================================================
175
+ # Stuff
176
+
177
+ attr_reader :served
178
+
179
+ def build!(&blk)
180
+ session = Rack::Test::Session.new app
181
+
182
+ get = lambda { |path|
183
+ response = session.get(path)
184
+ out = response.body
185
+ mtime = Time.parse(response.headers['Last-Modified']) if response.headers['Last-Modified']
186
+
187
+ [ out, mtime ]
188
+ }
189
+
190
+ packages.each { |_, pack|
191
+ out, mtime = get[pack.path]
192
+
193
+ write pack.path, out, mtime, &blk
194
+ write pack.production_path, out, mtime, &blk
195
+ }
196
+
197
+ files.each { |path, local|
198
+ out, mtime = get[path]
199
+
200
+ write path, out, mtime, &blk
201
+ write BusterHelpers.add_cache_buster(path, local), out, mtime, &blk
202
+ }
203
+ end
204
+
205
+ # Caches the packages.
206
+ def cache!(&blk)
207
+ return false if app.reload_templates
208
+
209
+ session = Rack::Test::Session.new app
210
+ packages.each { |_, pack|
211
+ yield pack.path if block_given?
212
+ session.get(pack.path)
213
+ }
214
+
215
+ true
216
+ end
217
+
218
+ def served?(path)
219
+ !! local_file_for(path)
220
+ end
221
+
222
+ # Returns the local file for a given URI path.
223
+ # Returns nil if a file is not found.
224
+ def local_file_for(path)
225
+ path = path.squeeze('/')
226
+
227
+ uri, local = served.detect { |uri, local| path[0...uri.size] == uri }
228
+
229
+ if local
230
+ path = path[uri.size..-1]
231
+ path = File.join app.root, local, path
232
+
233
+ path if File.exists?(path)
234
+ end
235
+ end
236
+
237
+ # Returns the local file for a given URI path. (for dynamic files)
238
+ # Returns nil if a file is not found.
239
+ # TODO: consolidate with local_file_for
240
+ def dyn_local_file_for(requested_file, from)
241
+ # Remove extension
242
+ file = requested_file
243
+ extension = ''
244
+
245
+ file.sub(/^(.*)(\.[^\.]+)$/) { file, extension = $1, $2 }
246
+
247
+ # Remove cache-buster (/js/app.28389.js => /js/app)
248
+ file = $1 if file =~ /^(.*)\.[0-9]+$/
249
+
250
+ Dir[File.join(app.root, from, "#{file}#{extension}")].first
251
+ end
252
+
253
+ # Writes `public/#{path}` based on contents of `output`.
254
+ def write(path, output, mtime=nil)
255
+ require 'fileutils'
256
+
257
+ path = File.join(@output_path, path)
258
+ yield path if block_given?
259
+
260
+ FileUtils.mkdir_p File.dirname(path)
261
+ File.open(path, 'w') { |f| f.write output }
262
+
263
+ if mtime
264
+ File.utime mtime, mtime, path
265
+ end
266
+ end
267
+
268
+ # Returns the files as a hash.
269
+ def files(match=nil)
270
+ # All
271
+ # A buncha tuples
272
+ tuples = @served.map { |prefix, local_path|
273
+ path = File.expand_path(File.join(@app.root, local_path))
274
+ spec = File.join(path, '**', '*')
275
+
276
+ Dir[spec].map { |f|
277
+ [ to_uri(f, prefix, path), f ] unless File.directory?(f)
278
+ }
279
+ }.flatten.compact
280
+
281
+ Hash[*tuples]
282
+ end
283
+
284
+ # Returns an array of URI paths of those matching given globs.
285
+ #
286
+ # glob('spec')
287
+ # glob(['spec1', 'spec2' ...])
288
+ # glob('spec', preserve: true)
289
+ #
290
+ # If `preserve` is set to true, it will preserve any specs that are not
291
+ # wildcards that don't match anything.
292
+ #
293
+ def glob(match, options={})
294
+
295
+ match = [*match] # Force array-ness
296
+
297
+ paths = match.map { |spec|
298
+ if options[:preserve] && !spec.include?('*')
299
+ spec
300
+ else
301
+ files.keys.select { |f| File.fnmatch?(spec, f) }.sort
302
+ end
303
+ }.flatten
304
+
305
+ paths = paths.uniq
306
+ tuples = paths.map { |key| [key, files[key]] }
307
+
308
+ HashArray[*tuples.flatten]
309
+ end
310
+
311
+ private
312
+ # Returns a URI for a given file
313
+ # path = '/projects/x/app/css'
314
+ # to_uri('/projects/x/app/css/file.sass', '/styles', path) => '/styles/file.css'
315
+ #
316
+ def to_uri(f, prefix, path)
317
+ fn = (prefix + f.gsub(path, '')).squeeze('/')
318
+
319
+ # Switch the extension ('x.sass' => 'x.css')
320
+ file_ext = File.extname(fn).to_s[1..-1]
321
+ out_ext = AssetPack.tilt_formats[file_ext]
322
+
323
+ fn = fn.gsub(/\.#{file_ext}$/, ".#{out_ext}") if file_ext && out_ext
324
+
325
+ fn
326
+ end
327
+ end
328
+ end
329
+ end
@@ -0,0 +1,126 @@
1
+ module Sinatra
2
+ module AssetPack
3
+ # A package.
4
+ #
5
+ # == Common usage
6
+ #
7
+ # package = assets.packages['application.css']
8
+ #
9
+ # package.files # List of local files
10
+ # package.paths # List of URI paths
11
+ #
12
+ # package.type # :css or :js
13
+ # package.css?
14
+ # package.js?
15
+ #
16
+ # package.path # '/css/application.css' => where to serve the compressed file
17
+ #
18
+ # package.to_development_html
19
+ # package.to_production_html
20
+ #
21
+ class Package
22
+ include HtmlHelpers
23
+ include BusterHelpers
24
+
25
+ def initialize(assets, name, type, path, filespecs)
26
+ @assets = assets # Options instance
27
+ @name = name # "application"
28
+ @type = type # :js or :css
29
+ @path = path # '/js/app.js' -- where to served the compressed file
30
+ @filespecs = filespecs # [ '/js/*.js' ]
31
+ end
32
+
33
+ attr_reader :type
34
+ attr_reader :path
35
+ attr_reader :filespecs
36
+ attr_reader :name
37
+
38
+ # Returns a list of URIs
39
+ def paths_and_files
40
+ list = @assets.glob(@filespecs, :preserve => true)
41
+ list.reject! { |path, file| @assets.ignored?(path) }
42
+ list
43
+ end
44
+
45
+ def files
46
+ paths_and_files.values
47
+ end
48
+
49
+ def paths
50
+ paths_and_files.keys
51
+ end
52
+
53
+ def mtime
54
+ BusterHelpers.mtime_for(files)
55
+ end
56
+
57
+ # Returns the regex for the route, including cache buster crap.
58
+ def route_regex
59
+ re = @path.gsub(/(.[^.]+)$/) { |ext| "(?:\.[0-9]+)?#{ext}" }
60
+ /^#{re}$/
61
+ end
62
+
63
+ def to_development_html(options={})
64
+ paths_and_files.map { |path, file|
65
+ path = add_cache_buster(path, file) # app.css => app.829378.css
66
+ link_tag(path, options)
67
+ }.join("\n")
68
+ end
69
+
70
+ # The URI path of the minified file (with cache buster)
71
+ def production_path
72
+ add_cache_buster @path, *files
73
+ end
74
+
75
+ def to_production_html(options={})
76
+ link_tag production_path, options
77
+ end
78
+
79
+ def minify
80
+ engine = @assets.send(:"#{@type}_compression")
81
+ options = @assets.send(:"#{@type}_compression_options")
82
+
83
+ Compressor.compress combined, @type, engine, options
84
+ end
85
+
86
+ # The cache hash.
87
+ def hash
88
+ if @assets.app.development?
89
+ "#{name}.#{type}/#{mtime}"
90
+ else
91
+ "#{name}.#{type}"
92
+ end
93
+ end
94
+
95
+ def combined
96
+ session = Rack::Test::Session.new(@assets.app)
97
+ paths.map { |path|
98
+ result = session.get(path)
99
+ response_encoding = result.content_type.split(/;\s*charset\s*=\s*/).last.upcase rescue 'ASCII-8BIT'
100
+ # p [:combined, path, result.body.encoding.name, response_encoding]
101
+ result.body.force_encoding(response_encoding).encode(Encoding.default_external || 'ASCII-8BIT') if result.status == 200
102
+ }.join("\n")
103
+ end
104
+
105
+ def js?() @type == :js; end
106
+ def css?() @type == :css; end
107
+
108
+ private
109
+ def link_tag(file, options={})
110
+ # allow for building of assets from an application context and CDN host
111
+ context = options[:context]
112
+ host = options[:host]
113
+ file = "#{context}#{file}" unless context.nil? || context.empty?
114
+ file = "#{host}#{file}" unless host.nil? || host.empty?
115
+ options_copy = options.clone
116
+ options_copy.delete(:context)
117
+ options_copy.delete(:host)
118
+ if js?
119
+ "<script src='#{e file}'#{kv options_copy}></script>"
120
+ elsif css?
121
+ "<link rel='stylesheet' href='#{e file}'#{kv options_copy} />"
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,29 @@
1
+ unless defined?(APP_FILE) && defined?(APP_CLASS)
2
+ $stderr.write "Error: Please set APP_FILE and APP_CLASS before setting up AssetPack rake tasks.\n"
3
+ $stderr.write "Example:\n"
4
+ $stderr.write " APP_FILE = 'init.rb'\n"
5
+ $stderr.write " APP_CLASS = 'Application'\n"
6
+ $stderr.write " require 'sinatra/assetpack/rake'\n"
7
+ $stderr.write "\n"
8
+ exit
9
+ end
10
+
11
+ def class_from_string(str)
12
+ str.split('::').inject(Object) do |mod, class_name|
13
+ mod.const_get(class_name)
14
+ end
15
+ end
16
+
17
+ def app
18
+ require File.expand_path(APP_FILE, Dir.pwd)
19
+ class_from_string(APP_CLASS)
20
+ end
21
+
22
+ namespace :assetpack do
23
+ desc "Build assets"
24
+ task :build do
25
+ app.assets.build! { |file|
26
+ puts "+ #{file.gsub(Dir.pwd, '')}"
27
+ }
28
+ end
29
+ end