rant 0.3.8 → 0.4.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.
Files changed (55) hide show
  1. data/NEWS +19 -0
  2. data/README +51 -24
  3. data/Rantfile +7 -8
  4. data/doc/advanced.rdoc +3 -1
  5. data/doc/package.rdoc +280 -0
  6. data/doc/rantfile.rdoc +9 -19
  7. data/doc/rubyproject.rdoc +24 -16
  8. data/lib/rant/archive/minitar.rb +983 -0
  9. data/lib/rant/archive/rubyzip/ioextras.rb +122 -0
  10. data/lib/rant/archive/rubyzip/stdrubyext.rb +114 -0
  11. data/lib/rant/archive/rubyzip/tempfile_bugfixed.rb +195 -0
  12. data/lib/rant/archive/rubyzip.rb +1575 -0
  13. data/lib/rant/import/archive/tgz.rb +49 -0
  14. data/lib/rant/import/archive/zip.rb +67 -0
  15. data/lib/rant/import/archive.rb +312 -0
  16. data/lib/rant/import/autoclean.rb +2 -2
  17. data/lib/rant/import/c/dependencies.rb +3 -3
  18. data/lib/rant/import/clean.rb +1 -1
  19. data/lib/rant/import/directedrule.rb +1 -1
  20. data/lib/rant/import/package/tgz.rb +35 -0
  21. data/lib/rant/import/package/zip.rb +36 -0
  22. data/lib/rant/import/rubydoc.rb +1 -1
  23. data/lib/rant/import/rubypackage.rb +19 -77
  24. data/lib/rant/import/rubytest.rb +1 -1
  25. data/lib/rant/import/subfile.rb +28 -14
  26. data/lib/rant/import/win32/rubycmdwrapper.rb +1 -1
  27. data/lib/rant/import.rb +36 -16
  28. data/lib/rant/plugin/csharp.rb +1 -1
  29. data/lib/rant/rantenv.rb +2 -13
  30. data/lib/rant/rantfile.rb +11 -11
  31. data/lib/rant/rantlib.rb +7 -3
  32. data/lib/rant/rantsys.rb +53 -2
  33. data/lib/rant/rantvar.rb +62 -1
  34. data/misc/TODO +41 -0
  35. data/{devel-notes → misc/devel-notes} +6 -0
  36. data/misc/mt.rb +3 -0
  37. data/misc/t.rb +18 -0
  38. data/test/import/c/dependencies/test_c_dependencies.rb +18 -0
  39. data/test/import/package/MANIFEST +4 -0
  40. data/test/import/package/Rantfile +49 -0
  41. data/test/import/package/deep/sub/sub/f1 +1 -0
  42. data/test/import/package/sub/f1 +1 -0
  43. data/test/import/package/sub2/f1 +1 -0
  44. data/test/import/package/test_package.rb +425 -0
  45. data/test/import/subfile/Rantfile +8 -0
  46. data/test/import/subfile/test_subfile.rb +12 -0
  47. data/test/project_rb1/rantfile.rb +3 -4
  48. data/test/project_rb1/test_project_rb1.rb +16 -40
  49. data/test/rant-import/test_rant-import.rb +3 -3
  50. data/test/test_filelist.rb +39 -2
  51. data/test/tutil.rb +89 -3
  52. metadata +35 -6
  53. data/TODO +0 -21
  54. data/lib/rant/import/package.rb +0 -258
  55. /data/{rantmethods.rb → misc/rantmethods.rb} +0 -0
