buildr 0.18.0 → 0.19.0

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.
@@ -0,0 +1,34 @@
1
+ module Buildr
2
+
3
+ # A file task that concatenates all its prerequisites to create a new file.
4
+ #
5
+ # For example:
6
+ # concat("master.sql"=>["users.sql", "orders.sql", reports.sql"]
7
+ #
8
+ # See also Buildr#concat.
9
+ class ConcatTask < Rake::FileTask
10
+ def initialize(*args) #:nodoc:
11
+ super
12
+ enhance do |task|
13
+ content = prerequisites.inject("") do |content, prereq|
14
+ content << File.read(prereq.to_s) if File.exists?(prereq) && !File.directory?(prereq)
15
+ content
16
+ end
17
+ File.open(task.name, "w") { |file| file.write content }
18
+ end
19
+ end
20
+ end
21
+
22
+ # :call-seq:
23
+ # concat(target=>files) => task
24
+ #
25
+ # Creates and returns a file task that concatenates all its prerequisites to create
26
+ # a new file. See #ConcatTask.
27
+ #
28
+ # For example:
29
+ # concat("master.sql"=>["users.sql", "orders.sql", reports.sql"]
30
+ def concat(args)
31
+ file, deps = Rake.application.resolve_args(args)
32
+ ConcatTask.define_task File.expand_path(file)=>deps
33
+ end
34
+ end
@@ -1,7 +1,12 @@
1
1
  require "tempfile"
2
+ require "core/transports"
2
3
 
3
4
  module Buildr
4
5
 
6
+ # :call-seq:
7
+ # download(url_or_uri) => task
8
+ # download(path=>url_or_uri) =>task
9
+ #
5
10
  # Create a task that will download a file from a URL.
6
11
  #
7
12
  # Takes a single argument, a hash with one pair. The key is the file being
data/lib/tasks/filter.rb CHANGED
@@ -1,88 +1,138 @@
1
1
  module Buildr
2
2
 
3
- class FilterTask < Rake::Task
3
+ # A filter knows how to copy a set of source files into a target directory, and apply
4
+ # mapping to these files.
5
+ #
6
+ # You can specify the mapping using a Hash, and it will map ${key} fields found in each
7
+ # source file into the appropriate value. For example:
8
+ # filter.using "version"=>"1.2"
9
+ # will replace all occurrences of "${version}" with "1.2".
10
+ #
11
+ # You can also specify the mapping by passing a proc or a method, that will be called for
12
+ # each source file, with the file name and content, returning the modified content.
13
+ #
14
+ # Without any mapping, the filter simply copies the source files into the target directory.
15
+ #
16
+ # See Buildr#filter.
17
+ class Filter
4
18
 
5
19
  # The target directory.
6
20
  attr_reader :target
7
- # Filter to use.
8
- attr_accessor :filter
21
+ # The mapping. See #using.
22
+ attr_accessor :mapping
23
+ # The source files and directories.
24
+ attr_accessor :sources
9
25
 
10
- def initialize(*args)
11
- super
12
- enhance do |task|
13
- fail "No target directory specified" if !target || (File.exist?(target) && !File.directory?(target))
14
- unless copy_map.empty?
15
- verbose(Rake.application.options.trace || false) do
16
- copy_map do |dest, src|
17
- mkpath File.dirname(dest) rescue nil
18
- case filter
19
- when Proc, Method
20
- filtered = filter.call(File.read(src))
21
- File.open(dest, "w") { |file| file.write filtered }
22
- when Hash
23
- filtered = File.read(src).gsub(/\$\{.*\}/) { |str| filter[str[2..-2]] || str }
24
- File.open(dest, "w") { |file| file.write filtered }
25
- when nil
26
- cp src, dest
27
- else
28
- fail "Filter can be a hash (key=>value), or a proc/method; I don't understand #{filter}"
29
- end
30
- end
31
- touch target if File.exist?(target)
32
- end
33
- end
34
- end
26
+ def initialize() #:nodoc:
27
+ @sources = FileList[]
35
28
  end
36
29
 
30
+ # :call-seq:
31
+ # include(*files) => self
32
+ #
33
+ # Specifies files to include and returns self. See FileList#include.
37
34
  def include(*files)
38
- prerequisites.include *files
35
+ @sources.include *files
39
36
  self
