alegscogs-asset_library 0.3.4

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Alex Cox
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,177 @@
1
+ = alegscogs-asset_library
2
+
3
+ *****
4
+ This is a fork of AdamH's asset_library gem, hosted at github.com/adamh.
5
+ *****
6
+
7
+
8
+ Bundles your JavaScript and CSS, so your development environment is simple to
9
+ code and your production environment is as fast as possible.
10
+
11
+ == Installation
12
+
13
+ First, install the gem:
14
+
15
+ sudo gem install alegscogs-asset_library --source http://gems.github.com
16
+
17
+ Next, add the gem into your Rails project's config/environment.rb:
18
+
19
+ config.gem 'alegscogs-asset_library', :lib => 'asset_library', :source => 'http://gems.github.com'
20
+
21
+ Finally, include the Rake tasks in your project:
22
+
23
+ echo "begin; require 'asset_library/rake_tasks'; rescue LoadError; end" > lib/tasks/asset_library.rake
24
+
25
+ == Usage
26
+
27
+ In your Rails project, edit <tt>config/asset_library.yml</tt> as described
28
+ in the following section.
29
+
30
+ Once configured, asset_library provides two helper methods for your views:
31
+
32
+ <%# outputs library.js (production) or its files (development) %>
33
+ <%= asset_library_javascript_tags(:javascripts) %>
34
+
35
+ <%# outputs library.css (production) or its files (development) %>
36
+ <%= asset_library_stylesheet_tags(:stylesheets) %>
37
+
38
+ <%# outputs library.ie6.css (production) or its files (development) %>
39
+ <!--[if lte IE 6]>
40
+ <%= asset_library_stylesheet_tags(:stylesheets, :ie6) %>
41
+ <![endif]-->
42
+
43
+ Both helpers behave differently depending on whether
44
+ <tt>ActionController::Base.perform_caching</tt> is true (that is, whether you
45
+ are in <tt>development</tt> environment or not). When caching is disabled, each
46
+ file in the module will be included. (Internet Explorer only allows 30
47
+ <tt>style</tt> and <tt>link</tt> stylesheet tags; in development mode,
48
+ <tt>import</tt> rules are used to work around the bug.) When caching is
49
+ enabled, a single tag is output.
50
+
51
+ When caching is enabled, the modules to include must be generated using:
52
+
53
+ rake asset_library:write
54
+
55
+ These moduels can be removed using:
56
+
57
+ rake asset_library:clean
58
+
59
+ A cached module is simply the concatenation of its constituent files.
60
+
61
+ == Configuration
62
+
63
+ A typical configuration (Yaml) file might look like this. In Rails, this
64
+ should be in <tt>config/asset_library.yml</tt>.
65
+
66
+ javascripts:
67
+ cache: library
68
+ optional_suffix: compressed
69
+ base: javascripts
70
+ suffix: js
71
+ files:
72
+ - vendor/jquery
73
+
74
+ # jQuery UI parts we need
75
+ - vendor/jquery-ui/ui.core
76
+ - vendor/jquery-ui/ui.draggable
77
+ - vendor/jquery-ui/ui.droppable
78
+ - vendor/jquery-ui/ui.sortable
79
+ - vendor/jquery-ui/effects.core
80
+
81
+ - lib
82
+ - plugins/*
83
+ - application
84
+ - initializers/*
85
+
86
+ tiny_mce_javascripts:
87
+ # TinyMCE doesn't give us a choice on cache name
88
+ cache: vendor/tiny_mce/tiny_mce_gzip
89
+ optional_suffix: compressed
90
+ base: javascripts
91
+ suffix: js
92
+ files:
93
+ - vendor/tiny_mce/tiny_mce
94
+ # ... it's possible to bundle all of TinyMCE together with a bit of magic
95
+
96
+ stylesheets:
97
+ cache: library
98
+ base: stylesheets
99
+ suffix: css
100
+ formats:
101
+ - ie6: [null, ie8, ie7, ie6]
102
+ - ie7: [null, ie8, ie7]
103
+ - ie8: [null, ie8]
104
+ files:
105
+ - reset
106
+ - application
107
+ - layout
108
+ - views/**/*
109
+
110
+ # in general...
111
+ #module_name:
112
+ # cache: cache_file
113
+ # base: base_path_of_assets_in_web_root
114
+ # suffix: suffix ("css" or "js")
115
+ # formats:
116
+ # format1: ["extra_suffix_1", "extra_suffix_2"]
117
+ # format2: [null, "extra_suffix3"]
118
+ # optional_suffix: optional_suffix
119
+ # files:
120
+ # - file1_relative_to_base
121
+ # - file2_relative_to_base
122
+ # - ...
123
+
124
+ Here are what the various configuration elements mean:
125
+
126
+ <tt>module_name</tt> is the name of the module. It is passed as the first
127
+ parameter to <tt>asset_library_javascript_tags</tt> or
128
+ <tt>asset_library_stylesheet_tags</tt>.
129
+
130
+ <tt>cache</tt> is a filename, without suffix, relative to <tt>base</tt>, where
131
+ the module will be bundled when caching is enabled. (Ensure that <tt>files</tt>
132
+ does not include <tt>cache_file</tt>, even with globbing.)
133
+
134
+ <tt>base</tt> is the base path of the assets in URLs. For instance, in Rails,
135
+ where stylesheets are usually served under <tt>/stylesheets</tt>, <tt>base</tt>
136
+ should be <tt>stylesheets</tt>.
137
+
138
+ <tt>suffix</tt> is either "js" or "css", depending on whether you are including
139
+ JavaScript or CSS files.
140
+
141
+ <tt>formats</tt> allows construction of parallel modules. By default, for a
142
+ module named <tt>styles</tt> will use <tt>*.css</tt> (where <tt>*</tt> is
143
+ specified by the <tt>files</tt> option) to generate <tt>styles.css</tt>; but
144
+ filenames of the format <tt>*.suffix.css</tt> will be ignored in that search.
145
+ If a <tt>format</tt> called <tt>format1</tt> is specified as
146
+ <tt>[suffix1, suffix2]</tt>, then <tt>*.suffix1.css</tt> and
147
+ <tt>*.suffix2.css</tt> will be included, but not <tt>*.css</tt>. (To specify
148
+ <tt>*.css</tt>, put <tt>null</tt> in the format list.)
149
+
150
+ <tt>optional_suffix</tt> will cause asset_library to check for the existence of
151
+ files with <tt>optional_suffix</tt> suffixes, falling back to files without
152
+ <tt>optional_suffix</tt> if necessary. For instance, if you run all your
153
+ JavaScript files through
154
+ {YUI Compressor}[http://developer.yahoo.com/yui/compressor/] and output the
155
+ compressed version of <tt>file1.js</tt> as <tt>file1.compressed.js</tt>, then
156
+ set <tt>optional_suffix</tt> to <tt>compressed</tt>. In your development
157
+ environment, where <tt>compressed</tt> javascripts are missing,
158
+ <tt>file1.js</tt> will be included and you can debug your JavaScript. In your
159
+ production environment, create the <tt>compressed</tt> JavaScripts in the same
160
+ directory, and they will be included instead, for optimal download speed.
161
+
162
+ <tt>files</tt> is a list of files, relative to <tt>base</tt>. File globbing is
163
+ allowed; <tt>**</tt> expands to "any nesting of directories". Files which do
164
+ not exist will be excluded; globbing will include/exclude files with
165
+ <tt>formats</tt> as appropriate; and files without <tt>optional_suffix</tt>
166
+ will not be output alongside files with the same name endowed with the suffix.
167
+
168
+ == Copyright
169
+
170
+ I believe in software freedom, not any abomination thereof. This project is
171
+ released under the Public Domain, meaning I relinquish my copyright (to any
172
+ extend the law allows) and grant you all rights to use, modify, sell, or
173
+ otherwise take advantage of my software.
174
+
175
+ However, I do kindly request that, as a favor, you refrain from using my
176
+ software as part of an evil plan involving velociraptors and mind-controlling
177
+ robots (even though I would not be legally entitled to sue you for doing so).
@@ -0,0 +1,71 @@
1
+ begin
2
+ require 'glob_fu'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'glob_fu'
6
+ end
7
+
8
+ require File.dirname(__FILE__) + '/asset_library/asset_module'
9
+ require File.dirname(__FILE__) + '/asset_library/util'
10
+
11
+ class AssetLibrary
12
+ class << self
13
+ def config_path
14
+ @config_path
15
+ end
16
+
17
+ def config_path=(config_path)
18
+ @config_path = config_path
19
+ end
20
+
21
+ def root
22
+ @root
23
+ end
24
+
25
+ def root=(root)
26
+ @root = root
27
+ end
28
+
29
+ def cache
30
+ return true if @cache.nil?
31
+ @cache
32
+ end
33
+
34
+ def cache=(cache)
35
+ @config = nil
36
+ @cache_vars = nil
37
+ @cache = cache
38
+ end
39
+
40
+ def cache_vars
41
+ # We store cache_vars even if not caching--this is our "globals"
42
+ @cache_vars ||= {}
43
+ end
44
+
45
+ def config
46
+ return @config if cache && @config
47
+ ret = if File.exist?(config_path)
48
+ yaml = YAML.load_file(config_path) || {}
49
+ Util::symbolize_hash_keys(yaml)
50
+ else
51
+ {}
52
+ end
53
+ @config = cache ? ret : nil
54
+ ret
55
+ end
56
+
57
+ def asset_module(key)
58
+ module_config = config[key.to_sym]
59
+ if module_config
60
+ AssetModule.new(module_config)
61
+ end
62
+ end
63
+
64
+ def write_all_caches
65
+ config.keys.each do |key|
66
+ m = asset_module(key)
67
+ m.write_all_caches
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,35 @@
1
+ class AssetLibrary
2
+ class Asset
3
+ attr_reader(:absolute_path)
4
+
5
+ def initialize(absolute_path)
6
+ @absolute_path = absolute_path
7
+ end
8
+
9
+ def eql?(other)
10
+ self.class === other && absolute_path == other.absolute_path
11
+ end
12
+
13
+ def hash
14
+ self.absolute_path.hash
15
+ end
16
+
17
+ def relative_path
18
+ if AssetLibrary.root == '/'
19
+ absolute_path
20
+ else
21
+ absolute_path[AssetLibrary.root.length..-1]
22
+ end
23
+ end
24
+
25
+ def timestamp
26
+ File.mtime(absolute_path)
27
+ rescue Errno::ENOENT
28
+ nil
29
+ end
30
+
31
+ def relative_url
32
+ "#{relative_path}?#{timestamp.to_i.to_s}"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,84 @@
1
+ require File.dirname(__FILE__) + '/asset'
2
+
3
+ class AssetLibrary
4
+ class AssetModule
5
+ attr_reader(:config)
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ # Returns an Array of Assets to include.
12
+ #
13
+ # Arguments:
14
+ # extra_suffix: if set, finds files with the given extra suffix
15
+ def assets(format = nil)
16
+ if format
17
+ assets_with_format(format)
18
+ else
19
+ assets_with_extra_suffix(nil)
20
+ end
21
+ end
22
+
23
+ # Returns an Array of Assets to include.
24
+ #
25
+ # Arguments:
26
+ # extra_suffix: if set, finds files with the given extra suffix
27
+ def assets_with_extra_suffix(extra_suffix)
28
+ return nil unless config
29
+
30
+ GlobFu.find(
31
+ config[:files],
32
+ :suffix => config[:suffix],
33
+ :extra_suffix => extra_suffix,
34
+ :root => File.join(*([AssetLibrary.root, config[:base]].compact)),
35
+ :optional_suffix => config[:optional_suffix]
36
+ ).collect { |f| Asset.new(f) }
37
+ end
38
+
39
+ # Returns an Array of Assets to include.
40
+ #
41
+ # Calls assets_with_extra_suffix for each suffix in the given format
42
+ #
43
+ # Arguments:
44
+ # format: format specified in the config
45
+ def assets_with_format(format)
46
+ return nil unless config
47
+
48
+ extra_suffixes = config[:formats][format.to_sym]
49
+ extra_suffixes.inject([]) { |r, s| r.concat(assets_with_extra_suffix(s)) }
50
+ end
51
+
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
+ # Returns an Asset representing the cache file
66
+ def cache_asset(format = nil)
67
+ extra = format ? ".#{format}" : ''
68
+ Asset.new(File.join(AssetLibrary.root, config[:base], "#{config[:cache]}#{extra}.#{config[:suffix]}"))
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
+ end
84
+ end
@@ -0,0 +1,96 @@
1
+ class AssetLibrary
2
+ module Helpers
3
+ def asset_library_javascript_tags(module_key, format = nil)
4
+
5
+ m = AssetLibrary.asset_module(module_key)
6
+ if AssetLibrary.cache
7
+ AssetLibrary.cache_vars[:javascript_tags] ||= {}
8
+ AssetLibrary.cache_vars[:javascript_tags][module_key] ||= asset_library_priv.script_tag(m.cache_asset)
9
+ else
10
+ m.assets(format).collect{|a| asset_library_priv.script_tag(a)}.join("\n")
11
+ end
12
+ end
13
+
14
+ def asset_library_stylesheet_tags(module_key, *args)
15
+ html_options = args.last.is_a?(Hash) ? args.pop : {}
16
+ format = args[0]
17
+
18
+ m = AssetLibrary.asset_module(module_key)
19
+ if AssetLibrary.cache
20
+ AssetLibrary.cache_vars[:stylesheet_tags] ||= {}
21
+ AssetLibrary.cache_vars[:stylesheet_tags][[module_key, format, request.protocol, request.host_with_port]] ||= asset_library_priv.style_tag(m.cache_asset(format), html_options)
22
+ else
23
+ asset_library_priv.import_styles_tag(m.assets(format), html_options)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def asset_library_priv
30
+ @asset_library_priv ||= Priv.new(self)
31
+ end
32
+
33
+ class Priv
34
+ # Don't pollute helper's class's namespace with all our methods; put
35
+ # them here instead
36
+
37
+ attr_accessor :helper
38
+
39
+ def initialize(helper)
40
+ @helper = helper
41
+ end
42
+
43
+ def url(asset)
44
+ absolute_url(asset.relative_url)
45
+ end
46
+
47
+ def absolute_url(relative_url)
48
+ host = helper.__send__(:compute_asset_host, relative_url) if helper.respond_to?(:compute_asset_host, true)
49
+
50
+ host = nil if host == '' # Rails sets '' by default
51
+
52
+ if host && !(host =~ %r{^[-a-z]+://})
53
+ controller = helper.instance_variable_get(:@controller)
54
+ request = controller && controller.respond_to?(:request) && controller.request
55
+ host = request && "#{request.protocol}#{host}"
56
+ end
57
+
58
+ if host
59
+ "#{host}#{relative_url}"
60
+ else
61
+ relative_url
62
+ end
63
+ end
64
+
65
+ def script_tag(asset)
66
+ content_tag(:script, "", {:type => "text/javascript", :src => url(asset)})
67
+ end
68
+
69
+ def style_tag(asset, html_options = {})
70
+ "<link rel=\"stylesheet\" type=\"text/css\" href=\"#{url(asset)}\" #{attributes_from_hash(html_options)}/>"
71
+ end
72
+
73
+ def import_styles_tag(assets, html_options = {})
74
+ a = []
75
+ assets.each_slice(30) do |subset|
76
+ a << import_style_tag(subset, html_options)
77
+ end
78
+ a.join("\n")
79
+ end
80
+
81
+ def import_style_tag(assets, html_options = {})
82
+ imports = assets.collect{ |a| "@import \"#{url(a)}\";" }
83
+ content_tag(:style, "\n#{imports.join("\n")}\n", html_options.merge(:type => "text/css"))
84
+ end
85
+
86
+ def content_tag(name, content, options = {})
87
+ "<#{name} #{attributes_from_hash(options)}>#{content}</#{name}>"
88
+ end
89
+
90
+ def attributes_from_hash(options = {})
91
+ options.to_a.collect{|k, v| "#{k}=\"#{v}\""}.join(" ")
92
+ end
93
+
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,25 @@
1
+ def init_asset_library
2
+ require 'asset_library'
3
+
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')
7
+ end
8
+
9
+ namespace(:asset_library) do
10
+ desc "Writes all asset caches specified in config/asset.yml by concatenating the constituent files."
11
+ task(:write) do
12
+ init_asset_library
13
+ AssetLibrary.write_all_caches
14
+ end
15
+
16
+ desc "Deletes all asset caches specified in config/asset.yml"
17
+ task(:clean) do
18
+ 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
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ class AssetLibrary
2
+ 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
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
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')
@@ -0,0 +1,216 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ require 'set'
4
+
5
+ describe(AssetLibrary::AssetModule) do
6
+ before(:each) do
7
+ AssetLibrary.stub!(:root).and_return(prefix)
8
+ end
9
+
10
+ after(:each) do
11
+ wipe_fs
12
+ end
13
+
14
+ describe('#assets') do
15
+ it('should include file1 and file2') do
16
+ files = [ '/c/file1.css', '/c/file2.css' ]
17
+ stub_fs(files)
18
+ m(css_config(:files => ['file1', 'file2'])).assets.collect{|a| a.absolute_path}.should == ["#{prefix}/c/file1.css", "#{prefix}/c/file2.css"]
19
+ end
20
+
21
+ it('should not include file2 if that does not exist') do
22
+ files = [ '/c/file1.css' ]
23
+ stub_fs(files)
24
+ m(css_config(:files => ['file1', 'file2'])).assets.collect{|a| a.absolute_path}.should == [ "#{prefix}/c/file1.css" ]
25
+ end
26
+
27
+ it('should not include other files') do
28
+ files = [ '/c/file1.css', '/c/file2.css' ]
29
+ stub_fs(files)
30
+ m(css_config(:files => ['file1'])).assets.collect{|a| a.absolute_path}.should == [ "#{prefix}/c/file1.css" ]
31
+ end
32
+
33
+ it('should glob filenames') do
34
+ files = [ '/c/file1.css', '/c/file2.css', '/c/other_file.css' ]
35
+ stub_fs(files)
36
+ m(css_config(:files => ['file*'])).assets.collect{|a| a.absolute_path}.should == ["#{prefix}/c/file1.css", "#{prefix}/c/file2.css"]
37
+ end
38
+
39
+ it('should glob directories') do
40
+ files = [ '/c/file1.css', '/c/a/file2.css', '/c/b/a/file3.css' ]
41
+ stub_fs(files)
42
+ m(css_config(:files => ['**/file*'])).assets.collect{|a| a.absolute_path}.should == ["#{prefix}/c/a/file2.css", "#{prefix}/c/b/a/file3.css", "#{prefix}/c/file1.css"]
43
+ end
44
+
45
+ it('should use :optional_suffix when appropriate') do
46
+ files = [ '/c/file1.css', '/c/file1.css.o' ]
47
+ stub_fs(files)
48
+ m(css_config(:optional_suffix => 'o', :files => ['file1'])).assets.collect{|a| a.absolute_path}.should == ["#{prefix}/c/file1.css.o"]
49
+ end
50
+
51
+ it('should show :optional_suffix file even if original is absent') do
52
+ files = [ '/c/file1.css.o' ]
53
+ stub_fs(files)
54
+ m(css_config(:optional_suffix => 'o', :files => ['file1'])).assets.collect{|a| a.absolute_path}.should == ["#{prefix}/c/file1.css.o"]
55
+ end
56
+
57
+ it('should ignore :optional_suffix when suffixed file is not present') do
58
+ stub_fs([ '/c/file1.css' ])
59
+ m(css_config(:optional_suffix => 'o', :files => ['file1'])).assets.collect{|a| a.absolute_path}.should == [ "#{prefix}/c/file1.css" ]
60
+ end
61
+
62
+ it('should pick files with :extra_suffix') do
63
+ stub_fs([ '/c/file1.e.css' ])
64
+ m(css_config(:files => ['file1'])).assets_with_extra_suffix('e').collect{|a| a.absolute_path}.should == [ "#{prefix}/c/file1.e.css" ]
65
+ end
66
+
67
+ it('should ignore non-suffixed files when :extra_suffix is set') do
68
+ stub_fs([ '/c/file1.css' ])
69
+ m(css_config(:files => ['file1'])).assets_with_extra_suffix('e').collect{|a| a.absolute_path}.should == []
70
+ end
71
+
72
+ it('should use extra suffixes with format') do
73
+ stub_fs([ '/c/file1.e1.css', '/c/file1.e2.css' ])
74
+ m(css_config(:files => ['file1'], :formats => { :f1 => [ 'e1', 'e2' ] })).assets_with_format(:f1).collect{|a| a.absolute_path}.should == [ "#{prefix}/c/file1.e1.css", "#{prefix}/c/file1.e2.css" ]
75
+ end
76
+
77
+ it('should ignore extra suffixes unspecified in format') do
78
+ stub_fs([ '/c/file1.e1.css', '/c/file1.e2.css' ])
79
+ m(css_config(:files => ['file1'], :formats => { :f1 => [ 'e1' ] })).assets_with_format(:f1).collect{|a| a.absolute_path}.should == [ "#{prefix}/c/file1.e1.css" ]
80
+ end
81
+
82
+ it('should allow nil suffixes in format') do
83
+ stub_fs([ '/c/file1.css', '/c/file1.e1.css' ])
84
+ m(css_config(:files => ['file1'], :formats => { :f1 => [nil, 'e1'] })).assets_with_format(:f1).collect{|a| a.absolute_path}.should == ["#{prefix}/c/file1.css", "#{prefix}/c/file1.e1.css" ]
85
+ end
86
+
87
+ it('should combine :extra_suffix with :optional_suffix') do
88
+ stub_fs([ '/c/file1.e.css', '/c/file1.e.css.o' ])
89
+ m(css_config(:files => ['file1'], :optional_suffix => 'o')).assets_with_extra_suffix('e').collect{|a| a.absolute_path}.should == [ "#{prefix}/c/file1.e.css.o" ]
90
+ end
91
+
92
+ it('should ignore too many dots when globbing') do
93
+ stub_fs([ '/c/file1.x.css' ])
94
+ m(css_config(:files => ['file1*'])).assets.collect{|a| a.absolute_path}.should == []
95
+ end
96
+
97
+ it('should pick files with :extra_suffix when globbing') do
98
+ stub_fs([ '/c/file1.e.css', '/c/file2.css' ])
99
+ m(css_config(:files => ['file*'])).assets_with_extra_suffix('e').collect{|a| a.absolute_path}.should == [ "#{prefix}/c/file1.e.css" ]
100
+ end
101
+
102
+ it('should pick files with :optional_suffix when globbing') do
103
+ stub_fs([ '/c/file.css', '/c/file.css.o' ])
104
+ m(css_config(:optional_suffix => 'o', :files => ['file*'])).assets.collect{|a| a.absolute_path}.should == [ "#{prefix}/c/file.css.o" ]
105
+ end
106
+
107
+ it('should pick files with both :extra_suffix and :optional_suffix when globbing') do
108
+ stub_fs([ '/c/file.css', '/c/file.e.css', '/c/file.e.css.o' ])
109
+ m(css_config(:optional_suffix => 'o', :files => ['file*'])).assets_with_extra_suffix('e').collect{|a| a.absolute_path}.should == [ "#{prefix}/c/file.e.css.o" ]
110
+ end
111
+ end
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
+ describe('#cache_asset') do
126
+ it('should use options[:cache]') do
127
+ m(css_config).cache_asset.absolute_path.should == "#{prefix}/c/cache.css"
128
+ end
129
+
130
+ it('should use :format if set') do
131
+ m(css_config).cache_asset(:e).absolute_path.should == "#{prefix}/c/cache.e.css"
132
+ end
133
+ end
134
+
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
+ private
170
+
171
+ def m(config)
172
+ AssetLibrary::AssetModule.new(config)
173
+ end
174
+
175
+ def js_config(options = {})
176
+ {
177
+ :cache => 'cache',
178
+ :base => 'j',
179
+ :suffix => 'js',
180
+ :files => [ 'file1', 'file2' ]
181
+ }.merge(options)
182
+ end
183
+
184
+ def css_config(options = {})
185
+ {
186
+ :cache => 'cache',
187
+ :base => 'c',
188
+ :suffix => 'css',
189
+ :files => [ 'file1', 'file2' ]
190
+ }.merge(options)
191
+ end
192
+
193
+ def prefix
194
+ @prefix ||= File.dirname(__FILE__) + '/deleteme'
195
+ end
196
+
197
+ def stub_fs(filenames)
198
+ wipe_fs
199
+ FileUtils.mkdir(prefix)
200
+
201
+ filenames.each do |file|
202
+ path = File.join(prefix, file)
203
+ dir = File.dirname(path)
204
+ unless File.exist?(dir)
205
+ FileUtils.mkdir_p(dir)
206
+ end
207
+ File.open(path, 'w') { |f| f.write("#{file}\n") }
208
+ end
209
+ end
210
+
211
+ def wipe_fs
212
+ if File.exist?(prefix)
213
+ FileUtils.rm_r(prefix)
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,49 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ require File.dirname(__FILE__) + '/../../lib/asset_library/asset'
4
+
5
+ describe(AssetLibrary::Asset) do
6
+ it('should have eql? work for identical assets') do
7
+ a('/a/b.css').should eql(a('/a/b.css'))
8
+ end
9
+
10
+ it('should use absolute_path as its hash') do
11
+ a('/a/b.css').hash.should == '/a/b.css'.hash
12
+ end
13
+
14
+ context('#relative_path') do
15
+ it('should strip AssetLibrary.root') do
16
+ AssetLibrary.stub!(:root).and_return('/r')
17
+ a('/r/a/b.css').relative_path.should == '/a/b.css'
18
+ end
19
+
20
+ it('should strip nothing if root is "/"') do
21
+ AssetLibrary.stub!(:root).and_return('/')
22
+ a('/r/a/b.css').relative_path.should == '/r/a/b.css'
23
+ end
24
+ end
25
+
26
+ context('#timestamp') do
27
+ it('should return the file mtime') do
28
+ File.stub!(:mtime).with('/r/a/b.css').and_return(Time.at(123))
29
+ a('/r/a/b.css').timestamp.should == Time.at(123)
30
+ end
31
+ end
32
+
33
+ context('#relative_url') do
34
+ before(:each) do
35
+ AssetLibrary.stub!(:root).and_return('/r')
36
+ end
37
+
38
+ it('should use relative path and mtime') do
39
+ File.stub!(:mtime).with('/r/a/b.css').and_return(Time.at(123))
40
+ a('/r/a/b.css').relative_url.should == '/a/b.css?123'
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def a(*args)
47
+ AssetLibrary::Asset.new(*args)
48
+ end
49
+ end
@@ -0,0 +1,195 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ require File.dirname(__FILE__) + '/../../lib/asset_library/helpers'
4
+
5
+ describe(AssetLibrary::Helpers) do
6
+ before(:each) do
7
+ @h = nil
8
+ AssetLibrary.stub!(:root).and_return('/')
9
+ end
10
+ before(:each) do
11
+ @old_cache = AssetLibrary.cache # Empty globals
12
+ end
13
+
14
+ after(:each) do
15
+ @h = nil
16
+ end
17
+ after(:each) do
18
+ AssetLibrary.cache = @old_cache
19
+ @old_cache = nil
20
+ end
21
+
22
+ describe('#asset_library_javascript_tags') do
23
+ describe('when not caching') do
24
+ before(:each) do
25
+ AssetLibrary.stub!(:cache).and_return(false)
26
+ end
27
+
28
+ it('should fetch using asset_module') do
29
+ m = mock(:assets => [])
30
+ AssetLibrary.should_receive(:asset_module).with(:m).and_return(m)
31
+ h.asset_library_javascript_tags(:m)
32
+ end
33
+
34
+ it('should output nothing when a module is empty') do
35
+ m = mock(:assets => [])
36
+ AssetLibrary.stub!(:asset_module).and_return(m)
37
+ h.asset_library_javascript_tags(:m).should == ''
38
+ end
39
+
40
+ it('should output a <script> tag for a file') do
41
+ m = mock(:assets => [a('/f.js')])
42
+ AssetLibrary.stub!(:asset_module).and_return(m)
43
+ h.asset_library_javascript_tags(:m).should == '<script type="text/javascript" src="/f.js?123"></script>'
44
+ end
45
+
46
+ it('should join <script> tags with newlines') do
47
+ m = mock(:assets => [a('/f.js'), a('/f2.js')])
48
+ AssetLibrary.stub!(:asset_module).and_return(m)
49
+ h.asset_library_javascript_tags(:m).should == '<script type="text/javascript" src="/f.js?123"></script>' + "\n" + '<script type="text/javascript" src="/f2.js?123"></script>'
50
+ end
51
+
52
+ it('should use compute_asset_host if available') do
53
+ m = mock(:assets => [a('/f.js')])
54
+ AssetLibrary.stub!(:asset_module).and_return(m)
55
+ h.should_receive(:compute_asset_host).with('/f.js?123').and_return('http://assets.test')
56
+ h.asset_library_javascript_tags(:m).should =~ %r{"http://assets.test/f.js\?123"}
57
+ end
58
+
59
+ it('should not use compute_asset_host if it returns nil') do
60
+ m = mock(:assets => [a('/f.js')])
61
+ AssetLibrary.stub!(:asset_module).and_return(m)
62
+ h.should_receive(:compute_asset_host).and_return(nil)
63
+ h.asset_library_javascript_tags(:m).should =~ %r{"/f.js\?123"}
64
+ end
65
+
66
+ it('should not use compute_asset_host if it returns ""') do
67
+ m = mock(:assets => [a('/f.js')])
68
+ AssetLibrary.stub!(:asset_module).and_return(m)
69
+ h.should_receive(:compute_asset_host).and_return("")
70
+ h.asset_library_javascript_tags(:m).should =~ %r{"/f.js\?123"}
71
+ end
72
+
73
+ it('should add request protocol to compute_asset_host output if applicable') do
74
+ m = mock(:assets => [a('/f.js')])
75
+ AssetLibrary.stub!(:asset_module).and_return(m)
76
+ h.stub!(:compute_asset_host).and_return('assets.test')
77
+ h.instance_variable_set(:@controller, mock(:request => mock(:protocol => 'http://')))
78
+ h.asset_library_javascript_tags(:m).should =~ %r{"http://assets.test/f.js\?123"}
79
+ end
80
+ end
81
+
82
+ describe('when caching') do
83
+ before(:each) do
84
+ AssetLibrary.cache = true
85
+ end
86
+
87
+ it('should output a single <script> tag with the cache filename') do
88
+ m = mock(:cache_asset => a('/cache.js'))
89
+ AssetLibrary.stub!(:asset_module).and_return(m)
90
+ h.asset_library_javascript_tags(:m).should == '<script type="text/javascript" src="/cache.js?123"></script>'
91
+ end
92
+
93
+ it('should use compute_asset_host if available') do
94
+ m = mock(:cache_asset => a('/cache.js'))
95
+ AssetLibrary.stub!(:asset_module).and_return(m)
96
+ h.should_receive(:compute_asset_host).with('/cache.js?123').and_return('http://assets.test')
97
+ h.asset_library_javascript_tags(:m)
98
+ #h.asset_library_javascript_tags(:m).should =~ %r{"http://assets.test/cache.js\?123"}
99
+ end
100
+ end
101
+ end
102
+
103
+ describe('#asset_library_stylesheet_tags') do
104
+ describe('when not caching') do
105
+ before(:each) do
106
+ AssetLibrary.stub!(:cache).and_return(false)
107
+ end
108
+
109
+ it('should fetch using asset_module') do
110
+ m = mock(:assets => [])
111
+ AssetLibrary.should_receive(:asset_module).with(:m).and_return(m)
112
+ h.asset_library_stylesheet_tags(:m)
113
+ end
114
+
115
+ it('should output nothing when a module is empty') do
116
+ m = mock(:assets => [])
117
+ AssetLibrary.stub!(:asset_module).and_return(m)
118
+ h.asset_library_stylesheet_tags(:m).should == ''
119
+ end
120
+
121
+ it('should output a single <script> with a single @import when there is one file') do
122
+ m = mock(:assets => [a('/f.css')])
123
+ AssetLibrary.stub!(:asset_module).and_return(m)
124
+ h.asset_library_stylesheet_tags(:m).should == "<style type=\"text/css\">\n@import \"\/f.css?123\";\n</style>"
125
+ end
126
+
127
+ it('should use formats to find cache filename') do
128
+ m = mock
129
+ m.should_receive(:assets).with(:e).and_return([a('f.e.css')])
130
+ AssetLibrary.stub!(:asset_module).and_return(m)
131
+ h.asset_library_stylesheet_tags(:m, :e).should == "<style type=\"text/css\">\n@import \"f.e.css?123\";\n</style>"
132
+ end
133
+
134
+ it('should output a single <script> tag with 30 @import') do
135
+ m = mock(:assets => (1..30).collect{|i| a("/f#{i}.css") })
136
+ AssetLibrary.stub!(:asset_module).and_return(m)
137
+ h.asset_library_stylesheet_tags(:m).should =~ /\<style type=\"text\/css\"\>(\n@import \"\/f\d+.css\?123\";){30}\n\<\/style\>/
138
+ end
139
+
140
+ it('should output two <script> tags with 31 @imports') do
141
+ m = mock(:assets => (1..31).collect{|i| a("/f#{i}.css") })
142
+ AssetLibrary.stub!(:asset_module).and_return(m)
143
+ h.asset_library_stylesheet_tags(:m).should =~ /\<style type="text\/css"\>(\n@import "\/f\d+.css\?123";){30}\n\<\/style\>\n<style type="text\/css"\>\n@import "\/f31.css\?123";\n\<\/style\>/
144
+ end
145
+
146
+ it('should output a final hash in the parameters as html attributes') do
147
+ m = mock(:assets => [a('/f.css')])
148
+ AssetLibrary.stub!(:asset_module).and_return(m)
149
+ optional_hash = {:key1 => "val1", :key2 => "val2", :key3 => "val3"}
150
+ attributes_to_hash( h.asset_library_stylesheet_tags(:m, optional_hash), [:type] ).should == optional_hash
151
+ end
152
+ end
153
+
154
+ describe('when caching') do
155
+ before(:each) do
156
+ AssetLibrary.stub!(:cache).and_return(true)
157
+ end
158
+
159
+ it('should output a single <style> tag with the cache filename') do
160
+ m = mock(:cache_asset => a('/cache.css'))
161
+ AssetLibrary.stub!(:asset_module).and_return(m)
162
+ h.asset_library_stylesheet_tags(:m).should == '<link rel="stylesheet" type="text/css" href="/cache.css?123" />'
163
+ end
164
+
165
+ it('should use format for the cache filename') do
166
+ m = mock
167
+ m.should_receive(:cache_asset).with(:e).and_return(a('/cache.e.css'))
168
+ AssetLibrary.stub!(:asset_module).and_return(m)
169
+ h.asset_library_stylesheet_tags(:m, :e).should == '<link rel="stylesheet" type="text/css" href="/cache.e.css?123" />'
170
+ end
171
+
172
+ it('should output a final hash in the parameters as html attributes') do
173
+ m = mock(:cache_asset => a('/cache.css'))
174
+ AssetLibrary.stub!(:asset_module).and_return(m)
175
+ optional_hash = {:key1 => "val1", :key2 => "val2", :key3 => "val3"}
176
+ attributes_to_hash( h.asset_library_stylesheet_tags(:m, optional_hash), [:type, :rel, :href] ).should == optional_hash
177
+ end
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ def a(path)
184
+ File.stub!(:mtime).and_return(Time.at(123))
185
+ AssetLibrary::Asset.new(path)
186
+ end
187
+
188
+ def h
189
+ return @h if @h
190
+ c = Class.new do
191
+ include AssetLibrary::Helpers
192
+ end
193
+ @h = c.new
194
+ end
195
+ end
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
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
14
+ end
15
+
16
+ describe('#config') do
17
+ it('should YAML.load_file the config from config_path') do
18
+ AssetLibrary.config_path = '/config.yml'
19
+ File.stub!(:exist?).with('/config.yml').and_return(true)
20
+ YAML.should_receive(:load_file).with('/config.yml')
21
+ AssetLibrary.config
22
+ end
23
+
24
+ it('should return {} if config_path does not exist') do
25
+ AssetLibrary.config_path = '/config.yml'
26
+ File.stub!(:exist?).with('/config.yml').and_return(false)
27
+ AssetLibrary.config.should == {}
28
+ end
29
+
30
+ it('should cache config if cache is set') do
31
+ AssetLibrary.cache = true
32
+ AssetLibrary.config_path = '/config.yml'
33
+
34
+ File.stub!(:exist?).with('/config.yml').and_return(true)
35
+ YAML.should_receive(:load_file).with('/config.yml').once
36
+
37
+ AssetLibrary.config
38
+ AssetLibrary.config
39
+ end
40
+
41
+ it('should not cache config if cache is not set') do
42
+ AssetLibrary.cache = false
43
+ AssetLibrary.config_path = '/config.yml'
44
+
45
+ File.stub!(:exist?).with('/config.yml').and_return(true)
46
+ YAML.should_receive(:load_file).with('/config.yml').twice
47
+
48
+ AssetLibrary.config
49
+ AssetLibrary.config
50
+ end
51
+
52
+ it('should symbolize config hash keys') do
53
+ AssetLibrary.cache = false
54
+ AssetLibrary.config_path = '/config.yml'
55
+
56
+ File.stub!(:exist?).with('/config.yml').and_return(true)
57
+ YAML.should_receive(:load_file).with('/config.yml').and_return(
58
+ { 'a' => { 'b' => 'c' } }
59
+ )
60
+
61
+ AssetLibrary.config.should == { :a => { :b => 'c' } }
62
+ end
63
+ end
64
+
65
+ describe('#asset_module') do
66
+ before(:each) do
67
+ @config = {}
68
+ AssetLibrary.stub!(:config).and_return(@config)
69
+ end
70
+
71
+ it('should return nil when given an invalid key') do
72
+ AssetLibrary.asset_module(:foo).should == nil
73
+ end
74
+
75
+ it('should return an AssetModule when given a valid key') do
76
+ @config[:foo] = {}
77
+ AssetLibrary.asset_module(:foo).should(be_a(AssetLibrary::AssetModule))
78
+ end
79
+ end
80
+
81
+ describe('#write_all_caches') do
82
+ it('should call write_all_caches on all asset_modules') do
83
+ mock1 = mock
84
+ mock2 = mock
85
+
86
+ mock1.should_receive(:write_all_caches)
87
+ mock2.should_receive(:write_all_caches)
88
+
89
+ AssetLibrary.stub!(:asset_module).with(:mock1).and_return(mock1)
90
+ AssetLibrary.stub!(:asset_module).with(:mock2).and_return(mock2)
91
+
92
+ AssetLibrary.stub!(:config).and_return({ :mock1 => {}, :mock2 => {} })
93
+
94
+ AssetLibrary.write_all_caches
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ require File.dirname(__FILE__) + '/../lib/asset_library'
5
+
6
+ def attributes_to_hash(string, without = [])
7
+ hash_from_tag_attributes = {}
8
+
9
+ string.scan(/\s([^\s=]+="[^"]*)"/).each do |attr|
10
+ a = attr[0].split("=\"")
11
+ hash_from_tag_attributes.merge!( a[0].to_sym => a[1] )
12
+ end
13
+
14
+ without.each{|k| hash_from_tag_attributes.delete k}
15
+
16
+ hash_from_tag_attributes
17
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: alegscogs-asset_library
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.4
5
+ platform: ruby
6
+ authors:
7
+ - Alex Cox
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-12 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: adamh-glob_fu
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.4
24
+ version:
25
+ description:
26
+ email: alexc@patch.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - lib/asset_library.rb
36
+ - lib/asset_library/asset.rb
37
+ - lib/asset_library/asset_module.rb
38
+ - lib/asset_library/helpers.rb
39
+ - lib/asset_library/rake_tasks.rb
40
+ - lib/asset_library/util.rb
41
+ - rails/init.rb
42
+ - LICENSE
43
+ - README.rdoc
44
+ has_rdoc: true
45
+ homepage: http://github.com/alegscogs/alegscogs-asset_library
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --charset=UTF-8
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.3.5
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Manage and bundle CSS and JavaScript files
72
+ test_files:
73
+ - spec/asset_library/asset_module_spec.rb
74
+ - spec/asset_library/asset_spec.rb
75
+ - spec/asset_library/helpers_spec.rb
76
+ - spec/asset_library_spec.rb
77
+ - spec/spec_helper.rb