crush 0.2.0 → 0.3.0

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.
@@ -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