40
37
  end
41
38
  alias :add :include
42
39
 
40
+ # :call-seq:
41
+ # exclude(*files) => self
42
+ #
43
+ # Specifies files to exclude and returns self. See FileList#exclude.
43
44
  def exclude(*files)
44
- prerequisites.exclude *files
45
+ @sources.exclude *files
45
46
  self
46
47
  end
47
48
 
49
+ # :call-seq:
50
+ # into(dir) => self
51
+ #
52
+ # Specifies the target directory and return self. This tells the filter task where
53
+ # to copy the source files to.
54
+ #
55
+ # For example:
56
+ # filter.include("*.HTML").into("docs").run
48
57
  def into(dir)
49
- dir = File.expand_path(dir)
50
- unless @target == dir
51
- @target = dir
52
- file(dir).enhance [self]
53
- end
58
+ @target = File.expand_path(dir.to_s)
54
59
  self
55
60
  end
56
61
 
57
- def using(filter, &block)
58
- self.filter = filter || block
62
+ # :call-seq:
63
+ # using(mapping) => self
64
+ # using() { |file_name, contents| ... } => self
65
+ #
66
+ # Specifies the mapping to use and returns self.
67
+ #
68
+ # The mapping can be a proc or a method called with the file name and content, returning
69
+ # the modified content. Or the mapping can be a Hash for mapping each ${key} into a value.
70
+ # Without any mapping, all files are copied as is.
71
+ #
72
+ # For example:
73
+ # filter.using "version"=>"1.2"
74
+ # will replace all occurrences of "${version}" with "1.2".
75
+ def using(mapping, &block)
76
+ self.mapping = mapping || block
59
77
  self
60
78
  end
61
79
 
62
- def needed?
80
+ # Run the filter.
81
+ def run()
82
+ #@sources.each { |src| Rake.application[src, Rake.application.current_scope].invoke }
83
+ if needed?
84
+ fail "No target directory specified" if !target || (File.exist?(target.to_s) && !File.directory?(target.to_s))
85
+ unless copy_map.empty?
86
+ verbose(Rake.application.options.trace || false) do
87
+ mkpath target.to_s
88
+ copy_map do |dest, src|
89
+ mkpath File.dirname(dest) rescue nil
90
+ case mapping
91
+ when Proc, Method # Call on input, accept output.
92
+ mapped = mapping.call(src, File.read(src))
93
+ File.open(dest, "w") { |file| file.write mapped }
94
+ when Hash # Map ${key} to value
95
+ mapped = File.read(src).gsub(/\$\{.*\}/) { |str| mapping[str[2..-2]] || str }
96
+ File.open(dest, "w") { |file| file.write mapped }
97
+ when nil # No mapping.
98
+ cp src, dest
99
+ else
100
+ fail "Filter can be a hash (key=>value), or a proc/method; I don't understand #{mapping}"
101
+ end
102
+ end
103
+ touch target.to_s
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ # Returns the target directory.
110
+ def to_s()
111
+ @target.to_s
112
+ end
113
+
114
+ private
115
+
116
+ def needed?()
63
117
  return false if target.nil? || copy_map.empty?
64
- return true unless File.exist?(target)
118
+ return true unless File.exist?(target.to_s)
65
119
  return true if copy_map.any? { |dest, src| !File.exist?(dest) || File.mtime(src) > File.mtime(dest) }
66
120
  false
67
121
  end
68
-
69
- protected
70
122
 
71
- # Return a copy map of all the files that need copying: the key is
72
- # the file to copy to, the value is the source file. If called with
73
- # a block, yields with each dest/source pair.
123
+ # Return a copy map of all the files that need copying: the key is the file to copy to,
124
+ # the value is the source file. If called with a block, yields with each dest/source pair.
74
125
  def copy_map(&block)
75
- # Create a map between the source file and the similarly named
76
- # file in the target directory, including all files nested inside
77
- # directories.
78
- @copy_map ||= prerequisites.map(&:to_s).inject({}) do |map, path|
126
+ # Create a map between the source file and the similarly named file in the target directory,
127
+ # including all files nested inside directories.
128
+ @copy_map ||= @sources.map(&:to_s).inject({}) do |map, path|
79
129
  if File.directory?(path)
