asset_library 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -58,63 +58,66 @@ A cached module is simply the concatenation of its constituent files.
58
58
  A typical configuration (Yaml) file might look like this. In Rails, this
59
59
  should be in <tt>config/asset_library.yml</tt>.
60
60
 
61
- javascripts:
62
- cache: library
63
- optional_suffix: compressed
64
- base: javascripts
65
- suffix: js
66
- files:
67
- - vendor/jquery
68
-
69
- # jQuery UI parts we need
70
- - vendor/jquery-ui/ui.core
71
- - vendor/jquery-ui/ui.draggable
72
- - vendor/jquery-ui/ui.droppable
73
- - vendor/jquery-ui/ui.sortable
74
- - vendor/jquery-ui/effects.core
75
-
76
- - lib
77
- - plugins/*
78
- - application
79
- - initializers/*
80
-
81
- tiny_mce_javascripts:
82
- # TinyMCE doesn't give us a choice on cache name
83
- cache: vendor/tiny_mce/tiny_mce_gzip
84
- optional_suffix: compressed
85
- base: javascripts
86
- suffix: js
87
- files:
88
- - vendor/tiny_mce/tiny_mce
89
- # ... it's possible to bundle all of TinyMCE together with a bit of magic
90
-
91
- stylesheets:
92
- cache: library
93
- base: stylesheets
94
- suffix: css
95
- formats:
96
- - ie6: [null, ie8, ie7, ie6]
97
- - ie7: [null, ie8, ie7]
98
- - ie8: [null, ie8]
99
- files:
100
- - reset
101
- - application
102
- - layout
103
- - views/**/*
61
+ modules:
62
+ javascripts:
63
+ cache: library
64
+ optional_suffix: compressed
65
+ base: javascripts
66
+ suffix: js
67
+ files:
68
+ - vendor/jquery
69
+
70
+ # jQuery UI parts we need
71
+ - vendor/jquery-ui/ui.core
72
+ - vendor/jquery-ui/ui.draggable
73
+ - vendor/jquery-ui/ui.droppable
74
+ - vendor/jquery-ui/ui.sortable
75
+ - vendor/jquery-ui/effects.core
76
+
77
+ - lib
78
+ - plugins/*
79
+ - application
80
+ - initializers/*
81
+
82
+ tiny_mce_javascripts:
83
+ # TinyMCE doesn't give us a choice on cache name
84
+ cache: vendor/tiny_mce/tiny_mce_gzip
85
+ optional_suffix: compressed
86
+ base: javascripts
87
+ suffix: js
88
+ files:
89
+ - vendor/tiny_mce/tiny_mce
90
+ # ... it's possible to bundle all of TinyMCE together with a bit of magic
91
+
92
+ stylesheets:
93
+ cache: library
94
+ base: stylesheets
95
+ suffix: css
96
+ formats:
97
+ - ie6: [null, ie8, ie7, ie6]
98
+ - ie7: [null, ie8, ie7]
99
+ - ie8: [null, ie8]
100
+ files:
101
+ - reset
102
+ - application
103
+ - layout
104
+ - views/**/*
104
105
 
105
106
  # in general...
106
- #module_name:
107
- # cache: cache_file
108
- # base: base_path_of_assets_in_web_root
109
- # suffix: suffix ("css" or "js")
110
- # formats:
111
- # format1: ["extra_suffix_1", "extra_suffix_2"]
112
- # format2: [null, "extra_suffix3"]
113
- # optional_suffix: optional_suffix
114
- # files:
115
- # - file1_relative_to_base
116
- # - file2_relative_to_base
117
- # - ...
107
+ # modules:
108
+ # module_name:
109
+ # cache: cache_file
110
+ # base: base_path_of_assets_in_web_root
111
+ # suffix: suffix ("css" or "js")
112
+ # formats:
113
+ # format1: ["extra_suffix_1", "extra_suffix_2"]
114
+ # format2: [null, "extra_suffix3"]
115
+ # optional_suffix: optional_suffix
116
+ # files:
117
+ # - file1_relative_to_base
118
+ # - file2_relative_to_base
119
+ # - ...
120
+ # ...
118
121
 
119
122
  Here are what the various configuration elements mean:
120
123
 
@@ -160,6 +163,51 @@ not exist will be excluded; globbing will include/exclude files with
160
163
  <tt>formats</tt> as appropriate; and files without <tt>optional_suffix</tt>
161
164
  will not be output alongside files with the same name endowed with the suffix.
162
165
 
166
+ == Compilers
167
+
168
+ If you would like asset library to do more than just concatenate the
169
+ files to produce your modules, then you need to specify a compiler.
170
+ Asset Library currently ships with support for one compiler: Closure
171
+ Compiler, and has an extendable framework to add more.
172
+
173
+ To specify that a module is to be compiled with the Closure Compiler,
174
+ use the <tt>compiler</tt> key for that module. You may pass
175
+ additional flags with the <tt>compiler_flags</tt> option:
176
+
177
+ modules:
178
+ javascripts:
179
+ cache: library
180
+ compiler: closure
181
+ compiler_flags: --warning_level QUIET
182
+ base: javascripts
183
+ suffix: js
184
+ files:
185
+ - javascripts/**/*
186
+
187
+ You also need to configure the compiler by using a
188
+ <tt>compilers.closure</tt> key:
189
+
190
+ compilers:
191
+ closure:
192
+ path: /path/to/closure.jar
193
+ flags: --warning_level QUIET
194
+
195
+ The full list of Closure Compiler settings are:
196
+
197
+ <tt>path</tt>: The path to the Closure Compiler jar. This is
198
+ mandatory.
199
+
200
+ <tt>flags</tt>: Flags to pass to Closure Compiler. These are combined
201
+ with any per-module flags given via the <tt>compiler_flags</tt> option
202
+ in the individual modules. Default: no flags.
203
+
204
+ <tt>java</tt>: The java command to use. This will be searched for in
205
+ the <tt>PATH</tt> unless it is an absolute filename. Default:
206
+ <tt>java</tt>.
207
+
208
+ <tt>java_flags</tt>: Flags to pass to the java interpreter. Default:
209
+ no flags.
210
+
163
211
  == Copyright
