patch-asset_library 0.5.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,220 @@
1
+ = asset_library
2
+
3
+ Bundles your JavaScript and CSS, so your development environment is simple to
4
+ code and your production environment is as fast as possible.
5
+
6
+ == Installation
7
+
8
+ First, install the gem:
9
+
10
+ sudo gem install alegscogs-asset_library --source http://gems.github.com
11
+
12
+ Next, add the gem into your Rails project's config/environment.rb:
13
+
14
+ config.gem 'alegscogs-asset_library', :lib => 'asset_library', :source => 'http://gems.github.com'
15
+
16
+ Finally, include the Rake tasks in your project:
17
+
18
+ echo "begin; require 'asset_library/rake_tasks'; rescue LoadError; end" > lib/tasks/asset_library.rake
19
+
20
+ == Usage
21
+
22
+ In your Rails project, edit <tt>config/asset_library.yml</tt> as described
23
+ in the following section.
24
+
25
+ Once configured, asset_library provides two helper methods for your views:
26
+
27
+ <%# outputs library.js (production) or its files (development) %>
28
+ <%= asset_library_javascript_tags(:javascripts) %>
29
+
30
+ <%# outputs library.css (production) or its files (development) %>
31
+ <%= asset_library_stylesheet_tags(:stylesheets) %>
32
+
33
+ <%# outputs library.ie6.css (production) or its files (development) %>
34
+ <!--[if lte IE 6]>
35
+ <%= asset_library_stylesheet_tags(:stylesheets, :ie6) %>
36
+ <![endif]-->
37
+
38
+ Both helpers behave differently depending on whether
39
+ <tt>ActionController::Base.perform_caching</tt> is true (that is, whether you
40
+ are in <tt>development</tt> environment or not). When caching is disabled, each
41
+ file in the module will be included. (Internet Explorer only allows 30
42
+ <tt>style</tt> and <tt>link</tt> stylesheet tags; in development mode,
43
+ <tt>import</tt> rules are used to work around the bug.) When caching is
44
+ enabled, a single tag is output.
45
+
46
+ When caching is enabled, the modules to include must be generated using:
47
+
48
+ rake asset_library:write
49
+
50
+ These moduels can be removed using:
51
+
52
+ rake asset_library:clean
53
+
54
+ A cached module is simply the concatenation of its constituent files.
55
+
56
+ == Configuration
57
+
58
+ A typical configuration (Yaml) file might look like this. In Rails, this
59
+ should be in <tt>config/asset_library.yml</tt>.
60
+
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/**/*
105
+
106
+ # in general...
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
+ # ...
121
+
122
+ Here are what the various configuration elements mean:
123
+
124
+ <tt>module_name</tt> is the name of the module. It is passed as the first
125
+ parameter to <tt>asset_library_javascript_tags</tt> or
126
+ <tt>asset_library_stylesheet_tags</tt>.
127
+
128
+ <tt>cache</tt> is a filename, without suffix, relative to <tt>base</tt>, where
129
+ the module will be bundled when caching is enabled. (Ensure that <tt>files</tt>
130
+ does not include <tt>cache_file</tt>, even with globbing.)
131
+
132
+ <tt>base</tt> is the base path of the assets in URLs. For instance, in Rails,
133
+ where stylesheets are usually served under <tt>/stylesheets</tt>, <tt>base</tt>
134
+ should be <tt>stylesheets</tt>.
135
+
136
+ <tt>suffix</tt> is either "js" or "css", depending on whether you are including
137
+ JavaScript or CSS files.
138
+
139
+ <tt>formats</tt> allows construction of parallel modules. By default, for a
140
+ module named <tt>styles</tt> will use <tt>*.css</tt> (where <tt>*</tt> is
141
+ specified by the <tt>files</tt> option) to generate <tt>styles.css</tt>; but
142
+ filenames of the format <tt>*.suffix.css</tt> will be ignored in that search.
143
+ If a <tt>format</tt> called <tt>format1</tt> is specified as
144
+ <tt>[suffix1, suffix2]</tt>, then <tt>*.suffix1.css</tt> and
145
+ <tt>*.suffix2.css</tt> will be included, but not <tt>*.css</tt>. (To specify
146
+ <tt>*.css</tt>, put <tt>null</tt> in the format list.)
147
+
148
+ <tt>optional_suffix</tt> will cause asset_library to check for the existence of
149
+ files with <tt>optional_suffix</tt> suffixes, falling back to files without
150
+ <tt>optional_suffix</tt> if necessary. For instance, if you run all your
151
+ JavaScript files through
152
+ {YUI Compressor}[http://developer.yahoo.com/yui/compressor/] and output the
153
+ compressed version of <tt>file1.js</tt> as <tt>file1.compressed.js</tt>, then
154
+ set <tt>optional_suffix</tt> to <tt>compressed</tt>. In your development
155
+ environment, where <tt>compressed</tt> javascripts are missing,
156
+ <tt>file1.js</tt> will be included and you can debug your JavaScript. In your
157
+ production environment, create the <tt>compressed</tt> JavaScripts in the same
158
+ directory, and they will be included instead, for optimal download speed.
159
+
160
+ <tt>files</tt> is a list of files, relative to <tt>base</tt>. File globbing is
161
+ allowed; <tt>**</tt> expands to "any nesting of directories". Files which do
162
+ not exist will be excluded; globbing will include/exclude files with
163
+ <tt>formats</tt> as appropriate; and files without <tt>optional_suffix</tt>
164
+ will not be output alongside files with the same name endowed with the suffix.
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
+
211
+ == Copyright
212
+
213
+ I believe in software freedom, not any abomination thereof. This project is
214
+ released under the Public Domain, meaning I relinquish my copyright (to any
215
+ extend the law allows) and grant you all rights to use, modify, sell, or
216
+ otherwise take advantage of my software.
217
+
218
+ However, I do kindly request that, as a favor, you refrain from using my
219
+ software as part of an evil plan involving velociraptors and mind-controlling
220
+ robots (even though I would not be legally entitled to sue you for doing so).
@@ -0,0 +1,121 @@
1
+ begin
2
+ require 'glob_fu'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'glob_fu'
6
+ end
7
+
8
+ require 'yaml'
9
+ require File.dirname(__FILE__) + '/asset_library/compiler'
10
+ require File.dirname(__FILE__) + '/asset_library/asset_module'
11
+ require File.dirname(__FILE__) + '/asset_library/util'
12
+
13
+ class AssetLibrary
14
+ ConfigurationError = Class.new(RuntimeError)
15
+
16
+ class << self
17
+ def config_path
18
+ @config_path
19
+ end
20
+
21
+ def config_path=(config_path)
22
+ @config_path = config_path
23
+ end
24
+
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
32
+
33
+ #
34
+ # Root directory of your output files.
35
+ #
36
+ # Output files are resolved relative to this path.
37
+ #
38
+ attr_accessor :root
39
+
40
+ def cache
41
+ return true if @cache.nil?
42
+ @cache
43
+ end
44
+
45
+ def cache=(cache)
46
+ reset!
47
+ @cache = cache
48
+ end
49
+
50
+ def cache_vars
51
+ # We store cache_vars even if not caching--this is our "globals"
52
+ @cache_vars ||= {}
53
+ end
54
+
55
+ def config
56
+ return @config if cache && @config
57
+ ret = if File.exist?(config_path)
58
+ yaml = YAML.load_file(config_path) || {}
59
+ Util::symbolize_hash_keys(yaml)
60
+ else
61
+ {}
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] ||= {}
74
+ @config = cache ? ret : nil
75
+ ret
76
+ end
77
+
78
+ def compilers
79
+ @compilers ||= {}
80
+ end
81
+
82
+ def asset_module(key)
83
+ module_config = config[:modules][key.to_sym]
84
+ if module_config
85
+ AssetModule.new(key, module_config)
86
+ end
87
+ end
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
+
95
+ def write_all_caches
96
+ config[:modules].keys.each do |key|
97
+ m = asset_module(key)
98
+ c = compiler(m)
99
+ c.add_asset_module(m)
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!
119
+ end
120
+ end
121
+ 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,94 @@
1
+ require File.dirname(__FILE__) + '/asset'
2
+
3
+ class AssetLibrary
4
+ class AssetModule
5
+ attr_reader(:config)
6
+
7
+ def initialize(name, config)
8
+ @name = name.to_s
9
+ @config = config
10
+ add_default_format
11
+ end
12
+
13
+ attr_reader :name
14
+
15
+ # Returns the type of compiler to use for this asset module.
16
+ def compiler_type
17
+ (config[:compiler] || :default).to_sym
18
+ end
19
+
20
+ # Returns the Array of compiler flags to use.
21
+ def compiler_flags
22
+ Util.normalize_flags(config[:compiler_flags])
23
+ end
24
+
25
+ # Returns an Array of Assets to include.
26
+ #
27
+ # Arguments:
28
+ # extra_suffix: if set, finds files with the given extra suffix
29
+ def assets(format = nil)
30
+ if format
31
+ assets_with_format(format)
32
+ else
33
+ assets_with_extra_suffix(nil)
34
+ end
35
+ end
36
+
37
+ # Returns an Array of Assets to include.
38
+ #
39
+ # Arguments:
40
+ # extra_suffix: if set, finds files with the given extra suffix
41
+ def assets_with_extra_suffix(extra_suffix)
42
+ return nil unless config
43
+
44
+ GlobFu.find(
45
+ config[:files],
46
+ :suffix => config[:suffix],
47
+ :extra_suffix => extra_suffix,
48
+ :root => File.join(*([AssetLibrary.root, config[:base]].compact)),
49
+ :optional_suffix => config[:optional_suffix]
50
+ ).collect { |f| Asset.new(f) }
51
+ end
52
+
53
+ # Returns an Array of Assets to include.
54
+ #
55
+ # Calls assets_with_extra_suffix for each suffix in the given format
56
+ #
57
+ # Arguments:
58
+ # format: format specified in the config
59
+ def assets_with_format(format)
60
+ return nil unless config
61
+
62
+ extra_suffixes = config[:formats][format.to_sym]
63
+ extra_suffixes.inject([]) { |r, s| r.concat(assets_with_extra_suffix(s)) }
64
+ end
65
+
66
+ # Returns an Asset representing the cache file
67
+ def cache_asset(format = nil)
68
+ extra = format ? ".#{format}" : ''
69
+ Asset.new(File.join(AssetLibrary.root, config[:base], "#{config[:cache]}#{extra}.#{config[:suffix]}"))
70
+ end
71
+
72
+ # Return the formats to generate.
73
+ def formats
74
+ (config[:formats] || {}).keys.sort_by{|sym| sym.to_s}
75
+ end
76
+
77
+ # Yield each set of input paths with their output path. All paths
78
+ # are absolute.
79
+ def each_compilation
80
+ formats.each do |format|
81
+ inputs = assets(format).map { |asset| asset.absolute_path }
82
+ output = cache_asset(format).absolute_path
83
+ yield inputs, output
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def add_default_format
90
+ config[:formats] ||= {}
91
+ config[:formats][nil] = [nil]
92
+ end
93
+ end
94
+ end