80
- Dir[File.join(path, "**", "*")].each do |file|
81
- map[file.sub(File.dirname(path), target)] = file unless
82
- File.directory?(file) || prerequisites.exclude?(file)
130
+ Dir["#{path}/**/*"].each do |file|
131
+ map[file.sub(File.dirname(path), target.to_s)] = file unless
132
+ File.directory?(file) || @sources.exclude?(file)
83
133
  end
84
134
  elsif File.exist?(path)
85
- map[File.join(target, File.basename(path))] = path
135
+ map[File.join(target.to_s, File.basename(path))] = path
86
136
  end
87
137
  map
88
138
  end.reject do |dest, src|
@@ -98,10 +148,12 @@ module Buildr
98
148
 
99
149
  end
100
150
 
151
+ # :call-seq:
152
+ # filter(*files) => Filter
153
+ #
154
+ # Creates a filter task to operate on all the specified files.
101
155
  def filter(*files)
102
- task = nil
103
- namespace { task = FilterTask.define_task("filter").include *files }
104
- task
156
+ Filter.new.include *files
105
157
  end
106
158
 
107
159
  end
data/lib/tasks/zip.rb CHANGED
@@ -7,12 +7,43 @@ module Buildr
7
7
  # The ZipTask creates a new ZIP file. You can include any number of files and
8
8
  # and directories, use exclusion patterns, and include files into specific
9
9
  # directories.
10
+ #
11
+ # For example:
12
+ # zip("test.zip").tap do |task|
13
+ # task.include "srcs"
14
+ # task.include "README", "LICENSE"
15
+ # end
16
+ #
17
+ # See Buildr#zip.
10
18
  class ZipTask < Rake::FileTask
11
19
 
12
- # :nodoc:
13
- module IncludeFiles
20
+ # Which files go where. All the rules for including, excluding and merging files
21
+ # are handled by this object. A zip has at least one path.
22
+ class Path #:nodoc:
23
+
24
+ attr_reader :actions
25
+
26
+ def initialize(zip, path)
27
+ @zip = zip
28
+ @path = "#{path}/" if path
29
+ expand_src = proc { (@files || []).map(&:to_s).uniq }
30
+ @sources = [ expand_src ]
31
+ @actions = [] << proc do |zip|
32
+ expand_src.call.each do |file|
33
+ if File.directory?(file)
34
+ in_directory(file, @files) do |file, rel_path|
35
+ puts "Adding #{@path}#{rel_path}" if Rake.application.options.trace
36
+ zip.add("#{@path}#{rel_path}", file) { true }
37
+ end
38
+ else
39
+ puts "Adding #{@path}#{File.basename(file)}" if Rake.application.options.trace
40
+ zip.add("#{@path}#{File.basename(file)}", file) { true }
41
+ end
42
+ end
43
+ end
44
+ end
14
45
 
15
- # Include the specified files or directories.
46
+ # Documented in ZipTask.
16
47
  def include(*files)
17
48
  if Hash === files.last
18
49
  options = files.pop
@@ -37,14 +68,15 @@ module Buildr
37
68
  end
38
69
  self
39
70
  end
71
+ alias :add :include
40
72
 
41
- # Exclude the specified file or directories.
73
+ # Documented in ZipTask.
42
74
  def exclude(*files)
43
75
  (@files ||= FileList[]).exclude *files
44
76
  self
45
77
  end
46
- alias :add :include
47
78
 
79
+ # Documented in ZipTask.
48
80
  def merge(*files)
49
81
  if Hash === files.last
50
82
  options = files.pop
@@ -56,9 +88,9 @@ module Buildr
56
88
  path(options[:path]).merge *files +[ options.reject { |k,v| k == :path } ]
57
89
  elsif options.keys.empty?
58
90
  files.collect do |file|
59
- @expand_sources << proc { file.to_s }
91
+ @sources << proc { file.to_s }
60
92
  expander = ZipExpander.new(file)
61
- @add_files << proc { |zip| expander.expand(zip, @path) }
93
+ @actions << proc { |zip| expander.expand(zip, @path) }
62
94
  expander
63
95
  end.first
64
96
  else
@@ -66,97 +98,55 @@ module Buildr
66
98
  end
67
99
  end
68
100
 
