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.
Files changed (98) hide show
  1. data/History.txt +30 -0
  2. data/Manifest.txt +58 -0
  3. data/Rakefile +96 -0
  4. data/Readme.rdoc +312 -0
  5. data/VERSION +1 -0
  6. data/bin/juicer +8 -0
  7. data/lib/juicer.rb +70 -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 +130 -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 +80 -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 +136 -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/data/Changelog.txt +10 -0
  50. data/test/data/a.css +3 -0
  51. data/test/data/a.js +5 -0
  52. data/test/data/a1.css +5 -0
  53. data/test/data/b.css +1 -0
  54. data/test/data/b.js +5 -0
  55. data/test/data/b1.css +5 -0
  56. data/test/data/c1.css +3 -0
  57. data/test/data/css/2.gif +1 -0
  58. data/test/data/css/test.css +11 -0
  59. data/test/data/css/test2.css +1 -0
  60. data/test/data/d1.css +3 -0
  61. data/test/data/images/1.png +1 -0
  62. data/test/data/my_app.js +2 -0
  63. data/test/data/not-ok.js +2 -0
  64. data/test/data/ok.js +3 -0
  65. data/test/data/path_test.css +5 -0
  66. data/test/data/path_test2.css +14 -0
  67. data/test/data/pkg/module/moda.js +2 -0
  68. data/test/data/pkg/module/modb.js +3 -0
  69. data/test/data/pkg/pkg.js +1 -0
  70. data/test/test_helper.rb +169 -0
  71. data/test/unit/juicer/asset/path_resolver_test.rb +76 -0
  72. data/test/unit/juicer/asset/path_test.rb +370 -0
  73. data/test/unit/juicer/cache_buster_test.rb +104 -0
  74. data/test/unit/juicer/chainable_test.rb +94 -0
  75. data/test/unit/juicer/command/install_test.rb +58 -0
  76. data/test/unit/juicer/command/list_test.rb +81 -0
  77. data/test/unit/juicer/command/merge_test.rb +162 -0
  78. data/test/unit/juicer/command/util_test.rb +58 -0
  79. data/test/unit/juicer/command/verify_test.rb +48 -0
  80. data/test/unit/juicer/css_cache_buster_test.rb +71 -0
  81. data/test/unit/juicer/datafy_test.rb +37 -0
  82. data/test/unit/juicer/dependency_resolver/css_dependency_resolver_test.rb +36 -0
  83. data/test/unit/juicer/dependency_resolver/javascript_dependency_resolver_test.rb +50 -0
  84. data/test/unit/juicer/ext/string_test.rb +59 -0
  85. data/test/unit/juicer/ext/symbol_test.rb +27 -0
  86. data/test/unit/juicer/image_embed_test.rb +271 -0
  87. data/test/unit/juicer/install/installer_base_test.rb +214 -0
  88. data/test/unit/juicer/install/jslint_installer_test.rb +54 -0
  89. data/test/unit/juicer/install/rhino_installer_test.rb +57 -0
  90. data/test/unit/juicer/install/yui_compressor_test.rb +56 -0
  91. data/test/unit/juicer/jslint_test.rb +60 -0
  92. data/test/unit/juicer/merger/base_test.rb +122 -0
  93. data/test/unit/juicer/merger/javascript_merger_test.rb +74 -0
  94. data/test/unit/juicer/merger/stylesheet_merger_test.rb +180 -0
  95. data/test/unit/juicer/minifyer/closure_compressor_test.rb +107 -0
  96. data/test/unit/juicer/minifyer/yui_compressor_test.rb +116 -0
  97. data/test/unit/juicer_test.rb +1 -0
  98. metadata +265 -0
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0.ktheory1
data/bin/juicer ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
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")
7
+
8
+ Juicer::Cli.run(ARGV)
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