superp-rubyzip 0.1.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,622 @@
1
+ require 'zip'
2
+
3
+ module Zip
4
+
5
+ # The ZipFileSystem API provides an API for accessing entries in
6
+ # a zip archive that is similar to ruby's builtin File and Dir
7
+ # classes.
8
+ #
9
+ # Requiring 'zip/zipfilesystem' includes this module in ZipFile
10
+ # making the methods in this module available on ZipFile objects.
11
+ #
12
+ # Using this API the following example creates a new zip file
13
+ # <code>my.zip</code> containing a normal entry with the name
14
+ # <code>first.txt</code>, a directory entry named <code>mydir</code>
15
+ # and finally another normal entry named <code>second.txt</code>
16
+ #
17
+ # require 'zip/filesystem'
18
+ #
19
+ # Zip::File.open("my.zip", Zip::ZipFile::CREATE) {
20
+ # |zipfile|
21
+ # zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" }
22
+ # zipfile.dir.mkdir("mydir")
23
+ # zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" }
24
+ # }
25
+ #
26
+ # Reading is as easy as writing, as the following example shows. The
27
+ # example writes the contents of <code>first.txt</code> from zip archive
28
+ # <code>my.zip</code> to standard out.
29
+ #
30
+ # require 'zip/filesystem'
31
+ #
32
+ # Zip::File.open("my.zip") {
33
+ # |zipfile|
34
+ # puts zipfile.file.read("first.txt")
35
+ # }
36
+
37
+ module FileSystem
38
+
39
+ def initialize # :nodoc:
40
+ mappedZip = ZipFileNameMapper.new(self)
41
+ @zipFsDir = ZipFsDir.new(mappedZip)
42
+ @zipFsFile = ZipFsFile.new(mappedZip)
43
+ @zipFsDir.file = @zipFsFile
44
+ @zipFsFile.dir = @zipFsDir
45
+ end
46
+
47
+ # Returns a ZipFsDir which is much like ruby's builtin Dir (class)
48
+ # object, except it works on the ZipFile on which this method is
49
+ # invoked
50
+ def dir
51
+ @zipFsDir
52
+ end
53
+
54
+ # Returns a ZipFsFile which is much like ruby's builtin File (class)
55
+ # object, except it works on the ZipFile on which this method is
56
+ # invoked
57
+ def file
58
+ @zipFsFile
59
+ end
60
+
61
+ # Instances of this class are normally accessed via the accessor
62
+ # ZipFile::file. An instance of ZipFsFile behaves like ruby's
63
+ # builtin File (class) object, except it works on ZipFile entries.
64
+ #
65
+ # The individual methods are not documented due to their
66
+ # similarity with the methods in File
67
+ class ZipFsFile
68
+
69
+ attr_writer :dir
70
+ # protected :dir
71
+
72
+ class ZipFsStat
73
+
74
+ class << self
75
+
76
+ def delegate_to_fs_file(*methods)
77
+ methods.each do |method|
78
+ self.class_eval <<-end_eval, __FILE__, __LINE__ + 1
79
+ def #{method} # def file?
80
+ @zipFsFile.#{method}(@entryName) # @zipFsFile.file?(@entryName)
81
+ end # end
82
+ end_eval
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ def initialize(zipFsFile, entryName)
89
+ @zipFsFile = zipFsFile
90
+ @entryName = entryName
91
+ end
92
+
93
+ def kind_of?(t)
94
+ super || t == ::File::Stat
95
+ end
96
+
97
+ delegate_to_fs_file :file?, :directory?, :pipe?, :chardev?, :symlink?,
98
+ :socket?, :blockdev?, :readable?, :readable_real?, :writable?, :ctime,
99
+ :writable_real?, :executable?, :executable_real?, :sticky?, :owned?,
100
+ :grpowned?, :setuid?, :setgid?, :zero?, :size, :size?, :mtime, :atime
101
+
102
+ def blocks; nil; end
103
+
104
+ def get_entry
105
+ @zipFsFile.__send__(:get_entry, @entryName)
106
+ end
107
+ private :get_entry
108
+
109
+ def gid
110
+ e = get_entry
111
+ if e.extra.member? "IUnix"
112
+ e.extra["IUnix"].gid || 0
113
+ else
114
+ 0
115
+ end
116
+ end
117
+
118
+ def uid
119
+ e = get_entry
120
+ if e.extra.member? "IUnix"
121
+ e.extra["IUnix"].uid || 0
122
+ else
123
+ 0
124
+ end
125
+ end
126
+
127
+ def ino; 0; end
128
+
129
+ def dev; 0; end
130
+
131
+ def rdev; 0; end
132
+
133
+ def rdev_major; 0; end
134
+
135
+ def rdev_minor; 0; end
136
+
137
+ def ftype
138
+ if file?
139
+ return "file"
140
+ elsif directory?
141
+ return "directory"
142
+ else
143
+ raise StandardError, "Unknown file type"
144
+ end
145
+ end
146
+
147
+ def nlink; 1; end
148
+
149
+ def blksize; nil; end
150
+
151
+ def mode
152
+ e = get_entry
153
+ if e.fstype == 3
154
+ e.external_file_attributes >> 16
155
+ else
156
+ 33206 # 33206 is equivalent to -rw-rw-rw-
157
+ end
158
+ end
159
+ end
160
+
161
+ def initialize(mappedZip)
162
+ @mappedZip = mappedZip
163
+ end
164
+
165
+ def get_entry(fileName)
166
+ if ! exists?(fileName)
167
+ raise Errno::ENOENT, "No such file or directory - #{fileName}"
168
+ end
169
+ @mappedZip.find_entry(fileName)
170
+ end
171
+ private :get_entry
172
+
173
+ def unix_mode_cmp(fileName, mode)
174
+ begin
175
+ e = get_entry(fileName)
176
+ e.fstype == 3 && ((e.external_file_attributes >> 16) & mode ) != 0
177
+ rescue Errno::ENOENT
178
+ false
179
+ end
180
+ end
181
+ private :unix_mode_cmp
182
+
183
+ def exists?(fileName)
184
+ expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil
185
+ end
186
+ alias :exist? :exists?
187
+
188
+ # Permissions not implemented, so if the file exists it is accessible
189
+ alias owned? exists?
190
+ alias grpowned? exists?
191
+
192
+ def readable?(fileName)
193
+ unix_mode_cmp(fileName, 0444)
194
+ end
195
+ alias readable_real? readable?
196
+
197
+ def writable?(fileName)
198
+ unix_mode_cmp(fileName, 0222)
199
+ end
200
+ alias writable_real? writable?
201
+
202
+ def executable?(fileName)
203
+ unix_mode_cmp(fileName, 0111)
204
+ end
205
+ alias executable_real? executable?
206
+
207
+ def setuid?(fileName)
208
+ unix_mode_cmp(fileName, 04000)
209
+ end
210
+
211
+ def setgid?(fileName)
212
+ unix_mode_cmp(fileName, 02000)
213
+ end
214
+
215
+ def sticky?(fileName)
216
+ unix_mode_cmp(fileName, 01000)
217
+ end
218
+
219
+ def umask(*args)
220
+ ::File.umask(*args)
221
+ end
222
+
223
+ def truncate(fileName, len)
224
+ raise StandardError, "truncate not supported"
225
+ end
226
+
227
+ def directory?(fileName)
228
+ entry = @mappedZip.find_entry(fileName)
229
+ expand_path(fileName) == "/" || (entry != nil && entry.directory?)
230
+ end
231
+
232
+ def open(fileName, openMode = "r", permissionInt = 0644, &block)
233
+ openMode.gsub!("b", "") # ignore b option
234
+ case openMode
235
+ when "r"
236
+ @mappedZip.get_input_stream(fileName, &block)
237
+ when "w"
238
+ @mappedZip.get_output_stream(fileName, permissionInt, &block)
239
+ else
240
+ raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r"
241
+ end
242
+ end
243
+
244
+ def new(fileName, openMode = "r")
245
+ open(fileName, openMode)
246
+ end
247
+
248
+ def size(fileName)
249
+ @mappedZip.get_entry(fileName).size
250
+ end
251
+
252
+ # Returns nil for not found and nil for directories
253
+ def size?(fileName)
254
+ entry = @mappedZip.find_entry(fileName)
255
+ return (entry == nil || entry.directory?) ? nil : entry.size
256
+ end
257
+
258
+ def chown(ownerInt, groupInt, *filenames)
259
+ filenames.each { |fileName|
260
+ e = get_entry(fileName)
261
+ unless e.extra.member?("IUnix")
262
+ e.extra.create("IUnix")
263
+ end
264
+ e.extra["IUnix"].uid = ownerInt
265
+ e.extra["IUnix"].gid = groupInt
266
+ }
267
+ filenames.size
268
+ end
269
+
270
+ def chmod (modeInt, *filenames)
271
+ filenames.each { |fileName|
272
+ e = get_entry(fileName)
273
+ e.fstype = 3 # force convertion filesystem type to unix
274
+ e.unix_perms = modeInt
275
+ e.external_file_attributes = modeInt << 16
276
+ e.dirty = true
277
+ }
278
+ filenames.size
279
+ end
280
+
281
+ def zero?(fileName)
282
+ sz = size(fileName)
283
+ sz == nil || sz == 0
284
+ rescue Errno::ENOENT
285
+ false
286
+ end
287
+
288
+ def file?(fileName)
289
+ entry = @mappedZip.find_entry(fileName)
290
+ entry != nil && entry.file?
291
+ end
292
+
293
+ def dirname(fileName)
294
+ ::File.dirname(fileName)
295
+ end
296
+
297
+ def basename(fileName)
298
+ ::File.basename(fileName)
299
+ end
300
+
301
+ def split(fileName)
302
+ ::File.split(fileName)
303
+ end
304
+
305
+ def join(*fragments)
306
+ ::File.join(*fragments)
307
+ end
308
+
309
+ def utime(modifiedTime, *fileNames)
310
+ fileNames.each { |fileName|
311
+ get_entry(fileName).time = modifiedTime
312
+ }
313
+ end
314
+
315
+ def mtime(fileName)
316
+ @mappedZip.get_entry(fileName).mtime
317
+ end
318
+
319
+ def atime(fileName)
320
+ e = get_entry(fileName)
321
+ if e.extra.member? "UniversalTime"
322
+ e.extra["UniversalTime"].atime
323
+ else
324
+ nil
325
+ end
326
+ end
327
+
328
+ def ctime(fileName)
329
+ e = get_entry(fileName)
330
+ if e.extra.member? "UniversalTime"
331
+ e.extra["UniversalTime"].ctime
332
+ else
333
+ nil
334
+ end
335
+ end
336
+
337
+ def pipe?(filename)
338
+ false
339
+ end
340
+
341
+ def blockdev?(filename)
342
+ false
343
+ end
344
+
345
+ def chardev?(filename)
346
+ false
347
+ end
348
+
349
+ def symlink?(fileName)
350
+ false
351
+ end
352
+
353
+ def socket?(fileName)
354
+ false
355
+ end
356
+
357
+ def ftype(fileName)
358
+ @mappedZip.get_entry(fileName).directory? ? "directory" : "file"
359
+ end
360
+
361
+ def readlink(fileName)
362
+ raise NotImplementedError, "The readlink() function is not implemented"
363
+ end
364
+
365
+ def symlink(fileName, symlinkName)
366
+ raise NotImplementedError, "The symlink() function is not implemented"
367
+ end
368
+
369
+ def link(fileName, symlinkName)
370
+ raise NotImplementedError, "The link() function is not implemented"
371
+ end
372
+
373
+ def pipe
374
+ raise NotImplementedError, "The pipe() function is not implemented"
375
+ end
376
+
377
+ def stat(fileName)
378
+ if ! exists?(fileName)
379
+ raise Errno::ENOENT, fileName
380
+ end
381
+ ZipFsStat.new(self, fileName)
382
+ end
383
+
384
+ alias lstat stat
385
+
386
+ def readlines(fileName)
387
+ open(fileName) { |is| is.readlines }
388
+ end
389
+
390
+ def read(fileName)
391
+ @mappedZip.read(fileName)
392
+ end
393
+
394
+ def popen(*args, &aProc)
395
+ ::File.popen(*args, &aProc)
396
+ end
397
+
398
+ def foreach(fileName, aSep = $/, &aProc)
399
+ open(fileName) { |is| is.each_line(aSep, &aProc) }
400
+ end
401
+
402
+ def delete(*args)
403
+ args.each {
404
+ |fileName|
405
+ if directory?(fileName)
406
+ raise Errno::EISDIR, "Is a directory - \"#{fileName}\""
407
+ end
408
+ @mappedZip.remove(fileName)
409
+ }
410
+ end
411
+
412
+ def rename(fileToRename, newName)
413
+ @mappedZip.rename(fileToRename, newName) { true }
414
+ end
415
+
416
+ alias :unlink :delete
417
+
418
+ def expand_path(aPath)
419
+ @mappedZip.expand_path(aPath)
420
+ end
421
+ end
422
+
423
+ # Instances of this class are normally accessed via the accessor
424
+ # ZipFile::dir. An instance of ZipFsDir behaves like ruby's
425
+ # builtin Dir (class) object, except it works on ZipFile entries.
426
+ #
427
+ # The individual methods are not documented due to their
428
+ # similarity with the methods in Dir
429
+ class ZipFsDir
430
+
431
+ def initialize(mappedZip)
432
+ @mappedZip = mappedZip
433
+ end
434
+
435
+ attr_writer :file
436
+
437
+ def new(aDirectoryName)
438
+ ZipFsDirIterator.new(entries(aDirectoryName))
439
+ end
440
+
441
+ def open(aDirectoryName)
442
+ dirIt = new(aDirectoryName)
443
+ if block_given?
444
+ begin
445
+ yield(dirIt)
446
+ return nil
447
+ ensure
448
+ dirIt.close
449
+ end
450
+ end
451
+ dirIt
452
+ end
453
+
454
+ def pwd; @mappedZip.pwd; end
455
+ alias getwd pwd
456
+
457
+ def chdir(aDirectoryName)
458
+ unless @file.stat(aDirectoryName).directory?
459
+ raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}"
460
+ end
461
+ @mappedZip.pwd = @file.expand_path(aDirectoryName)
462
+ end
463
+
464
+ def entries(aDirectoryName)
465
+ entries = []
466
+ foreach(aDirectoryName) { |e| entries << e }
467
+ entries
468
+ end
469
+
470
+ def glob(*args,&block)
471
+ @mappedZip.glob(*args,&block)
472
+ end
473
+
474
+ def foreach(aDirectoryName)
475
+ unless @file.stat(aDirectoryName).directory?
476
+ raise Errno::ENOTDIR, aDirectoryName
477
+ end
478
+ path = @file.expand_path(aDirectoryName)
479
+ path << '/' unless path.end_with?('/')
480
+ path = Regexp.escape(path)
481
+ subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$")
482
+ @mappedZip.each {
483
+ |fileName|
484
+ match = subDirEntriesRegex.match(fileName)
485
+ yield(match[1]) unless match == nil
486
+ }
487
+ end
488
+
489
+ def delete(entryName)
490
+ unless @file.stat(entryName).directory?
491
+ raise Errno::EINVAL, "Invalid argument - #{entryName}"
492
+ end
493
+ @mappedZip.remove(entryName)
494
+ end
495
+ alias rmdir delete
496
+ alias unlink delete
497
+
498
+ def mkdir(entryName, permissionInt = 0755)
499
+ @mappedZip.mkdir(entryName, permissionInt)
500
+ end
501
+
502
+ def chroot(*args)
503
+ raise NotImplementedError, "The chroot() function is not implemented"
504
+ end
505
+
506
+ end
507
+
508
+ class ZipFsDirIterator # :nodoc:all
509
+ include Enumerable
510
+
511
+ def initialize(arrayOfFileNames)
512
+ @fileNames = arrayOfFileNames
513
+ @index = 0
514
+ end
515
+
516
+ def close
517
+ @fileNames = nil
518
+ end
519
+
520
+ def each(&aProc)
521
+ raise IOError, "closed directory" if @fileNames == nil
522
+ @fileNames.each(&aProc)
523
+ end
524
+
525
+ def read
526
+ raise IOError, "closed directory" if @fileNames == nil
527
+ @fileNames[(@index+=1)-1]
528
+ end
529
+
530
+ def rewind
531
+ raise IOError, "closed directory" if @fileNames == nil
532
+ @index = 0
533
+ end
534
+
535
+ def seek(anIntegerPosition)
536
+ raise IOError, "closed directory" if @fileNames == nil
537
+ @index = anIntegerPosition
538
+ end
539
+
540
+ def tell
541
+ raise IOError, "closed directory" if @fileNames == nil
542
+ @index
543
+ end
544
+ end
545
+
546
+ # All access to ZipFile from ZipFsFile and ZipFsDir goes through a
547
+ # ZipFileNameMapper, which has one responsibility: ensure
548
+ class ZipFileNameMapper # :nodoc:all
549
+ include Enumerable
550
+
551
+ def initialize(zipFile)
552
+ @zipFile = zipFile
553
+ @pwd = "/"
554
+ end
555
+
556
+ attr_accessor :pwd
557
+
558
+ def find_entry(fileName)
559
+ @zipFile.find_entry(expand_to_entry(fileName))
560
+ end
561
+
562
+ def get_entry(fileName)
563
+ @zipFile.get_entry(expand_to_entry(fileName))
564
+ end
565
+
566
+ def get_input_stream(fileName, &aProc)
567
+ @zipFile.get_input_stream(expand_to_entry(fileName), &aProc)
568
+ end
569
+
570
+ def get_output_stream(fileName, permissionInt = nil, &aProc)
571
+ @zipFile.get_output_stream(expand_to_entry(fileName), permissionInt, &aProc)
572
+ end
573
+
574
+ def read(fileName)
575
+ @zipFile.read(expand_to_entry(fileName))
576
+ end
577
+
578
+ def remove(fileName)
579
+ @zipFile.remove(expand_to_entry(fileName))
580
+ end
581
+
582
+ def rename(fileName, newName, &continueOnExistsProc)
583
+ @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName),
584
+ &continueOnExistsProc)
585
+ end
586
+
587
+ def mkdir(fileName, permissionInt = 0755)
588
+ @zipFile.mkdir(expand_to_entry(fileName), permissionInt)
589
+ end
590
+
591
+ # Turns entries into strings and adds leading /
592
+ # and removes trailing slash on directories
593
+ def each
594
+ @zipFile.each {
595
+ |e|
596
+ yield("/"+e.to_s.chomp("/"))
597
+ }
598
+ end
599
+
600
+ def expand_path(aPath)
601
+ expanded = aPath.start_with?("/") ? aPath : ::File.join(@pwd, aPath)
602
+ expanded.gsub!(/\/\.(\/|$)/, "")
603
+ expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "")
604
+ expanded.empty? ? "/" : expanded
605
+ end
606
+
607
+ private
608
+
609
+ def expand_to_entry(aPath)
610
+ expand_path(aPath)[1..-1]
611
+ end
612
+ end
613
+ end
614
+
615
+ class File
616
+ include FileSystem
617
+ end
618
+ end
619
+
620
+ # Copyright (C) 2002, 2003 Thomas Sondergaard
621
+ # rubyzip is free software; you can redistribute it and/or
622
+ # modify it under the terms of the ruby license.