69
- protected
101
+ # Documented in ZipTask.
102
+ def path(path)
103
+ path.blank? ? self : @zip.path("#{@path}#{path}")
104
+ end
70
105
 
71
- def setup_path(path = nil)
72
- @path = "#{path}/" if path
73
- expand_src = proc { (@files || []).map(&:to_s).uniq }
74
- @expand_sources = [ expand_src ]
75
- @add_files = [] << proc do |zip|
76
- expand_src.call.each do |file|
77
- if File.directory?(file)
78
- in_directory(file, @files) do |file, rel_path|
79
- puts "Adding #{@path}#{rel_path}" if Rake.application.options.trace
80
- zip.add "#{@path}#{rel_path}", file
81
- end
82
- else
83
- puts "Adding #{@path}#{File.basename(file)}" if Rake.application.options.trace
84
- zip.add "#{@path}#{File.basename(file)}", file
85
- end
86
- end
87
- end
106
+ # Documented in ZipTask.
107
+ def root()
108
+ @zip
109
+ end
110
+
111
+ # Returns all the source files.
112
+ def sources()
113
+ @sources.map(&:call).flatten
88
114
  end
89
115
 
116
+ protected
117
+
90
118
  def include_as(source, as)
91
- @expand_sources << proc { source }
92
- @add_files << proc do |zip|
119
+ @sources << proc { source }
120
+ @actions << proc do |zip|
93
121
  file = source.to_s
94
122
  if File.directory?(file)
95
123
  in_directory(file) do |file, rel_path|
96
- puts "Adding #{@path}#{as}/#{rel_path}" if Rake.application.options.trace
97
- zip.add file, "#{@path}#{as}/#{rel_path}"
124
+ if as == "."
125
+ dest = (@path || "") + rel_path.split("/")[1..-1].join("/")
126
+ else
127
+ dest = "#{@path}#{as}#{rel_path}"
128
+ end
129
+ puts "Adding #{dest}" if Rake.application.options.trace
130
+ zip.add(dest, file) { true }
98
131
  end
99
132
  else
100
133
  puts "Adding #{@path}#{as}" if Rake.application.options.trace
101
- zip.add "#{@path}#{as}", file
134
+ zip.add("#{@path}#{as}", file) { true }
102
135
  end
103
136
  end
104
137
  end
105
138
 
106
139
  def in_directory(dir, excludes = nil)
107
140
  prefix = Regexp.new("^" + Regexp.escape(File.dirname(dir) + File::SEPARATOR))
108
- Dir[File.join(dir, "**", "*")].
141
+ Dir["#{dir}/**/*"].
109
142
  reject { |file| File.directory?(file) || (excludes && excludes.exclude?(file)) }.
110
143
  each { |file| yield file, file.sub(prefix, "") }
111
144
  end
112
145
 
113
- def expand_sources()
114
- @expand_sources.map(&:call).flatten
115
- end
116
-
117
- def add_file(zip)
118
- @add_files.each { |action| action.call zip }
119
- end
120
-
121
- end
122
-
123
-
124
- # Which files go where.
125
- class Path
126
-
127
- include IncludeFiles
128
-
129
- def initialize(path)
130
- setup_path path
131
- end
132
-
133
- end
134
-
135
- include IncludeFiles
136
-
137
- def initialize(*args)
138
- super
139
- @paths = { nil=>self }
140
- setup_path
141
- enhance do |task|
142
- puts "Creating #{task.name}" if verbose
143
- # We're here because the Zip file does not exist, or one of the files is
144
- # newer than the Zip contents; in the later case, opening the Zip file
145
- # will add to its contents instead of replacing it, so we want the Zip
146
- # gone before we change it. We also don't want to see any partial updates.
147
- rm task.name, :verbose=>false rescue nil
148
- mkpath File.dirname(task.name), :verbose=>false
149
- begin
150
- Zip::ZipFile.open(task.name, Zip::ZipFile::CREATE) { |zip| create zip }
151
- rescue
152
- rm task.name, :verbose=>false rescue nil
153
- raise
154
- end
155
- end
156
146
  end
157
147
 
158
-
159
- class ZipExpander
148
+ # Extend one Zip file into another.
149
+ class ZipExpander #:nodoc:
160
150
 
161
151
  def initialize(zip_file)
162
152
  @zip_file = zip_file.to_s