164
212
 
165
213
  I believe in software freedom, not any abomination thereof. This project is
@@ -4,10 +4,23 @@ class AssetLibrary
4
4
  class AssetModule
5
5
  attr_reader(:config)
6
6
 
7
- def initialize(config)
7
+ def initialize(name, config)
8
+ @name = name.to_s
8
9
  @config = config
9
10
  end
10
11
 
12
+ attr_reader :name
13
+
14
+ # Returns the type of compiler to use for this asset module.
15
+ def compiler_type
16
+ (config[:compiler] || :default).to_sym
17
+ end
18
+
19
+ # Returns the Array of compiler flags to use.
20
+ def compiler_flags
21
+ Util.normalize_flags(config[:compiler_flags])
22
+ end
23
+
11
24
  # Returns an Array of Assets to include.
12
25
  #
13
26
  # Arguments:
@@ -49,36 +62,10 @@ class AssetLibrary
49
62
  extra_suffixes.inject([]) { |r, s| r.concat(assets_with_extra_suffix(s)) }
50
63
  end
51
64
 
52
- def contents(format = nil)
53
- s = StringIO.new
54
-
55
- assets(format).each do |asset|
56
- File.open(asset.absolute_path, 'r') do |infile|
57
- s.write(infile.read)
58
- end
59
- end
60
- s.rewind
61
-
62
- s
63
- end
64
-
65
65
  # Returns an Asset representing the cache file
66
66
  def cache_asset(format = nil)
67
67
  extra = format ? ".#{format}" : ''
68
68
  Asset.new(File.join(AssetLibrary.root, config[:base], "#{config[:cache]}#{extra}.#{config[:suffix]}"))
69
69
  end
70
-
71
- def write_cache(format = nil)
72
- File.open(cache_asset(format).absolute_path, 'w') do |outfile|
73
- outfile.write(contents(format).read)
74
- end
75
- end
76
-
77
- def write_all_caches
78
- write_cache
79
- (config[:formats] || {}).keys.each do |format|
80
- write_cache(format)
81
- end
82
- end
83
70
  end
84
71
  end
@@ -0,0 +1,32 @@
1
+ class AssetLibrary
2
+ module Compiler
3
+ class Base
4
+ def initialize(config)
5
+ @config = config || {}
6
+ @asset_modules = []
7
+ end
8
+
9
+ attr_reader :config, :asset_modules
10
+
11
+ def add_asset_module(asset_module)
12
+ @asset_modules << asset_module
13
+ end
14
+
15
+ def write_all_caches(format = nil)
16
+ raise NotImplementedError, "abstract method"
17
+ end
18
+
19
+ protected
20
+
21
+ # Return the absolute output path for the given asset module.
22
+ def output_path(asset_module, format)
23
+ asset_module.cache_asset(format).absolute_path
24
+ end
25
+
26
+ # Return the absolute input paths for the given asset module.
27
+ def input_paths(asset_module, format)
28
+ asset_module.assets(format).map{|asset| asset.absolute_path}
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,39 @@
1
+ require 'tmpdir'
2
+
3
+ class AssetLibrary
4
+ module Compiler
5
+ class Closure < Base
6
+ def initialize(config)
7
+ super
8
+ config[:path] or
9
+ raise ConfigurationError, "Please set path of closure jar with compilers.closure.path configuration setting"
10
+ config[:java] ||= 'java'
11
+ config[:java_flags] = Util.normalize_flags(config[:java_flags])
12
+ config[:path] = normalize_path(config[:path])
13
+ config[:flags] = Util.normalize_flags(config[:flags])
14
+ end
15
+
16
+ def write_all_caches(format = nil)
17
+ asset_modules.each do |asset_module|
18
+ command = [config[:java]]
19
+ command.concat(config[:java_flags])
20
+ command << '-jar' << config[:path]
21
+ command.concat(config[:flags])
22
+ command.concat(asset_module.compiler_flags)
23
+ command << '--js_output_file' << "#{output_path(asset_module, format)}"
24
+ input_paths(asset_module, format).each do |input|
25
+ command << '--js' << input
26
+ end
27
+ system *command or
28
+ raise Error, "closure compiler failed"
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def normalize_path(path)
35
+ (Pathname(AssetLibrary.app_root) + path).to_s
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ class AssetLibrary
2
+ module Compiler
3
+ class Default < Base
4
+ def write_all_caches(format = nil)
5
+ asset_modules.each do |asset_module|
6
+ open(output_path(asset_module, format), 'w') do |file|
7
+ input_paths(asset_module, format).each do |path|
8
+ file << File.read(path)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ require File.dirname(__FILE__) + '/compiler/base'
2
+ require File.dirname(__FILE__) + '/compiler/default'
3
+ require File.dirname(__FILE__) + '/compiler/closure'
4
+
5
+ class AssetLibrary
6
+ module Compiler
7
+ Error = Class.new(RuntimeError)
8
+
9
+ class << self
10
+ # Create an instance of a compiler for the given compiler type.
11
+ def create(type, config={})
12
+ klass = compiler_classes[type] ||= built_in_class_for(type)
13
+ klass.new(config)
14
+ end
15
+
16
+ # Register a custom compiler class.
17
+ def register(type, klass)
18
+ compiler_classes[type] = klass
19
+ end
20
+
21
+ def reset!
22
+ compiler_classes.clear
23
+ end
24
+
25
+ private
26
+
27
+ def compiler_classes
28
+ @compiler_classes ||= {}
29
+ end
30
+
31
+ def built_in_class_for(type)
32
+ class_name = type.to_s.gsub(/(?:\A|_)(.)/){$1.upcase}
33
+ const_get(class_name)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,5 @@
1
+ require 'enumerator'
2
+
1
3
  class AssetLibrary
