asset_library 0.4.0 → 0.5.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.
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