@@ -183,7 +173,6 @@ module Buildr
183
173
  !@excludes.any? { |pattern| File.fnmatch(pattern, entry.name) }
184
174
  puts "Adding #{path}#{entry.name}" if Rake.application.options.trace
185
175
  zip.get_output_stream("#{path}#{entry.name}") { |output| output.write source.read(entry) }
186
- # TODO: read and write file
187
176
  end
188
177
  end
189
178
  end
@@ -191,17 +180,123 @@ module Buildr
191
180
 
192
181
  end
193
182
 
183
+ def initialize(*args) #:nodoc:
184
+ super
185
+ @paths = { nil=>Path.new(self, nil) }
186
+ enhance do |task|
187
+ puts "Creating #{task.name}" if verbose
188
+ # We're here because the Zip file does not exist, or one of the files is
189
+ # newer than the Zip contents; in the later case, opening the Zip file
190
+ # will add to its contents instead of replacing it, so we want the Zip
191
+ # gone before we change it. We also don't want to see any partial updates.
192
+ rm task.name, :verbose=>false rescue nil
193
+ mkpath File.dirname(task.name), :verbose=>false
194
+ begin
195
+ Zip::ZipFile.open(task.name, Zip::ZipFile::CREATE) do |zip|
196
+ zip.restore_permissions = true
197
+ create zip
198
+ end
199
+ rescue
200
+ rm task.name, :verbose=>false rescue nil
201
+ raise
202
+ end
203
+ end
204
+ end
194
205
 
195
- # Returns a path to which you can include/exclude files.
206
+ # :call-seq:
207
+ # include(*files) => self
208
+ # include(*files, :path=>path) => self
209
+ # include(file, :as=>name) => self
210
+ # include(*zips, :merge=>true) => self
211
+ #
212
+ # Include files in the ZIP (or current path) and returns self.
196
213
  #
214
+ # This method accepts three options. You can use :path to include files under
215
+ # a specific path, for example:
197
216
  # zip(..).include("foo", :path=>"bar")
198
- # is equivalen to:
217
+ # includes the file bar as foo/bar. See also #path.
218
+ #
219
+ # You can use :as to include a file under a different name, for example:
220
+ # zip(..).include("foo", :as=>"bar")
221
+ # You can use the :as option in combination with the :path option, but only with
222
+ # a single file at a time.
223
+ #
224
+ # As a special case, you can include the entire contents of a directory by including
225
+ # the directory and using :as=>".". This:
226
+ # zip(..).include("srcs", :as=>".")
227
+ # will include all the source files, using the directory as a prerequisite. This:
228
+ # zip(..).include("srcs/*")
229
+ # includes all the same source files, using the source files as a prerequisite.
230
+ #
231
+ # You can use :merge option to include the contents of another ZIP file, for example:
232
+ # zip(..).include("foo.zip", :merge=>true)
233
+ # You can use the :merge option in combination with the :path option. See also #merge.
234
+ def include(*files)
235
+ @paths[nil].include *files
236
+ self
237
+ end
238
+ alias :add :include
239
+
240
+ # :call-seq:
241
+ # exclude(*files) => self
242
+ #
243
+ # Excludes files and returns self. Can be used in combination with include to
244
+ # prevent some files from being included.
245
+ def exclude(*files)
246
+ @paths[nil].exclude *files
247
+ self
248
+ end
249
+
250
+ # :call-seq:
251
+ # merge(*files) => Merge
252
+ # merge(*files, :path=>name) => Merge
253
+ #
254
+ # Merges ZIP files and returns a merge object. The contents of the merged ZIP file is
255
+ # extracted into this ZIP file (or current path).
256
+ #
257
+ # The returned object supports two methods: include and exclude. You can use these to
258
+ # merge only specific files from the ZIP. For example:
259
+ # zip(..).merge("src.zip").include("module1/*")
260
+ #
261
+ # This differs from include with the :merge option, which returns self.
262
+ def merge(*files)
263
+ @paths[nil].merge *files
264
+ end
265
+
266
+ # :call-seq:
267
+ # path(name) => Path
268
+ #
269
+ # Returns a path object. You can use the path object to include files in a given
270
+ # path inside the ZIP file. The path object implements the include, exclude, merge,
271
+ # path and root methods.
272
+ #
273
+ # For example:
199
274
  # zip(..).path("bar").include("foo")
