archive-tar 0.9.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,110 @@
1
+ module Archive #:nodoc:
2
+ end
3
+
4
+ #
5
+ # == Extract Options
6
+ #
7
+ # * <tt>:permissions</tt> - If set to <tt>:preserve</tt>, then the Reader will
8
+ # attempt to chown and chmod the extracted to files to match the mode, UID
9
+ # and GID stored in the archive. For every other value, UID and GID will be
10
+ # taken from the executing user, and mode from that user's <tt>umask</tt>.
11
+ #
12
+ # <b>Note:</b> on most operating systems (notably UNIX/Linux) the only user
13
+ # with rights to change ownership of files and directory is the <tt>root</tt>
14
+ # user.
15
+ #
16
+ # == Archive Options
17
+ #
18
+ # * <tt>:compression</tt> - If set to <tt>:gzip</tt>, then the Reader will
19
+ # decompress the tar file on-the-fly (without any temp files) as it extracts
20
+ # the tarred files. For every other value, no decompression is done.
21
+ # <i>Bzip2 compression is not yet supported</i>.
22
+ #
23
+ module Archive::Tar
24
+ # :stopdoc:
25
+ UNIX_VERBOSE = Proc.new { |s, _|
26
+ if (s[:type] == :link)
27
+ print 'h'
28
+ else
29
+ print '-'
30
+ end
31
+ [ (s[:mode] & 0700) >> 6, (s[:mode] & 0070) >> 3, (s[:mode] & 0007) ].each do |p|
32
+ print p & 04 == 04 ? 'r' : '-'
33
+ print p & 02 == 02 ? 'w' : '-'
34
+ print p & 01 == 01 ? 'x' : '-'
35
+ end
36
+ print ' '
37
+ print "#{s[:user]}/#{s[:group]}".ljust(16, ' ')
38
+ print s[:size].to_s.rjust(8,' ')
39
+ print ' '
40
+ print Time.at(s[:mtime]).strftime("%Y-%m-%d %H:%M")
41
+ print ' '
42
+ print s[:path]
43
+ if (s[:type] == :link)
44
+ print ' link to '
45
+ print s[:dest]
46
+ end
47
+ puts
48
+ }
49
+ # :startdoc:
50
+
51
+ class << self
52
+ #
53
+ # Extract a tar archive (+source_file+) into +dest_dir+. +source_file+
54
+ # must exist and the executing user must be able to read it.
55
+ # Additionally, +dest_dir+ must exist, and the executing user must be
56
+ # allowed to write to it.
57
+ #
58
+ # +options+ is an optional hash of parameters that influence the nature
59
+ # of the extraction. See Extract Options and Archive Options (above)
60
+ # for information on possible values.
61
+ #
62
+ def extract(source_file, dest_dir, options = {})
63
+ Reader.new(source_file, options).extract(dest_dir, options)
64
+ end
65
+
66
+ #
67
+ # Creates a new tar archive in +dest_file+. A list of paths to store in the
68
+ # archive should be passed next. The last argument can optionally be a hash
69
+ # of options that will dictate how the archive is created (see Archive
70
+ # Options, above).
71
+ #
72
+ # Example:
73
+ #
74
+ # Archive::Tar.create(
75
+ # '~/ruby.tar.gz', # Where to put the archive
76
+ # Dir["~/code/**/*.rb"], # What to tar up
77
+ # :compression => :gzip, # Gzip it, to save some space
78
+ # )
79
+ #
80
+ def create(dest_file, filenames = [], options = {})
81
+ Writer.new(filenames).write(dest_file, options)
82
+ end
83
+
84
+ #
85
+ # Cycle through the entries in a tar archive, and pass each entry to a
86
+ # caller-supplied block. This is the easiest way to emulate the <tt>-t</tt>
87
+ # option to the canonical UNIX <tt>tar</tt> utility.
88
+ #
89
+ # Each time +block+ is invoked, it will be passed a symbolic-key hash of the
90
+ # header information (owner, size, last modification time, etc.) and the
91
+ # contents of the file. For all file types except a normal file, contents
92
+ # will be nil.
93
+ #
94
+ # To list the path names of all entries in an archive:
95
+ #
96
+ # Archive::Tar.traverse('ruby.tar.gz', :compression => :gzip) do |header, contents|
97
+ # puts header[:path]
98
+ # end
99
+ #
100
+ def traverse(source_file, options = {}, &block) # :yields: header, contents
101
+ block ||= UNIX_VERBOSE
102
+ Reader.new(source_file, options).each(&block)
103
+ end
104
+ end
105
+ end
106
+
107
+ require 'archive/tar/format'
108
+ require 'archive/tar/reader'
109
+ require 'archive/tar/writer'
110
+ require 'archive/tar/stat'
@@ -0,0 +1,110 @@
1
+ class Archive::Tar::Format # :nodoc:
2
+ DEC_TYPES = {
3
+ '0' => :file,
4
+ "\0" => :file,
5
+ '1' => :link,
6
+ '2' => :symlink,
7
+ '3' => :character,
8
+ '4' => :block,
9
+ '5' => :directory,
10
+ '6' => :fifo,
11
+ '7' => :contiguous
12
+ }
13
+
14
+ ENC_TYPES = {
15
+ :file => '0',
16
+ :link => '1',
17
+ :symlink => '2',
18
+ :character => '3',
19
+ :block => '4',
20
+ :directory => '5',
21
+ :fifo => '6',
22
+ :contiguous => '7'
23
+ }
24
+
25
+
26
+ class << self
27
+ def next_block(io)
28
+ block = ''
29
+ loop do
30
+ block = io.read(512)
31
+ return block unless block == "\0" * 512
32
+ return nil if io.eof?
33
+ end
34
+ end
35
+
36
+ def unpack_header(block)
37
+ h = block.unpack("Z100 Z8 Z8 Z8 Z12 Z12 A8 a Z100 Z6 Z2 Z32 Z32 Z8 Z8 Z155")
38
+ {
39
+ :path => h.shift,
40
+ :mode => h.shift.oct,
41
+ :uid => h.shift.oct,
42
+ :gid => h.shift.oct,
43
+ :size => h.shift.oct,
44
+ :mtime => Time.at(h.shift.oct),
45
+ :cksum => h.shift.oct,
46
+ :type => DEC_TYPES[h.shift],
47
+ :dest => h.shift,
48
+ :ustar => h.shift.strip == 'ustar',
49
+ :uvers => h.shift.strip,
50
+ :user => h.shift,
51
+ :group => h.shift,
52
+ :major => h.shift.oct,
53
+ :minor => h.shift.oct,
54
+ :pre => h.shift
55
+ }
56
+ end
57
+
58
+ def pack_header(h)
59
+ packed = [
60
+ h[:path],
61
+ "%07o" % h[:mode],
62
+ "%07o" % h[:uid],
63
+ "%07o" % h[:gid],
64
+ "%011o" % h[:size],
65
+ "%011o" % h[:mtime].to_i,
66
+ '',
67
+ ENC_TYPES[h[:type]],
68
+ h[:dest],
69
+ h[:ustar] ? "ustar" : '',
70
+ "00",
71
+ h[:user],
72
+ h[:group],
73
+ "%07o" % h[:major],
74
+ "%07o" % h[:minor],
75
+ h[:pre]
76
+ ].pack("a100 a8 a8 a8 a12 a12 A8 a a100 a6 a2 a32 a32 a8 a8 a155 x12")
77
+ packed[148, 7] = "%06o\0" % packed.unpack("C*").inject { |total, byte| total + byte }
78
+ packed
79
+ end
80
+
81
+ def stat(path, inodes)
82
+ lstat = File.lstat(path)
83
+ dest = lstat.symlink? ? File.readlink(path) : inodes[lstat.ino]
84
+
85
+ inodes[lstat.ino] ||= path
86
+
87
+ size = lstat.size
88
+ size = 0 if dest || !lstat.file?
89
+
90
+ {
91
+ :path => path[0,1] == '/' ? path[1, path.size - 1] : path,
92
+ :mode => lstat.mode,
93
+ :uid => lstat.uid,
94
+ :gid => lstat.gid,
95
+ :size => size,
96
+ :mtime => lstat.mtime.to_i,
97
+ :type => (lstat.file? && dest ? :link : lstat.tar_type),
98
+ :dest => dest.to_s,
99
+
100
+ :ustar => true,
101
+ :uvers => "00",
102
+ :user => Etc.getpwuid(lstat.uid).name,
103
+ :group => Etc.getgrgid(lstat.gid).name,
104
+ :major => lstat.dev_major.to_i,
105
+ :minor => lstat.dev_minor.to_i,
106
+ :pre => '', # FIXME: implement
107
+ }
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,67 @@
1
+ require 'zlib'
2
+ require 'fileutils'
3
+
4
+ class Archive::Tar::Reader
5
+ def initialize(source, options = {})
6
+ @source = source
7
+
8
+ @options = {
9
+ :compression => :none
10
+ }.merge(options)
11
+ end
12
+
13
+ def each(full = false, &block)
14
+ case @source
15
+ when IO: parse(@source, full); @source.rewind
16
+ else File.open(@source, 'r') { |f| parse(f, full) }
17
+ end
18
+
19
+ @index.each { |path| yield *(@records[path]) }
20
+ end
21
+
22
+ def extract(dest, options = {})
23
+ options = {
24
+ :permissions => nil
25
+ }.merge(options)
26
+
27
+ raise "No such directory: #{dest}" unless File.directory?(dest)
28
+
29
+ each(true) do |header, body|
30
+ path = File.join(dest, header[:path])
31
+ FileUtils.mkdir_p( File.dirname(path) )
32
+
33
+ case header[:type]
34
+ when :file: File.open(path, 'w') { |fio| fio.write(body) }
35
+ when :directory: FileUtils.mkdir(path)
36
+ when :link: File.link( File.join(dest, header[:dest]), path )
37
+ when :symlink: File.symlink( header[:dest], path )
38
+ end
39
+
40
+ if options[:permissions] == :preserve && !File.symlink?(path)
41
+ File.chmod header[:mode], path
42
+ File.chown header[:uid].to_i, header[:gid].to_i, path
43
+ end
44
+ end
45
+ end
46
+
47
+ def parse(io, full = false)
48
+ @index = []
49
+ @records = {}
50
+
51
+ io = Zlib::GzipReader.new(io) if @options[:compression] == :gzip
52
+
53
+ until io.eof?
54
+ hblock = Archive::Tar::Format.next_block(io)
55
+ break unless hblock
56
+
57
+ header = Archive::Tar::Format.unpack_header(hblock)
58
+ path = header[:path]
59
+ size = header[:size]
60
+
61
+ @index << path
62
+ @records[path] = [ header, io.read(size % 512 == 0 ? size : size + (512 - size % 512)) ]
63
+ @records[path][1] = nil unless full
64
+ end
65
+ end
66
+ private :parse
67
+ end
@@ -0,0 +1,16 @@
1
+ module Archive # :nodoc:
2
+ module Tar
3
+ module Stat # :nodoc:
4
+ def tar_type
5
+ case self.ftype
6
+ when 'file': :file
7
+ when 'directory': :directory
8
+ when 'blockSpecial': :block
9
+ when 'characterSpecial': :character
10
+ when 'link': :symlink
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ File::Stat.send(:include, Archive::Tar::Stat)
@@ -0,0 +1,51 @@
1
+ require 'etc'
2
+ require 'zlib'
3
+
4
+ class Archive::Tar::Writer
5
+ def initialize(filenames)
6
+ @inodes = {}
7
+ @records = {}
8
+ @index = []
9
+ filenames.each { |path| self << path }
10
+ end
11
+
12
+ def <<(filename)
13
+ @index << filename
14
+ @records[filename] = Archive::Tar::Format.stat(filename, @inodes)
15
+ if File.directory?(filename)
16
+ Dir.open(filename) do |dh|
17
+ dh.entries.each do |file|
18
+ self << File.join(filename, file) unless [".",".."].include? file
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def write(dest, options = {})
25
+ if dest.is_a? IO
26
+ write_to(dest, options)
27
+ else
28
+ File.open(dest, 'w') { |io| write_to(io, options) }
29
+ end
30
+ end
31
+
32
+ def write_to(io, options = {})
33
+ options = {
34
+ :compression => :none
35
+ }.merge(options)
36
+
37
+ io = Zlib::GzipWriter.new(io) if options[:compression] == :gzip
38
+
39
+ @index.each do |path|
40
+ stat = @records[path]
41
+ io.write Archive::Tar::Format.pack_header(stat)
42
+
43
+ File.open(path, 'r') do |fio|
44
+ io.write [fio.read(512)].pack("Z512") until fio.eof?
45
+ end if stat[:dest].empty?
46
+ end
47
+ io.write("\0" * 1024)
48
+ io.close
49
+ end
50
+ private :write_to
51
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: archive-tar
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - James R Hunt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-04 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: james@niftylogic.net
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/archive/tar.rb
26
+ - lib/archive/tar/reader.rb
27
+ - lib/archive/tar/writer.rb
28
+ - lib/archive/tar/format.rb
29
+ - lib/archive/tar/stat.rb
30
+ has_rdoc: true
31
+ homepage: http://gems.niftylogic.net/activedirectory
32
+ post_install_message:
33
+ rdoc_options: []
34
+
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: "0"
42
+ version:
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ requirements: []
50
+
51
+ rubyforge_project: archive-tar
52
+ rubygems_version: 1.2.0
53
+ signing_key:
54
+ specification_version: 2
55
+ summary: An interface library for reading and writing UNIX tar files.
56
+ test_files: []
57
+