2
4
  module Helpers
3
5
  def asset_library_javascript_tags(module_key, format = nil)
@@ -2,8 +2,9 @@ def init_asset_library
2
2
  require 'asset_library'
3
3
 
4
4
  # TODO: Find a way to not-hard-code these paths?
5
- AssetLibrary.config_path = File.join(RAILS_ROOT, 'config', 'asset_library.yml')
6
- AssetLibrary.root = File.join(RAILS_ROOT, 'public')
5
+ AssetLibrary.config_path = Rails.root + 'config/asset_library.yml'
6
+ AssetLibrary.root = Rails.public_path
7
+ AssetLibrary.app_root = Rails.root
7
8
  end
8
9
 
9
10
  namespace(:asset_library) do
@@ -16,10 +17,6 @@ namespace(:asset_library) do
16
17
  desc "Deletes all asset caches specified in config/asset.yml"
17
18
  task(:clean) do
18
19
  init_asset_library
19
- keys = AssetLibrary.config.keys
20
- asset_modules = keys.collect{|k| AssetLibrary.asset_module(k)}
21
- asset_modules.each do |m|
22
- FileUtils.rm_f(m.cache_asset.absolute_path)
23
- end
20
+ AssetLibrary.delete_all_caches
24
21
  end
25
22
  end
@@ -1,10 +1,25 @@
1
+ require 'shellwords'
2
+
1
3
  class AssetLibrary
2
4
  module Util
3
- def self.symbolize_hash_keys(hash)
4
- return hash unless Hash === hash # because we recurse
5
- hash.inject({}) do |ret, (key, value)|
6
- ret[(key.to_sym rescue key) || key] = symbolize_hash_keys(value)
7
- ret
5
+ class << self
6
+ def symbolize_hash_keys(hash)
7
+ return hash unless Hash === hash # because we recurse
8
+ hash.inject({}) do |ret, (key, value)|
9
+ ret[(key.to_sym rescue key) || key] = symbolize_hash_keys(value)
10
+ ret
11
+ end
12
+ end
13
+
14
+ def normalize_flags(flags)
15
+ case flags
16
+ when String
17
+ Shellwords.shellwords(flags)
18
+ when nil
19
+ []
20
+ else
21
+ flags
22
+ end
8
23
  end
9
24
  end
10
25
  end
data/lib/asset_library.rb CHANGED
@@ -5,10 +5,14 @@ rescue LoadError
5
5
  require 'glob_fu'
6
6
  end
7
7
 
8
+ require 'yaml'
9
+ require File.dirname(__FILE__) + '/asset_library/compiler'
8
10
  require File.dirname(__FILE__) + '/asset_library/asset_module'
9
11
  require File.dirname(__FILE__) + '/asset_library/util'
10
12
 
11
13
  class AssetLibrary
14
+ ConfigurationError = Class.new(RuntimeError)
15
+
12
16
  class << self
13
17
  def config_path
14
18
  @config_path
@@ -18,13 +22,20 @@ class AssetLibrary
18
22
  @config_path = config_path
19
23
  end
20
24
 
21
- def root
22
- @root
23
- end
25
+ #
26
+ # Root of your application.
27
+ #
28
+ # Paths of external programs (if required) are resolved relative
29
+ # to this path.
30
+ #
31
+ attr_accessor :app_root
24
32
 
25
- def root=(root)
26
- @root = root
27
- end
33
+ #
34
+ # Root directory of your output files.
35
+ #
36
+ # Output files are resolved relative to this path.
37
+ #
38
+ attr_accessor :root
28
39
 
29
40
  def cache
30
41
  return true if @cache.nil?
@@ -32,8 +43,7 @@ class AssetLibrary
32
43
  end
33
44
 
34
45
  def cache=(cache)
35
- @config = nil
36
- @cache_vars = nil
46
+ reset!
37
47
  @cache = cache
38
48
  end
39
49
 
@@ -50,22 +60,62 @@ class AssetLibrary
50
60
  else
51
61
  {}
52
62
  end