200
- def path(path)
201
- path.blank? ? @paths[nil] : (@paths[path] ||= Path.new(path))
275
+ # Will add the file foo under the name bar/foo.
276
+ #
277
+ # As a shorthand, you can also use the :path option:
278
+ # zip(..).include("foo", :path=>"bar")
279
+ def path(name)
280
+ name.blank? ? @paths[nil] : (@paths[name] ||= Path.new(self, name))
202
281
  end
203
282
 
204
- # Pass options to the task. Returns self.
283
+ # :call-seq:
284
+ # root() => ZipTask
285
+ #
286
+ # Returns the root path, essentially the ZipTask object itself. In case you are wondering
287
+ # down paths and want to go back.
288
+ def root()
289
+ self
290
+ end
291
+
292
+ # :call-seq:
293
+ # with(options) => self
294
+ #
295
+ # Pass options to the task. Returns self. ZipTask itself does not support any options,
296
+ # but other tasks (e.g. JarTask, WarTask) do.
297
+ #
298
+ # For example:
299
+ # package(:jar).with(:manifest=>"MANIFEST_MF")
205
300
  def with(options)
206
301
  options.each do |key, value|
207
302
  self[key] = value
@@ -209,16 +304,22 @@ module Buildr
209
304
  self
210
305
  end
211
306
 
307
+ # :call-seq:
308
+ # [name] = value
309
+ #
310
+ # Used by with method to set specific options. For example:
311
+ # package(:jar).with(:manifest=>"MANIFEST_MF")
312
+ # Or:
313
+ # package(:jar)[:manifest] = "MANIFEST_MF"
212
314
  def []=(key, value)
213
315
  fail "#{self.class} does not support the attribute #{key}"
214
316
  end
215
-
216
- def invoke_prerequisites()
217
- super
218
- @paths.collect { |name, path| path.expand_sources }.flatten.each { |src| file(src).invoke }
219
- end
220
317
 
221
- def needed?()
318
+ def prerequisites() #:nodoc:
319
+ super + @paths.collect { |name, path| path.sources }.flatten.each { |src| file(src) }
320
+ end
321
+
322
+ def needed?() #:nodoc:
222
323
  return true unless File.exist?(name)
223
324
  # You can do something like:
224
325
  # include("foo", :path=>"foo").exclude("foo/bar", path=>"foo").
@@ -230,26 +331,31 @@ module Buildr
230
331
  # contents of the ZIP. The file itself but also the directory it's
231
332
  # coming from, since some tasks touch the directory, e.g. when the
232
333
  # content of target/classes is included into a WAR.
233
- most_recent = @paths.collect { |name, path| path.expand_sources }.flatten.
234
- each { |src| File.directory?(src) ? FileList[File.join(src, "**", "*")] | [src] : src }.flatten.
334
+ most_recent = @paths.collect { |name, path| path.sources }.flatten.
335
+ each { |src| File.directory?(src) ? FileList["#{src}/**/*"] | [src] : src }.flatten.
235
336
  select { |file| File.exist?(file) }.collect { |file| File.stat(file).mtime }.max
236
337
  File.stat(name).mtime < (most_recent || Rake::EARLY) || super
237
338
  end
238
339
 
239
340
  protected
240
341
 
342
+ # Sub-classes override this method to perform additional creation tasks,
343
+ # e.g. creating a manifest file in a JAR.
241
344
  def create(zip)
242
- @paths.each { |name, obj| obj.add_file zip }
345
+ @paths.each { |name, obj| obj.actions.each { |action| action[zip] } }
243
346
  end
244
347
 
245
348
  end
246
349
 
350
+ # :call-seq:
351
+ # zip(file) => ZipTask
352
+ #
247
353
  # The ZipTask creates a new ZIP file. You can include any number of files and
248
354
  # and directories, use exclusion patterns, and include files into specific
249
355
  # directories.
250
356
  #
251
357
  # For example:
252
- # returning(zip("test.zip")) { |task|
358
+ # zip("test.zip").tap do |task|
253
359
  # task.include "srcs"
254
360
  # task.include "README", "LICENSE"
255
361
  # end
@@ -258,56 +364,68 @@ module Buildr
258
364
  end
259
365
 
260
366
 
