psyho_juicer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +58 -0
- data/Manifest.txt +58 -0
- data/Rakefile +96 -0
- data/Readme.rdoc +313 -0
- data/VERSION +1 -0
- data/bin/juicer +6 -0
- data/lib/juicer.rb +69 -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 +131 -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 +90 -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 +129 -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/fixtures/yui-download.html +425 -0
- data/test/test_helper.rb +175 -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 +278 -0
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/juicer
ADDED
data/lib/juicer.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
module Juicer
|
4
|
+
|
5
|
+
# :stopdoc:
|
6
|
+
VERSION = '1.0.0'
|
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.
|
55
|
+
def self.require_all_libs
|
56
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
57
|
+
glob = File.join(dir, "juicer", '**', '*.rb')
|
58
|
+
|
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 }
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
Juicer.require_all_libs
|
67
|
+
|
68
|
+
class FileNotFoundError < Exception
|
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
|
@@ -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
|