zippo 0.2.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.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +83 -0
- data/Rakefile +11 -0
- data/lib/zippo/binary_structure/base.rb +119 -0
- data/lib/zippo/binary_structure/binary_packer.rb +17 -0
- data/lib/zippo/binary_structure/binary_unpacker.rb +32 -0
- data/lib/zippo/binary_structure/meta.rb +146 -0
- data/lib/zippo/binary_structure/structure.rb +24 -0
- data/lib/zippo/binary_structure/structure_member.rb +31 -0
- data/lib/zippo/binary_structure.rb +6 -0
- data/lib/zippo/cd_file_header.rb +36 -0
- data/lib/zippo/central_directory_entries_unpacker.rb +23 -0
- data/lib/zippo/central_directory_reader.rb +44 -0
- data/lib/zippo/end_cd_record.rb +21 -0
- data/lib/zippo/filter/base.rb +29 -0
- data/lib/zippo/filter/compressor/deflate.rb +23 -0
- data/lib/zippo/filter/compressor/store.rb +12 -0
- data/lib/zippo/filter/compressor.rb +42 -0
- data/lib/zippo/filter/compressors.rb +3 -0
- data/lib/zippo/filter/null_filters.rb +15 -0
- data/lib/zippo/filter/uncompressor/deflate.rb +25 -0
- data/lib/zippo/filter/uncompressor/store.rb +12 -0
- data/lib/zippo/filter/uncompressor.rb +59 -0
- data/lib/zippo/filter/uncompressors.rb +3 -0
- data/lib/zippo/io_zip_member.rb +24 -0
- data/lib/zippo/local_file_header.rb +28 -0
- data/lib/zippo/version.rb +3 -0
- data/lib/zippo/zip_directory.rb +80 -0
- data/lib/zippo/zip_file.rb +121 -0
- data/lib/zippo/zip_file_writer.rb +57 -0
- data/lib/zippo/zip_member.rb +85 -0
- data/lib/zippo.rb +18 -0
- data/spec/binary_structure_spec.rb +132 -0
- data/spec/central_directory_entries_unpacker_spec.rb +29 -0
- data/spec/central_directory_parser_spec.rb +50 -0
- data/spec/central_directory_unpacker_spec.rb +31 -0
- data/spec/compressor_spec.rb +14 -0
- data/spec/data/comment.zip +0 -0
- data/spec/data/deflate.zip +0 -0
- data/spec/data/multi.zip +0 -0
- data/spec/data/not_a.zip +1 -0
- data/spec/data/test.zip +0 -0
- data/spec/deflate_compressor_spec.rb +21 -0
- data/spec/deflate_uncompressor_spec.rb +23 -0
- data/spec/integration/compressors_spec.rb +21 -0
- data/spec/integration/zippo_spec.rb +55 -0
- data/spec/io_zip_member_spec.rb +32 -0
- data/spec/local_file_header_spec.rb +18 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/store_compressor_spec.rb +19 -0
- data/spec/store_uncompressor_spec.rb +19 -0
- data/spec/uncompressor_spec.rb +14 -0
- data/spec/zip_directory_spec.rb +63 -0
- data/spec/zip_file_spec.rb +50 -0
- data/spec/zip_file_writer_spec.rb +42 -0
- data/spec/zip_member_spec.rb +42 -0
- data/yard_extensions.rb +10 -0
- data/zippo.gemspec +23 -0
- metadata +163 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
# TODO - spec that unpackers can take strings
|
4
|
+
|
5
|
+
require 'zippo/binary_structure'
|
6
|
+
|
7
|
+
module Zippo
|
8
|
+
describe BinaryStructure do
|
9
|
+
let(:klass) { Class.new }
|
10
|
+
let(:obj) { klass.new }
|
11
|
+
let(:unpacker) { klass::Unpacker }
|
12
|
+
before(:each) do
|
13
|
+
klass.send :extend, BinaryStructure
|
14
|
+
end
|
15
|
+
context "when used to configure a class as a binary structure" do
|
16
|
+
before(:each) do
|
17
|
+
klass.class_eval do
|
18
|
+
binary_structure do
|
19
|
+
field :foo, 'L'
|
20
|
+
field :yay, 'a4', :signature => "baz"
|
21
|
+
field :bar, 'S'
|
22
|
+
field :quux, 'a*', :size => :foo
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
it "should store field information in the class" do
|
27
|
+
klass.structure.should have(4).fields
|
28
|
+
klass.structure.fields[0].name.should eq :foo
|
29
|
+
klass.structure.fields[3].options[:size].should eq :foo
|
30
|
+
klass.structure.fields[1].should be_signature
|
31
|
+
end
|
32
|
+
it "should have dependent fields" do
|
33
|
+
obj.quux = "foobar"
|
34
|
+
obj.foo.should eq 6
|
35
|
+
obj.quux.should eq "foobar"
|
36
|
+
end
|
37
|
+
it "should not allow mutation of dependent fields" do
|
38
|
+
-> { obj.foo = 5 }.should raise_error
|
39
|
+
end
|
40
|
+
it "should have regular fields" do
|
41
|
+
obj.bar = 10
|
42
|
+
obj.bar.should eq 10
|
43
|
+
end
|
44
|
+
it "should have an unpacker" do
|
45
|
+
array = [10,"baz", 42,"foobar baz"]
|
46
|
+
packed = array.pack 'La4Sa*'
|
47
|
+
io = StringIO.new packed
|
48
|
+
obj = klass::Unpacker.new(io).unpack
|
49
|
+
obj.foo.should eq 10
|
50
|
+
obj.bar.should eq 42
|
51
|
+
obj.quux.should eq "foobar baz"
|
52
|
+
end
|
53
|
+
it "should unpack oversized strings correctly" do
|
54
|
+
array = [3,"baz", 42,"foobar baz"]
|
55
|
+
packed = array.pack 'La4Sa*'
|
56
|
+
io = StringIO.new packed
|
57
|
+
obj = klass::Unpacker.new(io).unpack
|
58
|
+
obj.quux.should eq "foo"
|
59
|
+
end
|
60
|
+
it "should have a packer" do
|
61
|
+
array = [10,"baz", 42,"foobar baz"]
|
62
|
+
packed = array.pack 'La4Sa*'
|
63
|
+
io = StringIO.new
|
64
|
+
obj.bar = 42
|
65
|
+
obj.yay = "baz"
|
66
|
+
obj.quux = "foobar baz"
|
67
|
+
klass::Packer.new(io).pack obj
|
68
|
+
io.string.should eq packed
|
69
|
+
end
|
70
|
+
it "should have a .size method" do
|
71
|
+
obj.bar = 42
|
72
|
+
obj.quux = "foobar baz"
|
73
|
+
obj.size.should eq 20
|
74
|
+
end
|
75
|
+
context "when there is another class" do
|
76
|
+
let(:other_klass) { Class.new }
|
77
|
+
before(:each) do
|
78
|
+
other_klass.send :extend, BinaryStructure
|
79
|
+
other_klass.class_eval do
|
80
|
+
binary_structure do
|
81
|
+
field :bar, 'S'
|
82
|
+
field :yay, 'a4', :signature => "quux"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
describe ".convert_to" do
|
87
|
+
it "should convert to the other class" do
|
88
|
+
obj.quux = "foobar"
|
89
|
+
obj.bar = 5
|
90
|
+
other_obj = obj.convert_to other_klass
|
91
|
+
other_obj.bar.should eq 5
|
92
|
+
end
|
93
|
+
it "should not convert a signature" do
|
94
|
+
obj.quux = "foobar"
|
95
|
+
obj.bar = 5
|
96
|
+
obj.yay = "baz"
|
97
|
+
other_obj = obj.convert_to other_klass
|
98
|
+
other_obj.bar.should eq 5
|
99
|
+
other_obj.yay.should eq "quux"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
pending "should handle binary structures with multiple lots of fixed fields interspersed with variable length fields"
|
105
|
+
# none of the current binary structures use a
|
106
|
+
# feature like this, they're all a bunch of fixed
|
107
|
+
# fields followed by some variable length fields.
|
108
|
+
# need to test a few fixed fields, followed by a
|
109
|
+
# variable length field, then a few more fixed
|
110
|
+
# fields
|
111
|
+
=begin
|
112
|
+
it "should complain if the order is bad" do
|
113
|
+
pending
|
114
|
+
klass = Class.new BinaryStructure
|
115
|
+
lambda { klass.class_eval do
|
116
|
+
field :foo, 'S'
|
117
|
+
field :bar, 'a*', :size => :baz
|
118
|
+
field :baz, 'S'
|
119
|
+
end }.should raise_error "size not found"
|
120
|
+
end
|
121
|
+
it "should remain silent if the order is ok" do
|
122
|
+
pending
|
123
|
+
klass = Class.new BinaryStructure
|
124
|
+
lambda { klass.class_eval do
|
125
|
+
field :foo, 'S'
|
126
|
+
field :baz, 'S'
|
127
|
+
field :bar, 'a*', :size => :baz
|
128
|
+
end }.should_not raise_error
|
129
|
+
end
|
130
|
+
=end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo/central_directory_entries_unpacker"
|
4
|
+
|
5
|
+
module Zippo
|
6
|
+
describe CentralDirectoryEntriesUnpacker do
|
7
|
+
subject { CentralDirectoryEntriesUnpacker.new(io, size, offset).unpack }
|
8
|
+
|
9
|
+
context "when it's a simple file" do
|
10
|
+
let(:io) { File.open(test_file "test.zip") }
|
11
|
+
let(:offset) { 112 }
|
12
|
+
let(:size) { 79 }
|
13
|
+
|
14
|
+
specify { subject.should have(1).items }
|
15
|
+
specify { subject.first.name.should eq "test.file" }
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when it's a complex file" do
|
19
|
+
let(:io) { File.open(test_file "multi.zip") }
|
20
|
+
let(:offset) { 242 }
|
21
|
+
let(:size) { 158 }
|
22
|
+
|
23
|
+
specify { subject.should have(2).items }
|
24
|
+
specify { subject[0].name.should eq "test.file" }
|
25
|
+
specify { subject[1].name.should eq "other.test" }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo/central_directory_reader"
|
4
|
+
require "zippo/zip_file"
|
5
|
+
|
6
|
+
module Zippo
|
7
|
+
describe CentralDirectoryReader do
|
8
|
+
let(:io) { File.open(file, "rb:ASCII-8BIT") }
|
9
|
+
let(:file) { test_file "test.zip" }
|
10
|
+
let(:parser) { CentralDirectoryReader.new(io) }
|
11
|
+
after(:each) { io.close }
|
12
|
+
|
13
|
+
context "when parsing a simple file" do
|
14
|
+
let(:file) { test_file "test.zip" }
|
15
|
+
it "should parse the End of Central Directory Record" do
|
16
|
+
parser.end_of_cd_record.total_records.should eq 1
|
17
|
+
end
|
18
|
+
|
19
|
+
specify { parser.end_of_cd_record_position.should eq 0xbf }
|
20
|
+
specify { parser.should have(1).cd_file_headers }
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when parsing a file with a comment" do
|
24
|
+
let(:file) { test_file "comment.zip" }
|
25
|
+
specify { parser.end_of_cd_record_position.should eq 0xbf }
|
26
|
+
specify { parser.end_of_cd_record.comment.should eq "this is a comment to make things tricky" }
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when parsing a multi-entry file" do
|
30
|
+
let(:file) { test_file "multi.zip" }
|
31
|
+
specify { parser.end_of_cd_record_position.should eq 0x191 }
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when parsing a file that is not a zip" do
|
35
|
+
let(:file) { test_file "not_a.zip" }
|
36
|
+
specify { -> {parser.end_of_cd_record_position}.should raise_error(/not found/) }
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when parsing a zip file larger than the maximum comment size" do
|
40
|
+
it "should not barf" do
|
41
|
+
in_working_directory do
|
42
|
+
Zippo::ZipFile.open("large.zip", "w") do |v|
|
43
|
+
v["test.file"] = "a" * 65535
|
44
|
+
end
|
45
|
+
Zippo::ZipFile.open("large.zip") {|v| v["test.file"].read }.should eq "a" * 65535
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo/end_cd_record"
|
4
|
+
|
5
|
+
module Zippo
|
6
|
+
describe "EndCdRecord::Unpacker" do
|
7
|
+
subject { EndCdRecord::Unpacker.new(str).unpack }
|
8
|
+
|
9
|
+
context "when it's a simple file" do
|
10
|
+
let(:str) { File.binread(test_file("test.zip"))[-22..-1] }
|
11
|
+
|
12
|
+
specify { subject.comment.should eq "" }
|
13
|
+
specify { subject.total_records.should eq 1 }
|
14
|
+
specify { subject.cd_offset.should eq 112 }
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when it's a multi entry file" do
|
18
|
+
let(:str) { File.binread(test_file("multi.zip"))[-22..-1] }
|
19
|
+
|
20
|
+
specify { subject.total_records.should eq 2 }
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when there is a comment" do
|
24
|
+
let(:str) { File.binread(test_file("comment.zip"))[-61..-1] }
|
25
|
+
|
26
|
+
specify { subject.total_records.should eq 1 }
|
27
|
+
specify { subject.comment.should eq "this is a comment to make things tricky" }
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo/filter/compressor"
|
4
|
+
require "zippo/filter/compressors"
|
5
|
+
|
6
|
+
module Zippo::Filter
|
7
|
+
describe Compressor do
|
8
|
+
it "should be a factory for compressors" do
|
9
|
+
Compressor.for(StoreCompressor::METHOD).should eq StoreCompressor
|
10
|
+
Compressor.for(DeflateCompressor::METHOD).should eq DeflateCompressor
|
11
|
+
-> { Compressor.for(113) }.should raise_error(/unknown compression method/)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
Binary file
|
Binary file
|
data/spec/data/multi.zip
ADDED
Binary file
|
data/spec/data/not_a.zip
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
this is not a zip file
|
data/spec/data/test.zip
ADDED
Binary file
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Encoding: BINARY
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
require "zippo/filter/compressor/deflate"
|
5
|
+
|
6
|
+
module Zippo::Filter
|
7
|
+
describe DeflateCompressor do
|
8
|
+
let (:data) { "a" * 20 }
|
9
|
+
let (:zdata) { "KL\xC4\u0004\u0000" }
|
10
|
+
let (:io) { StringIO.new data }
|
11
|
+
let (:out) { StringIO.new "" }
|
12
|
+
it "should write the data" do
|
13
|
+
compressor = DeflateCompressor.new(io)
|
14
|
+
csize, size, crc32 = compressor.compress_to out
|
15
|
+
size.should eq 20
|
16
|
+
csize.should eq zdata.size
|
17
|
+
crc32.should eq Zlib.crc32(data)
|
18
|
+
out.string.should eq zdata
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo/filter/uncompressor/deflate"
|
4
|
+
|
5
|
+
module Zippo::Filter
|
6
|
+
describe DeflateUncompressor do
|
7
|
+
|
8
|
+
let (:zstring) { Zlib::Deflate.new(Zlib::BEST_COMPRESSION, -Zlib::MAX_WBITS).deflate("a" * 20, Zlib::FINISH) }
|
9
|
+
let (:size) { zstring.size }
|
10
|
+
let (:string) { "aaa" + zstring + "bbb" }
|
11
|
+
let (:out) { StringIO.new }
|
12
|
+
let (:io) { StringIO.new string }
|
13
|
+
it "should deflate only the compressed string" do
|
14
|
+
io.seek 3
|
15
|
+
DeflateUncompressor.new(io, size).uncompress.should eq "a" * 20
|
16
|
+
end
|
17
|
+
it "should deflate to an IO" do
|
18
|
+
io.seek 3
|
19
|
+
DeflateUncompressor.new(io, size).uncompress_to(out)
|
20
|
+
out.string.should eq "a" * 20
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo/filter/uncompressor"
|
4
|
+
require "zippo/filter/uncompressors"
|
5
|
+
require "zippo/filter/compressor"
|
6
|
+
require 'zippo/filter/compressors'
|
7
|
+
|
8
|
+
module Zippo::Filter
|
9
|
+
describe Uncompressor do
|
10
|
+
let (:zstring) { Zlib::Deflate.new(Zlib::BEST_COMPRESSION, -Zlib::MAX_WBITS).deflate("a" * 20, Zlib::FINISH) }
|
11
|
+
it "should allow uncompressors to work with compressors" do
|
12
|
+
stream = StringIO.new zstring
|
13
|
+
deflater = DeflateUncompressor.new(stream, zstring.size)
|
14
|
+
storer = StoreCompressor.new(deflater)
|
15
|
+
out = StringIO.new
|
16
|
+
out.set_encoding "BINARY"
|
17
|
+
storer.compress_to(out)
|
18
|
+
out.string.should eq "a" * 20
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Encoding: BINARY
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
require 'zippo'
|
5
|
+
|
6
|
+
module Zippo
|
7
|
+
describe ZipFile do
|
8
|
+
let(:file) { test_file "test.zip" }
|
9
|
+
let(:member_data) { "The quick brown fox jumps over the lazy dog.\n" }
|
10
|
+
let(:zip) { Zippo::ZipFile.open(file) }
|
11
|
+
describe "when reading a zip file" do
|
12
|
+
after(:each) do
|
13
|
+
zip.close
|
14
|
+
end
|
15
|
+
it "should be able to read a member of the file" do
|
16
|
+
data = zip["test.file"].read
|
17
|
+
data.should eq member_data
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should read a compressed file" do
|
21
|
+
zip = Zippo::ZipFile.open(test_file("deflate.zip"))
|
22
|
+
zip["weasels.txt"].read.should eq "Methinks it is like a weasel.\n" * 10
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should work like File.open" do
|
26
|
+
io = File.open(file)
|
27
|
+
File.should_receive(:open).with(file, 'r:ASCII-8BIT').and_return(io)
|
28
|
+
s = Zippo.open(file) { |v| v['test.file'].read }
|
29
|
+
s.should eq member_data
|
30
|
+
io.should be_closed
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should create zip files" do
|
34
|
+
in_working_directory do
|
35
|
+
File.write "xyzzy.txt", "plugh"
|
36
|
+
Zippo.open("new.zip", "w") {|v| v['xyzzy.txt'] = "plugh" }
|
37
|
+
Pathname.new("new.zip").should exist
|
38
|
+
Zippo.open("new.zip") {|v| v['xyzzy.txt'].read }.should eq "plugh"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should update in place with mode rw" do
|
43
|
+
in_working_directory do
|
44
|
+
Zippo.open("out.zip", "w") { |v| v['foo1'] = "data1" }
|
45
|
+
Zippo.open("out.zip", "rw") { |v| v['foo2'] = "data2" }
|
46
|
+
IO.write "/tmp/bar.zip", IO.read("out.zip")
|
47
|
+
Zippo.open("out.zip") do |v|
|
48
|
+
v['foo1'].read.should eq "data1"
|
49
|
+
v['foo2'].read.should eq "data2"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo/io_zip_member"
|
4
|
+
require "zippo/filter/compressor/store"
|
5
|
+
|
6
|
+
module Zippo
|
7
|
+
describe IOZipMember do
|
8
|
+
let(:io) { StringIO.new "foobar" }
|
9
|
+
let(:out) { StringIO.new "" }
|
10
|
+
let(:name) { "name" }
|
11
|
+
subject { IOZipMember.new name, io }
|
12
|
+
specify { subject.name.should eq "name" }
|
13
|
+
specify { subject.read.should eq "foobar" }
|
14
|
+
context "when writing" do
|
15
|
+
before(:each) do
|
16
|
+
@size, @csize, @crc32 = subject.write_to out, 0
|
17
|
+
end
|
18
|
+
it "should write to the io" do
|
19
|
+
out.string.should eq "foobar"
|
20
|
+
end
|
21
|
+
it "should have returned the original size" do
|
22
|
+
@size.should eq 6
|
23
|
+
end
|
24
|
+
it "should return the compressed size" do
|
25
|
+
@csize.should eq 6
|
26
|
+
end
|
27
|
+
it "should return the CRC32" do
|
28
|
+
@crc32.should eq Zlib.crc32("foobar")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo/local_file_header"
|
4
|
+
|
5
|
+
module Zippo
|
6
|
+
describe "LocalFileHeader::Unpacker" do
|
7
|
+
before(:each) do
|
8
|
+
@io = File.open test_file("test.zip"), "rb:ASCII-8BIT"
|
9
|
+
@io.seek 0
|
10
|
+
end
|
11
|
+
after(:each) do
|
12
|
+
@io.close
|
13
|
+
end
|
14
|
+
it "should unpack from an io" do
|
15
|
+
LocalFileHeader::Unpacker.new(@io.read).unpack.name.should eq "test.file"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
def io_for(filename)
|
2
|
+
File.open test_file(filename), "rb:ASCII-8BIT"
|
3
|
+
end
|
4
|
+
def test_file(filename)
|
5
|
+
File.join(File.join(File.dirname(__FILE__), 'data'), filename)
|
6
|
+
end
|
7
|
+
require 'tmpdir'
|
8
|
+
def in_working_directory
|
9
|
+
Dir.mktmpdir do |dir|
|
10
|
+
Dir.chdir(dir) { yield }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo/filter/compressor/store"
|
4
|
+
|
5
|
+
module Zippo::Filter
|
6
|
+
describe StoreCompressor do
|
7
|
+
let (:data) { "a" * 20 }
|
8
|
+
let (:io) { StringIO.new data }
|
9
|
+
let (:out) { StringIO.new }
|
10
|
+
it "should write the data" do
|
11
|
+
compressor = StoreCompressor.new(io)
|
12
|
+
size, csize, crc32 = compressor.compress_to out
|
13
|
+
size.should eq 20
|
14
|
+
csize.should eq 20
|
15
|
+
crc32.should eq Zlib.crc32(data)
|
16
|
+
out.string.should eq data
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo/filter/uncompressor/store"
|
4
|
+
|
5
|
+
module Zippo::Filter
|
6
|
+
describe StoreUncompressor do
|
7
|
+
let(:io) { StringIO.new "foobarbazquux" }
|
8
|
+
let (:out) { StringIO.new }
|
9
|
+
it "should read only the specified amount" do
|
10
|
+
io.seek 3
|
11
|
+
StoreUncompressor.new(io, 3).uncompress.should eq "bar"
|
12
|
+
end
|
13
|
+
it "should deflate to an IO" do
|
14
|
+
io.seek 3
|
15
|
+
StoreUncompressor.new(io, 3).uncompress_to(out)
|
16
|
+
out.string.should eq "bar"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo/filter/uncompressor"
|
4
|
+
require "zippo/filter/uncompressors"
|
5
|
+
|
6
|
+
module Zippo::Filter
|
7
|
+
describe Uncompressor do
|
8
|
+
it "should be a factory for uncompressors" do
|
9
|
+
Uncompressor.for(0).should eq StoreUncompressor
|
10
|
+
Uncompressor.for(8).should eq DeflateUncompressor
|
11
|
+
-> { Uncompressor.for(113) }.should raise_error(/unknown compression method/)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'zippo/zip_file'
|
4
|
+
|
5
|
+
module Zippo
|
6
|
+
describe ZipDirectory do
|
7
|
+
context "when opening a file" do
|
8
|
+
let(:io) { File.open(file, "rb:ASCII-8BIT") }
|
9
|
+
after(:each) { io.close }
|
10
|
+
subject { ZipDirectory.new io }
|
11
|
+
context "when reading a simple file" do
|
12
|
+
let(:file) { test_file "test.zip" }
|
13
|
+
it { should have(1).entries }
|
14
|
+
specify { subject["test.file"].should_not be_nil }
|
15
|
+
it { should_not be_empty }
|
16
|
+
end
|
17
|
+
context "when reading a larger zip" do
|
18
|
+
let(:file) { test_file "multi.zip" }
|
19
|
+
it { should have(2).entries }
|
20
|
+
specify { subject["test.file"].should_not be_nil }
|
21
|
+
specify { subject["other.test"].should_not be_nil }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
context "when inserting" do
|
25
|
+
let(:io) { StringIO.new "" }
|
26
|
+
it "should allow the insertion of new members" do
|
27
|
+
subject["new.file"] = "file data"
|
28
|
+
subject["new.file"].read.should eq "file data"
|
29
|
+
subject["new.file"].write_to io, 0
|
30
|
+
io.string.should eq "file data"
|
31
|
+
end
|
32
|
+
it "should allow the insertion of IO objects" do
|
33
|
+
in_working_directory do
|
34
|
+
File.write "xyzzy.txt", "plugh"
|
35
|
+
file = File.open "xyzzy.txt"
|
36
|
+
subject.insert "woo.txt", file
|
37
|
+
subject["woo.txt"].read.should eq "plugh"
|
38
|
+
subject["woo.txt"].write_to io, 0
|
39
|
+
file.close
|
40
|
+
io.string.should eq "plugh"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
it "should allow insertion of file contents" do
|
44
|
+
in_working_directory do
|
45
|
+
File.write "xyzzy.txt", "plugh"
|
46
|
+
subject.insert "woo.txt", "xyzzy.txt"
|
47
|
+
subject["woo.txt"].read.should eq "plugh"
|
48
|
+
subject["woo.txt"].write_to io, 0
|
49
|
+
io.string.should eq "plugh"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
it "should allow the insertion of ZipMember contents" do
|
53
|
+
zip = Zippo::ZipFile.open test_file "test.zip"
|
54
|
+
subject.insert "hmm.txt", zip['test.file']
|
55
|
+
subject["hmm.txt"].read.should eq "The quick brown fox jumps over the lazy dog.\n"
|
56
|
+
subject["hmm.txt"].write_to io, 0
|
57
|
+
zip.close
|
58
|
+
io.string.should eq "The quick brown fox jumps over the lazy dog.\n"
|
59
|
+
-> { subject["hmm.txt"].read }.should raise_error # after closing original zip
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo/zip_file"
|
4
|
+
require "zippo/filter/uncompressors"
|
5
|
+
require "zippo/filter/compressors"
|
6
|
+
|
7
|
+
module Zippo
|
8
|
+
describe ZipFile do
|
9
|
+
# XXX unit test for open
|
10
|
+
describe ".open" do
|
11
|
+
let(:zip) { ZipFile.open(file, mode) }
|
12
|
+
let(:file) { test_file "test.zip" }
|
13
|
+
context "when in read mode" do
|
14
|
+
let(:mode) { "r" }
|
15
|
+
it "should allow reading of zip members" do
|
16
|
+
zip['test.file'].should be_a ZipMember
|
17
|
+
zip['test.file'].read.should eq "The quick brown fox jumps over the lazy dog.\n"
|
18
|
+
end
|
19
|
+
it "should allow iterating over the directory" do
|
20
|
+
files = []
|
21
|
+
zip.each { |m| files << m.name }
|
22
|
+
files.should include("test.file")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
context "when in write mode" do
|
26
|
+
let(:mode) { "w" }
|
27
|
+
let(:file) { "/tmp/foo.zip" }
|
28
|
+
it "should not allow reading of zip members that haven't been written" do
|
29
|
+
zip['test.file'].should be_nil
|
30
|
+
end
|
31
|
+
context "when a member is inserted" do
|
32
|
+
before(:each) do
|
33
|
+
zip['new.file'] = "foo"
|
34
|
+
end
|
35
|
+
it "should write the file when closed" do
|
36
|
+
zip.close
|
37
|
+
Pathname.new(file).should exist
|
38
|
+
Zippo::ZipFile.open(file) {|f| f["new.file"].read }.should eq "foo"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
context "when in update mode" do
|
43
|
+
let(:mode) { "rw" }
|
44
|
+
it "should read the zip file" do
|
45
|
+
zip.directory.should_not be_empty
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
require "zippo" #
|
4
|
+
require "zippo/zip_file_writer"
|
5
|
+
require "zippo/zip_directory"
|
6
|
+
|
7
|
+
module Zippo
|
8
|
+
describe ZipFileWriter do
|
9
|
+
describe ".write" do
|
10
|
+
it "should write the zip file" do
|
11
|
+
in_working_directory do
|
12
|
+
directory = ZipDirectory.new
|
13
|
+
directory["file.ext"] = "foo"
|
14
|
+
writer = ZipFileWriter.new directory, "out.zip"
|
15
|
+
writer.write
|
16
|
+
File.open("/home/jma/yay.zip","w") {|f| f.write File.read("out.zip") }
|
17
|
+
Zippo.open("out.zip") do |f|
|
18
|
+
f['file.ext'].crc32.should eq 0x8c736521
|
19
|
+
f['file.ext'].compressed_size.should eq 5
|
20
|
+
f['file.ext'].uncompressed_size.should eq 3
|
21
|
+
f['file.ext'].read.should eq "foo"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
it "should perform direct zip copying" do
|
26
|
+
in_working_directory do
|
27
|
+
zip = Zippo.open(test_file "test.zip")
|
28
|
+
Zippo.open("out.zip", "w") do |out|
|
29
|
+
out.directory.insert 'out.file', zip['test.file']
|
30
|
+
end
|
31
|
+
zip.close
|
32
|
+
|
33
|
+
IO.write "/tmp/out.zip", IO.read("out.zip")
|
34
|
+
|
35
|
+
Zippo.open("out.zip") { |v| v['out.file'].read }.should eq "The quick brown fox jumps over the lazy dog.\n"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
pending "should be able to peform direct zip copying after the source zip is closed"
|
39
|
+
pending "should be configurable in terms of compression method and effort"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|