juicer 0.2.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. data/History.txt +28 -0
  2. data/Rakefile +84 -36
  3. data/Readme.rdoc +192 -23
  4. data/VERSION +1 -0
  5. data/bin/juicer +2 -4
  6. data/lib/juicer.rb +9 -10
  7. data/lib/juicer/asset/path.rb +275 -0
  8. data/lib/juicer/asset/path_resolver.rb +79 -0
  9. data/lib/juicer/binary.rb +3 -5
  10. data/lib/juicer/cache_buster.rb +112 -27
  11. data/lib/juicer/command/install.rb +4 -2
  12. data/lib/juicer/command/list.rb +16 -9
  13. data/lib/juicer/command/merge.rb +30 -14
  14. data/lib/juicer/command/verify.rb +1 -1
  15. data/lib/juicer/css_cache_buster.rb +31 -47
  16. data/lib/juicer/datafy/datafy.rb +20 -0
  17. data/lib/juicer/dependency_resolver/css_dependency_resolver.rb +29 -0
  18. data/lib/juicer/dependency_resolver/dependency_resolver.rb +101 -0
  19. data/lib/juicer/dependency_resolver/javascript_dependency_resolver.rb +23 -0
  20. data/lib/juicer/ext/logger.rb +5 -0
  21. data/lib/juicer/ext/string.rb +47 -0
  22. data/lib/juicer/ext/symbol.rb +15 -0
  23. data/lib/juicer/image_embed.rb +129 -0
  24. data/lib/juicer/install/base.rb +2 -2
  25. data/lib/juicer/install/closure_compiler_installer.rb +69 -0
  26. data/lib/juicer/install/jslint_installer.rb +3 -3
  27. data/lib/juicer/install/rhino_installer.rb +3 -2
  28. data/lib/juicer/install/yui_compressor_installer.rb +3 -2
  29. data/lib/juicer/jslint.rb +1 -1
  30. data/lib/juicer/merger/base.rb +1 -1
  31. data/lib/juicer/merger/javascript_merger.rb +3 -4
  32. data/lib/juicer/merger/stylesheet_merger.rb +13 -15
  33. data/lib/juicer/minifyer/closure_compiler.rb +90 -0
  34. data/lib/juicer/minifyer/java_base.rb +77 -0
  35. data/lib/juicer/minifyer/yui_compressor.rb +15 -48
  36. data/test/bin/jslint-1.0.js +523 -0
  37. data/test/bin/jslint.js +523 -0
  38. data/test/bin/rhino1_7R1.zip +0 -0
  39. data/test/bin/rhino1_7R2-RC1.jar +0 -0
  40. data/test/bin/rhino1_7R2-RC1.zip +0 -0
  41. data/test/bin/yuicompressor +0 -0
  42. data/test/bin/yuicompressor-2.3.5.zip +0 -0
  43. data/test/bin/yuicompressor-2.4.2.jar +0 -0
  44. data/test/bin/yuicompressor-2.4.2.zip +0 -0
  45. data/test/data/Changelog.txt +10 -0
  46. data/test/data/a.css +3 -0
  47. data/test/data/a.js +5 -0
  48. data/test/data/a1.css +5 -0
  49. data/test/data/b.css +1 -0
  50. data/test/data/b.js +5 -0
  51. data/test/data/b1.css +5 -0
  52. data/test/data/c1.css +3 -0
  53. data/test/data/css/2.gif +1 -0
  54. data/test/data/css/test.css +11 -0
  55. data/test/data/css/test2.css +1 -0
  56. data/test/data/d1.css +3 -0
  57. data/test/data/images/1.png +1 -0
  58. data/test/data/my_app.js +2 -0
  59. data/test/data/not-ok.js +2 -0
  60. data/test/data/ok.js +3 -0
  61. data/test/data/path_test.css +5 -0
  62. data/test/data/path_test2.css +14 -0
  63. data/test/data/pkg/module/moda.js +2 -0
  64. data/test/data/pkg/module/modb.js +3 -0
  65. data/test/data/pkg/pkg.js +1 -0
  66. data/test/fixtures/yui-download.html +425 -0
  67. data/test/test_helper.rb +36 -7
  68. data/test/unit/juicer/asset/path_resolver_test.rb +76 -0
  69. data/test/unit/juicer/asset/path_test.rb +370 -0
  70. data/test/unit/juicer/cache_buster_test.rb +104 -0
  71. data/test/{juicer/test_chainable.rb → unit/juicer/chainable_test.rb} +1 -1
  72. data/test/unit/juicer/command/install_test.rb +58 -0
  73. data/test/{juicer/command/test_list.rb → unit/juicer/command/list_test.rb} +26 -14
  74. data/test/unit/juicer/command/merge_test.rb +162 -0
  75. data/test/{juicer/command/test_util.rb → unit/juicer/command/util_test.rb} +10 -6
  76. data/test/unit/juicer/command/verify_test.rb +48 -0
  77. data/test/{juicer/test_css_cache_buster.rb → unit/juicer/css_cache_buster_test.rb} +10 -30
  78. data/test/unit/juicer/datafy_test.rb +37 -0
  79. data/test/{juicer/merger/test_css_dependency_resolver.rb → unit/juicer/dependency_resolver/css_dependency_resolver_test.rb} +2 -2
  80. data/test/{juicer/merger/test_javascript_dependency_resolver.rb → unit/juicer/dependency_resolver/javascript_dependency_resolver_test.rb} +13 -2
  81. data/test/unit/juicer/ext/{#string_test.rb# → string_test.rb} +0 -7
  82. data/test/unit/juicer/ext/symbol_test.rb +27 -0
  83. data/test/unit/juicer/image_embed_test.rb +271 -0
  84. data/test/unit/juicer/install/installer_base_test.rb +214 -0
  85. data/test/{juicer/install/test_jslint_installer.rb → unit/juicer/install/jslint_installer_test.rb} +1 -1
  86. data/test/{juicer/install/test_rhino_installer.rb → unit/juicer/install/rhino_installer_test.rb} +1 -1
  87. data/test/{juicer/install/test_yui_compressor_installer.rb → unit/juicer/install/yui_compressor_test.rb} +16 -16
  88. data/test/unit/juicer/jslint_test.rb +60 -0
  89. data/test/{juicer/merger/test_base.rb → unit/juicer/merger/base_test.rb} +1 -1
  90. data/test/{juicer/merger/test_javascript_merger.rb → unit/juicer/merger/javascript_merger_test.rb} +2 -2
  91. data/test/{juicer/merger/test_stylesheet_merger.rb → unit/juicer/merger/stylesheet_merger_test.rb} +15 -13
  92. data/test/unit/juicer/minifyer/closure_compressor_test.rb +107 -0
  93. data/test/{integration → unit}/juicer/minifyer/yui_compressor_test.rb +30 -47
  94. data/test/unit/juicer_test.rb +1 -0
  95. metadata +207 -113
  96. data/lib/juicer/core.rb +0 -61
  97. data/lib/juicer/merger/css_dependency_resolver.rb +0 -25
  98. data/lib/juicer/merger/dependency_resolver.rb +0 -82
  99. data/lib/juicer/merger/javascript_dependency_resolver.rb +0 -21
  100. data/tasks/ann.rake +0 -80
  101. data/tasks/bones.rake +0 -20
  102. data/tasks/gem.rake +0 -201
  103. data/tasks/git.rake +0 -40
  104. data/tasks/notes.rake +0 -27
  105. data/tasks/post_load.rake +0 -34
  106. data/tasks/rdoc.rake +0 -51
  107. data/tasks/rubyforge.rake +0 -55
  108. data/tasks/setup.rb +0 -292
  109. data/tasks/spec.rake +0 -54
  110. data/tasks/svn.rake +0 -47
  111. data/tasks/test.rake +0 -40
  112. data/tasks/test/setup.rake +0 -35
  113. data/tasks/zentest.rake +0 -36
  114. data/test/juicer/command/test_install.rb +0 -53
  115. data/test/juicer/command/test_merge.rb +0 -160
  116. data/test/juicer/command/test_verify.rb +0 -33
  117. data/test/juicer/install/test_installer_base.rb +0 -195
  118. data/test/juicer/minifyer/test_yui_compressor.rb +0 -159
  119. data/test/juicer/test_cache_buster.rb +0 -58
  120. data/test/juicer/test_core.rb +0 -47
  121. data/test/juicer/test_jslint.rb +0 -33
  122. data/test/test_juicer.rb +0 -4
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/bin/juicer CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  require "rubygems"
3
- dir = File.dirname(File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__)
4
- base = File.expand_path(File.join(dir, %w[.. lib juicer]))
5
- require base
6
- require File.join(base, "cli")
3
+ require "juicer"
4
+ require "juicer/cli"
7
5
 
8
6
  Juicer::Cli.run(ARGV)
data/lib/juicer.rb CHANGED
@@ -3,7 +3,7 @@ require "logger"
3
3
  module Juicer
4
4
 
5
5
  # :stopdoc:
6
- VERSION = '0.2.6'
6
+ VERSION = '1.0.0'
7
7
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
8
8
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
9
9
  LOGGER = Logger.new(STDOUT)
@@ -51,20 +51,19 @@ module Juicer
51
51
  end
52
52
 
53
53
  # Utility method used to require all files ending in .rb that lie in the
54
- # directory below this file that has the same name as the filename passed
55
- # in. Optionally, a specific _directory_ name can be passed in such that
56
- # the _filename_ does not have to be equivalent to the directory.
57
- #
58
- def self.require_all_libs_relative_to( fname, dir = nil )
59
- dir ||= ::File.basename(fname, '.*')
60
- search_me = ::File.expand_path(::File.join(::File.dirname(fname), dir, '**', '*.rb'))
54
+ # directory below this file.
55
+ def self.require_all_libs
56
+ dir = File.dirname(File.expand_path(__FILE__))
57
+ glob = File.join(dir, "juicer", '**', '*.rb')
61
58
 
62
- Dir.glob(search_me).sort.each { |rb| require rb }
59
+ # Unexpand paths (avoids requiring the same file twice)
60
+ paths = Dir.glob(glob).map { |path| path.sub("#{dir}/", '').sub(/\.rb$/, "") }
61
+ paths.each { |rb| require rb }
63
62
  end
64
63
 
65
64
  end
66
65
 
67
- Juicer.require_all_libs_relative_to(__FILE__)
66
+ Juicer.require_all_libs
68
67
 
69
68
  class FileNotFoundError < Exception
70
69
  end
@@ -0,0 +1,275 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "pathname"
4
+ require "juicer/cache_buster"
5
+
6
+ module Juicer
7
+ module Asset
8
+ #
9
+ # Assets are files used by CSS and JavaScript files. The Asset class provides
10
+ # tools for manipulating asset paths, such as rebasing, adding cache busters,
11
+ # and cycling asset hosts.
12
+ #
13
+ # Asset::Path objects are most commonly created by <tt>Juicer::Asset::PathResolver#resolve</tt>
14
+ # which resolves include paths to file names. It is possible, however, to use
15
+ # the asset class directly:
16
+ #
17
+ # Dir.pwd
18
+ # #=> "/home/christian/projects/mysite/design/css"
19
+ #
20
+ # asset = Juicer::Asset::Path.new "../images/logo.png"
21
+ # asset.path
22
+ # #=> "../images/logo.png"
23
+ #
24
+ # asset.rebase("~/projects/mysite/design").path
25
+ # #=> "images/logo.png"
26
+ #
27
+ # asset.filename
28
+ # #=> "/home/christian/projects/mysite/design/images/logo.png"
29
+ #
30
+ # asset.path(:cache_buster_type => :soft)
31
+ # #=> "../images/logo.png?jcb=1234567890"
32
+ #
33
+ # asset.path(:cache_buster_type => :soft, :cache_buster => nil)
34
+ # #=> "../images/logo.png?1234567890"
35
+ #
36
+ # asset.path(:cache_buster => "bustIT")
37
+ # #=> "../images/logo.png?bustIT=1234567890"
38
+ #
39
+ # asset = Juicer::Asset::Path.new "../images/logo.png", :document_root
40
+ # #=> "/home/christian/projects/mysite"
41
+ #
42
+ # asset.absolute_path(:cache_buster_type => :hard)
43
+ # #=> "/images/logo-jcb1234567890.png"
44
+ #
45
+ # asset.absolute_path(:host => "http://localhost")
46
+ # #=> "http://localhost/images/logo.png"
47
+ #
48
+ # asset.absolute_path(:host => "http://localhost", :cache_buster_type => :hard)
49
+ # #=> "http://localhost/images/logo-jcb1234567890.png"
50
+ #
51
+ #
52
+ # Author:: Christian Johansen (christian@cjohansen.no)
53
+ # Copyright:: Copyright (c) 2009 Christian Johansen
54
+ # License:: BSD
55
+ #
56
+ class Path
57
+ # Base directory to resolve relative path from, see Juicer::Asset::Path#initialize
58
+ attr_reader :base
59
+
60
+ # Hosts served from <tt>:document_root</tt>, see Juicer::Asset::Path#initialize
61
+ attr_reader :hosts
62
+
63
+ # Directory served as root through a web server, see Juicer::Asset::Path#initialize
64
+ attr_reader :document_root
65
+
66
+ @@scheme_pattern = %r{^[a-zA-Z]{3,5}://}
67
+
68
+ #
69
+ # Initialize asset at <tt>path</tt>. Accepts an optional hash of options:
70
+ #
71
+ # [<tt>:base</tt>]
72
+ # Base context from which asset is required. Given a <tt>path</tt> of
73
+ # <tt>../images/logo.png</tt> and a <tt>:base</tt> of <tt>/project/design/css</tt>,
74
+ # the asset file will be assumed to live in <tt>/project/design/images/logo.png</tt>
75
+ # Defaults to the current directory.
76
+ # [<tt>:hosts</tt>]
77
+ # Array of host names that are served from <tt>:document_root</tt>. May also
78
+ # include scheme/protocol. If not, http is assumed.
79
+ # [<tt>:document_root</tt>]
80
+ # The root directory for absolute URLs (ie, the server's document root). This
81
+ # option is needed when resolving absolute URLs that include a hostname as well
82
+ # as when generating absolute paths.
83
+ #
84
+ def initialize(path, options = {})
85
+ @path = path
86
+ @filename = nil
87
+ @absolute_path = nil
88
+ @relative_path = nil
89
+ @path_has_host = @path =~ @@scheme_pattern
90
+ @path_is_absolute = @path_has_host || @path =~ /^\//
91
+
92
+ # Options
93
+ @base = options[:base] || Dir.pwd
94
+ @document_root = options[:document_root]
95
+ @hosts = Juicer::Asset::Path.hosts_with_scheme(options[:hosts])
96
+ end
97
+
98
+ #
99
+ # Returns absolute path calculated using the <tt>#document_root</tt>.
100
+ # Optionally accepts a hash of options:
101
+ #
102
+ # [<tt>:host</tt>] Return fully qualified URL with this host name. May include
103
+ # scheme/protocol. Default scheme is http.
104
+ # [<tt>:cache_buster</tt>] The parameter name for the cache buster.
105
+ # [<tt>:cache_buster_type</tt>] The kind of cache buster to add, <tt>:soft</tt>
106
+ # or <tt>:hard</tt>.
107
+ #
108
+ # A cache buster will be added if either (or both) of the <tt>:cache_buster</tt>
109
+ # or <tt>:cache_buster_type</tt> options are provided. The default cache buster
110
+ # type is <tt>:soft</tt>.
111
+ #
112
+ # Raises an ArgumentException if no <tt>document_root</tt> has been set.
113
+ #
114
+ def absolute_path(options = {})
115
+ if !@absolute_path
116
+ # Pre-conditions
117
+ raise ArgumentError.new("No document root set") if @document_root.nil?
118
+
119
+ @absolute_path = filename.sub(%r{^#@document_root}, '').sub(/^\/?/, '/')
120
+ @absolute_path = "#{Juicer::Asset::Path.host_with_scheme(options[:host])}#@absolute_path"
121
+ end
122
+
123
+ path_with_cache_buster(@absolute_path, options)
124
+ end
125
+
126
+ #
127
+ # Return path relative to <tt>#base</tt>
128
+ #
129
+ # Accepts an optional hash of options for cache busters:
130
+ #
131
+ # [<tt>:cache_buster</tt>] The parameter name for the cache buster.
132
+ # [<tt>:cache_buster_type</tt>] The kind of cache buster to add, <tt>:soft</tt>
133
+ # or <tt>:hard</tt>.
134
+ #
135
+ # A cache buster will be added if either (or both) of the <tt>:cache_buster</tt>
136
+ # or <tt>:cache_buster_type</tt> options are provided. The default cache buster
137
+ # type is <tt>:soft</tt>.
138
+ #
139
+ def relative_path(options = {})
140
+ @relative_path ||= Pathname.new(filename).relative_path_from(Pathname.new(base)).to_s
141
+ path_with_cache_buster(@relative_path, options)
142
+ end
143
+
144
+ #
145
+ # Returns the original path.
146
+ #
147
+ # Accepts an optional hash of options for cache busters:
148
+ #
149
+ # [<tt>:cache_buster</tt>] The parameter name for the cache buster.
150
+ # [<tt>:cache_buster_type</tt>] The kind of cache buster to add, <tt>:soft</tt>
151
+ # or <tt>:hard</tt>.
152
+ #
153
+ # A cache buster will be added if either (or both) of the <tt>:cache_buster</tt>
154
+ # or <tt>:cache_buster_type</tt> options are provided. The default cache buster
155
+ # type is <tt>:soft</tt>.
156
+ #
157
+ def path(options = {})
158
+ path_with_cache_buster(@path, options)
159
+ end
160
+
161
+ #
162
+ # Return filename on disk. Requires the <tt>#document_root</tt> to be set if
163
+ # original path was an absolute one.
164
+ #
165
+ # If asset path includes scheme/protocol and host, it can only be resolved if
166
+ # a match is found in <tt>#hosts</tt>. Otherwise, an exeception is raised.
167
+ #
168
+ def filename
169
+ return @filename if @filename
170
+
171
+ # Pre-conditions
172
+ raise ArgumentError.new("No document root set") if @path_is_absolute && @document_root.nil?
173
+ raise ArgumentError.new("No hosts served from document root") if @path_has_host && @hosts.empty?
174
+
175
+ path = strip_host(@path)
176
+ raise ArgumentError.new("No matching host found for #{@path}") if path =~ @@scheme_pattern
177
+
178
+ dir = @path_is_absolute ? document_root : base
179
+ @filename = File.expand_path(File.join(dir, path))
180
+ end
181
+
182
+ #
183
+ # Rebase path and return a new Asset::Path object.
184
+ #
185
+ # asset = Juicer::Asset::Path.new "../images/logo.png", :base => "/var/www/public/stylesheets"
186
+ # asset2 = asset.rebase("/var/www/public")
187
+ # asset2.relative_path #=> "images/logo.png"
188
+ #
189
+ def rebase(base_path)
190
+ path = Pathname.new(filename).relative_path_from(Pathname.new(base_path)).to_s
191
+
192
+ Juicer::Asset::Path.new(path,
193
+ :base => base_path,
194
+ :hosts => hosts,
195
+ :document_root => document_root)
196
+ end
197
+
198
+ #
199
+ # Returns basename of filename on disk
200
+ #
201
+ def basename
202
+ File.basename(filename)
203
+ end
204
+
205
+ #
206
+ # Returns basename of filename on disk
207
+ #
208
+ def dirname
209
+ File.dirname(filename)
210
+ end
211
+
212
+ #
213
+ # Returns <tt>true</tt> if file exists on disk
214
+ #
215
+ def exists?
216
+ File.exists?(filename)
217
+ end
218
+
219
+ #
220
+ # Accepts a single host, or an array of hosts and returns an array of hosts
221
+ # that include scheme/protocol, and don't have trailing slash.
222
+ #
223
+ def self.hosts_with_scheme(hosts)
224
+ hosts.nil? ? [] : [hosts].flatten.collect { |host| self.host_with_scheme(host) }
225
+ end
226
+
227
+ #
228
+ # Assures that a host has scheme/protocol and no trailing slash
229
+ #
230
+ def self.host_with_scheme(host)
231
+ return host if host.nil?
232
+ (host !~ @@scheme_pattern ? "http://#{host}" : host).sub(/\/$/, '')
233
+ end
234
+
235
+ def <=>(other)
236
+ filename <=> other.filename
237
+ end
238
+
239
+ private
240
+ #
241
+ # Adds cache buster to paths if :cache_buster_type and :cache_buster indicates
242
+ # they should be added.
243
+ #
244
+ def path_with_cache_buster(path, options = {})
245
+ return path if !options.key?(:cache_buster) && options[:cache_buster_type].nil?
246
+
247
+ buster_path = nil
248
+ type = options[:cache_buster_type] || :soft
249
+
250
+ if options.key?(:cache_buster)
251
+ # Pass :cache_buster even if it's nil
252
+ buster_path = Juicer::CacheBuster.send(type, filename, options[:cache_buster])
253
+ else
254
+ # If :cache_buster wasn't specified, rely on default value
255
+ buster_path = Juicer::CacheBuster.send(type, filename)
256
+ end
257
+
258
+ path.sub(File.basename(path), File.basename(buster_path))
259
+ end
260
+
261
+ #
262
+ # Strip known hosts from path
263
+ #
264
+ def strip_host(path)
265
+ hosts.each do |host|
266
+ return path if path !~ @@scheme_pattern
267
+
268
+ path.sub!(%r{^#{host}}, '')
269
+ end
270
+
271
+ return path
272
+ end
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,79 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "juicer/asset/path"
4
+
5
+ module Juicer
6
+ module Asset
7
+ #
8
+ # Factory class that creates <tt>Juicer::Asset::Path</tt> objects from a common set of
9
+ # options. Also facilitates asset host cycling on a set of asset paths.
10
+ #
11
+ # path_resolver = Juicer::Asset::PathResolver.new(
12
+ # :document_root => "/var/www",
13
+ # :hosts => ["assets1.mysite.com", "assets2.mysite.com"]
14
+ # )
15
+ #
16
+ # asset = path_resolver.resolve("../images/logo.png")
17
+ # asset.document_root
18
+ # #=> "/var/www"
19
+ #
20
+ # asset.absolute_path(path_resolver.cycle_hosts)
21
+ # #=> "http://assets1.mysite.com/images/logo.png"
22
+ #
23
+ # asset = path_resolver.resolve("/favicon.ico")
24
+ # asset.absolute_path(path_resolver.cycle_hosts)
25
+ # #=> "http://assets2.mysite.com/favicon.ico"
26
+ #
27
+ # Author:: Christian Johansen (christian@cjohansen.no)
28
+ # Copyright:: Copyright (c) 2009 Christian Johansen
29
+ # License:: BSD
30
+ #
31
+ class PathResolver
32
+ attr_reader :hosts, :document_root, :base
33
+
34
+ #
35
+ # Initialize resolver. All options set on the resolver will be carried on to the
36
+ # resolved assets.
37
+ #
38
+ def initialize(options = {})
39
+ options[:base] ||= Dir.pwd
40
+ @options = options
41
+ @base = options[:base]
42
+ @hosts = Juicer::Asset::Path.hosts_with_scheme(options[:hosts]) || []
43
+ @current_host = 0
44
+ @document_root = @options[:document_root]
45
+ end
46
+
47
+ #
48
+ # Returns a <tt>Juicer::Asset::Path</tt> object for the given path, and the options
49
+ # set on the resolver.
50
+ #
51
+ def resolve(path)
52
+ Juicer::Asset::Path.new(path, @options)
53
+ end
54
+
55
+ #
56
+ # Set new base directory. Will affect any assets resolved from here, but any
57
+ # assets previously resolved will not be changed
58
+ #
59
+ def base=(base)
60
+ @base = base
61
+ @options[:base] = base
62
+ end
63
+
64
+ #
65
+ # Cycle asset hosts. Returns an asset host
66
+ #
67
+ def cycle_hosts
68
+ return nil if @hosts.length == 0
69
+
70
+ host = @hosts[@current_host % @hosts.length]
71
+ @current_host += 1
72
+
73
+ host
74
+ end
75
+
76
+ alias host cycle_hosts
77
+ end
78
+ end
79
+ end
data/lib/juicer/binary.rb CHANGED
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), "chainable"))
1
+ require "juicer/chainable"
2
2
 
3
3
  module Juicer
4
4
 
@@ -12,12 +12,11 @@ module Juicer
12
12
  # allowed to set on the binary.
13
13
  #
14
14
  module Binary
15
-
16
15
  # Initialize binary with options
17
16
  # options = Hash of options, optional
18
17
  #
19
18
  def initialize(binary, options = {})
20
- @options = self.respond_to?(:defualt_options) ? default_options.merge(options) : options
19
+ @options = self.respond_to?(:default_options) ? default_options.merge(options) : options
21
20
  @opt_set = false
22
21
  @command = nil
23
22
  @binary = binary
@@ -31,7 +30,6 @@ module Juicer
31
30
  # Run command
32
31
  #
33
32
  def execute(params = nil)
34
- #puts "#{self.command} #{params}"
35
33
  cmd = IO.popen("#{self.command} #{params}", "r")
36
34
  results = cmd.gets(nil)
37
35
  cmd.close
@@ -131,7 +129,7 @@ module Juicer
131
129
  #
132
130
  def locate(bin_glob, env = nil)
133
131
  path << ENV[env] if env && ENV.key?(env) && File.exist?(ENV[env])
134
-
132
+
135
133
  (path << Dir.pwd).each do |path|
136
134
  files = Dir.glob(File.expand_path(File.join(path, bin_glob)))
137
135
  return files unless files.empty?
@@ -1,45 +1,130 @@
1
+ # -*- coding: utf-8 -*-
2
+
1
3
  module Juicer
2
4
  #
3
- # Tool that assists in creating filenames that update everytime the file
4
- # contents change. There's two ways of generating filenames, soft and hard.
5
- # The point of all this is to facilitate configuring web servers to send
6
- # static assets with a far future expires header - improving end user
7
- # performance through caching.
5
+ # Assists in creating filenames that reflect the last change to the file. These
6
+ # kinds of filenames are useful when serving static content through a web server.
7
+ # If the filename changes everytime the file is modified, you can safely configure
8
+ # the web server to cache files indefinately, and know that the updated filename
9
+ # will cause the file to be downloaded again - only once - when it has changed.
10
+ #
11
+ # = Types of cache busters
8
12
  #
9
- # Soft cache busters require no web server configuration, but will not work
10
- # as intended with older default configurations for popular proxy server
11
- # Squid. The soft busters use query parameters to create unique file names,
12
- # and these may not force an update in some cases. The soft cache busters
13
- # transforms /images/logo.png to /images/logo.png?cb=1232923789
13
+ # == Query string / "soft" cache busters
14
+ # Soft cache busters require no web server configuration. However, it is not
15
+ # guaranteed to work in all settings. For example, older default
16
+ # configurations for popular proxy server Squid does not consider a known URL
17
+ # with a new query string a new URL, and thus will not download the file over.
14
18
  #
19
+ # The soft cache busters transforms
20
+ # <tt>/images/logo.png</tt> to <tt>/images/logo.png?cb=1232923789</tt>
21
+ #
22
+ # == Filename change / "hard" cache busters
15
23
  # Hard cache busters change the file name itself, and thus requires either
16
24
  # the web server to (internally) rewrite requests for these files to the
17
25
  # original ones, or the file names to actually change. Hard cache busters
18
- # transforms /images/logo.png to /images/logo-1232923789.png
26
+ # transforms <tt>/images/logo.png</tt> to <tt>/images/logo-1232923789.png</tt>
27
+ #
28
+ # Hard cache busters are guaranteed to work, and is the recommended variant.
29
+ # An example configuration for the Apache web server that does not require
30
+ # you to actually change the filenames can be seen below.
31
+ #
32
+ # <VirtualHost *>
33
+ # # Application/website configuration
34
+ #
35
+ # # Cache static resources for a year
36
+ # <FilesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)$">
37
+ # ExpiresActive On
38
+ # ExpiresDefault "access plus 1 year"
39
+ # </FilesMatch>
40
+ #
41
+ # # Rewrite URLs like /images/logo-cb1234567890.png to /images/logo.png
42
+ # RewriteEngine On
43
+ # RewriteRule (.*)-cb\d+\.(.*)$ $1.$2 [L]
44
+ # </VirtualHost>])
45
+ #
46
+ # = Consecutive calls
47
+ #
48
+ # Consecutive calls to add a cache buster to a path will replace the existing
49
+ # cache buster *as long as the parameter name is the same*. Consider this:
50
+ #
51
+ # file = Juicer::CacheBuster.hard("/home/file.png") #=> "/home/file-cb1234567890.png"
52
+ # Juicer::CacheBuster.hard(file) #=> "/home/file-cb1234567891.png"
53
+ #
54
+ # # Changing the parameter name breaks this
55
+ # Juicer::CacheBuster.hard(file, :juicer) #=> "/home/file-cb1234567891-juicer1234567892.png"
56
+ #
57
+ # Avoid this type of trouble simply be cleaning the URL with the old name first:
58
+ #
59
+ # Juicer::CacheBuster.clean(file) #=> "/home/file.png"
60
+ # file = Juicer::CacheBuster.hard(file, :juicer) #=> "/home/file-juicer1234567892.png"
61
+ # Juicer::CacheBuster.clean(file, :juicer) #=> "/home/file.png"
62
+ #
63
+ # Author:: Christian Johansen (christian@cjohansen.no)
64
+ # Copyright:: Copyright (c) 2009 Christian Johansen
65
+ # License:: BSD
19
66
  #
20
67
  module CacheBuster
68
+ DEFAULT_PARAMETER = "jcb"
69
+
21
70
  #
22
71
  # Creates a unique file name for every revision to the files contents.
23
- # Default parameter name for soft cache busters is cb (ie ?cb=<timestamp>)
24
- # while default parameter names for hard cache busters is none (ie
25
- # file-<timestamp>.png).
72
+ # Raises an <tt>ArgumentError</tt> if the file can not be found.
73
+ #
74
+ # The type indicates which type of cache buster you want, <tt>:soft</tt>
75
+ # or <tt>:hard</tt>. Default is <tt>:soft</tt>. If an unsupported value
76
+ # is specified, <tt>:soft</tt> will be used.
26
77
  #
27
- def self.path(file, type = :soft, param = :undef)
28
- param = (type == :soft ? "jcb" : nil) if param == :undef
29
- f = File.new(file.split("?").first)
30
- mtime = f.mtime.to_i
31
- f.close
78
+ # See <tt>#hard</tt> and <tt>#soft</tt> for explanation of the parameter
79
+ # argument.
80
+ #
81
+ def self.path(file, type = :soft, parameter = DEFAULT_PARAMETER)
82
+ file = self.clean(file, parameter)
83
+ filename = file.split("?").first
84
+ raise ArgumentError.new("#{file} could not be found") unless File.exists?(filename)
85
+ mtime = File.mtime(filename).to_i
86
+ type = [:soft, :hard].include?(type) ? type : :soft
32
87
 
33
88
  if type == :soft
34
- param = "#{param}".length == 0 ? "" : "#{param}="
35
- file = file.sub(/#{param}\d+/, "").sub(/(\?|\&)$/, "")
36
- "#{file}#{file.index('?') ? '&' : '?'}#{param}#{mtime}"
37
- else
38
- parts = file.split(".")
39
- suffix = parts.pop
40
- file = parts.join.sub(/-#{param}\d+/, "")
41
- "#{parts.join('.')}-#{param}#{mtime}.#{suffix}"
89
+ parameter = "#{parameter}=".sub(/^=$/, '')
90
+ return "#{file}#{file.index('?') ? '&' : '?'}#{parameter}#{mtime}"
42
91
  end
92
+
93
+ file.sub(/(\.[^\.]+$)/, "-#{parameter}#{mtime}" + '\1')
94
+ end
95
+
96
+ #
97
+ # Add a hard cache buster to a filename. The parameter is an optional prefix
98
+ # that is added before the mtime timestamp. It results in filenames of the form:
99
+ # <tt>file-[parameter name][timestamp].suffix</tt>, ie
100
+ # <tt>images/logo-cb1234567890.png</tt> which is the case for the default
101
+ # parameter name "cb" (as in *c*ache *b*uster).
102
+ #
103
+ def self.hard(file, parameter = DEFAULT_PARAMETER)
104
+ self.path(file, :hard, parameter)
105
+ end
106
+
107
+ #
108
+ # Add a soft cache buster to a filename. The parameter is an optional name
109
+ # for the mtime timestamp value. It results in filenames of the form:
110
+ # <tt>file.suffix?[parameter name]=[timestamp]</tt>, ie
111
+ # <tt>images/logo.png?cb=1234567890</tt> which is the case for the default
112
+ # parameter name "cb" (as in *c*ache *b*uster).
113
+ #
114
+ def self.soft(file, parameter = DEFAULT_PARAMETER)
115
+ self.path(file, :soft, parameter)
116
+ end
117
+
118
+ #
119
+ # Remove cache buster from a URL for a given parameter name. Parameter name is
120
+ # "cb" by default.
121
+ #
122
+ def self.clean(file, parameter = DEFAULT_PARAMETER)
123
+ query_param = "#{parameter}".length == 0 ? "" : "#{parameter}="
124
+ new_file = file.sub(/#{query_param}\d+&?/, "").sub(/(\?|&)$/, "")
125
+ return new_file unless new_file == file
126
+
127
+ file.sub(/-#{parameter}\d+(\.\w+)($|\?)/, '\1\2')
43
128
  end
44
129
  end
45
130
  end