@@ -0,0 +1,983 @@
1
+
2
+ #--
3
+ # This is a slightly modified (especially module and classnames)
4
+ # version of archive-tar-minitar 0.5.1.
5
+ #
6
+ # Archive::Tar::Minitar 0.5.1
7
+ # Copyright � 2004 Mauricio Julio Fern�ndez Pradier and Austin Ziegler
8
+ #
9
+ # This program is based on and incorporates parts of RPA::Package from
10
+ # rpa-base (lib/rpa/package.rb and lib/rpa/util.rb) by Mauricio and has been
11
+ # adapted to be more generic by Austin.
12
+ #
13
+ # It is licensed under the GNU General Public Licence or Ruby's licence.
14
+ #
15
+ # $Id: minitar.rb,v 1.3 2004/09/22 17:47:43 austin Exp $
16
+ #++
17
+
18
+ module Rant; end
19
+ module Rant::Archive; end
20
+ module Rant::Archive::Minitar; end
21
+
22
+ # = Archive::Tar::PosixHeader
23
+ # Implements the POSIX tar header as a Ruby class. The structure of
24
+ # the POSIX tar header is:
25
+ #
26
+ # struct tarfile_entry_posix
27
+ # { // pack/unpack
28
+ # char name[100]; // ASCII (+ Z unless filled) a100/Z100
29
+ # char mode[8]; // 0 padded, octal, null a8 /A8
30
+ # char uid[8]; // ditto a8 /A8
31
+ # char gid[8]; // ditto a8 /A8
32
+ # char size[12]; // 0 padded, octal, null a12 /A12
33
+ # char mtime[12]; // 0 padded, octal, null a12 /A12
34
+ # char checksum[8]; // 0 padded, octal, null, space a8 /A8
35
+ # char typeflag[1]; // see below a /a
36
+ # char linkname[100]; // ASCII + (Z unless filled) a100/Z100
37
+ # char magic[6]; // "ustar\0" a6 /A6
38
+ # char version[2]; // "00" a2 /A2
39
+ # char uname[32]; // ASCIIZ a32 /Z32
40
+ # char gname[32]; // ASCIIZ a32 /Z32
41
+ # char devmajor[8]; // 0 padded, octal, null a8 /A8
42
+ # char devminor[8]; // 0 padded, octal, null a8 /A8
43
+ # char prefix[155]; // ASCII (+ Z unless filled) a155/Z155
44
+ # };
45
+ #
46
+ # The +typeflag+ may be one of the following known values:
47
+ #
48
+ # <tt>"0"</tt>:: Regular file. NULL should be treated as a synonym, for
49
+ # compatibility purposes.
50
+ # <tt>"1"</tt>:: Hard link.
51
+ # <tt>"2"</tt>:: Symbolic link.
52
+ # <tt>"3"</tt>:: Character device node.
53
+ # <tt>"4"</tt>:: Block device node.
54
+ # <tt>"5"</tt>:: Directory.
55
+ # <tt>"6"</tt>:: FIFO node.
56
+ # <tt>"7"</tt>:: Reserved.
57
+ #
58
+ # POSIX indicates that "A POSIX-compliant implementation must treat any
59
+ # unrecognized typeflag value as a regular file."
60
+ class Rant::Archive::Minitar::PosixHeader
61
+ FIELDS = %w(name mode uid gid size mtime checksum typeflag linkname) +
62
+ %w(magic version uname gname devmajor devminor prefix)
63
+
64
+ FIELDS.each { |field| attr_reader field.intern }
65
+
66
+ HEADER_PACK_FORMAT = "a100a8a8a8a12a12a7aaa100a6a2a32a32a8a8a155"
67
+ HEADER_UNPACK_FORMAT = "Z100A8A8A8A12A12A8aZ100A6A2Z32Z32A8A8Z155"
68
+
69
+ # Creates a new PosixHeader from a data stream.
70
+ def self.new_from_stream(stream)
71
+ data = stream.read(512)
72
+ fields = data.unpack(HEADER_UNPACK_FORMAT)
73
+ name = fields.shift
74
+ mode = fields.shift.oct
75
+ uid = fields.shift.oct
76
+ gid = fields.shift.oct
77
+ size = fields.shift.oct
78
+ mtime = fields.shift.oct
79
+ checksum = fields.shift.oct
80
+ typeflag = fields.shift
81
+ linkname = fields.shift
82
+ magic = fields.shift
83
+ version = fields.shift.oct
84
+ uname = fields.shift
85
+ gname = fields.shift
86
+ devmajor = fields.shift.oct
87
+ devminor = fields.shift.oct
88
+ prefix = fields.shift
89
+
90
+ empty = (data == "\0" * 512)
91
+
92
+ new(:name => name, :mode => mode, :uid => uid, :gid => gid,
93
+ :size => size, :mtime => mtime, :checksum => checksum,
94
+ :typeflag => typeflag, :magic => magic, :version => version,
95
+ :uname => uname, :gname => gname, :devmajor => devmajor,
96
+ :devminor => devminor, :prefix => prefix, :empty => empty)
97
+ end
98
+
99
+ # Creates a new PosixHeader. A PosixHeader cannot be created unless the
100
+ # #name, #size, #prefix, and #mode are provided.
101
+ def initialize(vals)
102
+ unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode]
103
+ raise ArgumentError
104
+ end
105
+
106
+ vals[:mtime] ||= 0
107
+ vals[:checksum] ||= ""
108
+ vals[:typeflag] ||= "0"
109
+ vals[:magic] ||= "ustar"
110
+ vals[:version] ||= "00"
111
+
112
+ FIELDS.each do |field|
113
+ instance_variable_set("@#{field}", vals[field.intern])
114
+ end
115
+ @empty = vals[:empty]
116
+ end
117
+
118
+ def empty?
119
+ @empty
120
+ end
121
+
122
+ def to_s
123
+ update_checksum
124
+ header(@checksum)
125
+ end
126
+
127
+ # Update the checksum field.
128
+ def update_checksum
129
+ hh = header(" " * 8)
130
+ @checksum = oct(calculate_checksum(hh), 6)
131
+ end
132
+
133
+ private
134
+ def oct(num, len)
135
+ if num.nil?
136
+ "\0" * (len + 1)
137
+ else
138
+ "%0#{len}o" % num
139
+ end
140
+ end
141
+
142
+ def calculate_checksum(hdr)
143
+ hdr.unpack("C*").inject { |aa, bb| aa + bb }
144
+ end
145
+
146
+ def header(chksum)
147
+ arr = [name, oct(mode, 7), oct(uid, 7), oct(gid, 7), oct(size, 11),
148
+ oct(mtime, 11), chksum, " ", typeflag, linkname, magic, version,
149
+ uname, gname, oct(devmajor, 7), oct(devminor, 7), prefix]
150
+ str = arr.pack(HEADER_PACK_FORMAT)
151
+ str + "\0" * ((512 - str.size) % 512)
152
+ end
153
+ end
154
+
155
+ require 'fileutils'
156
+ require 'find'
157
+
158
+ # = Archive::Tar::Minitar 0.5.1
159
+ # Archive::Tar::Minitar is a pure-Ruby library and command-line
160
+ # utility that provides the ability to deal with POSIX tar(1) archive
161
+ # files. The implementation is based heavily on Mauricio Fern�ndez's
162
+ # implementation in rpa-base, but has been reorganised to promote
163
+ # reuse in other projects.
164
+ #
165
+ # This tar class performs a subset of all tar (POSIX tape archive)
166
+ # operations. We can only deal with typeflags 0, 1, 2, and 5 (see
167
+ # Archive::Tar::PosixHeader). All other typeflags will be treated as
168
+ # normal files.
169
+ #
170
+ # NOTE::: support for typeflags 1 and 2 is not yet implemented in this
171
+ # version.
172
+ #
173
+ # This release is version 0.5.1. The library can only handle files and
174
+ # directories at this point. A future version will be expanded to
175
+ # handle symbolic links and hard links in a portable manner. The
176
+ # command line utility, minitar, can only create archives, extract
177
+ # from archives, and list archive contents.
178
+ #
179
+ # == Synopsis
180
+ # Using this library is easy. The simplest case is:
181
+ #
182
+ # require 'zlib'
183
+ # require 'archive/tar/minitar'
184
+ # include Archive::Tar
185
+ #
186
+ # # Packs everything that matches Find.find('tests')
187
+ # File.open('test.tar', 'wb') { |tar| Minitar.pack('tests', tar) }
188
+ # # Unpacks 'test.tar' to 'x', creating 'x' if necessary.
189
+ # Minitar.unpack('test.tar', 'x')
190
+ #
191
+ # A gzipped tar can be written with:
192
+ #
193
+ # tgz = Zlib::GzipWriter.new(File.open('test.tgz', 'wb'))
194
+ # # Warning: tgz will be closed!
195
+ # Minitar.pack('tests', tgz)
196
+ #
197
+ # tgz = Zlib::GzipReader.new(File.open('test.tgz', 'rb'))
198
+ # # Warning: tgz will be closed!
199
+ # Minitar.unpack(tgz, 'x')
200
+ #
201
+ # As the case above shows, one need not write to a file. However, it
202
+ # will sometimes require that one dive a little deeper into the API,
203
+ # as in the case of StringIO objects. Note that I'm not providing a
204
+ # block with Minitar::Output, as Minitar::Output#close automatically
205
+ # closes both the Output object and the wrapped data stream object.
206
+ #
207
+ # begin
208
+ # sgz = Zlib::GzipWriter.new(StringIO.new(""))
209
+ # tar = Output.new(sgz)
210
+ # Find.find('tests') do |entry|
211
+ # Minitar.pack_file(entry, tar)
212
+ # end
213
+ # ensure
214
+ # # Closes both tar and sgz.
215
+ # tar.close
216
+ # end
217
+ #
218
+ # == Copyright
219
+ # Copyright 2004 Mauricio Julio Fern�ndez Pradier and Austin Ziegler
220
+ #
221
+ # This program is based on and incorporates parts of RPA::Package from
222
+ # rpa-base (lib/rpa/package.rb and lib/rpa/util.rb) by Mauricio and
223
+ # has been adapted to be more generic by Austin.
224
+ #
225
+ # 'minitar' contains an adaptation of Ruby/ProgressBar by Satoru
226
+ # Takabayashi <satoru@namazu.org>, copyright � 2001 - 2004.
227
+ #
228
+ # This program is free software. It may be redistributed and/or
229
+ # modified under the terms of the GPL version 2 (or later) or Ruby's
230
+ # licence.
231
+ module Rant::Archive::Minitar
232
+ VERSION = "0.5.1"
233
+
234
+ # The exception raised when a wrapped data stream class is expected to
235
+ # respond to #rewind or #pos but does not.
236
+ class NonSeekableStream < StandardError; end
237
+ # The exception raised when a block is required for proper operation of
238
+ # the method.
239
+ class BlockRequired < ArgumentError; end
240
+ # The exception raised when operations are performed on a stream that has
241
+ # previously been closed.
242
+ class ClosedStream < StandardError; end
243
+ # The exception raised when a filename exceeds 256 bytes in length,
244
+ # the maximum supported by the standard Tar format.
245
+ class FileNameTooLong < StandardError; end
246
+ # The exception raised when a data stream ends before the amount of data
247
+ # expected in the archive's PosixHeader.
248
+ class UnexpectedEOF < StandardError; end
249
+
250
+ # The class that writes a tar format archive to a data stream.
251
+ class Writer
252
+ # A stream wrapper that can only be written to. Any attempt to read
253
+ # from this restricted stream will result in a NameError being thrown.
254
+ class RestrictedStream
255
+ def initialize(anIO)
256
+ @io = anIO
257
+ end
258
+
259
+ def write(data)
260
+ @io.write(data)
261
+ end
262
+ end
263
+
264
+ # A RestrictedStream that also has a size limit.
265
+ class BoundedStream < Rant::Archive::Minitar::Writer::RestrictedStream
266
+ # The exception raised when the user attempts to write more data to
267
+ # a BoundedStream than has been allocated.
268
+ class FileOverflow < RuntimeError; end
269
+
270
+ # The maximum number of bytes that may be written to this data
271
+ # stream.
272
+ attr_reader :limit
273
+ # The current total number of bytes written to this data stream.
274
+ attr_reader :written
275
+
276
+ def initialize(io, limit)
277
+ @io = io
278
+ @limit = limit
279
+ @written = 0
280
+ end
281
+
282
+ def write(data)
283
+ raise FileOverflow if (data.size + @written) > @limit
284
+ @io.write(data)
285
+ @written += data.size
286
+ data.size
287
+ end
288
+ end
289
+
290
+ # With no associated block, +Writer::open+ is a synonym for
291
+ # +Writer::new+. If the optional code block is given, it will be
292
+ # passed the new _writer_ as an argument and the Writer object will
293
+ # automatically be closed when the block terminates. In this instance,
294
+ # +Writer::open+ returns the value of the block.
295
+ def self.open(anIO)
296
+ writer = Writer.new(anIO)
297
+
298
+ return writer unless block_given?
299
+
300
+ begin
301
+ res = yield writer
302
+ ensure
303
+ writer.close
304
+ end
305
+
306
+ res
307
+ end
308
+
309
+ # Creates and returns a new Writer object.
310
+ def initialize(anIO)
311
+ @io = anIO
312
+ @closed = false
313
+ end
314
+
315
+ # Adds a file to the archive as +name+. +opts+ must contain the
316
+ # following values:
317
+ #
318
+ # <tt>:mode</tt>:: The Unix file permissions mode value.
319
+ # <tt>:size</tt>:: The size, in bytes.
320
+ #
321
+ # +opts+ may contain the following values:
322
+ #
323
+ # <tt>:uid</tt>: The Unix file owner user ID number.
324
+ # <tt>:gid</tt>: The Unix file owner group ID number.
325
+ # <tt>:mtime</tt>:: The *integer* modification time value.
326
+ #
327
+ # It will not be possible to add more than <tt>opts[:size]</tt> bytes
328
+ # to the file.
329
+ def add_file_simple(name, opts = {}) # :yields BoundedStream:
330
+ raise Rant::Archive::Minitar::BlockRequired unless block_given?
331
+ raise Rant::Archive::Minitar::ClosedStream if @closed
332
+
333
+ name, prefix = split_name(name)
334
+
335
+ header = { :name => name, :mode => opts[:mode], :mtime => opts[:mtime],
336
+ :size => opts[:size], :gid => opts[:gid], :uid => opts[:uid],
337
+ :prefix => prefix }
338
+ header = Rant::Archive::Minitar::PosixHeader.new(header).to_s
339
+ @io.write(header)
340
+
341
+ os = BoundedStream.new(@io, opts[:size])
342
+ yield os
343
+ # FIXME: what if an exception is raised in the block?
344
+
345
+ min_padding = opts[:size] - os.written
346
+ @io.write("\0" * min_padding)
347
+ remainder = (512 - (opts[:size] % 512)) % 512
348
+ @io.write("\0" * remainder)
349
+ end
350
+
351
+ # Adds a file to the archive as +name+. +opts+ must contain the
352
+ # following value:
353
+ #
354
+ # <tt>:mode</tt>:: The Unix file permissions mode value.
355
+ #
356
+ # +opts+ may contain the following values:
357
+ #
358
+ # <tt>:uid</tt>: The Unix file owner user ID number.
359
+ # <tt>:gid</tt>: The Unix file owner group ID number.
360
+ # <tt>:mtime</tt>:: The *integer* modification time value.
361
+ #
362
+ # The file's size will be determined from the amount of data written
363
+ # to the stream.
364
+ #
365
+ # For #add_file to be used, the Archive::Tar::Minitar::Writer must be
366
+ # wrapping a stream object that is seekable (e.g., it responds to
367
+ # #pos=). Otherwise, #add_file_simple must be used.
368
+ #
369
+ # +opts+ may be modified during the writing to the stream.
370
+ def add_file(name, opts = {}) # :yields RestrictedStream, +opts+:
371
+ raise Rant::Archive::Minitar::BlockRequired unless block_given?
372
+ raise Rant::Archive::Minitar::ClosedStream if @closed
373
+ raise Rant::Archive::Minitar::NonSeekableStream unless @io.respond_to?(:pos=)
374
+
375
+ name, prefix = split_name(name)
376
+ init_pos = @io.pos
377
+ @io.write("\0" * 512) # placeholder for the header
378
+
379
+ yield RestrictedStream.new(@io), opts
380
+ # FIXME: what if an exception is raised in the block?
381
+
382
+ size = @io.pos - (init_pos + 512)
383
+ remainder = (512 - (size % 512)) % 512
384
+ @io.write("\0" * remainder)
385
+
386
+ final_pos = @io.pos
387
+ @io.pos = init_pos
388
+
389
+ header = { :name => name, :mode => opts[:mode], :mtime => opts[:mtime],
390
+ :size => size, :gid => opts[:gid], :uid => opts[:uid],
391
+ :prefix => prefix }
392
+ header = Rant::Archive::Minitar::PosixHeader.new(header).to_s
393
+ @io.write(header)
394
+ @io.pos = final_pos
395
+ end
396
+
397
+ # Creates a directory in the tar.
398
+ def mkdir(name, opts = {})
399
+ raise ClosedStream if @closed
400
+ name, prefix = split_name(name)
401
+ header = { :name => name, :mode => opts[:mode], :typeflag => "5",
402
+ :size => 0, :gid => opts[:gid], :uid => opts[:uid],
403
+ :mtime => opts[:mtime], :prefix => prefix }
404
+ header = Rant::Archive::Minitar::PosixHeader.new(header).to_s
405
+ @io.write(header)
406
+ nil
407
+ end
408
+
409
+ # Passes the #flush method to the wrapped stream, used for buffered
410
+ # streams.
411
+ def flush
412
+ raise ClosedStream if @closed
413
+ @io.flush if @io.respond_to?(:flush)
414
+ end
415
+
416
+ # Closes the Writer.
417
+ def close
418
+ return if @closed
419
+ @io.write("\0" * 1024)
420
+ @closed = true
421
+ end
422
+
423
+ private
424
+ def split_name(name)
425
+ raise FileNameTooLong if name.size > 256
426
+ if name.size <= 100
427
+ prefix = ""
428
+ else
429
+ parts = name.split(/\//)
430
+ newname = parts.pop
431
+
432
+ nxt = ""
433
+
434
+ loop do
435
+ nxt = parts.pop
436
+ break if newname.size + 1 + nxt.size > 100
437
+ newname = "#{nxt}/#{newname}"
438
+ end
439
+
440
+ prefix = (parts + [nxt]).join("/")
441
+
442
+ name = newname
443
+
444
+ raise FileNameTooLong if name.size > 100 || prefix.size > 155
445
+ end
446
+ return name, prefix
447
+ end
448
+ end
449
+
450
+ # The class that reads a tar format archive from a data stream. The data
451
+ # stream may be sequential or random access, but certain features only work
452
+ # with random access data streams.
453
+ class Reader
454
+ # This marks the EntryStream closed for reading without closing the
455
+ # actual data stream.
456
+ module InvalidEntryStream
457
+ def read(len = nil); raise ClosedStream; end
458
+ def getc; raise ClosedStream; end
459
+ def rewind; raise ClosedStream; end
460
+ end
461
+
462
+ # EntryStreams are pseudo-streams on top of the main data stream.
463
+ class EntryStream
464
+ Rant::Archive::Minitar::PosixHeader::FIELDS.each do |field|
465
+ attr_reader field.intern
466
+ end
467
+
468
+ def initialize(header, anIO)
469
+ @io = anIO
470
+ @name = header.name
471
+ @mode = header.mode
472
+ @uid = header.uid
473
+ @gid = header.gid
474
+ @size = header.size
475
+ @mtime = header.mtime
476
+ @checksum = header.checksum
477
+ @typeflag = header.typeflag
478
+ @linkname = header.linkname
479
+ @magic = header.magic
480
+ @version = header.version
481
+ @uname = header.uname
482
+ @gname = header.gname
483
+ @devmajor = header.devmajor
484
+ @devminor = header.devminor
485
+ @prefix = header.prefix
486
+ @read = 0
487
+ @orig_pos = @io.pos
488
+ end
489
+
490
+ # Reads +len+ bytes (or all remaining data) from the entry. Returns
491
+ # +nil+ if there is no more data to read.
492
+ def read(len = nil)
493
+ return nil if @read >= @size
494
+ len ||= @size - @read
495
+ max_read = [len, @size - @read].min
496
+ ret = @io.read(max_read)
497
+ @read += ret.size
498
+ ret
499
+ end
500
+
501
+ # Reads one byte from the entry. Returns +nil+ if there is no more data
502
+ # to read.
503
+ def getc
504
+ return nil if @read >= @size
505
+ ret = @io.getc
506
+ @read += 1 if ret
507
+ ret
508
+ end
509
+
510
+ # Returns +true+ if the entry represents a directory.
511
+ def directory?
512
+ @typeflag == "5"
513
+ end
514
+ alias_method :directory, :directory?
515
+
516
+ # Returns +true+ if the entry represents a plain file.
517
+ def file?
518
+ @typeflag == "0"
519
+ end
520
+ alias_method :file, :file?
521
+
522
+ # Returns +true+ if the current read pointer is at the end of the
523
+ # EntryStream data.
524
+ def eof?
525
+ @read >= @size
526
+ end
527
+
528
+ # Returns the current read pointer in the EntryStream.
529
+ def pos
530
+ @read
531
+ end
532
+
533
+ # Sets the current read pointer to the beginning of the EntryStream.
534
+ def rewind
535
+ raise NonSeekableStream unless @io.respond_to?(:pos=)
536
+ @io.pos = @orig_pos
537
+ @read = 0
538
+ end
539
+
540
+ def bytes_read
541
+ @read
542
+ end
543
+
544
+ # Returns the full and proper name of the entry.
545
+ def full_name
546
+ if @prefix != ""
547
+ File.join(@prefix, @name)
548
+ else
549
+ @name
550
+ end
551
+ end
552
+
553
+ # Closes the entry.
554
+ def close
555
+ invalidate
556
+ end
557
+
558
+ private
559
+ def invalidate
560
+ extend InvalidEntryStream
561
+ end
562
+ end
563
+
564
+ # With no associated block, +Reader::open+ is a synonym for
565
+ # +Reader::new+. If the optional code block is given, it will be passed
566
+ # the new _writer_ as an argument and the Reader object will
567
+ # automatically be closed when the block terminates. In this instance,
568
+ # +Reader::open+ returns the value of the block.
569
+ def self.open(anIO)
570
+ reader = Reader.new(anIO)
571
+
572
+ return reader unless block_given?
573
+
574
+ begin
575
+ res = yield reader
576
+ ensure
577
+ reader.close
578
+ end
579
+
580
+ res
581
+ end
582
+
583
+ # Creates and returns a new Reader object.
584
+ def initialize(anIO)
585
+ @io = anIO
586
+ @init_pos = anIO.pos
587
+ end
588
+
589
+ # Iterates through each entry in the data stream.
590
+ def each(&block)
591
+ each_entry(&block)
592
+ end
593
+
594
+ # Resets the read pointer to the beginning of data stream. Do not call
595
+ # this during a #each or #each_entry iteration. This only works with
596
+ # random access data streams that respond to #rewind and #pos.
597
+ def rewind
598
+ if @init_pos == 0
599
+ raise NonSeekableStream unless @io.respond_to?(:rewind)
600
+ @io.rewind
601
+ else
602
+ raise NonSeekableStream unless @io.respond_to?(:pos=)
603
+ @io.pos = @init_pos
604
+ end
605
+ end
606
+
607
+ # Iterates through each entry in the data stream.
608
+ def each_entry
609
+ loop do
610
+ return if @io.eof?
611
+
612
+ header = Rant::Archive::Minitar::PosixHeader.new_from_stream(@io)
613
+ return if header.empty?
614
+
615
+ entry = EntryStream.new(header, @io)
616
+ size = entry.size
617
+
618
+ yield entry
619
+
620
+ skip = (512 - (size % 512)) % 512
621
+
622
+ if @io.respond_to?(:seek)
623
+ # avoid reading...
624
+ @io.seek(size - entry.bytes_read, IO::SEEK_CUR)
625
+ else
626
+ pending = size - entry.bytes_read
627
+ while pending > 0
628
+ bread = @io.read([pending, 4096].min).size
629
+ raise UnexpectedEOF if @io.eof?
630
+ pending -= bread
631
+ end
632
+ end
633
+ @io.read(skip) # discard trailing zeros
634
+ # make sure nobody can use #read, #getc or #rewind anymore
635
+ entry.close
636
+ end
637
+ end
638
+
639
+ def close
640
+ end
641
+ end
642
+
643
+ # Wraps a Archive::Tar::Minitar::Reader with convenience methods and
644
+ # wrapped stream management; Input only works with random access data
645
+ # streams. See Input::new for details.
646
+ class Input
647
+ include Enumerable
648
+
649
+ # With no associated block, +Input::open+ is a synonym for
650
+ # +Input::new+. If the optional code block is given, it will be passed
651
+ # the new _writer_ as an argument and the Input object will
652
+ # automatically be closed when the block terminates. In this instance,
653
+ # +Input::open+ returns the value of the block.
654
+ def self.open(input)
655
+ stream = Input.new(input)
656
+ return stream unless block_given?
657
+
658
+ begin
659
+ res = yield stream
660
+ ensure
661
+ stream.close
662
+ end
663
+
664
+ res
665
+ end
666
+
667
+ # Creates a new Input object. If +input+ is a stream object that responds
668
+ # to #read), then it will simply be wrapped. Otherwise, one will be
669
+ # created and opened using Kernel#open. When Input#close is called, the
670
+ # stream object wrapped will be closed.
671
+ def initialize(input)
672
+ if input.respond_to?(:read)
673
+ @io = input
674
+ else
675
+ @io = open(input, "rb")
676
+ end
677
+ @tarreader = Rant::Archive::Minitar::Reader.new(@io)
678
+ end
679
+
680
+ # Iterates through each entry and rewinds to the beginning of the stream
681
+ # when finished.
682
+ def each(&block)
683
+ @tarreader.each { |entry| yield entry }
684
+ ensure
685
+ @tarreader.rewind
686
+ end
687
+
688
+ # Extracts the current +entry+ to +destdir+. If a block is provided, it
689
+ # yields an +action+ Symbol, the full name of the file being extracted
690
+ # (+name+), and a Hash of statistical information (+stats+).
691
+ #
692
+ # The +action+ will be one of:
693
+ # <tt>:dir</tt>:: The +entry+ is a directory.
694
+ # <tt>:file_start</tt>:: The +entry+ is a file; the extract of the
695
+ # file is just beginning.
696
+ # <tt>:file_progress</tt>:: Yielded every 4096 bytes during the extract
697
+ # of the +entry+.
698
+ # <tt>:file_done</tt>:: Yielded when the +entry+ is completed.
699
+ #
700
+ # The +stats+ hash contains the following keys:
701
+ # <tt>:current</tt>:: The current total number of bytes read in the
702
+ # +entry+.
703
+ # <tt>:currinc</tt>:: The current number of bytes read in this read
704
+ # cycle.
705
+ # <tt>:entry</tt>:: The entry being extracted; this is a
706
+ # Reader::EntryStream, with all methods thereof.
707
+ def extract_entry(destdir, entry) # :yields action, name, stats:
708
+ stats = {
709
+ :current => 0,
710
+ :currinc => 0,
711
+ :entry => entry
712
+ }
713
+
714
+ if entry.directory?
715
+ dest = File.join(destdir, entry.full_name)
716
+
717
+ yield :dir, entry.full_name, stats if block_given?
718
+
719
+ if Rant::Archive::Minitar.dir?(dest)
720
+ begin
721
+ FileUtils.chmod(entry.mode, dest)
722
+ rescue Exception
723
+ nil
724
+ end
725
+ else
726
+ FileUtils.mkdir_p(dest, :mode => entry.mode)
727
+ FileUtils.chmod(entry.mode, dest)
728
+ end
729
+
730
+ fsync_dir(dest)
731
+ fsync_dir(File.join(dest, ".."))
732
+ return
733
+ else # it's a file
734
+ destdir = File.join(destdir, File.dirname(entry.full_name))
735
+ FileUtils.mkdir_p(destdir, :mode => 0755)
736
+
737
+ destfile = File.join(destdir, File.basename(entry.full_name))
738
+ FileUtils.chmod(0600, destfile) rescue nil # Errno::ENOENT
739
+
740
+ yield :file_start, entry.full_name, stats if block_given?
741
+
742
+ File.open(destfile, "wb", entry.mode) do |os|
743
+ loop do
744
+ data = entry.read(4096)
745
+ break unless data
746
+
747
+ stats[:currinc] = os.write(data)
748
+ stats[:current] += stats[:currinc]
749
+
750
+ yield :file_progress, entry.full_name, stats if block_given?
751
+ end
752
+ os.fsync
753
+ end
754
+
755
+ FileUtils.chmod(entry.mode, destfile)
756
+ fsync_dir(File.dirname(destfile))
757
+ fsync_dir(File.join(File.dirname(destfile), ".."))
758
+
759
+ yield :file_done, entry.full_name, stats if block_given?
760
+ end
761
+ end
762
+
763
+ # Returns the Reader object for direct access.
764
+ def tar
765
+ @tarreader
766
+ end
767
+
768
+ # Closes the Reader object and the wrapped data stream.
769
+ def close
770
+ @io.close
771
+ @tarreader.close
772
+ end
773
+
774
+ private
775
+ def fsync_dir(dirname)
776
+ # make sure this hits the disc
777
+ dir = open(dirname, 'rb')
778
+ dir.fsync
779
+ rescue # ignore IOError if it's an unpatched (old) Ruby
780
+ nil
781
+ ensure
782
+ dir.close if dir rescue nil
783
+ end
784
+ end
785
+
786
+ # Wraps a Archive::Tar::Minitar::Writer with convenience methods and
787
+ # wrapped stream management; Output only works with random access data
788
+ # streams. See Output::new for details.
789
+ class Output
790
+ # With no associated block, +Output::open+ is a synonym for
791
+ # +Output::new+. If the optional code block is given, it will be passed
792
+ # the new _writer_ as an argument and the Output object will
793
+ # automatically be closed when the block terminates. In this instance,
794
+ # +Output::open+ returns the value of the block.
795
+ def self.open(output)
796
+ stream = Output.new(output)
797
+ return stream unless block_given?
798
+
799
+ begin
800
+ res = yield stream
801
+ ensure
802
+ stream.close
803
+ end
804
+
805
+ res
806
+ end
807
+
808
+ # Creates a new Output object. If +output+ is a stream object that
809
+ # responds to #read), then it will simply be wrapped. Otherwise, one will
810
+ # be created and opened using Kernel#open. When Output#close is called,
811
+ # the stream object wrapped will be closed.
812
+ def initialize(output)
813
+ if output.respond_to?(:write)
814
+ @io = output
815
+ else
816
+ @io = ::File.open(output, "wb")
817
+ end
818
+ @tarwriter = Rant::Archive::Minitar::Writer.new(@io)
819
+ end
820
+
821
+ # Returns the Writer object for direct access.
822
+ def tar
823
+ @tarwriter
824
+ end
825
+
826
+ # Closes the Writer object and the wrapped data stream.
827
+ def close
828
+ @tarwriter.close
829
+ @io.close
830
+ end
831
+ end
832
+
833
+ class << self
834
+ # Tests if +path+ refers to a directory. Fixes an apparently
835
+ # corrupted <tt>stat()</tt> call on Windows.
836
+ def dir?(path)
837
+ File.directory?((path[-1] == ?/) ? path : "#{path}/")
838
+ end
839
+
840
+ # A convenience method for wrapping Archive::Tar::Minitar::Input.open
841
+ # (mode +r+) and Archive::Tar::Minitar::Output.open (mode +w+). No other
842
+ # modes are currently supported.
843
+ def open(dest, mode = "r", &block)
844
+ case mode
845
+ when "r"
846
+ Input.open(dest, &block)
847
+ when "w"
848
+ Output.open(dest, &block)
849
+ else
850
+ raise "Unknown open mode for Rant::Archive::Minitar.open."
851
+ end
852
+ end
853
+
854
+ # A convenience method to packs the file provided. +entry+ may either be
855
+ # a filename (in which case various values for the file (see below) will
856
+ # be obtained from <tt>File#stat(entry)</tt> or a Hash with the fields:
857
+ #
858
+ # <tt>:name</tt>:: The filename to be packed into the tarchive.
859
+ # *REQUIRED*.
860
+ # <tt>:mode</tt>:: The mode to be applied.
861
+ # <tt>:uid</tt>:: The user owner of the file. (Ignored on Windows.)
862
+ # <tt>:gid</tt>:: The group owner of the file. (Ignored on Windows.)
863
+ # <tt>:mtime</tt>:: The modification Time of the file.
864
+ #
865
+ # During packing, if a block is provided, #pack_file yields an +action+
866
+ # Symol, the full name of the file being packed, and a Hash of
867
+ # statistical information, just as with
868
+ # Archive::Tar::Minitar::Input#extract_entry.
869
+ #
870
+ # The +action+ will be one of:
871
+ # <tt>:dir</tt>:: The +entry+ is a directory.
872
+ # <tt>:file_start</tt>:: The +entry+ is a file; the extract of the
873
+ # file is just beginning.
874
+ # <tt>:file_progress</tt>:: Yielded every 4096 bytes during the extract
875
+ # of the +entry+.
876
+ # <tt>:file_done</tt>:: Yielded when the +entry+ is completed.
877
+ #
878
+ # The +stats+ hash contains the following keys:
879
+ # <tt>:current</tt>:: The current total number of bytes read in the
880
+ # +entry+.
881
+ # <tt>:currinc</tt>:: The current number of bytes read in this read
882
+ # cycle.
883
+ # <tt>:name</tt>:: The filename to be packed into the tarchive.
884
+ # *REQUIRED*.
885
+ # <tt>:mode</tt>:: The mode to be applied.
886
+ # <tt>:uid</tt>:: The user owner of the file. (+nil+ on Windows.)
887
+ # <tt>:gid</tt>:: The group owner of the file. (+nil+ on Windows.)
888
+ # <tt>:mtime</tt>:: The modification Time of the file.
889
+ def pack_file(entry, outputter) #:yields action, name, stats:
890
+ outputter = outputter.tar if outputter.kind_of?(Rant::Archive::Minitar::Output)
891
+
892
+ stats = {}
893
+
894
+ if entry.kind_of?(Hash)
895
+ name = entry[:name]
896
+
897
+ entry.each { |kk, vv| stats[kk] = vv unless vv.nil? }
898
+ else
899
+ name = entry
900
+ end
901
+
902
+ name = name.sub(%r{\./}, '')
903
+ stat = File.stat(name)
904
+ stats[:mode] ||= stat.mode
905
+ stats[:mtime] ||= stat.mtime
906
+ stats[:size] = stat.size
907
+
908
+ if RUBY_PLATFORM =~ /win32/
909
+ stats[:uid] = nil
910
+ stats[:gid] = nil
911
+ else
912
+ stats[:uid] ||= stat.uid
913
+ stats[:gid] ||= stat.gid
914
+ end
915
+
916
+ case
917
+ when File.file?(name)
918
+ outputter.add_file_simple(name, stats) do |os|
919
+ stats[:current] = 0
920
+ yield :file_start, name, stats if block_given?
921
+ File.open(name, "rb") do |ff|
922
+ until ff.eof?
923
+ stats[:currinc] = os.write(ff.read(4096))
924
+ stats[:current] += stats[:currinc]
925
+ yield :file_progress, name, stats if block_given?
926
+ end
927
+ end
928
+ yield :file_done, name, stats if block_given?
929
+ end
930
+ when dir?(name)
931
+ yield :dir, name, stats if block_given?
932
+ outputter.mkdir(name, stats)
933
+ else
934
+ raise "Don't yet know how to pack this type of file."
935
+ end
936
+ end
937
+
938
+ # A convenience method to pack files specified by +src+ into +dest+. If
939
+ # +src+ is an Array, then each file detailed therein will be packed into
940
+ # the resulting Archive::Tar::Minitar::Output stream; if +recurse_dirs+
941
+ # is true, then directories will be recursed.
942
+ #
943
+ # If +src+ is an Array, it will be treated as the argument to Find.find;
944
+ # all files matching will be packed.
945
+ def pack(src, dest, recurse_dirs = true, &block)
946
+ Output.open(dest) do |outp|
947
+ if src.kind_of?(Array)
948
+ src.each do |entry|
949
+ pack_file(entry, outp, &block)
950
+ if dir?(entry) and recurse_dirs
951
+ Dir["#{entry}/**/**"].each do |ee|
952
+ pack_file(ee, outp, &block)
953
+ end
954
+ end
955
+ end
956
+ else
957
+ Find.find(src) do |entry|
958
+ pack_file(entry, outp, &block)
959
+ end
960
+ end
961
+ end
962
+ end
963
+
964
+ # A convenience method to unpack files from +src+ into the directory
965
+ # specified by +dest+. Only those files named explicitly in +files+
966
+ # will be extracted.
967
+ def unpack(src, dest, files = [], &block)
968
+ Input.open(src) do |inp|
969
+ if File.exist?(dest) and (not dir?(dest))
970
+ raise "Can't unpack to a non-directory."
971
+ elsif not File.exist?(dest)
972
+ FileUtils.mkdir_p(dest)
973
+ end
974
+
975
+ inp.each do |entry|
976
+ if files.empty? or files.include?(entry.full_name)
977
+ inp.extract_entry(dest, entry, &block)
978
+ end
979
+ end
980
+ end
981
+ end
982
+ end
983
+ end