psyho_juicer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/History.txt +58 -0
  2. data/Manifest.txt +58 -0
  3. data/Rakefile +96 -0
  4. data/Readme.rdoc +313 -0
  5. data/VERSION +1 -0
  6. data/bin/juicer +6 -0
  7. data/lib/juicer.rb +69 -0
  8. data/lib/juicer/asset/path.rb +275 -0
  9. data/lib/juicer/asset/path_resolver.rb +79 -0
  10. data/lib/juicer/binary.rb +171 -0
  11. data/lib/juicer/cache_buster.rb +131 -0
  12. data/lib/juicer/chainable.rb +106 -0
  13. data/lib/juicer/cli.rb +56 -0
  14. data/lib/juicer/command/install.rb +61 -0
  15. data/lib/juicer/command/list.rb +57 -0
  16. data/lib/juicer/command/merge.rb +205 -0
  17. data/lib/juicer/command/util.rb +32 -0
  18. data/lib/juicer/command/verify.rb +60 -0
  19. data/lib/juicer/css_cache_buster.rb +90 -0
  20. data/lib/juicer/datafy/datafy.rb +20 -0
  21. data/lib/juicer/dependency_resolver/css_dependency_resolver.rb +29 -0
  22. data/lib/juicer/dependency_resolver/dependency_resolver.rb +101 -0
  23. data/lib/juicer/dependency_resolver/javascript_dependency_resolver.rb +23 -0
  24. data/lib/juicer/ext/logger.rb +5 -0
  25. data/lib/juicer/ext/string.rb +47 -0
  26. data/lib/juicer/ext/symbol.rb +15 -0
  27. data/lib/juicer/image_embed.rb +129 -0
  28. data/lib/juicer/install/base.rb +186 -0
  29. data/lib/juicer/install/closure_compiler_installer.rb +69 -0
  30. data/lib/juicer/install/jslint_installer.rb +51 -0
  31. data/lib/juicer/install/rhino_installer.rb +53 -0
  32. data/lib/juicer/install/yui_compressor_installer.rb +67 -0
  33. data/lib/juicer/jslint.rb +90 -0
  34. data/lib/juicer/merger/base.rb +74 -0
  35. data/lib/juicer/merger/javascript_merger.rb +29 -0
  36. data/lib/juicer/merger/stylesheet_merger.rb +110 -0
  37. data/lib/juicer/minifyer/closure_compiler.rb +90 -0
  38. data/lib/juicer/minifyer/java_base.rb +77 -0
  39. data/lib/juicer/minifyer/yui_compressor.rb +96 -0
  40. data/test/bin/jslint-1.0.js +523 -0
  41. data/test/bin/jslint.js +523 -0
  42. data/test/bin/rhino1_7R1.zip +0 -0
  43. data/test/bin/rhino1_7R2-RC1.jar +0 -0
  44. data/test/bin/rhino1_7R2-RC1.zip +0 -0
  45. data/test/bin/yuicompressor +0 -0
  46. data/test/bin/yuicompressor-2.3.5.zip +0 -0
  47. data/test/bin/yuicompressor-2.4.2.jar +0 -0
  48. data/test/bin/yuicompressor-2.4.2.zip +0 -0
  49. data/test/fixtures/yui-download.html +425 -0
  50. data/test/test_helper.rb +175 -0
  51. data/test/unit/juicer/asset/path_resolver_test.rb +76 -0
  52. data/test/unit/juicer/asset/path_test.rb +370 -0
  53. data/test/unit/juicer/cache_buster_test.rb +104 -0
  54. data/test/unit/juicer/chainable_test.rb +94 -0
  55. data/test/unit/juicer/command/install_test.rb +58 -0
  56. data/test/unit/juicer/command/list_test.rb +81 -0
  57. data/test/unit/juicer/command/merge_test.rb +162 -0
  58. data/test/unit/juicer/command/util_test.rb +58 -0
  59. data/test/unit/juicer/command/verify_test.rb +48 -0
  60. data/test/unit/juicer/css_cache_buster_test.rb +71 -0
  61. data/test/unit/juicer/datafy_test.rb +37 -0
  62. data/test/unit/juicer/dependency_resolver/css_dependency_resolver_test.rb +36 -0
  63. data/test/unit/juicer/dependency_resolver/javascript_dependency_resolver_test.rb +50 -0
  64. data/test/unit/juicer/ext/string_test.rb +59 -0
  65. data/test/unit/juicer/ext/symbol_test.rb +27 -0
  66. data/test/unit/juicer/image_embed_test.rb +271 -0
  67. data/test/unit/juicer/install/installer_base_test.rb +214 -0
  68. data/test/unit/juicer/install/jslint_installer_test.rb +54 -0
  69. data/test/unit/juicer/install/rhino_installer_test.rb +57 -0
  70. data/test/unit/juicer/install/yui_compressor_test.rb +56 -0
  71. data/test/unit/juicer/jslint_test.rb +60 -0
  72. data/test/unit/juicer/merger/base_test.rb +122 -0
  73. data/test/unit/juicer/merger/javascript_merger_test.rb +74 -0
  74. data/test/unit/juicer/merger/stylesheet_merger_test.rb +180 -0
  75. data/test/unit/juicer/minifyer/closure_compressor_test.rb +107 -0
  76. data/test/unit/juicer/minifyer/yui_compressor_test.rb +116 -0
  77. data/test/unit/juicer_test.rb +1 -0
  78. metadata +278 -0
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "juicer"
4
+ require "juicer/cli"
5
+
6
+ Juicer::Cli.run(ARGV)
@@ -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