ruby_archive 0.1.2

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