crush 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+
data/README.md CHANGED
@@ -1,80 +1,116 @@
1
1
  Crush
2
2
  =====
3
3
 
4
- Crush is a generic interface, like Tilt, for the various compression engines in Ruby.
5
- It is useful for asset libraries that support multiple javascript and stylesheet compression engines.
4
+ Crush is a set of Tilt templates for the various JavaScript and CSS compression libraries in Ruby.
5
+
6
+ See here for more information about Tilt templates: [http://github.com/rtomayko/tilt](https://github.com/rtomayko/tilt)
7
+
8
+ Well, they're not really templates. They're more like engines or processors. But, anyway, they fit
9
+ in very well with Tilt, because each one likes to do things a little differently. Tilt + Crush cures
10
+ the headache by providing a generic API to use any of the engines you need.
6
11
 
7
12
  Basic Usage
8
13
  -----------
9
14
 
15
+ Step 1, Install:
16
+
17
+ ```
18
+ gem install crush
19
+ ```
20
+
21
+ Step 2, Compress:
22
+
10
23
  ```ruby
11
- require "uglifier"
12
24
  require "crush"
13
- Crush.new("application.js").compress
25
+ Crush.register
26
+ Tilt.new("application.js").render
27
+ # => compressed JavaScript...
14
28
  ```
15
29
 
16
- This would automatically compress the data found in the `application.js` file using Uglifier.
17
- Crush favors engines whose libraries have already been loaded.
30
+ Tilt Mappings
31
+ -------------
18
32
 
19
- If you have multiple compression libraries loaded, and you want to control which one to use,
20
- you can initialize the engine directly.
33
+ If you look closely at the above example, you had to call `Crush.register` before you could
34
+ use any of the engines. That's because, by default, Crush does not automatically register
35
+ its templates with Tilt. But, fear not, it's insanely easy to register them.
21
36
 
22
37
  ```ruby
23
- require "uglifier"
24
- require "jsmin"
25
38
  require "crush"
26
- Crush::Uglifier.new("application.js").compress
39
+ Crush.register
40
+ # or you can use this shortcut to do the same thing:
41
+ require "crush/all"
27
42
  ```
28
43
 
29
- Or you could use `Crush.prefer` to tell Crush which engine you'd like to use.
44
+ If you only want to use the JavaScript templates:
30
45
 
31
46
  ```ruby
32
- require "uglifier"
33
- require "jsmin"
34
47
  require "crush"
35
- Crush.prefer(Crush::Uglifier) # or Crush.prefer(:uglifier)
36
- Crush.new("application.js").compress
48
+ Crush.register_js
49
+ # or just:
50
+ require "crush/js"
37
51
  ```
38
52
 
39
- API
40
- ---
41
-
42
- There a few different ways to compress some data. The API, for the most part, follows the Tilt
43
- API. So this is the standard way of compressing (reading the data from the file):
53
+ CSS engines only:
44
54
 
45
55
  ```ruby
46
- Crush.new("file.js", :mangle => true).compress
56
+ require "crush"
57
+ Crush.register_css
58
+ # or, because I love shortcuts so much:
59
+ require "crush/css"
47
60
  ```
48
61
 
49
- You can also pass the data using a block, like Tilt.
62
+ And finally, it's not hard to register only the ones you need, manually:
50
63
 
51
64
  ```ruby
52
- Crush.new(:uglifier, :mangle => true) { "some data to compress" }.compress
65
+ require "crush"
66
+ Tilt.register Crush::Uglifier, "js"
67
+ Tilt.register Crush::Rainpress, "css"
53
68
  ```
54
69
 
55
- _Note how I declared which engine to use by its name (as a Symbol)._
70
+ Generic API
71
+ -----------
72
+
73
+ The included templates are actually subclasses of `Crush::Engine`, which adds a few
74
+ methods somewhat common to compression libraries.
75
+
76
+ `Crush::Engine.compress` takes the given string and immediately compresses it. It is also
77
+ aliased as `compile`.
78
+
79
+ ```ruby
80
+ Crush::CSSMin.compress "body { color: red; }"
81
+ # => "body{color:red;}"
82
+
83
+ # Using Tilt's interface:
84
+ Tilt[:css].compress "body { color: red; }"
85
+ # => "body{color:red;}"
86
+ ```
56
87
 
57
- I've also included a way to pass data that is more consistent with the other compression engines:
88
+ `Crush::Engine`s do not require data from a file or block like `Tilt::Template`s. They can
89
+ be initialized and given data through the `Crush::Engine#compress` instance method, which
90
+ is also aliased as `compile`.
58
91
 
59
92
  ```ruby
60
- Crush.new(:uglifier, :mangle => true).compress("some data to compress")
93
+ engine = Crush::CSSMin.new
94
+ # Does not through an ArgumentError like a Tilt::Template
95
+ engine.compress "body { color: red; }"
96
+ # => "body{color:red;}"
61
97
  ```
62
98
 
63
- Engines
64
- -------
99
+ Included Engines
100
+ ----------------
65
101
 
66
102
  Support fo these compression engines are included:
67
103
 
68
- ENGINES FILE EXTENSIONS NAME REQUIRED LIBRARIES
69
- -------------------------- ------------------ ---------- ------------------
70
- JSMin .js, .min.js jsmin jsmin
71
- Packr .js, .pack.js packr packr
72
- YUI::JavaScriptCompressor .js, .yui.js yui_js yui/compressor
73
- Closure::Compiler .js, .closure.js closure closure-compiler
74
- Uglifier .js, .ugly.js uglifier uglifier
75
- CSSMin .css, .min.css cssmin cssmin
76
- Rainpress .css, .rain.css rainpress rainpress
77
- YUI::CssCompressor .css, .yui.css yui_css yui/compressor
104
+ ENGINES EXTENSIONS REQUIRED LIBRARIES
105
+ -------------------------- ----------- -----------------------
106
+ JSMin .js jsmin
107
+ Packr .js packr
108
+ YUI::JavaScriptCompressor .js yui/compressor
109
+ Closure::Compiler .js closure-compiler
110
+ Uglifier .js uglifier
111
+ CSSMin .css cssmin
112
+ Rainpress .css rainpress
113
+ YUI::CssCompressor .css yui/compressor
78
114
 
79
115
  Copyright
80
116
  ---------
@@ -5,15 +5,18 @@ require "crush/version"
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "crush"
7
7
  s.version = Crush::VERSION
8
- s.authors = %w(Pete Browne)
8
+ s.authors = [ "Pete Browne" ]
9
9
  s.email = "me@petebrowne.com"
10
10
  s.homepage = "http://github.com/petebrowne/crush"
11
- s.summary = "A generic interface to multiple Ruby compression engines."
12
- s.description = "Crush is a generic interface, like Tilt, for the various compression engines in Ruby."
11
+ s.summary = "Tilt templates for various JavaScript and CSS compression libraries."
12
+ s.description = "Crush is a set of Tilt templates for the various JavaScript and CSS compression libraries in Ruby."
13
13
 
14
14
  s.rubyforge_project = "crush"
15
+
16
+ s.add_dependency "tilt", "~> 1.3"
15
17
 
16
- s.add_development_dependency "rspec", "~> 2.6.0"
18
+ s.add_development_dependency "rspec", "~> 2.6"
19
+ s.add_development_dependency "rake"
17
20
  s.add_development_dependency "jsmin"
18
21
  s.add_development_dependency "packr"
19
22
  s.add_development_dependency "uglifier"
@@ -1,4 +1,9 @@
1
+ require "tilt"
2
+ require "crush/version"
3
+
1
4
  module Crush
5
+ extend self
6
+
2
7
  autoload :Closure, "crush/closure"
3
8
  autoload :CSSMin, "crush/cssmin"
4
9
  autoload :Engine, "crush/engine"
@@ -8,101 +13,42 @@ module Crush
8
13
  autoload :Uglifier, "crush/uglifier"
9
14
  autoload :YUI, "crush/yui"
10
15
 
11
- class EngineNotFound < StandardError; end
12
-
13
- @preferred_engines = {}
14
- @engine_mappings = Hash.new { |h, k| h[k] = [] }
15
-
16
- # Hash of registered compression engines.
17
- def self.mappings
18
- @engine_mappings
19
- end
20
-
21
- # Ensures the extension doesn't include the "."
22
- def self.normalize(ext)
23
- ext.to_s.downcase.sub /^\./, ""
24
- end
25
-
26
- # Registers a compression engine for the given file extension(s).
27
- def self.register(engine, *extensions)
28
- extensions.each do |ext|
29
- ext = normalize(ext)
30
- mappings[ext].unshift(engine).uniq!
31
- end
32
- end
33
-
34
- # Returns true when an engine exists for the given file extension.
35
- def self.registered?(ext)
36
- ext = normalize(ext)
37
- mappings.key?(ext) && !mappings[ext].empty?
38
- end
39
-
40
- # Make the given compression engine preferred. Which means,
41
- # it will reordered the beginning of its mapping.
42
- def self.prefer(engine_or_name, *extensions)
43
- engine = find_by_name(engine_or_name) unless engine_or_name.is_a?(Class)
44
-
45
- if extensions.empty?
46
- mappings.each do |ext, engines|
47
- @preferred_engines[ext] = engine if engines.include?(engine)
48
- end
49
- else
50
- register(engine, *extensions)
51
- extensions.each do |ext|
52
- ext = normalize(ext)
53
- @preferred_engines[ext] = engine
54
- end
55
- end
56
- end
57
-
58
- # Looks for a compression engine. When given a Symbol, it will look for an engine
59
- # by name. When given a String, it will look for a compression engine by filename
60
- # or file extension.
61
- def self.[](path_or_name)
62
- path_or_name.is_a?(Symbol) ? find_by_name(path_or_name) : find_by_path(path_or_name)
63
- end
64
-
65
- # Create a new compression engine. The first argument is used to determine
66
- # which engine to use. This can be either an engine name (as a Symbol) or a path
67
- # to a file (as a String). If a path is given, the engine is initialized with
68
- # a reference to that path.
69
- def self.new(path_or_name, options = {}, &block)
70
- if engine = self[path_or_name]
71
- path = path_or_name.is_a?(Symbol) ? nil : path_or_name
72
- engine.new(path, options, &block)
73
- else
74
- raise EngineNotFound.new("No compression engine registered for '#{path}'")
75
- end
76
- end
77
-
78
- # Look for a compression engine with the given engine name.
79
- # Return nil when no engine is found.
80
- def self.find_by_name(engine_name)
81
- mappings.values.flatten.detect { |engine| engine.engine_name == engine_name.to_s }
82
- end
83
-
84
- # Look for a compression engine for the given filename or file
85
- # extension. Return nil when no engine is found.
86
- def self.find_by_path(path)
87
- pattern = File.basename(path.to_s).downcase
88
- until pattern.empty? || registered?(pattern)
89
- pattern.sub! /^[^.]*\.?/, ""
90
- end
91
- pattern = normalize(pattern)
92
-
93
- preferred_engine = @preferred_engines[pattern]
94
- return preferred_engine unless preferred_engine.nil?
95
-
96
- mappings[pattern].detect(&:engine_initialized?) || mappings[pattern].first
97
- end
98
-
99
- register Crush::JSMin, "js", "min.js"
100
- register Crush::Packr, "js", "pack.js"
101
- register Crush::YUI::JavaScriptCompressor, "js", "yui.js"
102
- register Crush::Closure::Compiler, "js", "closure.js"
103
- register Crush::Uglifier, "js", "ugly.js"
104
-
105
- register Crush::CSSMin, "css", "min.css"
106
- register Crush::Rainpress, "css", "rain.css"
107
- register Crush::YUI::CssCompressor, "css", "yui.css"
16
+ # Registers all of the JavaScripts engines
17
+ # with Tilt in the following order of importance:
18
+ #
19
+ # 1. Crush::Uglifer
20
+ # 2. Crush::Closure::Compiler
21
+ # 3. Crush::YUI::JavaScriptCompressor
22
+ # 4. Crush::Packr
23
+ # 5. Crush::JSMin
24
+ def register_js
25
+ Tilt.register JSMin, "js"
26
+ Tilt.register Packr, "js"
27
+ Tilt.register YUI::JavaScriptCompressor, "js"
28
+ Tilt.register Closure::Compiler, "js"
29
+ Tilt.register Uglifier, "js"
30
+ end
31
+
32
+ # Registers all of the CSS engines
33
+ # with Tilt in the following order of importance:
34
+ #
35
+ # 3. Crush::CSSMin
36
+ # 4. Crush::Rainpress
37
+ # 5. Crush::YUI::CssCompressor
38
+ def register_css
39
+ Tilt.register CSSMin, "css"
40
+ Tilt.register Rainpress, "css"
41
+ Tilt.register YUI::CssCompressor, "css"
42
+ end
43
+
44
+ # Registers all of the included engines
45
+ # with Tilt.
46
+ #
47
+ # (see #register_js)
48
+ # (see #register_css)
49
+ def register
50
+ register_js
51
+ register_css
52
+ end
53
+ alias :register_all :register
108
54
  end
@@ -0,0 +1,3 @@
1
+ require "crush"
2
+
3
+ Crush.register
@@ -1,12 +1,16 @@
1
+ require "crush/engine"
2
+
1
3
  module Crush
2
4
  module Closure
5
+ # Engine implementation of Google's Closure Compiler,
6
+ # using the closure-compiler gem. See:
7
+ #
8
+ # https://rubygems.org/gems/closure-compiler
3
9
  class Compiler < Crush::Engine
4
- def self.engine_name
5
- "closure"
6
- end
10
+ self.default_mime_type = "application/javascript"
7
11
 
8
12
  def self.engine_initialized?
9
- !!(defined? ::Closure) && !!(defined? ::Closure::Compiler)
13
+ !!(defined? ::Closure && defined? ::Closure::Compiler)
10
14
  end
11
15
 
12
16
  def initialize_engine
@@ -15,10 +19,11 @@ module Crush
15
19
 
16
20
  def prepare
17
21
  @engine = ::Closure::Compiler.new(options)
22
+ @output = nil
18
23
  end
19
24
 
20
- def evaluate
21
- @engine.compile(data)
25
+ def evaluate(scope, locals, &block)
26
+ @output ||= @engine.compile(data)
22
27
  end
23
28
  end
24
29
  end
@@ -0,0 +1,3 @@
1
+ require "crush"
2
+
3
+ Crush.register_css
@@ -1,8 +1,12 @@
1
+ require "crush/engine"
2
+
1
3
  module Crush
4
+ # Engine implementation of the CSS minification
5
+ # library, CSSMin. See:
6
+ #
7
+ # https://rubygems.org/gems/cssmin
2
8
  class CSSMin < Engine
3
- def self.engine_name
4
- "cssmin"
5
- end
9
+ self.default_mime_type = "text/css"
6
10
 
7
11
  def self.engine_initialized?
8
12
  !!(defined? ::CSSMin)
@@ -12,8 +16,12 @@ module Crush
12
16
  require_template_library "cssmin"
13
17
  end
14
18
 
15
- def evaluate
16
- ::CSSMin.minify(data)
19
+ def prepare
20
+ @output = nil
21
+ end
22
+
23
+ def evaluate(scope, locals, &block)
24
+ @output ||= ::CSSMin.minify(data)
17
25
  end
18
26
  end
19
27
  end
@@ -1,100 +1,64 @@
1
+ require "tilt/template"
2
+
1
3
  module Crush
2
- class Engine
3
- # The name of the file to be compressed
4
- attr_reader :file
5
-
6
- # A Hash of compression engine specific options. This is passed directly
7
- # to the underlying engine and is not used by the generic interface.
8
- attr_reader :options
9
-
10
- # The data to cmopress; loaded from a file or given directly.
11
- attr_reader :data
12
-
13
- # Used to determine if this class's initialize_engine method has
14
- # been called yet.
15
- @engine_initialized = false
4
+ # Crush::Engine is an abstract class like Tilt::Template,
5
+ # which adds methods common to compression library APIs.
6
+ class Engine < Tilt::Template
16
7
  class << self
17
- attr_accessor :engine_initialized
18
- alias :engine_initialized? :engine_initialized
19
-
20
- # Returns a lowercase, underscored name for the engine.
21
- def engine_name
22
- engine_name = name.to_s.dup
23
- engine_name.sub! /^.*::/ , ""
24
- engine_name.gsub! /([A-Z]+)([A-Z][a-z])/, '\1_\2'
25
- engine_name.gsub! /([a-z\d])([A-Z])/, '\1_\2'
26
- engine_name.tr! "-", "_"
27
- engine_name.downcase!
28
- engine_name
8
+ # Convenience method of initializing an
9
+ # engine and immediately compressing the given
10
+ # data.
11
+ #
12
+ # @param [String] data The data to compress.
13
+ # @param [Hash] options Options to pass to the
14
+ # underlying compressor.
15
+ # @return [String] the compressed data.
16
+ def compress(data, options = {})
17
+ self.new(options).compress(data)
29
18
  end
19
+ alias :compile :compress
30
20
  end
31
21
 
32
- # Create a new engine with the file and options specified. By
33
- # default, the data to compress is read from the file. When a block is given,
34
- # it should read data and return as a String.
22
+ # Override Tilt::Template#initialize so that it
23
+ # does not require a file or block. This is done
24
+ # so that Crush::Engines can follow the usual
25
+ # compressor API convention of having an instance
26
+ # method, #compress, which accepts the data to
27
+ # compress as an argument.
35
28
  #
36
- # All arguments are optional.
37
- def initialize(file = nil, options = nil)
38
- if file.respond_to?(:to_hash)
39
- @options = file.to_hash
40
- else
41
- @file = file
42
- @options = options || {}
43
- end
44
-
45
- unless self.class.engine_initialized?
46
- initialize_engine
47
- self.class.engine_initialized = true
29
+ # (see Tilt::Template#initialize)
30
+ def initialize(file = nil, *args, &block)
31
+ unless block_given? or args[0].respond_to?(:to_str)
32
+ block = Proc.new {}
48
33
  end
49
-
50
- @data = if block_given?
51
- yield
52
- elsif @file
53
- File.respond_to?(:binread) ? File.binread(@file) : File.read(@file)
54
- end
55
-
56
- prepare
34
+ super file, *args, &block
35
+ end
36
+
37
+ # Compresses the given data.
38
+ #
39
+ # @param [String] data The data to compress.
40
+ # @return [String] the compressed data.
41
+ def compress(data = nil)
42
+ @data = data.to_s unless data.nil?
43
+ render
57
44
  end
45
+ alias :compile :compress
58
46
 
59
- # Compresses the data. Data can be read through the file or the block
60
- # given at initialization, or through passing it here directly.
61
- def render(data = nil)
62
- @data = data unless data.nil?
63
- evaluate
47
+ # Override Tilt::Template#render to check for
48
+ # data and raise an error if there isn't any.
49
+ #
50
+ # (see Tilt::Template#render)
51
+ def render(*args)
52
+ raise ArgumentError, "data must be set before rendering" if @data.nil?
53
+ super
64
54
  end
65
- alias :compress :render
66
- alias :compile :render
67
55
 
68
56
  protected
69
57
 
70
- # Called once and only once for each template subclass the first time
71
- # the engine class is initialized. This should be used to require the
72
- # underlying engine library and perform any initial setup.
73
- def initialize_engine
74
-
75
- end
76
-
77
- # Do whatever preparation is necessary to setup the underlying compression
78
- # engine. Called immediately after template data is loaded. Instance
79
- # variables set in this method are available when #compress is called.
80
- def prepare
81
-
82
- end
83
-
84
- # Called when it's time to compress. Return the compressed data
85
- # from this method.
86
- def evaluate
87
-
88
- end
89
-
90
- # Like Kernel::require but issues a warning urging a manual require when
91
- # running under a threaded environment.
92
- def require_template_library(name)
93
- if Thread.list.size > 1
94
- warn "WARN: crush autoloading '#{name}' in a non thread-safe way; " +
95
- "explicit require '#{name}' suggested."
96
- end
97
- require name
98
- end
58
+ # Crush::Engines are usually very, very simple.
59
+ # There's no need for them to be required to
60
+ # implement #prepare.
61
+ def prepare
62
+ end
99
63
  end
100
64
  end