sinatra-assetpack-flexible-compression 0.0.1

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.
Files changed (100) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +2 -0
  3. data/HISTORY.md +131 -0
  4. data/README.md +664 -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.rb +39 -0
  10. data/examples/basic/app/css/test.sass +11 -0
  11. data/examples/basic/app/images/icon.png +0 -0
  12. data/examples/basic/app/js/app.js +4 -0
  13. data/examples/basic/app/js/vendor/jquery.js +2 -0
  14. data/examples/basic/app/js/vendor/jquery.plugin.js +2 -0
  15. data/examples/basic/app/js/vendor/underscore.js +2 -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.rb +45 -0
  22. data/examples/compass/app/css/main.scss +64 -0
  23. data/examples/compass/app/images/icon-scfd8d7d404.png +0 -0
  24. data/examples/compass/app/images/icon/mail.png +0 -0
  25. data/examples/compass/app/images/icon/refresh.png +0 -0
  26. data/examples/compass/app/images/junk/mail.png +0 -0
  27. data/examples/compass/app/images/junk/refresh.png +0 -0
  28. data/examples/compass/app/js/app.js +3 -0
  29. data/examples/compass/app/js/vendor/jquery.js +2 -0
  30. data/examples/compass/app/js/vendor/jquery.plugin.js +2 -0
  31. data/examples/compass/app/js/vendor/underscore.js +2 -0
  32. data/examples/compass/config.ru +3 -0
  33. data/examples/compass/views/index.erb +15 -0
  34. data/lib/sinatra/assetpack.rb +61 -0
  35. data/lib/sinatra/assetpack/buster_helpers.rb +36 -0
  36. data/lib/sinatra/assetpack/class_methods.rb +82 -0
  37. data/lib/sinatra/assetpack/compressor.rb +54 -0
  38. data/lib/sinatra/assetpack/configurator.rb +21 -0
  39. data/lib/sinatra/assetpack/css.rb +36 -0
  40. data/lib/sinatra/assetpack/engine.rb +20 -0
  41. data/lib/sinatra/assetpack/engines/closure.rb +19 -0
  42. data/lib/sinatra/assetpack/engines/jsmin.rb +10 -0
  43. data/lib/sinatra/assetpack/engines/sass.rb +11 -0
  44. data/lib/sinatra/assetpack/engines/simple.rb +11 -0
  45. data/lib/sinatra/assetpack/engines/sqwish.rb +21 -0
  46. data/lib/sinatra/assetpack/engines/uglify.rb +12 -0
  47. data/lib/sinatra/assetpack/engines/yui.rb +22 -0
  48. data/lib/sinatra/assetpack/hasharray.rb +66 -0
  49. data/lib/sinatra/assetpack/helpers.rb +61 -0
  50. data/lib/sinatra/assetpack/html_helpers.rb +17 -0
  51. data/lib/sinatra/assetpack/image.rb +59 -0
  52. data/lib/sinatra/assetpack/options.rb +326 -0
  53. data/lib/sinatra/assetpack/package.rb +116 -0
  54. data/lib/sinatra/assetpack/rake.rb +29 -0
  55. data/lib/sinatra/assetpack/version.rb +7 -0
  56. data/test/app/.gitignore +1 -0
  57. data/test/app/Rakefile +7 -0
  58. data/test/app/app.rb +62 -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.js +1 -0
  70. data/test/app/app/js/hi.coffee +2 -0
  71. data/test/app/app/js/ugly.js +7 -0
  72. data/test/app/app/js_glob/a/b/c1/hello.js +1 -0
  73. data/test/app/app/js_glob/a/b/c2/hi.js +1 -0
  74. data/test/app/app/js_glob/a/b/c2/hola.js +1 -0
  75. data/test/app/app/views/index.haml +1 -0
  76. data/test/arity_test.rb +26 -0
  77. data/test/build_test.rb +31 -0
  78. data/test/cache_test.rb +9 -0
  79. data/test/compressed_test.rb +30 -0
  80. data/test/glob_test.rb +42 -0
  81. data/test/helpers_test.rb +30 -0
  82. data/test/ignore_test.rb +30 -0
  83. data/test/img_test.rb +31 -0
  84. data/test/local_file_test.rb +21 -0
  85. data/test/mime_type_test.rb +33 -0
  86. data/test/non_existent_test.rb +45 -0
  87. data/test/options_test.rb +21 -0
  88. data/test/order_test.rb +20 -0
  89. data/test/preproc_test.rb +28 -0
  90. data/test/redundant_test.rb +11 -0
  91. data/test/simplecss_test.rb +16 -0
  92. data/test/sqwish_test.rb +31 -0
  93. data/test/stylus_test.rb +23 -0
  94. data/test/template_cache_test.rb +29 -0
  95. data/test/test_helper.rb +46 -0
  96. data/test/tilt_test.rb +11 -0
  97. data/test/uglifier_test.rb +23 -0
  98. data/test/unit_test.rb +109 -0
  99. data/test/yui_test.rb +22 -0
  100. metadata +276 -0