261
- # The UnzipTask expands the contents of a ZIP file into a target directory.
262
- # You can include any number of files and directories, use exclusion patterns,
263
- # and expand files from relative paths.
367
+ # An object for unzipping a file into a target directory. You can tell it to include
368
+ # or exclude only specific files and directories, and also to map files from particular
369
+ # paths inside the zip file into the target directory. Once ready, call #extract.
264
370
  #
265
- # The file(s) to unzip is the first prerequisite.
266
- class UnzipTask < Rake::Task
371
+ # Usually it is more convenient to create a file task for extracting the zip file
372
+ # (see #unzip) and pass this object as a prerequisite to other tasks.
373
+ #
374
+ # See Buildr#unzip.
375
+ class Unzip
267
376
 
268
- # The target directory.
377
+ # The zip file to extract.
378
+ attr_accessor :zip_file
379
+ # The target directory to extract to.
269
380
  attr_accessor :target
270
381
 
271
- def initialize(*args)
272
- super
382
+ # Initialize with hash argument of the form target=>zip_file.
383
+ def initialize(args)
384
+ @target, @zip_file = Rake.application.resolve_args(args)
273
385
  @paths = {}
274
- enhance do |task|
275
- fail "Where do you want the file unzipped" unless target
386
+ end
276
387
 
277
- # If no paths specified, then no include/exclude patterns
278
- # specified. Nothing will happen unless we include all files.
279
- if @paths.empty?
280
- @paths[nil] = FromPath.new(nil)
281
- @paths[nil].include "*"
282
- end
388
+ # :call-seq:
389
+ # extract()
390
+ #
391
+ # Extract the zip file into the target directory.
392
+ #
393
+ # You can call this method directly. However, if you are using the #unzip method,
394
+ # it creates a file task for the target directory: use that task instead as a
395
+ # prerequisite. For example:
396
+ # build unzip(dir=>zip_file)
397
+ # Or:
398
+ # unzip(dir=>zip_file).target.invoke
399
+ def extract()
400
+ # If no paths specified, then no include/exclude patterns
401
+ # specified. Nothing will happen unless we include all files.
402
+ if @paths.empty?
403
+ @paths[nil] = FromPath.new(nil)
404
+ @paths[nil].include "*"
405
+ end
283
406
 
284
- # Otherwise, empty unzip creates target as a file when touching.
285
- mkpath target, :verbose=>false
286
- prerequisites.each do |file|
287
- Zip::ZipFile.open(file) do |zip|
288
- entries = zip.collect
289
- @paths.each do |path, patterns|
290
- patterns.map(entries).each do |dest, entry|
291
- next if entry.directory?
292
- dest = File.expand_path(dest, target)
293
- puts "Extracting #{dest}" if Rake.application.options.trace
294
- mkpath File.dirname(dest), :verbose=>false rescue nil
295
- entry.extract(dest) { true }
296
- end
297
- end
407
+ # Otherwise, empty unzip creates target as a file when touching.
408
+ mkpath target.to_s, :verbose=>false
409
+ Zip::ZipFile.open(zip_file.to_s) do |zip|
410
+ entries = zip.collect
411
+ @paths.each do |path, patterns|
412
+ patterns.map(entries).each do |dest, entry|
413
+ next if entry.directory?
414
+ dest = File.expand_path(dest, target.to_s)
415
+ puts "Extracting #{dest}" if Rake.application.options.trace
416
+ mkpath File.dirname(dest), :verbose=>false rescue nil
417
+ entry.extract(dest) { true }
298
418
  end
299
419
  end
300
- # Let other tasks know we updated the target directory.
301
- touch target, :verbose=>false
302
420
  end
421
+ # Let other tasks know we updated the target directory.
422
+ touch target.to_s, :verbose=>false
303
423
  end
304
424
 
305
- # Specifies directory to unzip to and return self.
306
- def into(target)
307
- self.target = target
308
- self
309
- end
310
-
425
+ # :call-seq:
426
+ # include(*files) => self
427
+ # include(*files, :path=>name) => self
428
+ #
311
429
  # Include all files that match the patterns and returns self.
312
430
  #
313
431
  # Use include if you only want to unzip some of the files, by specifying
@@ -323,6 +441,9 @@ module Buildr
323
441
  end
