ktheory-juicer 1.0.0.ktheory1
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/History.txt +30 -0
- data/Manifest.txt +58 -0
- data/Rakefile +96 -0
- data/Readme.rdoc +312 -0
- data/VERSION +1 -0
- data/bin/juicer +8 -0
- data/lib/juicer.rb +70 -0
- data/lib/juicer/asset/path.rb +275 -0
- data/lib/juicer/asset/path_resolver.rb +79 -0
- data/lib/juicer/binary.rb +171 -0
- data/lib/juicer/cache_buster.rb +130 -0
- data/lib/juicer/chainable.rb +106 -0
- data/lib/juicer/cli.rb +56 -0
- data/lib/juicer/command/install.rb +61 -0
- data/lib/juicer/command/list.rb +57 -0
- data/lib/juicer/command/merge.rb +205 -0
- data/lib/juicer/command/util.rb +32 -0
- data/lib/juicer/command/verify.rb +60 -0
- data/lib/juicer/css_cache_buster.rb +80 -0
- data/lib/juicer/datafy/datafy.rb +20 -0
- data/lib/juicer/dependency_resolver/css_dependency_resolver.rb +29 -0
- data/lib/juicer/dependency_resolver/dependency_resolver.rb +101 -0
- data/lib/juicer/dependency_resolver/javascript_dependency_resolver.rb +23 -0
- data/lib/juicer/ext/logger.rb +5 -0
- data/lib/juicer/ext/string.rb +47 -0
- data/lib/juicer/ext/symbol.rb +15 -0
- data/lib/juicer/image_embed.rb +136 -0
- data/lib/juicer/install/base.rb +186 -0
- data/lib/juicer/install/closure_compiler_installer.rb +69 -0
- data/lib/juicer/install/jslint_installer.rb +51 -0
- data/lib/juicer/install/rhino_installer.rb +53 -0
- data/lib/juicer/install/yui_compressor_installer.rb +67 -0
- data/lib/juicer/jslint.rb +90 -0
- data/lib/juicer/merger/base.rb +74 -0
- data/lib/juicer/merger/javascript_merger.rb +29 -0
- data/lib/juicer/merger/stylesheet_merger.rb +110 -0
- data/lib/juicer/minifyer/closure_compiler.rb +90 -0
- data/lib/juicer/minifyer/java_base.rb +77 -0
- data/lib/juicer/minifyer/yui_compressor.rb +96 -0
- data/test/bin/jslint-1.0.js +523 -0
- data/test/bin/jslint.js +523 -0
- data/test/bin/rhino1_7R1.zip +0 -0
- data/test/bin/rhino1_7R2-RC1.jar +0 -0
- data/test/bin/rhino1_7R2-RC1.zip +0 -0
- data/test/bin/yuicompressor +0 -0
- data/test/bin/yuicompressor-2.3.5.zip +0 -0
- data/test/bin/yuicompressor-2.4.2.jar +0 -0
- data/test/bin/yuicompressor-2.4.2.zip +0 -0
- data/test/data/Changelog.txt +10 -0
- data/test/data/a.css +3 -0
- data/test/data/a.js +5 -0
- data/test/data/a1.css +5 -0
- data/test/data/b.css +1 -0
- data/test/data/b.js +5 -0
- data/test/data/b1.css +5 -0
- data/test/data/c1.css +3 -0
- data/test/data/css/2.gif +1 -0
- data/test/data/css/test.css +11 -0
- data/test/data/css/test2.css +1 -0
- data/test/data/d1.css +3 -0
- data/test/data/images/1.png +1 -0
- data/test/data/my_app.js +2 -0
- data/test/data/not-ok.js +2 -0
- data/test/data/ok.js +3 -0
- data/test/data/path_test.css +5 -0
- data/test/data/path_test2.css +14 -0
- data/test/data/pkg/module/moda.js +2 -0
- data/test/data/pkg/module/modb.js +3 -0
- data/test/data/pkg/pkg.js +1 -0
- data/test/test_helper.rb +169 -0
- data/test/unit/juicer/asset/path_resolver_test.rb +76 -0
- data/test/unit/juicer/asset/path_test.rb +370 -0
- data/test/unit/juicer/cache_buster_test.rb +104 -0
- data/test/unit/juicer/chainable_test.rb +94 -0
- data/test/unit/juicer/command/install_test.rb +58 -0
- data/test/unit/juicer/command/list_test.rb +81 -0
- data/test/unit/juicer/command/merge_test.rb +162 -0
- data/test/unit/juicer/command/util_test.rb +58 -0
- data/test/unit/juicer/command/verify_test.rb +48 -0
- data/test/unit/juicer/css_cache_buster_test.rb +71 -0
- data/test/unit/juicer/datafy_test.rb +37 -0
- data/test/unit/juicer/dependency_resolver/css_dependency_resolver_test.rb +36 -0
- data/test/unit/juicer/dependency_resolver/javascript_dependency_resolver_test.rb +50 -0
- data/test/unit/juicer/ext/string_test.rb +59 -0
- data/test/unit/juicer/ext/symbol_test.rb +27 -0
- data/test/unit/juicer/image_embed_test.rb +271 -0
- data/test/unit/juicer/install/installer_base_test.rb +214 -0
- data/test/unit/juicer/install/jslint_installer_test.rb +54 -0
- data/test/unit/juicer/install/rhino_installer_test.rb +57 -0
- data/test/unit/juicer/install/yui_compressor_test.rb +56 -0
- data/test/unit/juicer/jslint_test.rb +60 -0
- data/test/unit/juicer/merger/base_test.rb +122 -0
- data/test/unit/juicer/merger/javascript_merger_test.rb +74 -0
- data/test/unit/juicer/merger/stylesheet_merger_test.rb +180 -0
- data/test/unit/juicer/minifyer/closure_compressor_test.rb +107 -0
- data/test/unit/juicer/minifyer/yui_compressor_test.rb +116 -0
- data/test/unit/juicer_test.rb +1 -0
- metadata +265 -0
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0.ktheory1
|
data/bin/juicer
ADDED
data/lib/juicer.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
module Juicer
|
4
|
+
|
5
|
+
# :stopdoc:
|
6
|
+
VERSION = '1.0.0-a1'
|
7
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
8
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
9
|
+
LOGGER = Logger.new(STDOUT)
|
10
|
+
@@home = nil
|
11
|
+
# :startdoc:
|
12
|
+
|
13
|
+
# Returns the version string for the library.
|
14
|
+
#
|
15
|
+
def self.version
|
16
|
+
VERSION
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the installation directory for Juicer
|
20
|
+
#
|
21
|
+
def self.home
|
22
|
+
return @@home if @@home
|
23
|
+
return ENV['JUICER_HOME'] if ENV['JUICER_HOME']
|
24
|
+
return File.join(ENV['HOME'], ".juicer") if ENV['HOME']
|
25
|
+
return File.join(ENV['APPDATA'], "juicer") if ENV['APPDATA']
|
26
|
+
return File.join(ENV['HOMEDRIVE'], ENV['HOMEPATH'], "juicer") if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
|
27
|
+
return File.join(ENV['USERPROFILE'], "juicer") if ENV['USERPROFILE']
|
28
|
+
return File.join(ENV['Personal'], "juicer") if ENV['Personal']
|
29
|
+
end
|
30
|
+
|
31
|
+
# Set home directory
|
32
|
+
#
|
33
|
+
def self.home=(home)
|
34
|
+
@@home = home
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the library path for the module. If any arguments are given,
|
38
|
+
# they will be joined to the end of the libray path using
|
39
|
+
# <tt>File.join</tt>.
|
40
|
+
#
|
41
|
+
def self.libpath( *args )
|
42
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the lpath for the module. If any arguments are given,
|
46
|
+
# they will be joined to the end of the path using
|
47
|
+
# <tt>File.join</tt>.
|
48
|
+
#
|
49
|
+
def self.path( *args )
|
50
|
+
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
51
|
+
end
|
52
|
+
|
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'))
|
61
|
+
|
62
|
+
Dir.glob(search_me).sort.each { |rb| require rb }
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
Juicer.require_all_libs_relative_to(__FILE__)
|
68
|
+
|
69
|
+
class FileNotFoundError < Exception
|
70
|
+
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
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require "juicer/chainable"
|
2
|
+
|
3
|
+
module Juicer
|
4
|
+
|
5
|
+
# Defines an abstract implementation of a binary that needs to be "shelled
|
6
|
+
# out" to be run. Provides a starting point when wrapping and API around a
|
7
|
+
# shell binary.
|
8
|
+
#
|
9
|
+
# The module requires the including class to define the default_options
|
10
|
+
# method. It should return a hash of options where options are keys and
|
11
|
+
# default values are the values. Only options defined in this hash will be
|
12
|
+
# allowed to set on the binary.
|
13
|
+
#
|
14
|
+
module Binary
|
15
|
+
# Initialize binary with options
|
16
|
+
# options = Hash of options, optional
|
17
|
+
#
|
18
|
+
def initialize(binary, options = {})
|
19
|
+
@options = self.respond_to?(:default_options) ? default_options.merge(options) : options
|
20
|
+
@opt_set = false
|
21
|
+
@command = nil
|
22
|
+
@binary = binary
|
23
|
+
@path = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def path
|
27
|
+
@path
|
28
|
+
end
|
29
|
+
|
30
|
+
# Run command
|
31
|
+
#
|
32
|
+
def execute(params = nil)
|
33
|
+
cmd = IO.popen("#{self.command} #{params}", "r")
|
34
|
+
results = cmd.gets(nil)
|
35
|
+
cmd.close
|
36
|
+
results
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return the value of a given option
|
40
|
+
# opt = The option to return value for
|
41
|
+
#
|
42
|
+
def get_opt(opt)
|
43
|
+
@options[opt] || nil
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return options as a cli arguments string. Optionally accepts a list of
|
47
|
+
# options to exclude from the generated string
|
48
|
+
#
|
49
|
+
def options(*excludes)
|
50
|
+
excludes = excludes.flatten.collect { |exc| exc.to_sym }
|
51
|
+
@options.inject("") do |str, opt|
|
52
|
+
if opt[1].nil? || excludes.include?(opt[0].to_sym)
|
53
|
+
str
|
54
|
+
else
|
55
|
+
val = opt[1] == true ? '' : opt[1]
|
56
|
+
option = opt[0].to_s
|
57
|
+
option = (option.length == 1 ? "-" : "--") + option.gsub('_', '-')
|
58
|
+
"#{str} #{option} #{val}".strip
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Set an option. Important: you can only set options that are predefined by the
|
64
|
+
# implementing class
|
65
|
+
# opt = The option to set
|
66
|
+
# value = The value of the option
|
67
|
+
#
|
68
|
+
def set_opt(opt, value)
|
69
|
+
opt = opt.to_sym
|
70
|
+
if @options.key?(opt)
|
71
|
+
@options[opt] = value
|
72
|
+
@opt_set = true
|
73
|
+
else
|
74
|
+
msg = "Illegal option '#{opt}', specify one of: #{@options.keys.join(', ')}"
|
75
|
+
raise ArgumentError.new(msg)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Performs simple parsing of a string of parameters. All recognized
|
80
|
+
# parameters are set, non-existent arguments raise an ArgumentError
|
81
|
+
#
|
82
|
+
def set_opts(options)
|
83
|
+
options = options.split " "
|
84
|
+
option = nil
|
85
|
+
regex = /^--?([^=]*)(=(.*))?/
|
86
|
+
|
87
|
+
while word = options.shift
|
88
|
+
if word =~ regex
|
89
|
+
if option
|
90
|
+
set_opt option, true
|
91
|
+
end
|
92
|
+
|
93
|
+
if $3
|
94
|
+
set_opt $1, $3
|
95
|
+
else
|
96
|
+
option = $1
|
97
|
+
end
|
98
|
+
else
|
99
|
+
set_opt option, word
|
100
|
+
option = nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Constructs the command to use
|
106
|
+
#
|
107
|
+
def command
|
108
|
+
return @command if !@opt_set && @command
|
109
|
+
@opt_set = false
|
110
|
+
@command = "#{@binary} #{options}"
|
111
|
+
end
|
112
|
+
|
113
|
+
# Locate the binary to execute. The binary is searched for in the
|
114
|
+
# following places:
|
115
|
+
#
|
116
|
+
# 1) The paths specified through my_binary.path << "/usr/bin"
|
117
|
+
# 2) The path specified by the given environment variable
|
118
|
+
# 3) Current working directory
|
119
|
+
#
|
120
|
+
# The name of the binary may be a glob pattern, resulting in +locate+
|
121
|
+
# returning an array of matches. This is useful in cases where the path
|
122
|
+
# is expected to store several versions oof a binary in the same directory,
|
123
|
+
# like /usr/bin/ruby /usr/bin/ruby1.8 /usr/bin/ruby1.9
|
124
|
+
#
|
125
|
+
# +locate+ always returns an array, or nil if no binaries where found.
|
126
|
+
# The result is always all files matching the given pattern in *one* of
|
127
|
+
# the specified paths - ie the first path where the pattern matches
|
128
|
+
# something.
|
129
|
+
#
|
130
|
+
def locate(bin_glob, env = nil)
|
131
|
+
path << ENV[env] if env && ENV.key?(env) && File.exist?(ENV[env])
|
132
|
+
|
133
|
+
(path << Dir.pwd).each do |path|
|
134
|
+
files = Dir.glob(File.expand_path(File.join(path, bin_glob)))
|
135
|
+
return files unless files.empty?
|
136
|
+
end
|
137
|
+
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
|
141
|
+
# Allows for options to be set and read directly on the object as though they were
|
142
|
+
# standard attributes. compressor.verbose translates to
|
143
|
+
# compressor.get_opt('verbose') and compressor.verbose = true to
|
144
|
+
# compressor.set_opt('verbose', true)
|
145
|
+
def method_missing(m, *args)
|
146
|
+
if @options.key?(m)
|
147
|
+
# Only hit method_missing once per option
|
148
|
+
self.class.send(:define_method, m) do # def verbose
|
149
|
+
get_opt(m) # get_opt(:verbose)
|
150
|
+
end # end
|
151
|
+
|
152
|
+
return get_opt(m)
|
153
|
+
end
|
154
|
+
|
155
|
+
return super unless m.to_s =~ /=$/
|
156
|
+
|
157
|
+
opt = m.to_s.sub(/=$/, "").to_sym
|
158
|
+
|
159
|
+
if @options.key?(opt)
|
160
|
+
# Only hit method_missing once per option
|
161
|
+
self.class.send(:define_method, m) do # def verbose=(val)
|
162
|
+
set_opt(opt, args[0]) # set_opt(:verbose, val)
|
163
|
+
end # end
|
164
|
+
|
165
|
+
return set_opt(opt, args[0])
|
166
|
+
end
|
167
|
+
|
168
|
+
super
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|