gem 0.0.1.alpha

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,7 @@
1
+ module Gem::Tar
2
+ end
3
+
4
+ require 'gem/tar/header'
5
+ require 'gem/tar/entry'
6
+ require 'gem/tar/reader'
7
+ require 'gem/tar/writer'
@@ -0,0 +1,145 @@
1
+ # -*- coding: utf-8 -*-
2
+ #++
3
+ # Copyright (C) 2004 Mauricio Julio Fernández Pradier
4
+ # See LICENSE.txt for additional licensing information.
5
+ #--
6
+
7
+ ##
8
+ # Class for reading entries out of a tar file
9
+
10
+ class Gem::Tar::Entry
11
+
12
+ ##
13
+ # Header for this tar entry
14
+
15
+ attr_reader :header
16
+
17
+ ##
18
+ # Creates a new tar entry for +header+ that will be read from +io+
19
+
20
+ def initialize(header, io)
21
+ @closed = false
22
+ @header = header
23
+ @io = io
24
+ @orig_pos = @io.pos
25
+ @read = 0
26
+ end
27
+
28
+ def check_closed # :nodoc:
29
+ raise IOError, "closed #{self.class}" if closed?
30
+ end
31
+
32
+ ##
33
+ # Number of bytes read out of the tar entry
34
+
35
+ def bytes_read
36
+ @read
37
+ end
38
+
39
+ ##
40
+ # Closes the tar entry
41
+
42
+ def close
43
+ @closed = true
44
+ end
45
+
46
+ ##
47
+ # Is the tar entry closed?
48
+
49
+ def closed?
50
+ @closed
51
+ end
52
+
53
+ ##
54
+ # Are we at the end of the tar entry?
55
+
56
+ def eof?
57
+ check_closed
58
+
59
+ @read >= @header.size
60
+ end
61
+
62
+ ##
63
+ # Full name of the tar entry
64
+
65
+ def full_name
66
+ if @header.prefix != "" then
67
+ File.join @header.prefix, @header.name
68
+ else
69
+ @header.name
70
+ end
71
+ rescue ArgumentError => e
72
+ raise unless e.message == 'string contains null byte'
73
+ raise InvalidError,
74
+ 'tar is corrupt, name contains null byte'
75
+ end
76
+
77
+ ##
78
+ # Read one byte from the tar entry
79
+
80
+ def getc
81
+ check_closed
82
+
83
+ return nil if @read >= @header.size
84
+
85
+ ret = @io.getc
86
+ @read += 1 if ret
87
+
88
+ ret
89
+ end
90
+
91
+ ##
92
+ # Is this tar entry a directory?
93
+
94
+ def directory?
95
+ @header.typeflag == "5"
96
+ end
97
+
98
+ ##
99
+ # Is this tar entry a file?
100
+
101
+ def file?
102
+ @header.typeflag == "0"
103
+ end
104
+
105
+ ##
106
+ # The position in the tar entry
107
+
108
+ def pos
109
+ check_closed
110
+
111
+ bytes_read
112
+ end
113
+
114
+ ##
115
+ # Reads +len+ bytes from the tar file entry, or the rest of the entry if
116
+ # nil
117
+
118
+ def read(len = nil)
119
+ check_closed
120
+
121
+ return nil if @read >= @header.size
122
+
123
+ len ||= @header.size - @read
124
+ max_read = [len, @header.size - @read].min
125
+
126
+ ret = @io.read max_read
127
+ @read += ret.size
128
+
129
+ ret
130
+ end
131
+
132
+ ##
133
+ # Rewinds to the beginning of the tar file entry
134
+
135
+ def rewind
136
+ check_closed
137
+
138
+ raise NonSeekableIO unless @io.respond_to? :pos=
139
+
140
+ @io.pos = @orig_pos
141
+ @read = 0
142
+ end
143
+
144
+ end
145
+
@@ -0,0 +1,266 @@
1
+ # -*- coding: utf-8 -*-
2
+ #--
3
+ # Copyright (C) 2004 Mauricio Julio Fernández Pradier
4
+ # See LICENSE.txt for additional licensing information.
5
+ #++
6
+
7
+ ##
8
+ #--
9
+ # struct tarfile_entry_posix {
10
+ # char name[100]; # ASCII + (Z unless filled)
11
+ # char mode[8]; # 0 padded, octal, null
12
+ # char uid[8]; # ditto
13
+ # char gid[8]; # ditto
14
+ # char size[12]; # 0 padded, octal, null
15
+ # char mtime[12]; # 0 padded, octal, null
16
+ # char checksum[8]; # 0 padded, octal, null, space
17
+ # char typeflag[1]; # file: "0" dir: "5"
18
+ # char linkname[100]; # ASCII + (Z unless filled)
19
+ # char magic[6]; # "ustar\0"
20
+ # char version[2]; # "00"
21
+ # char uname[32]; # ASCIIZ
22
+ # char gname[32]; # ASCIIZ
23
+ # char devmajor[8]; # 0 padded, octal, null
24
+ # char devminor[8]; # o padded, octal, null
25
+ # char prefix[155]; # ASCII + (Z unless filled)
26
+ # };
27
+ #++
28
+ # A header for a tar file
29
+
30
+ class Gem::Tar::Header
31
+
32
+ ##
33
+ # Fields in the tar header
34
+
35
+ FIELDS = [
36
+ :checksum,
37
+ :devmajor,
38
+ :devminor,
39
+ :gid,
40
+ :gname,
41
+ :linkname,
42
+ :magic,
43
+ :mode,
44
+ :mtime,
45
+ :name,
46
+ :prefix,
47
+ :size,
48
+ :typeflag,
49
+ :uid,
50
+ :uname,
51
+ :version,
52
+ ]
53
+
54
+ ##
55
+ # Pack format for a tar header
56
+
57
+ PACK_FORMAT = 'a100' + # name
58
+ 'a8' + # mode
59
+ 'a8' + # uid
60
+ 'a8' + # gid
61
+ 'a12' + # size
62
+ 'a12' + # mtime
63
+ 'a7a' + # chksum
64
+ 'a' + # typeflag
65
+ 'a100' + # linkname
66
+ 'a6' + # magic
67
+ 'a2' + # version
68
+ 'a32' + # uname
69
+ 'a32' + # gname
70
+ 'a8' + # devmajor
71
+ 'a8' + # devminor
72
+ 'a155' # prefix
73
+
74
+ ##
75
+ # Unpack format for a tar header
76
+
77
+ UNPACK_FORMAT = 'A100' + # name
78
+ 'A8' + # mode
79
+ 'A8' + # uid
80
+ 'A8' + # gid
81
+ 'A12' + # size
82
+ 'A12' + # mtime
83
+ 'A8' + # checksum
84
+ 'A' + # typeflag
85
+ 'A100' + # linkname
86
+ 'A6' + # magic
87
+ 'A2' + # version
88
+ 'A32' + # uname
89
+ 'A32' + # gname
90
+ 'A8' + # devmajor
91
+ 'A8' + # devminor
92
+ 'A155' # prefix
93
+
94
+ attr_reader(*FIELDS)
95
+
96
+ ##
97
+ # Creates a tar header from IO +stream+
98
+
99
+ def self.from(stream)
100
+ header = stream.read 512
101
+ empty = (header == "\0" * 512)
102
+
103
+ fields = header.unpack UNPACK_FORMAT
104
+
105
+ name = fields.shift
106
+ mode = fields.shift.oct
107
+ uid = fields.shift.oct
108
+ gid = fields.shift.oct
109
+ size = fields.shift.oct
110
+ mtime = fields.shift.oct
111
+ checksum = fields.shift.oct
112
+ typeflag = fields.shift
113
+ linkname = fields.shift
114
+ magic = fields.shift
115
+ version = fields.shift.oct
116
+ uname = fields.shift
117
+ gname = fields.shift
118
+ devmajor = fields.shift.oct
119
+ devminor = fields.shift.oct
120
+ prefix = fields.shift
121
+
122
+ new :name => name,
123
+ :mode => mode,
124
+ :uid => uid,
125
+ :gid => gid,
126
+ :size => size,
127
+ :mtime => mtime,
128
+ :checksum => checksum,
129
+ :typeflag => typeflag,
130
+ :linkname => linkname,
131
+ :magic => magic,
132
+ :version => version,
133
+ :uname => uname,
134
+ :gname => gname,
135
+ :devmajor => devmajor,
136
+ :devminor => devminor,
137
+ :prefix => prefix,
138
+
139
+ :empty => empty
140
+
141
+ # HACK unfactor for Rubinius
142
+ #new :name => fields.shift,
143
+ # :mode => fields.shift.oct,
144
+ # :uid => fields.shift.oct,
145
+ # :gid => fields.shift.oct,
146
+ # :size => fields.shift.oct,
147
+ # :mtime => fields.shift.oct,
148
+ # :checksum => fields.shift.oct,
149
+ # :typeflag => fields.shift,
150
+ # :linkname => fields.shift,
151
+ # :magic => fields.shift,
152
+ # :version => fields.shift.oct,
153
+ # :uname => fields.shift,
154
+ # :gname => fields.shift,
155
+ # :devmajor => fields.shift.oct,
156
+ # :devminor => fields.shift.oct,
157
+ # :prefix => fields.shift,
158
+
159
+ # :empty => empty
160
+ end
161
+
162
+ ##
163
+ # Creates a new TarHeader using +vals+
164
+
165
+ def initialize(vals)
166
+ unless vals[:name] && vals[:size] && vals[:prefix] && vals[:mode] then
167
+ raise ArgumentError, ":name, :size, :prefix and :mode required"
168
+ end
169
+
170
+ vals[:uid] ||= 0
171
+ vals[:gid] ||= 0
172
+ vals[:mtime] ||= 0
173
+ vals[:checksum] ||= ""
174
+ vals[:typeflag] ||= "0"
175
+ vals[:magic] ||= "ustar"
176
+ vals[:version] ||= "00"
177
+ vals[:uname] ||= "wheel"
178
+ vals[:gname] ||= "wheel"
179
+ vals[:devmajor] ||= 0
180
+ vals[:devminor] ||= 0
181
+
182
+ FIELDS.each do |name|
183
+ instance_variable_set "@#{name}", vals[name]
184
+ end
185
+
186
+ @empty = vals[:empty]
187
+ end
188
+
189
+ ##
190
+ # Is the tar entry empty?
191
+
192
+ def empty?
193
+ @empty
194
+ end
195
+
196
+ def ==(other) # :nodoc:
197
+ self.class === other and
198
+ @checksum == other.checksum and
199
+ @devmajor == other.devmajor and
200
+ @devminor == other.devminor and
201
+ @gid == other.gid and
202
+ @gname == other.gname and
203
+ @linkname == other.linkname and
204
+ @magic == other.magic and
205
+ @mode == other.mode and
206
+ @mtime == other.mtime and
207
+ @name == other.name and
208
+ @prefix == other.prefix and
209
+ @size == other.size and
210
+ @typeflag == other.typeflag and
211
+ @uid == other.uid and
212
+ @uname == other.uname and
213
+ @version == other.version
214
+ end
215
+
216
+ def to_s # :nodoc:
217
+ update_checksum
218
+ header
219
+ end
220
+
221
+ ##
222
+ # Updates the TarHeader's checksum
223
+
224
+ def update_checksum
225
+ header = header " " * 8
226
+ @checksum = oct calculate_checksum(header), 6
227
+ end
228
+
229
+ private
230
+
231
+ def calculate_checksum(header)
232
+ header.unpack("C*").inject { |a, b| a + b }
233
+ end
234
+
235
+ def header(checksum = @checksum)
236
+ header = [
237
+ name,
238
+ oct(mode, 7),
239
+ oct(uid, 7),
240
+ oct(gid, 7),
241
+ oct(size, 11),
242
+ oct(mtime, 11),
243
+ checksum,
244
+ " ",
245
+ typeflag,
246
+ linkname,
247
+ magic,
248
+ oct(version, 2),
249
+ uname,
250
+ gname,
251
+ oct(devmajor, 7),
252
+ oct(devminor, 7),
253
+ prefix
254
+ ]
255
+
256
+ header = header.pack PACK_FORMAT
257
+
258
+ header << ("\0" * ((512 - header.size) % 512))
259
+ end
260
+
261
+ def oct(num, len)
262
+ "%0#{len}o" % num
263
+ end
264
+
265
+ end
266
+
@@ -0,0 +1,101 @@
1
+ # -*- coding: utf-8 -*-
2
+ #--
3
+ # Copyright (C) 2004 Mauricio Julio Fernández Pradier
4
+ # See LICENSE.txt for additional licensing information.
5
+ #++
6
+
7
+ ##
8
+ # TarReader reads tar files and allows iteration over their items
9
+
10
+ class Gem::Tar::Reader
11
+
12
+ ##
13
+ # Raised if the tar IO is not seekable
14
+
15
+ class UnexpectedEOF < StandardError; end
16
+
17
+ ##
18
+ # Creates a new TarReader on +io+ and yields it to the block, if given.
19
+
20
+ def self.new(io)
21
+ reader = super
22
+
23
+ return reader unless block_given?
24
+
25
+ begin
26
+ yield reader
27
+ ensure
28
+ reader.close
29
+ end
30
+
31
+ nil
32
+ end
33
+
34
+ ##
35
+ # Creates a new tar file reader on +io+ which needs to respond to #pos,
36
+ # #eof?, #read, #getc and #pos=
37
+
38
+ def initialize(io)
39
+ @io = io
40
+ @init_pos = io.pos
41
+ end
42
+
43
+ ##
44
+ # Close the tar file
45
+
46
+ def close
47
+ end
48
+
49
+ ##
50
+ # Iterates over files in the tarball yielding each entry
51
+
52
+ def each
53
+ loop do
54
+ return if @io.eof?
55
+
56
+ header = Gem::Tar::Header.from @io
57
+ return if header.empty?
58
+
59
+ entry = Gem::Tar::Entry.new header, @io
60
+ size = entry.header.size
61
+
62
+ yield entry
63
+
64
+ skip = (512 - (size % 512)) % 512
65
+ pending = size - entry.bytes_read
66
+
67
+ begin
68
+ # avoid reading...
69
+ @io.seek pending, IO::SEEK_CUR
70
+ pending = 0
71
+ rescue Errno::EINVAL, NameError
72
+ while pending > 0 do
73
+ bytes_read = @io.read([pending, 4096].min).size
74
+ raise UnexpectedEOF if @io.eof?
75
+ pending -= bytes_read
76
+ end
77
+ end
78
+
79
+ @io.read skip # discard trailing zeros
80
+
81
+ # make sure nobody can use #read, #getc or #rewind anymore
82
+ entry.close
83
+ end
84
+ end
85
+
86
+ alias each_entry each
87
+
88
+ ##
89
+ # NOTE: Do not call #rewind during #each
90
+
91
+ def rewind
92
+ if @init_pos == 0 then
93
+ raise NonSeekableIO unless @io.respond_to? :rewind
94
+ @io.rewind
95
+ else
96
+ raise NonSeekableIO unless @io.respond_to? :pos=
97
+ @io.pos = @init_pos
98
+ end
99
+ end
100
+
101
+ end