324
442
  alias :add :include
325
443
 
444
+ # :call-seq:
445
+ # exclude(*files) => self
446
+ #
326
447
  # Exclude all files that match the patterns and return self.
327
448
  #
328
449
  # Use exclude to unzip all files except those that match the pattern.
@@ -336,6 +457,9 @@ module Buildr
336
457
  self
337
458
  end
338
459
 
460
+ # :call-seq:
461
+ # from_path(name) => Path
462
+ #
339
463
  # Allows you to unzip from a path. Returns an object you can use to
340
464
  # specify which files to include/exclude relative to that path.
341
465
  # Expands the file relative to that path.
@@ -347,19 +471,16 @@ module Buildr
347
471
  # This is different from:
348
472
  # unzip("test.jar").into(Dir.pwd).include("etc/LICENSE")
349
473
  # which unzips etc/LICENSE into ./etc/LICENSE.
350
- def from_path(path)
351
- @paths[path] ||= FromPath.new(path)
474
+ def from_path(name)
475
+ @paths[name] ||= FromPath.new(name)
352
476
  end
353
477
 
354
- def needed?()
355
- #return true unless target && File.exist?(target)
356
- #return true if prerequisites.any? { |prereq| File.stat(prereq).mtime > File.stat(target).mtime }
357
- #false
358
- true
478
+ # Returns the path to the target directory.
479
+ def to_s()
480
+ target.to_s
359
481
  end
360
482
 
361
- # :nodoc:
362
- class FromPath
483
+ class FromPath #:nodoc:
363
484
 
364
485
  def initialize(path)
365
486
  if path
@@ -370,20 +491,19 @@ module Buildr
370
491
  end
371
492
 
372
493
  # See UnzipTask#include
373
- def include(*files)
494
+ def include(*files) #:doc:
374
495
  @include ||= []
375
496
  @include |= files
376
497
  self
377
498
  end
378
499
 
379
500
  # See UnzipTask#exclude
380
- def exclude(*files)
501
+ def exclude(*files) #:doc:
381
502
  @exclude ||= []
382
503
  @exclude |= files
383
504
  self
384
505
  end
385
506
 
386
- # :nodoc:
387
507
  def map(entries)
388
508
  includes = @include || ["*"]
389
509
  excludes = @exclude || []
@@ -401,22 +521,30 @@ module Buildr
401
521
 
402
522
  end
403
523
 
404
- # Defines a task that will unzip the specified file, into the directory
405
- # specified by calling #into. It is the second call to into that creates
406
- # and returns the task.
524
+ # :call-seq:
525
+ # unzip(to_dir=>zip_file) => Zip
526
+ #
527
+ # Creates a task that will unzip a file into the target directory. The task name
528
+ # is the target directory, the prerequisite is the file to unzip.
407
529
  #
408
- # You can unzip only some files by specifying an inclusion or exclusion
409
- # pattern, and unzip files from a path in the ZIP file. See UnzipTask
410
- # for more information.
530
+ # This method creates a file task to expand the zip file. It returns an Unzip object
531
+ # that specifies how the file will be extracted. You can include or exclude specific
532
+ # files from within the zip, and map to different paths.
533
+ #
534
+ # The Unzip object's to_s method return the path to the target directory, so you can
535
+ # use it as a prerequisite. By keeping the Unzip object separate from the file task,
536
+ # you overlay additional work on top of the file task.
411
537
  #
412
538
  # For example:
413
- # unzip("test.zip").into("test")
414
- # unzip("test.zip").into("etc").include("README", "LICENSE")
415
- # unzip("test.zip").into("src").from_path("srcs")
416
- def unzip(file)
417
- task = nil
418
- namespace { task = UnzipTask.define_task("unzip"=>file) }
419
- task
539
+ # unzip("all"=>"test.zip")
540
+ # unzip("src"=>"test.zip").include("README", "LICENSE")
541
+ # unzip("libs"=>"test.zip").from_path("libs")
542
+ def unzip(args)
543
+ target, zip_file = Rake.application.resolve_args(args)
544
+ task = file(File.expand_path(target.to_s)=>zip_file)
545
+ Unzip.new(task=>zip_file).tap do |setup|
546
+ task.enhance { setup.extract }
547
+ end
420
548
  end
421
549
 
422
550
  end