63
+ if !ret[:modules] && ret.values.any?{|value| value.is_a?(Hash) && value[:files]}
64
+ warn <<-EOS.gsub(/^ *\|/, '')
65
+ | WARNING: Your asset library configuration follows a
66
+ | deprecated format. Please move all your asset modules
67
+ | under a "modules:" key, as described in the
68
+ | documentation.
69
+ EOS
70
+ ret = { :modules => ret }
71
+ end
72
+ ret[:modules] ||= {}
73
+ ret[:compilers] ||= {}
53
74
  @config = cache ? ret : nil
54
75
  ret
55
76
  end
56
77
 
78
+ def compilers
79
+ @compilers ||= {}
80
+ end
81
+
57
82
  def asset_module(key)
58
- module_config = config[key.to_sym]
83
+ module_config = config[:modules][key.to_sym]
59
84
  if module_config
60
- AssetModule.new(module_config)
85
+ AssetModule.new(key, module_config)
61
86
  end
62
87
  end
63
88
 
89
+ def compiler(asset_module)
90
+ type = asset_module.compiler_type
91
+ config = self.config[:compilers][type] || {}
92
+ compilers[type] ||= Compiler.create(type, config)
93
+ end
94
+
64
95
  def write_all_caches
65
- config.keys.each do |key|
96
+ config[:modules].keys.each do |key|
66
97
  m = asset_module(key)
67
- m.write_all_caches
98
+ c = compiler(m)
99
+ c.add_asset_module(m)
68
100
  end
101
+
102
+ compilers.values.each do |compiler|
103
+ compiler.write_all_caches
104
+ end
105
+ end
106
+
107
+ def delete_all_caches
108
+ asset_modules = config[:modules].keys.collect{|k| AssetLibrary.asset_module(k)}
109
+ asset_modules.each do |m|
110
+ FileUtils.rm_f(m.cache_asset.absolute_path)
111
+ end
112
+ end
113
+
114
+ def reset!
115
+ @config = nil
116
+ @cache_vars = nil
117
+ @compilers = nil
118
+ Compiler.reset!
69
119
  end
70
120
  end
71
121
  end
data/rails/init.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  AssetLibrary.cache = ActionController::Base.perform_caching
2
- AssetLibrary.config_path = File.join(RAILS_ROOT, 'config', 'asset_library.yml')
3
- AssetLibrary.root = File.join(RAILS_ROOT, 'public')
2
+ AssetLibrary.config_path = Rails.root + 'config/asset_library.yml'
3
+ AssetLibrary.root = Rails.public_path
4
+ AssetLibrary.app_root = Rails.root
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/../spec_helper'
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  require 'set'
4
4
 
@@ -110,18 +110,6 @@ describe(AssetLibrary::AssetModule) do
110
110
  end
111
111
  end
112
112
 
113
- describe('#contents') do
114
- it('should return an IO object') do
115
- stub_fs([ '/c/file1.css', '/c/file2.css' ])
116
- m(css_config(:files => ['file*'])).contents.should(respond_to(:read))
117
- end
118
-
119
- it('should concatenate individual file contents') do
120
- stub_fs([ '/c/file1.css', '/c/file2.css' ])
121
- m(css_config(:files => ['file*'])).contents.read.should == "/c/file1.css\n/c/file2.css\n"
122
- end
123
- end
124
-
125
113
  describe('#cache_asset') do
126
114
  it('should use options[:cache]') do
127
115
  m(css_config).cache_asset.absolute_path.should == "#{prefix}/c/cache.css"
@@ -132,44 +120,10 @@ describe(AssetLibrary::AssetModule) do
132
120
  end
133
121
  end
134
122
 
135
- describe('#write_cache') do
136
- it('should write to cache.css') do
137
- File.should_receive(:open).with("#{prefix}/c/cache.css", 'w')
138
- m(css_config).write_cache
139
- end
140
-
141
- it('should write cache contents to cache') do
142
- stub_fs([ '/c/file1.css', '/c/file2.css' ])
143
- m(css_config(:files => ['file*'])).write_cache
144
- File.open("#{prefix}/c/cache.css") { |f| f.read.should == "/c/file1.css\n/c/file2.css\n" }
145
- end
146
-
147
- it('should use :format to determine CSS output file') do
148
- File.should_receive(:open).with("#{prefix}/c/cache.e.css", 'w')
149
- m(css_config).write_cache(:e)
150
- end
151
- end
152
-
153
- describe('#write_all_caches') do
154
- it('should write cache.css (no :format)') do
155
- File.should_receive(:open).with("#{prefix}/c/cache.css", 'w')
156
- m(css_config).write_all_caches
157
- end
158
-
159
- it('should write no-format and all format files') do
160
- formats = { :e1 => [], :e2 => [] }
161
- File.should_receive(:open).with("#{prefix}/c/cache.css", 'w')
162
- formats.keys.each do |format|
163
- File.should_receive(:open).with("#{prefix}/c/cache.#{format}.css", 'w')
164
- end
165
- m(css_config(:formats => formats)).write_all_caches
166
- end
167
- end
168
-
169
123
  private
170
124
 
171
125
  def m(config)
172
- AssetLibrary::AssetModule.new(config)
126
+ AssetLibrary::AssetModule.new(:name, config)
173
127
  end
174
128
 
175
129
  def js_config(options = {})
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/../spec_helper'
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  require File.dirname(__FILE__) + '/../../lib/asset_library/asset'
4
4
 
