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