@@ -0,0 +1,82 @@
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
+
17
+ # Cache the built files.
18
+ if assets.prebuild && !reload_templates
19
+ assets.cache! { |file| $stderr.write "** Building #{file}...\n" }
20
+ end
21
+ end
22
+
23
+ # Add routes for the compressed versions
24
+ def add_compressed_routes!
25
+ assets.packages.each do |name, package|
26
+ get package.route_regex do
27
+ mtime, contents = @template_cache.fetch(package.path) {
28
+ [ package.mtime, package.minify ]
29
+ }
30
+
31
+ content_type package.type
32
+ last_modified mtime
33
+ contents
34
+ end
35
+ end
36
+ end
37
+
38
+ # Add the routes for the individual files.
39
+ def add_individual_routes!
40
+ assets.served.each do |path, from|
41
+ get %r{#{"^/#{path}/".squeeze('/')}(.*)$} do |file|
42
+ fmt = File.extname(file)[1..-1]
43
+
44
+ # Sanity checks
45
+ pass unless AssetPack.supported_formats.include?(fmt)
46
+ fn = asset_path_for(file, from) or pass
47
+
48
+ pass if settings.assets.ignored?("#{path}/#{file}")
49
+
50
+ # Send headers
51
+ content_type fmt.to_sym
52
+ last_modified File.mtime(fn).to_i
53
+ expires 86400*30, :public
54
+
55
+ format = File.extname(fn)[1..-1]
56
+
57
+ if AssetPack.supported_formats.include?(format)
58
+ # It's a raw file, just send it
59
+ not_found unless format == fmt
60
+
61
+ if fmt == 'css'
62
+ @template_cache.fetch(fn) { asset_filter_css File.read(fn) }
63
+ else
64
+ send_file fn
65
+ end
66
+ else
67
+ # Dynamic file
68
+ not_found unless AssetPack.tilt_formats[format] == fmt
69
+
70
+ @template_cache.fetch(fn) {
71
+ out = render format.to_sym, File.read(fn)
72
+ out = asset_filter_css(out) if fmt == 'css'
73
+ out
74
+ }
75
+ end
76
+ end
77
+ end
78
+
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,54 @@
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
+ # Use defaults if no engine is given.
12
+ return fallback(str, type, options) if engine.nil?
13
+
14
+ # Ensure that the engine exists.
15
+ klass = compressors[[type, engine]]
16
+ raise Error, "Engine #{engine} (#{type}) doesn't exist." unless klass
17
+
18
+ # Ensure that the engine can support that type.
19
+ engine = klass.new
20
+ raise Error, "#{klass} does not support #{type.upcase} compression." unless engine.respond_to?(type)
21
+
22
+ # Build it using the engine, and fallback to defaults if it fails.
23
+ output = engine.send type, str, options
24
+ output ||= fallback(str, type, options) unless options[:no_fallback]
25
+ output
26
+ end
27
+
28
+ # Compresses a given string using the default engines.
29
+ def fallback(str, type, options)
30
+ if type == :js
31
+ compress str, :js, :jsmin, :no_fallback => true
32
+ elsif type == :css
33
+ compress str, :css, :simple, :no_fallback => true
34
+ end
35
+ end
36
+
37
+ def compressors
38
+ @compressors ||= Hash.new
39
+ end
40
+
41
+ def register(type, engine, meth)
42
+ compressors[[type, engine]] = meth
43
+ end
44
+ end
45
+
46
+ require "#{AssetPack::PREFIX}/assetpack/engines/simple"
47
+ require "#{AssetPack::PREFIX}/assetpack/engines/jsmin"
48
+ require "#{AssetPack::PREFIX}/assetpack/engines/yui"
49
+ require "#{AssetPack::PREFIX}/assetpack/engines/sass"
50
+ require "#{AssetPack::PREFIX}/assetpack/engines/sqwish"
51
+ require "#{AssetPack::PREFIX}/assetpack/engines/closure"
52
+ require "#{AssetPack::PREFIX}/assetpack/engines/uglify"
53
+ end
54
+ end
@@ -0,0 +1,21 @@
1
+ module Sinatra
2
+ module AssetPack
3
+ module Configurator
4
+ def self.included(klass)
5
+ klass.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def attrib(name)
10
+ define_method(:"#{name}") { |*a|
11
+ value = a.first
12
+ self.instance_variable_set :"@#{name}", value unless value.nil?
13
+ self.instance_variable_get :"@#{name}"
14
+ }
15
+
16
+ alias_method(:"#{name}=", :"#{name}")
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ module Sinatra
2
+ module AssetPack
3
+ module Css
4
+ def self.preproc(str, assets)
5
+ str.gsub(/url\(["']?(.*?)["']?\)/) { |url|
6
+ path = $1
7
+ file, options = path.split('?')
8
+ local = assets.local_file_for file
9
+
10
+ url = if local
11
+ if options.to_s.include?('embed')
12
+ to_data_uri(local)
13
+ else
14
+ BusterHelpers.add_cache_buster(file, local)
15
+ end
16
+ else
17
+ path
18
+ end
19
+
20
+ "url(#{url})"
21
+ }
22
+ end
23
+
24
+ def self.to_data_uri(file)
25
+ require 'base64'
26
+
27
+ data = File.read(file)
28
+ ext = File.extname(file)
29
+ mime = Sinatra::Base.mime_type(ext)
30
+ b64 = Base64.encode64(data).gsub("\n", '')
31
+
32
+ "data:#{mime};base64,#{b64}"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,20 @@
1
+ module Sinatra
2
+ module AssetPack
3
+ # The base class for all CSS/JS compression engines.
4
+ class Engine
5
+ # Helper for system files.
6
+ # Usage: sys('css', string, "sqwish %f")
7
+ # Returns stdout.
8
+ def sys(type, str, cmd)
9
+ t = Tempfile.new ['', ".#{type}"]
10
+ t.write(str)
11
+ t.close
12
+
13
+ output = `#{cmd.gsub('%f', t.path)}`
14
+ FileUtils.rm t
15
+
16
+ [output, t.path]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module Sinatra::AssetPack
2
+ class ClosureEngine < Engine
3
+ def js(str, options={})
4
+ require 'net/http'
5
+ require 'uri'
6
+
7
+ response = Net::HTTP.post_form(URI.parse('http://closure-compiler.appspot.com/compile'), {
8
+ 'js_code' => str,
9
+ 'compilation_level' => options[:level] || "ADVANCED_OPTIMIZATIONS",
10
+ 'output_format' => 'text',
11
+ 'output_info' => 'compiled_code'
12
+ })
13
+
14
+ response.body
15
+ end
16
+ end
17
+
18
+ Compressor.register :js, :closure, ClosureEngine
19
+ end
@@ -0,0 +1,10 @@
1
+ module Sinatra::AssetPack
2
+ class JsminEngine < Engine
3
+ def js(str, options={})
4
+ require 'jsmin'
5
+ JSMin.minify str
6
+ end
7
+ end
8
+
9
+ Compressor.register :js, :jsmin, JsminEngine
10
+ end
@@ -0,0 +1,11 @@
1
+ module Sinatra::AssetPack
2
+ class SassEngine < Engine
3
+ def css(str, options={})
4
+ Tilt.new("scss", {:style => :compressed}) { str }.render
5
+ rescue LoadError
6
+ nil
7
+ end
8
+ end
9
+
10
+ Compressor.register :css, :sass, SassEngine
11
+ end
@@ -0,0 +1,11 @@
1
+ module Sinatra::AssetPack
2
+ class SimpleEngine < Engine
3
+ def css(str, options={})
4
+ str.gsub! /[ \r\n\t]+/m, ' '
5
+ str.gsub! %r{ *([;\{\},:]) *}, '\1'
6
+ str
7
+ end
8
+ end
9
+
10
+ Compressor.register :css, :simple, SimpleEngine
11
+ end
@@ -0,0 +1,21 @@
1
+ module Sinatra::AssetPack
2
+ class SqwishEngine < Engine
3
+ def css(str, options={})
4
+ cmd = "#{sqwish_bin} %f "
5
+ cmd += "--strict" if options[:strict]
6
+
7
+ _, input = sys :css, str, cmd
8
+ output = input.gsub(/\.css/, '.min.css')
9
+
10
+ File.read(output)
11
+ rescue => e
12
+ nil
13
+ end
14
+
15
+ def sqwish_bin
16
+ ENV['SQWISH_PATH'] || "sqwish"
17
+ end
18
+ end
19
+
20
+ Compressor.register :css, :sqwish, SqwishEngine
21
+ end
@@ -0,0 +1,12 @@
1
+ module Sinatra::AssetPack
2
+ class UglifyEngine < Engine
3
+ def js(str, options={})
4
+ require 'uglifier'
5
+ Uglifier.compile str, options
6
+ rescue => e
7
+ nil
8
+ end
9
+ end
10
+
11
+ Compressor.register :js, :uglify, UglifyEngine
12
+ end
@@ -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,61 @@
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
+ if (settings.respond_to? 'compressed_env') ? settings.compressed_env : settings.production?
46
+ pack.to_production_html options
47
+ else
48
+ pack.to_development_html options
49
+ end
50
+ end
51
+
52
+ def asset_filter_css(str)
53
+ Css.preproc str, settings.assets
54
+ end
55
+
56
+ def asset_path_for(file, from)
57
+ settings.assets.dyn_local_file_for file, from
58
+ end
59
+ end
60
+ end
61
+ end