@@ -0,0 +1,11 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe(AssetLibrary::Compiler::Base) do
4
+ describe('#write_all_caches') do
5
+ it('should raise NotImplementedError') do
6
+ lambda do
7
+ AssetLibrary::Compiler::Base.new({}).write_all_caches
8
+ end.should raise_error(NotImplementedError)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,104 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe(AssetLibrary::Compiler::Closure) do
4
+ include TemporaryDirectory
5
+ include CompilerHelpers
6
+
7
+ before do
8
+ AssetLibrary.app_root = "#{tmp}/app_root"
9
+ AssetLibrary.root = "#{tmp}/root"
10
+ end
11
+
12
+ def compiler(configuration = {})
13
+ configuration[:path] = '/PATH/TO/CLOSURE.jar' unless configuration.key?(:path)
14
+ compiler = AssetLibrary::Compiler::Closure.new(configuration)
15
+ compiler.stub!(:system)
16
+ compiler
17
+ end
18
+
19
+ def add_one_compilation(compiler)
20
+ compiler.add_asset_module mock_asset_module('lib', :format, 'out.js', 'in.js')
21
+ end
22
+
23
+ describe('#initialize') do
24
+ it('should cry if the path to the compiler is not set') do
25
+ lambda{compiler(:path => nil)}.should raise_error(AssetLibrary::ConfigurationError)
26
+ end
27
+ end
28
+
29
+ describe('#write_all_caches') do
30
+ before do
31
+ @tmpdir = "#{tmp}/tmpdir"
32
+ FileUtils.mkdir_p @tmpdir
33
+ AssetLibrary::Util.stub!(:mktmpdir).and_yield(@tmpdir)
34
+ end
35
+
36
+ it("should run each module's files through closure compiler") do
37
+ compiler = self.compiler
38
+ compiler.add_asset_module mock_asset_module('lib1', :format, 'LIB1.js', 'lib1-file1.js')
39
+ compiler.add_asset_module mock_asset_module('lib2', :format, 'LIB2.js', 'lib2-file1.js', 'lib2-file2.js')
40
+ compiler.should_receive(:system).with(*%W"java -jar /PATH/TO/CLOSURE.jar --js_output_file LIB1.js --js lib1-file1.js").and_return(true)
41
+ compiler.should_receive(:system).with(*%W"java -jar /PATH/TO/CLOSURE.jar --js_output_file LIB2.js --js lib2-file1.js --js lib2-file2.js").and_return(true)
42
+ compiler.write_all_caches(:format)
43
+ end
44
+
45
+ it('should take the java executable name from the :java configuration option') do
46
+ compiler = compiler(:java => '/PATH/TO/JAVA')
47
+ add_one_compilation(compiler)
48
+ compiler.should_receive(:system).with{ |*args| args.first == '/PATH/TO/JAVA' }.and_return(true)
49
+ compiler.write_all_caches(:format)
50
+ end
51
+
52
+ it('should take the path to closure compiler from the :path configuration option') do
53
+ compiler = compiler(:path => '/CLOSURE.jar')
54
+ add_one_compilation(compiler)
55
+ compiler.should_receive(:system).with{ |*args| args[1..2] == ['-jar', '/CLOSURE.jar'] }.and_return(true)
56
+ compiler.write_all_caches(:format)
57
+ end
58
+
59
+ it('should interpret the path as relative to the application root') do
60
+ AssetLibrary.app_root = "#{tmp}/app_root"
61
+ compiler = compiler(:path => 'CLOSURE')
62
+ add_one_compilation(compiler)
63
+ compiler.should_receive(:system).with{ |*args| args[1..2] == ['-jar', "#{tmp}/app_root/CLOSURE"] }.and_return(true)
64
+ compiler.write_all_caches(:format)
65
+ end
66
+
67
+ it('should pass any configured java_flags to java') do
68
+ compiler = compiler(:java_flags => "-foo -bar")
69
+ add_one_compilation(compiler)
70
+ compiler.should_receive(:system).with{ |*args| args[1..2] == ['-foo', '-bar'] }.and_return(true)
71
+ compiler.write_all_caches(:format)
72
+ end
73
+
74
+ it('should accept an Array for java_flags') do
75
+ compiler = compiler(:java_flags => %w"-foo -bar")
76
+ add_one_compilation(compiler)
77
+ compiler.should_receive(:system).with{ |*args| args[1..2] == ['-foo', '-bar'] }.and_return(true)
78
+ compiler.write_all_caches(:format)
79
+ end
80
+
81
+ it('should pass any configured flags to the compiler') do
82
+ compiler = compiler(:flags => "--foo --bar")
83
+ add_one_compilation(compiler)
84
+ compiler.should_receive(:system).with{ |*args| args[3..4] == ['--foo', '--bar'] }.and_return(true)
85
+ compiler.write_all_caches(:format)
86
+ end
87
+
88
+ it('should add module compiler flags to the compiler command') do
89
+ compiler = self.compiler
90
+ compiler.add_asset_module mock_asset_module('lib1', :format, 'LIB1.js', 'lib1-file1.js', :compiler_flags => ['--foo'])
91
+ compiler.add_asset_module mock_asset_module('lib2', :format, 'LIB2.js', 'lib2-file1.js', :compiler_flags => ['--bar'])
92
+ compiler.should_receive(:system).with(*%W"java -jar /PATH/TO/CLOSURE.jar --foo --js_output_file LIB1.js --js lib1-file1.js").and_return(true)
93
+ compiler.should_receive(:system).with(*%W"java -jar /PATH/TO/CLOSURE.jar --bar --js_output_file LIB2.js --js lib2-file1.js").and_return(true)
94
+ compiler.write_all_caches(:format)
95
+ end
96
+
97
+ it('should raise a Compiler::Error if the compiler fails') do
98
+ compiler = self.compiler
99
+ compiler.add_asset_module mock_asset_module('lib1', :format, "lib1.js", "file1.js")
100
+ compiler.stub!(:system).and_return(false)
101
+ lambda{compiler.write_all_caches(:format)}.should raise_error(AssetLibrary::Compiler::Error)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,27 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe(AssetLibrary::Compiler::Default) do
4
+ include TemporaryDirectory
5
+ include CompilerHelpers
6
+
7
+ before do
8
+ @compiler = AssetLibrary::Compiler::Default.new(nil)
9
+ end
10
+
11
+ describe('#write_all_caches') do
12
+ it('should concatenate each set of input files to produce the respective output files') do
13
+ write_file "lib1-file1.txt", "lib1-file1\n"
14
+ write_file "lib2-file1.txt", "lib2-file1\n"
15
+ write_file "lib2-file2.txt", "lib2-file2\n"
16
+ @compiler.add_asset_module mock_asset_module('lib1', :format, "#{tmp}/lib1.txt", "#{tmp}/lib1-file1.txt")
17
+ @compiler.add_asset_module mock_asset_module('lib2', :format, "#{tmp}/lib2.txt", "#{tmp}/lib2-file1.txt", "#{tmp}/lib2-file2.txt")
18
+ @compiler.write_all_caches(:format)
19
+ File.read("#{tmp}/lib1.txt").should == "lib1-file1\n"
20
+ File.read("#{tmp}/lib2.txt").should == "lib2-file1\nlib2-file2\n"
21
+ end
22
+
23
+ def write_file(path, content)
24
+ open("#{tmp}/#{path}", 'w'){|f| f.print content}
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe(AssetLibrary::Compiler) do
4
+ Compiler = AssetLibrary::Compiler
5
+ include TemporaryDirectory
6
+
7
+ before do
8
+ AssetLibrary.app_root = "#{tmp}/app_root"
9
+ AssetLibrary.root = "#{tmp}/root"
10
+ end
11
+
12
+ describe('.create') do
13
+ it('should return a Default compiler for :default') do
14
+ Compiler.create(:default).should be_a(Compiler::Default)
15
+ end
16
+
17
+ it('should return a Closure compiler for :closure') do
18
+ Compiler.create(:closure, :path => '').should be_a(Compiler::Closure)
19
+ end
20
+
21
+ it('should pass the configuration to the compiler') do
22
+ Compiler.create(:default, {:param => 2}).config[:param].should == 2
23
+ end
24
+ end
25
+
26
+ describe('.register') do
27
+ TestCompiler = Class.new(Compiler::Base)
28
+
29
+ it('should register a custom compiler type') do
30
+ Compiler.register(:test, TestCompiler)
31
+ compiler = Compiler.create(:test)
32
+ compiler.should be_a(TestCompiler)
33
+ end
34
+ end
35
+ end
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/../spec_helper'
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  require File.dirname(__FILE__) + '/../../lib/asset_library/helpers'
4
4
 
@@ -1,16 +1,8 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe(AssetLibrary) do
4
- before(:each) do
5
- @old_root = AssetLibrary.root
6
- @old_config_path = AssetLibrary.config_path
7
- @old_cache = AssetLibrary.cache
8
- end
9
-
10
- after(:each) do
11
- AssetLibrary.root = @old_root
12
- AssetLibrary.config_path = @old_config_path
13
- AssetLibrary.cache = @old_cache
4
+ def config_skeleton
5
+ {:modules => {}, :compilers => {}}
14
6
  end
15
7
 
16
8
  describe('#config') do
@@ -21,10 +13,10 @@ describe(AssetLibrary) do
21
13
  AssetLibrary.config
22
14
  end
23
15
 
24
- it('should return {} if config_path does not exist') do
16
+ it('should return a skeletal configuration if config_path does not exist') do
25
17
  AssetLibrary.config_path = '/config.yml'
26
18
  File.stub!(:exist?).with('/config.yml').and_return(false)
27
- AssetLibrary.config.should == {}
19
+ AssetLibrary.config.should == config_skeleton
28
20
  end
29
21
 
30
22
  it('should cache config if cache is set') do
@@ -58,13 +50,26 @@ describe(AssetLibrary) do
58
50
  { 'a' => { 'b' => 'c' } }
59
51
  )
60
52
 
61
- AssetLibrary.config.should == { :a => { :b => 'c' } }
53
+ AssetLibrary.config.should == config_skeleton.merge(:a => {:b => 'c'})
54
+ end
55
+
56
+ it('should accept a v0.4 config file with a deprecation warning') do
57
+ AssetLibrary.cache = false
58
+ AssetLibrary.config_path = '/config.yml'
59
+
60
+ File.stub!(:exist?).with('/config.yml').and_return(true)
61
+ YAML.should_receive(:load_file).with('/config.yml').and_return(
62
+ { 'a' => { 'files' => ['a.js'] } }
63
+ )
64
+ AssetLibrary.should_receive(:warn)
65
+
66
+ AssetLibrary.config.should == { :compilers => {}, :modules => { :a => { :files => ['a.js'] } } }
62
67
  end
63
68
  end
64
69
 
65
70
  describe('#asset_module') do
66
71
  before(:each) do
67
- @config = {}
72
+ @config = config_skeleton
68
73
  AssetLibrary.stub!(:config).and_return(@config)
69
74
  end
70
75
 
@@ -73,25 +78,43 @@ describe(AssetLibrary) do
73
78
  end
74
79
 
75
80
  it('should return an AssetModule when given a valid key') do
76
- @config[:foo] = {}
81
+ @config[:modules][:foo] = {}
77
82
  AssetLibrary.asset_module(:foo).should(be_a(AssetLibrary::AssetModule))
78
83
  end
79
84
  end
80
85
 
81
- describe('#write_all_caches') do
82
- it('should call write_all_caches on all asset_modules') do
83
- mock1 = mock
84
- mock2 = mock
86
+ describe('#compiler') do
87
+ include TemporaryDirectory
88
+
89
+ before do
90
+ AssetLibrary.app_root = "#{tmp}/root"
91
+ end
85
92
 
86
- mock1.should_receive(:write_all_caches)
87
- mock2.should_receive(:write_all_caches)
93
+ it('should return a Default compiler if no compiler type has been configured for the given asset module') do
94
+ configure_compilers
95
+ asset_module = mock(:compiler_type => :default)
96
+ AssetLibrary.compiler(asset_module).should be_a(AssetLibrary::Compiler::Default)
97
+ end
88
98
 
89
- AssetLibrary.stub!(:asset_module).with(:mock1).and_return(mock1)
90
- AssetLibrary.stub!(:asset_module).with(:mock2).and_return(mock2)
99
+ it('should return a compiler of the configured type for the given asset module, if one is given') do
100
+ configure_compilers
101
+ asset_module = mock(:compiler_type => :closure)
102
+ AssetLibrary.compiler(asset_module).should be_a(AssetLibrary::Compiler::Closure)
103
+ end
91
104
 
92
- AssetLibrary.stub!(:config).and_return({ :mock1 => {}, :mock2 => {} })
105
+ it('should pass the right compiler configuration to the compiler') do
106
+ config = {:default => {:foo => 2}}
107
+ configure_compilers(config)
108
+ asset_module = mock(:compiler_type => :default)
109
+ AssetLibrary.compiler(asset_module).config[:foo].should == 2
110
+ end
93
111
 
94
- AssetLibrary.write_all_caches
112
+ def configure_compilers(config=nil)
113
+ config = {:compilers => config || {:closure => {:path => 'closure.jar'}}}
114
+ config_path = "#{tmp}/config.yml"
115
+ open(config_path, 'w'){|f| YAML.dump(config, f)}
116
+ AssetLibrary.config_path = config_path
117
+ AssetLibrary.config
95
118
  end
96
119
  end
97
120
  end
@@ -0,0 +1,88 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe(AssetLibrary) do
4
+ include TemporaryDirectory
5
+
6
+ class ReverseCompiler < AssetLibrary::Compiler::Base
7
+ def write_all_caches(format = nil)
8
+ asset_modules.each do |asset_module|
9
+ open(output_path(asset_module, format), 'w') do |file|
10
+ file.print config[:header]
11
+ input_paths(asset_module, format).reverse_each do |input|
12
+ file << File.read(input)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ before do
20
+ AssetLibrary::Compiler.register(:reverse, ReverseCompiler)
21
+ end
22
+
23
+ describe "AssetLibrary.write_all_caches" do
24
+ it "should generate cached asset libraries for each asset module, with the configured compiler" do
25
+ write_file "#{tmp}/root/cssbase/stylesheet-1.css", "style1 { background: #000 }"
26
+ write_file "#{tmp}/root/cssbase/stylesheet-2.css.opt", "style2 { background: #fff }"
27
+ write_file "#{tmp}/root/jsbase/javascript-1.js", "function f1(){alert('1');}"
28
+ write_file "#{tmp}/root/jsbase/javascript-2.js.opt", "function f2(){alert('2');}"
29
+ config_path = "#{tmp}/config.yml"
30
+ open(config_path, 'w'){|f| f.puts <<-EOS}
31
+ compilers:
32
+ reverse:
33
+ header: HEADER
34
+
35
+ modules:
36
+ stylsheets:
37
+ cache: lib
38
+ optional_suffix: opt
39
+ base: cssbase
40
+ suffix: css
41
+ files:
42
+ - stylesheet-1
43
+ - stylesheet-2
44
+
45
+ javascripts:
46
+ cache: lib
47
+ optional_suffix: opt
48
+ base: jsbase
49
+ suffix: js
50
+ compiler: reverse
51
+ files:
52
+ - javascript-1
53
+ - javascript-2
54
+ EOS
55
+ AssetLibrary.root = "#{tmp}/root"
56
+ AssetLibrary.config_path = config_path
57
+ AssetLibrary.write_all_caches
58
+ File.read("#{tmp}/root/cssbase/lib.css").should == "style1 { background: #000 }style2 { background: #fff }"
59
+ File.read("#{tmp}/root/jsbase/lib.js").should == "HEADERfunction f2(){alert('2');}function f1(){alert('1');}"
60
+ end
61
+ end
62
+
63
+ describe "AssetLibrary.delete_all_caches" do
64
+ it "should delete all caches" do
65
+ write_file "#{tmp}/root/cssbase/lib.css"
66
+ config_path = "#{tmp}/config.yml"
67
+ open(config_path, 'w'){|f| f.puts <<-EOS}
68
+ modules:
69
+ stylesheets:
70
+ cache: lib
71
+ base: cssbase
72
+ suffix: css
73
+ files:
74
+ - stylesheet-1
75
+ - stylesheet-2
76
+ EOS
77
+ AssetLibrary.root = "#{tmp}/root"
78
+ AssetLibrary.config_path = config_path
79
+ AssetLibrary.delete_all_caches
80
+ File.should_not exist("#{tmp}/root/cssbase/lib.css")
81
+ end
82
+ end
83
+
84
+ def write_file(path, content='...')
85
+ FileUtils.mkdir_p File.dirname(path)
86
+ open(path, 'w'){|f| f.print content}
87
+ end
88
+ end
data/spec/spec_helper.rb CHANGED
@@ -2,3 +2,72 @@ require 'rubygems'
2
2
  require 'spec'
3
3
 
4
4
  require File.dirname(__FILE__) + '/../lib/asset_library'
5
+
6
+ module TemporaryDirectory
7
+ TMP = File.expand_path(File.dirname(__FILE__) + '/tmp')
8
+
9
+ def self.included(base)
10
+ base.before do
11
+ remove_tmp
12
+ make_tmp
13
+ enter_tmp
14
+ end
15
+
16
+ base.after do
17
+ leave_tmp
18
+ remove_tmp
19
+ end
20
+ end
21
+
22
+ def make_tmp
23
+ FileUtils.mkdir_p tmp
24
+ end
25
+
26
+ def remove_tmp
27
+ FileUtils.rm_rf tmp
28
+ end
29
+
30
+ def enter_tmp
31
+ @original_pwd = Dir.pwd
32
+ Dir.chdir tmp
33
+ end
34
+
35
+ def leave_tmp
36
+ Dir.chdir @original_pwd
37
+ end
38
+
39
+ def tmp
40
+ TMP
41
+ end
42
+ end
43
+
44
+ module CompilerHelpers
45
+ def mock_asset_module(name, format, output_path, *input_paths)
46
+ config = input_paths.last.is_a?(Hash) ? input_paths.pop : {}
47
+ output_asset = mock(:absolute_path => output_path)
48
+ input_assets = input_paths.map{|path| mock(:absolute_path => path)}
49
+ asset_module = mock(:name => name)
50
+ asset_module.stub!(:cache_asset).with(format).and_return(output_asset)
51
+ asset_module.stub!(:assets).with(format).and_return(input_assets)
52
+ asset_module.stub!(:compiler_flags).and_return(config[:compiler_flags] || [])
53
+ asset_module.stub!(:config).and_return(config)
54
+ asset_module
55
+ end
56
+ end
57
+
58
+ Spec::Runner.configure do |config|
59
+ config.before do
60
+ @old_app_root = AssetLibrary.app_root
61
+ @old_root = AssetLibrary.root
62
+ @old_config_path = AssetLibrary.config_path
63
+ @old_cache = AssetLibrary.cache
64
+ end
65
+
66
+ config.after do
67
+ AssetLibrary.app_root = @app_root
68
+ AssetLibrary.root = @old_root
69
+ AssetLibrary.config_path = @old_config_path
70
+ AssetLibrary.cache = @old_cache
71
+ AssetLibrary.reset!
72
+ end
73
+ end
metadata CHANGED
@@ -1,16 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asset_library
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - adamh
8
8
  - alegscogs
9
+ - oggy
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
13
 
13
- date: 2010-02-09 00:00:00 -05:00
14
+ date: 2010-02-24 00:00:00 -05:00
14
15
  default_executable:
15
16
  dependencies:
16
17
  - !ruby/object:Gem::Dependency
@@ -35,6 +36,10 @@ files:
35
36
  - lib/asset_library.rb
36
37
  - lib/asset_library/asset.rb
37
38
  - lib/asset_library/asset_module.rb
39
+ - lib/asset_library/compiler.rb
40
+ - lib/asset_library/compiler/base.rb
41
+ - lib/asset_library/compiler/closure.rb
42
+ - lib/asset_library/compiler/default.rb
38
43
  - lib/asset_library/helpers.rb
39
44
  - lib/asset_library/rake_tasks.rb
40
45
  - lib/asset_library/util.rb
@@ -71,6 +76,11 @@ summary: Manage and bundle CSS and JavaScript files
71
76
  test_files:
72
77
  - spec/asset_library_spec.rb
73
78
  - spec/asset_library/asset_module_spec.rb
79
+ - spec/asset_library/compiler/default_spec.rb
80
+ - spec/asset_library/compiler/base_spec.rb
81
+ - spec/asset_library/compiler/closure_spec.rb
82
+ - spec/asset_library/compiler_spec.rb
74
83
  - spec/asset_library/asset_spec.rb
75
84
  - spec/asset_library/helpers_spec.rb
76
85
  - spec/spec_helper.rb
86
+ - spec/integration_spec.rb