multi_zip 0.1.4 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +1 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +1 -1
- data/README.md +47 -16
- data/TODO.md +2 -3
- data/lib/multi_zip.rb +35 -3
- data/lib/multi_zip/backend/archive_zip.rb +23 -6
- data/lib/multi_zip/backend/cli.rb +68 -0
- data/lib/multi_zip/backend/cli/info_zip.rb +292 -0
- data/lib/multi_zip/backend/rubyzip.rb +16 -1
- data/lib/multi_zip/backend/zipruby.rb +20 -1
- data/lib/multi_zip/errors.rb +2 -0
- data/lib/multi_zip/version.rb +1 -1
- data/spec/backend_shared_example.rb +85 -21
- data/spec/lib/multi_zip/backend/archive_zip_spec.rb +3 -1
- data/spec/lib/multi_zip/backend/cli_spec.rb +8 -0
- data/spec/lib/multi_zip/backend/rubyzip_spec.rb +1 -1
- data/spec/lib/multi_zip_spec.rb +5 -3
- data/spec/spec_helper.rb +49 -11
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ae79eb36822d59d1b6c74fbc01954469af7affc
|
4
|
+
data.tar.gz: e907a498f8b2f26bf0c5c09b79604e1ae3c55d5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fffb4dc3201100b24d680b0e7f1b34001f30ea653c286b378018c037ce99b66bc4628d57c7afed364a27f8318c2b0ac4b99fe2accd2d1b453ac10bfbe8d8b6ef
|
7
|
+
data.tar.gz: e02558c21895d05e01db7222d83dcf6be10067e074618b4bd7eebd1a90d24ec7927041bd6624c4e5507ad459ff519c48a2ee10a5e1052383c2ea3aa2ba400ab6
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
data/Gemfile
CHANGED
@@ -3,7 +3,7 @@ source 'https://rubygems.org'
|
|
3
3
|
gemspec
|
4
4
|
|
5
5
|
# can't use pry on old rubies or on rubinius
|
6
|
-
if RUBY_VERSION.to_f >= 2.
|
6
|
+
if RUBY_VERSION.to_f >= 2.2 && RUBY_ENGINE == 'ruby'
|
7
7
|
gem 'byebug', :require => false
|
8
8
|
gem 'guard-rspec', :require => false
|
9
9
|
end
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ interface regardless of which is being used. This allows for code that is more
|
|
7
7
|
portable and helps to avoid namespace collisions (zipruby vs. rubyzip for example)
|
8
8
|
and other implementation restrictions (MRI vs. Jruby, Unix vs. Windows, etc,).
|
9
9
|
|
10
|
-
It currently supports `.zip` archives only.
|
10
|
+
It currently supports `.zip` archives only.
|
11
11
|
|
12
12
|
MultiZip provides a very small and focused set of functions:
|
13
13
|
|
@@ -17,8 +17,6 @@ MultiZip provides a very small and focused set of functions:
|
|
17
17
|
* Extract files from an archive to a local file.
|
18
18
|
* List files contained in an archive.
|
19
19
|
* Delete files from an archive.
|
20
|
-
* Get information for a file in an archive. (Pending TODO)
|
21
|
-
* Add a local file to an archive. (Pending TODO).
|
22
20
|
|
23
21
|
It is meant for most common zip/unzip tasks. For anything more
|
24
22
|
complicated than these basics, you should use a specific (un)zipping library
|
@@ -26,16 +24,16 @@ instead.
|
|
26
24
|
|
27
25
|
Rubies supported (see [CI status](https://travis-ci.org/xunker/multi_zip) for more detail):
|
28
26
|
* MRI 2.x.x, 1.9.3, 1.8.7 and REE.
|
29
|
-
* Jruby
|
27
|
+
* Jruby (1.8 and 1.9 mode)
|
30
28
|
* Rubinius 2
|
31
29
|
|
32
30
|
For information about which backend gems work in which ruby, see [Supported Backend Gems](#supported-backend-gems).
|
33
31
|
|
34
|
-
This
|
35
|
-
and [multi_xml](https://github.com/sferik/multi_xml)
|
36
|
-
|
37
|
-
[
|
38
|
-
|
32
|
+
This gem was inspired by [multi_json](https://github.com/intridea/multi_json)
|
33
|
+
and [multi_xml](https://github.com/sferik/multi_xml), and is dedicated to the
|
34
|
+
Rubyists of [Asukusa.rb](https://asakusarb.doorkeeper.jp/) and
|
35
|
+
[John Mettraux](https://twitter.com/jmettraux) of Hiroshima.
|
36
|
+
おもてなしのあなたはありがとう!
|
39
37
|
|
40
38
|
## Installation
|
41
39
|
|
@@ -50,12 +48,14 @@ which ones can be used.
|
|
50
48
|
|
51
49
|
`multi_zip` will try to use the available gem backends in the following order:
|
52
50
|
|
53
|
-
* rubyzip
|
54
|
-
* archive/zip
|
55
|
-
* zipruby
|
51
|
+
* [rubyzip](https://rubygems.org/gems/rubyzip)
|
52
|
+
* [archive-zip](https://rubygems.org/gems/archive-zip)
|
53
|
+
* [zipruby](https://rubygems.org/gems/zipruby)
|
56
54
|
|
57
|
-
If no usable
|
58
|
-
|
55
|
+
If no usable gems are found, it will then look for a compatible `zip`/ `unzip`
|
56
|
+
program in your path and will try to use that instead of a gem. If no
|
57
|
+
compatible gems or program can be found, a `MultiZip::NoSupportedBackendError`
|
58
|
+
exception will be raised.
|
59
59
|
|
60
60
|
If you have multiple gems available and want to choose your backend, you can
|
61
61
|
do that in the initializer:
|
@@ -112,6 +112,38 @@ zip.list_members
|
|
112
112
|
# ]
|
113
113
|
```
|
114
114
|
|
115
|
+
#### Check if a file exists in an archive
|
116
|
+
|
117
|
+
Returns `true` if the file exists in the archive, otherwise `false`
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
zip.member_exists?('/path/inside/archive/to/file.txt')
|
121
|
+
# => true
|
122
|
+
|
123
|
+
zip.member_exists?('/doesnt_exist')
|
124
|
+
# => false
|
125
|
+
```
|
126
|
+
|
127
|
+
#### Get member information (file size, etc)
|
128
|
+
|
129
|
+
Returns a hash if the file exists in the archive, otherwise will raise exception.
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
zip.member_info('/path/inside/archive/to/file.txt')
|
133
|
+
# => { :path => '/path/inside/archive/to/file.txt', :size => 14323, type: :file }
|
134
|
+
```
|
135
|
+
|
136
|
+
At a minimum, the returned hash will contain:
|
137
|
+
:path the full path of the member file (should equal `member_path` arg)
|
138
|
+
:size the UNCOMPRESSED file size, in bytes
|
139
|
+
|
140
|
+
Optionally, it MAY contain these keys:
|
141
|
+
:type Filesystem type of the member (:directory, :file, :symlink, etc)
|
142
|
+
:created_at creation timestamp of the file as an instance of Time
|
143
|
+
:compressed_size size of the COMPRESSED file in bytes
|
144
|
+
:original The original member info object as returned from the backend
|
145
|
+
gem. Ex: using rubyzip, it would be an instace of Zip::Entry.
|
146
|
+
|
115
147
|
#### Read file from zip archive
|
116
148
|
|
117
149
|
```ruby
|
@@ -186,8 +218,7 @@ end
|
|
186
218
|
|
187
219
|
Planned for the future:
|
188
220
|
|
189
|
-
* [archive](https://rubygems.org/gems/archive)
|
190
|
-
* [unix_utils](https://rubygems.org/gems/unix_utils)
|
221
|
+
* [archive gem](https://rubygems.org/gems/archive)
|
191
222
|
* Other archive formats like gzip, bzip2, 7zip, tar, etc.
|
192
223
|
* Jruby-specific gems
|
193
224
|
* Others (please suggest them in a [new issue](https://github.com/xunker/multi_zip/issues/new))
|
data/TODO.md
CHANGED
@@ -5,12 +5,10 @@
|
|
5
5
|
* #add_member: add file to archive from filesystem (new method).
|
6
6
|
* Document exceptions raised, what they mean and how to use them.
|
7
7
|
* Add inline docs for methods.
|
8
|
-
* support *nix zip(1L)/unzip(1L) without needing backend gem (with warning).
|
9
8
|
|
10
9
|
#### Other things that need to be done, in no particular order:
|
11
10
|
|
12
11
|
* Add support for more backends.
|
13
|
-
* Support for backend gems to process other formats (gzip, bzip2, 7zip, tar, etc).
|
14
12
|
* Keep the backend archive open between method calls.
|
15
13
|
* Add soak tests for memory usage once archived are kept open.
|
16
14
|
* Ensure #close is executed when MultiZip instance goes out of scope.
|
@@ -39,8 +37,9 @@
|
|
39
37
|
#### Things that I'd **like** to do, but won't.
|
40
38
|
|
41
39
|
These are thinks that would be really nice to have but that are probably not
|
42
|
-
realistic because they cannot be abstracted across all backend gems:
|
40
|
+
realistic because they cannot be abstracted across all backend gems and systems:
|
43
41
|
|
42
|
+
* Support for backend gems to process other formats (gzip, bzip2, 7zip, tar, etc).
|
44
43
|
* Ability to set location and compression format and level of archive.
|
45
44
|
* Ability to set compression format and level of individual members (for Epub compatibility).
|
46
45
|
* Ability to set archive location of individual members (for Epub compatibility).
|
data/lib/multi_zip.rb
CHANGED
@@ -3,7 +3,7 @@ class MultiZip
|
|
3
3
|
|
4
4
|
attr_reader :filename
|
5
5
|
|
6
|
-
BACKEND_PREFERENCE = [ :rubyzip, :archive_zip, :zipruby ]
|
6
|
+
BACKEND_PREFERENCE = [ :rubyzip, :archive_zip, :zipruby, :cli ]
|
7
7
|
BACKENDS = {
|
8
8
|
:rubyzip => {
|
9
9
|
:fingerprints => [
|
@@ -24,6 +24,12 @@ class MultiZip
|
|
24
24
|
['constant', lambda { defined?(Zip::Archive) }]
|
25
25
|
],
|
26
26
|
:constant => lambda { MultiZip::Backend::Zipruby }
|
27
|
+
},
|
28
|
+
:cli => {
|
29
|
+
:fingerprints => [
|
30
|
+
[true, lambda { MultiZip::Backend::Cli.strategy_available? } ]
|
31
|
+
],
|
32
|
+
:constant => lambda { MultiZip::Backend::Cli.strategy.extend_class.call }
|
27
33
|
}
|
28
34
|
}
|
29
35
|
|
@@ -33,7 +39,15 @@ class MultiZip
|
|
33
39
|
self.backend = if b_end = options.delete(:backend)
|
34
40
|
b_end
|
35
41
|
else
|
36
|
-
|
42
|
+
begin
|
43
|
+
default_backend
|
44
|
+
rescue NoSupportedBackendError => e
|
45
|
+
if !!options[:allow_no_backends] # TODO: Fix this test mode hack
|
46
|
+
nil
|
47
|
+
else
|
48
|
+
raise e
|
49
|
+
end
|
50
|
+
end
|
37
51
|
end
|
38
52
|
|
39
53
|
if block_given?
|
@@ -46,7 +60,7 @@ class MultiZip
|
|
46
60
|
end
|
47
61
|
|
48
62
|
def backend=(backend_name)
|
49
|
-
return if backend_name.nil?
|
63
|
+
return @backend if backend_name.nil?
|
50
64
|
if BACKENDS.keys.include?(backend_name.to_sym)
|
51
65
|
@backend = backend_name.to_sym
|
52
66
|
require "multi_zip/backend/#{@backend}"
|
@@ -124,6 +138,23 @@ class MultiZip
|
|
124
138
|
list_members(nil, options).include?(member_path)
|
125
139
|
end
|
126
140
|
|
141
|
+
# Returns a hash of information about the member file. At a minimum, the hash
|
142
|
+
# MUST contain these keys:
|
143
|
+
# :path the full path of the member file (should equal `member_path` arg)
|
144
|
+
# :size the UNCOMPRESSED file size, in bytes
|
145
|
+
#
|
146
|
+
# Optionally, it MAY contain these keys:
|
147
|
+
# :created_at creation timestamp of the file as an instance of Time
|
148
|
+
# :compressed_size size of the COMPRESSED file
|
149
|
+
# :type Filesystem type of the member (:directory, :file, :symlink, etc)
|
150
|
+
# :original The original member info object as returned from the backend
|
151
|
+
# gem. Ex: using rubyzip, it would be an instace of Zip::Entry.
|
152
|
+
#
|
153
|
+
# This method MUST be overridden by a backend module.
|
154
|
+
def member_info(member_path, options={})
|
155
|
+
raise NotImplementedError
|
156
|
+
end
|
157
|
+
|
127
158
|
# Write string contents to a zip member file
|
128
159
|
def write_member(member_path, member_content, options={})
|
129
160
|
raise NotImplementedError
|
@@ -191,5 +222,6 @@ private
|
|
191
222
|
end
|
192
223
|
end
|
193
224
|
|
225
|
+
require "multi_zip/backend/cli"
|
194
226
|
require "multi_zip/version"
|
195
227
|
require "multi_zip/errors"
|
@@ -29,7 +29,7 @@ module MultiZip::Backend::ArchiveZip
|
|
29
29
|
tempfile = Tempfile.new('multizip_member')
|
30
30
|
tempfile.write(member_contents)
|
31
31
|
tempfile.close
|
32
|
-
|
32
|
+
|
33
33
|
zip = Archive::Zip.new(@filename, :w)
|
34
34
|
new_entry = Archive::Zip::Entry.from_file(tempfile.path, :zip_path => member_path)
|
35
35
|
zip.add_entry(new_entry)
|
@@ -47,7 +47,7 @@ module MultiZip::Backend::ArchiveZip
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
def extract_member(member_path, destination_path, options = {})
|
50
|
+
def extract_member(member_path, destination_path, options = {})
|
51
51
|
archive_operation do |zip|
|
52
52
|
member = zip.find{|m| m.zip_path == member_path}
|
53
53
|
if member && member.file?
|
@@ -73,18 +73,18 @@ module MultiZip::Backend::ArchiveZip
|
|
73
73
|
member_paths.each do |member_path|
|
74
74
|
exists!(member_path)
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
# archive-zip doesn't have the #remove_entry method any more, so we do
|
78
78
|
# this in a really slow way: we dump the entire dir to the filesystem,
|
79
79
|
# delete `member_path` and zip the whole thing up again.
|
80
|
-
|
80
|
+
|
81
81
|
Dir.mktmpdir do |tmp_dir|
|
82
82
|
Archive::Zip.extract(@filename, tmp_dir)
|
83
83
|
|
84
84
|
member_paths.each do |member_path|
|
85
85
|
FileUtils.rm("#{tmp_dir}/#{member_path}")
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
# create a tempfile and immediately delete it, we just want the name.
|
89
89
|
tempfile = Tempfile.new(['multizip_temp', '.zip'])
|
90
90
|
tempfile_path = tempfile.path
|
@@ -98,6 +98,23 @@ module MultiZip::Backend::ArchiveZip
|
|
98
98
|
true
|
99
99
|
end
|
100
100
|
|
101
|
+
def member_info(member_path, options = {})
|
102
|
+
archive_operation do |zip|
|
103
|
+
member = zip.find{|m| m.zip_path == member_path}
|
104
|
+
if member
|
105
|
+
return {
|
106
|
+
path: member.zip_path,
|
107
|
+
size: member.expected_data_descriptor.uncompressed_size.to_i,
|
108
|
+
type: member.ftype,
|
109
|
+
original: member
|
110
|
+
}
|
111
|
+
else
|
112
|
+
zip.close
|
113
|
+
member_not_found!(member_path)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
101
118
|
private
|
102
119
|
|
103
120
|
def archive_operation(mode = :r) # mode is either :r or :w
|
@@ -108,4 +125,4 @@ private
|
|
108
125
|
rescue Archive::Zip::UnzipError => e
|
109
126
|
raise MultiZip::InvalidArchiveError.new(@filename, e)
|
110
127
|
end
|
111
|
-
end
|
128
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# If no suitable gems are found, this is the last-gasp fallback. We use
|
2
|
+
# whatever command-line zipping/unzipping tools we can find and that we
|
3
|
+
# know how to use.
|
4
|
+
#
|
5
|
+
# This will likely be a huge work in progress since it involves a lot of
|
6
|
+
# detection. We'll need to detect the host OS and version, the tools available
|
7
|
+
# in the path (or specified by the user, optionally) and the command-line
|
8
|
+
# arguments to be used for those tools.
|
9
|
+
#
|
10
|
+
# OS Unzip Zip
|
11
|
+
#------------------------------------------------------------------------------
|
12
|
+
# OS X 10.10 Info-ZIP UnZip 5.52 Info-ZIP Zip 3.0
|
13
|
+
# RHEL-Based Info-ZIP UnZip 5.xx to 6.xx Info-ZIP Zip 2.x to 3.x
|
14
|
+
# Ubuntu Info-ZIP UnZip 6.xx Info-ZIP Zip 3.0
|
15
|
+
# Raspbian Info-ZIP UnZip 6.00 Info-ZIP Zip 3.0
|
16
|
+
# Windows
|
17
|
+
# GNU UnZip (from INFO-Zip UnZip) GNU Zip (from INFO-Zip Zip)
|
18
|
+
# 7-Zip Command Line Version 7-Zip Command Line Version
|
19
|
+
# PKWare pkunzip PKWare pkzip
|
20
|
+
# WinZip CLI WinZip CLI
|
21
|
+
# WinRAR CLI WinRAR CLI
|
22
|
+
#
|
23
|
+
# First, check for the programs that would be installed by default.
|
24
|
+
# If none can be found, raise an error and tell the user how they can resolve
|
25
|
+
# the problem: Either tell them to install a different backend gem (preferred)
|
26
|
+
# or tell them how to install a supported CLI program (depending on OS).
|
27
|
+
#
|
28
|
+
# When using this shell method, always emit a warning that it is very
|
29
|
+
# inefficient and should not never no-way no-how no-where be used in
|
30
|
+
# production environments.
|
31
|
+
|
32
|
+
module MultiZip::Backend::Cli
|
33
|
+
STRATEGY_MODULES = [
|
34
|
+
[ :info_zip, lambda { InfoZip }]
|
35
|
+
]
|
36
|
+
|
37
|
+
def self.extended(mod)
|
38
|
+
if strategy_available?
|
39
|
+
require "multi_zip/backend/cli/#{strategy.require_name}"
|
40
|
+
extend strategy.extend_class.call
|
41
|
+
warn([
|
42
|
+
"MultiZip is using the \"#{strategy.human_name}\" command-line program.",
|
43
|
+
'This feature is considered PRE-ALPHA, unstable and inefficient and',
|
44
|
+
'should not be used in production environments.'
|
45
|
+
].join("\n"))
|
46
|
+
else
|
47
|
+
raise MultiZip::NoSupportedBackendError, "MultiZip::Backend::Cli could find no suitable zipping/unzipping programs in path."
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.strategy_available?
|
52
|
+
!!strategy
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.strategy
|
56
|
+
@strategy ||= detect_strategy
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.detect_strategy
|
60
|
+
STRATEGY_MODULES.detect{|strategy_module_file, strategy_module_constant|
|
61
|
+
require "multi_zip/backend/cli/#{strategy_module_file}"
|
62
|
+
strategy_module = strategy_module_constant.call
|
63
|
+
if strategy_module.available?
|
64
|
+
return strategy_module
|
65
|
+
end
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,292 @@
|
|
1
|
+
module MultiZip::Backend::Cli
|
2
|
+
module InfoZip
|
3
|
+
require 'stringio'
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
BUFFER_SIZE = 8192
|
7
|
+
|
8
|
+
# TODO: better way to find full path to programs?
|
9
|
+
WHICH_PROGRAM = 'which'
|
10
|
+
|
11
|
+
ZIP_AND_UNZIP_ARE_SAME_PROGRAM = false
|
12
|
+
|
13
|
+
ZIP_PROGRAM = 'zip'
|
14
|
+
ZIP_PROGRAM_SIGNATURE = /This is Zip [2-3].\d+\s.+, by Info-ZIP/
|
15
|
+
ZIP_PROGRAM_SIGNATURE_SWITCH = '-v'
|
16
|
+
ZIP_PROGRAM_REMOVE_MEMBER_SWITCH = '-d'
|
17
|
+
|
18
|
+
UNZIP_PROGRAM ='unzip'
|
19
|
+
UNZIP_PROGRAM_SIGNATURE = /UnZip [5-6]\.\d+ of .+, by Info-ZIP/
|
20
|
+
UNZIP_PROGRAM_SIGNATURE_SWITCH = '-v'
|
21
|
+
UNZIP_PROGRAM_LIST_MEMBERS_SWITCHES = ['-Z', '-1']
|
22
|
+
UNZIP_PROGRAM_READ_MEMBER_SWITCH = '-p'
|
23
|
+
UNZIP_PROGRAM_MEMBER_INFO_SWITCHES = ['-Z']
|
24
|
+
|
25
|
+
# TODO: does this change between versions?
|
26
|
+
# TODO: does this change with system language?
|
27
|
+
UNZIP_PROGRAM_EMPTY_ZIPFILE_MESSAGE = 'Empty zipfile.'
|
28
|
+
UNZIP_PROGRAM_MEMBER_NOT_FOUND_MESSAGE = 'caution: filename not matched:'
|
29
|
+
UNZIP_PROGRAM_INVALID_FILE_MESSAGE = 'End-of-central-directory signature not found'
|
30
|
+
|
31
|
+
def self.require_name
|
32
|
+
'info_zip'
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.extend_class
|
36
|
+
lambda { MultiZip::Backend::Cli::InfoZip::InstanceMethods }
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.human_name
|
40
|
+
'Info-ZIP - zip(1L)/unzip(1L)'
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.available?
|
44
|
+
@available ||= programs_found?
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.programs_found?
|
48
|
+
if ZIP_AND_UNZIP_ARE_SAME_PROGRAM
|
49
|
+
zip_program_found?
|
50
|
+
else
|
51
|
+
zip_program_found? && unzip_program_found?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.zip_program_found?
|
56
|
+
zip_program_path = `#{WHICH_PROGRAM} #{ZIP_PROGRAM}`.strip
|
57
|
+
return false unless zip_program_path =~ /#{ZIP_PROGRAM}/
|
58
|
+
return false unless File.exists?(zip_program_path)
|
59
|
+
|
60
|
+
spawn([ZIP_PROGRAM, ZIP_PROGRAM_SIGNATURE_SWITCH]).first =~ ZIP_PROGRAM_SIGNATURE
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.unzip_program_found?
|
64
|
+
unzip_program_path = `#{WHICH_PROGRAM} #{UNZIP_PROGRAM}`.strip
|
65
|
+
return false unless unzip_program_path =~ /#{UNZIP_PROGRAM}/
|
66
|
+
return false unless File.exists?(unzip_program_path)
|
67
|
+
|
68
|
+
spawn([UNZIP_PROGRAM, UNZIP_PROGRAM_SIGNATURE_SWITCH]).first =~ UNZIP_PROGRAM_SIGNATURE
|
69
|
+
end
|
70
|
+
|
71
|
+
# Blatant copy from https://github.com/seamusabshere/unix_utils/blob/master/lib/unix_utils.rb
|
72
|
+
def self.spawn(argv, options = {}) # :nodoc:
|
73
|
+
input = if (read_from = options[:read_from])
|
74
|
+
if RUBY_DESCRIPTION =~ /jruby 1.7.0/
|
75
|
+
raise "MultiZip: Can't use `#{argv.first}` since JRuby 1.7.0 has a broken IO implementation!"
|
76
|
+
end
|
77
|
+
File.open(read_from, 'r')
|
78
|
+
end
|
79
|
+
output = if (write_to = options[:write_to])
|
80
|
+
output_redirected = true
|
81
|
+
File.open(write_to, 'wb')
|
82
|
+
else
|
83
|
+
output_redirected = false
|
84
|
+
StringIO.new
|
85
|
+
end
|
86
|
+
error = StringIO.new
|
87
|
+
if (chdir = options[:chdir])
|
88
|
+
Dir.chdir(chdir) do
|
89
|
+
_spawn argv, input, output, error
|
90
|
+
end
|
91
|
+
else
|
92
|
+
_spawn argv, input, output, error
|
93
|
+
end
|
94
|
+
error.rewind
|
95
|
+
whole_error = error.read
|
96
|
+
unless whole_error.empty?
|
97
|
+
$stderr.puts "MultiZip: `#{argv.join(' ')}` STDERR:"
|
98
|
+
$stderr.puts whole_error
|
99
|
+
end
|
100
|
+
unless output_redirected
|
101
|
+
output.rewind
|
102
|
+
[output.read, whole_error].map{|o| o.empty? ? nil : o}
|
103
|
+
end
|
104
|
+
ensure
|
105
|
+
[input, output, error].each { |io| io.close if io and not io.closed? }
|
106
|
+
end
|
107
|
+
|
108
|
+
# Blatant copy from https://github.com/seamusabshere/unix_utils/blob/master/lib/unix_utils.rb
|
109
|
+
def self._spawn(argv, input, output, error)
|
110
|
+
# lifted from posix-spawn
|
111
|
+
# https://github.com/rtomayko/posix-spawn/blob/master/lib/posix/spawn/child.rb
|
112
|
+
Open3.popen3(*argv) do |stdin, stdout, stderr|
|
113
|
+
readers = [stdout, stderr]
|
114
|
+
if RUBY_DESCRIPTION =~ /jruby 1.7.0/
|
115
|
+
readers.delete stderr
|
116
|
+
end
|
117
|
+
writers = if input
|
118
|
+
[stdin]
|
119
|
+
else
|
120
|
+
stdin.close
|
121
|
+
[]
|
122
|
+
end
|
123
|
+
while readers.any? or writers.any?
|
124
|
+
ready = IO.select(readers, writers, readers + writers)
|
125
|
+
# write to stdin stream
|
126
|
+
ready[1].each do |fd|
|
127
|
+
begin
|
128
|
+
boom = nil
|
129
|
+
size = fd.write input.read(BUFFER_SIZE)
|
130
|
+
rescue Errno::EPIPE => boom
|
131
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
132
|
+
end
|
133
|
+
if boom || size < BUFFER_SIZE
|
134
|
+
stdin.close
|
135
|
+
input.close
|
136
|
+
writers.delete stdin
|
137
|
+
end
|
138
|
+
end
|
139
|
+
# read from stdout and stderr streams
|
140
|
+
ready[0].each do |fd|
|
141
|
+
buf = (fd == stdout) ? output : error
|
142
|
+
if fd.eof?
|
143
|
+
readers.delete fd
|
144
|
+
fd.close
|
145
|
+
else
|
146
|
+
begin
|
147
|
+
# buf << fd.gets(BUFFER_SIZE) # maybe?
|
148
|
+
buf << fd.readpartial(BUFFER_SIZE)
|
149
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
# thanks @tmm1 and @rtomayko for showing how it's done!
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
module InstanceMethods
|
159
|
+
def list_members(prefix = nil, options={})
|
160
|
+
archive_exists!
|
161
|
+
response = MultiZip::Backend::Cli::InfoZip.spawn([
|
162
|
+
UNZIP_PROGRAM, UNZIP_PROGRAM_LIST_MEMBERS_SWITCHES, @filename
|
163
|
+
].flatten)
|
164
|
+
|
165
|
+
return [] if response.first.to_s =~ /^#{UNZIP_PROGRAM_EMPTY_ZIPFILE_MESSAGE}/
|
166
|
+
|
167
|
+
if response.first
|
168
|
+
member_list = response.first.split("\n").sort
|
169
|
+
member_list = member_list.select{|m| m =~ /^#{prefix}/} if prefix
|
170
|
+
return member_list
|
171
|
+
else # error, response.last should contain error message
|
172
|
+
raise_info_zip_error!(response.last)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def read_member(member_path, options={})
|
177
|
+
archive_exists!
|
178
|
+
member_not_found!(member_path) if member_path =~ /\/$/
|
179
|
+
response = MultiZip::Backend::Cli::InfoZip.spawn([
|
180
|
+
UNZIP_PROGRAM, UNZIP_PROGRAM_READ_MEMBER_SWITCH, @filename, member_path
|
181
|
+
].flatten)
|
182
|
+
|
183
|
+
return response.first if response.first
|
184
|
+
|
185
|
+
raise_info_zip_error!(response.last, :member_path => member_path)
|
186
|
+
end
|
187
|
+
|
188
|
+
def write_member(member_path, member_content, options={})
|
189
|
+
Dir.mktmpdir do |tempdir|
|
190
|
+
member_file = File.new("#{tempdir}/#{member_path}", 'wb')
|
191
|
+
member_file.print member_content
|
192
|
+
member_file.close
|
193
|
+
|
194
|
+
cwd = Dir.pwd
|
195
|
+
Dir.chdir(tempdir)
|
196
|
+
|
197
|
+
response = MultiZip::Backend::Cli::InfoZip.spawn([
|
198
|
+
ZIP_PROGRAM, @filename, member_path
|
199
|
+
])
|
200
|
+
|
201
|
+
Dir.chdir(cwd)
|
202
|
+
end
|
203
|
+
true
|
204
|
+
end
|
205
|
+
|
206
|
+
def remove_member(member_path, options={})
|
207
|
+
archive_exists!
|
208
|
+
raise MultiZip::MemberNotFoundError.new(member_path) unless member_exists?(member_path)
|
209
|
+
|
210
|
+
response = MultiZip::Backend::Cli::InfoZip.spawn([
|
211
|
+
ZIP_PROGRAM, ZIP_PROGRAM_REMOVE_MEMBER_SWITCH, @filename, member_path
|
212
|
+
].flatten)
|
213
|
+
|
214
|
+
return response.first if response.first
|
215
|
+
|
216
|
+
raise_info_zip_error!(response.last, :member_path => member_path)
|
217
|
+
end
|
218
|
+
|
219
|
+
def member_info(member_path, options={})
|
220
|
+
archive_exists!
|
221
|
+
|
222
|
+
response = MultiZip::Backend::Cli::InfoZip.spawn([
|
223
|
+
UNZIP_PROGRAM, UNZIP_PROGRAM_MEMBER_INFO_SWITCHES, @filename, member_path
|
224
|
+
].flatten).compact
|
225
|
+
|
226
|
+
if response.join =~ /#{UNZIP_PROGRAM_INVALID_FILE_MESSAGE}/
|
227
|
+
raise_info_zip_error!(response.join)
|
228
|
+
end
|
229
|
+
|
230
|
+
# example line:
|
231
|
+
# -rwx------ 2.1 unx 558 bX defN 15-Jun-22 17:53 ROBO3DR1PLUSV1/BlinkM.cpp
|
232
|
+
|
233
|
+
line = response.detect{|r| r.strip.match(/#{member_path}$/)}
|
234
|
+
|
235
|
+
raise MultiZip::MemberNotFoundError.new(member_path) if line.nil? || line =~ /#{UNZIP_PROGRAM_MEMBER_NOT_FOUND_MESSAGE}/i
|
236
|
+
|
237
|
+
fields = line.split(/\s+/)
|
238
|
+
|
239
|
+
path = fields.last
|
240
|
+
unless path == member_path
|
241
|
+
raise MultiZip::Backend::Cli::InfoZip::ResponseError, "Unexpected file name format or position: #{line.inspect}"
|
242
|
+
end
|
243
|
+
|
244
|
+
size = fields[3]
|
245
|
+
unless size =~ /\d+/
|
246
|
+
raise MultiZip::Backend::Cli::InfoZip::ResponseError, "Unexpected file size format or position: #{line.inspect}"
|
247
|
+
end
|
248
|
+
|
249
|
+
type = case fields[0].slice(0,1)
|
250
|
+
when 'd'
|
251
|
+
:directory
|
252
|
+
when 'l'
|
253
|
+
:symlink
|
254
|
+
when '-'
|
255
|
+
:file
|
256
|
+
else
|
257
|
+
raise MultiZip::Backend::Cli::InfoZip::ResponseError, "Unexpected file type field format or position: #{line.inspect}"
|
258
|
+
end
|
259
|
+
|
260
|
+
{
|
261
|
+
path: fields.last,
|
262
|
+
size: size.to_i,
|
263
|
+
type: type,
|
264
|
+
original: line
|
265
|
+
}
|
266
|
+
|
267
|
+
end
|
268
|
+
|
269
|
+
def raise_info_zip_error!(message, options={})
|
270
|
+
infozip_error = MultiZip::Backend::Cli::InfoZip::ResponseError.new(message)
|
271
|
+
case message
|
272
|
+
when /#{UNZIP_PROGRAM_INVALID_FILE_MESSAGE}/
|
273
|
+
raise MultiZip::InvalidArchiveError.new(@filename, infozip_error)
|
274
|
+
when /cannot find or open/
|
275
|
+
raise MultiZip::ArchiveNotFoundError.new(@filename, infozip_error)
|
276
|
+
when /filename not matched/
|
277
|
+
raise MultiZip::MemberNotFoundError.new(options[:member_path])
|
278
|
+
else
|
279
|
+
raise MultiZip::UnknownError.new(@filename, infozip_error)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
class ResponseError < MultiZip::InvalidArchiveError
|
286
|
+
attr_reader :message
|
287
|
+
def initialize(error_message)
|
288
|
+
@message = error_message
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
@@ -55,6 +55,21 @@ module MultiZip::Backend::Rubyzip
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
+
def member_info(member_path, options = {})
|
59
|
+
read_operation do |zip_file|
|
60
|
+
if zip_entry = zip_file.detect{|ze| ze.name == member_path}
|
61
|
+
{
|
62
|
+
path: zip_entry.name,
|
63
|
+
size: zip_entry.size.to_i,
|
64
|
+
type: zip_entry.ftype,
|
65
|
+
original: zip_entry
|
66
|
+
}
|
67
|
+
else
|
68
|
+
member_not_found!(member_path)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
58
73
|
private
|
59
74
|
|
60
75
|
def read_operation(&blk)
|
@@ -70,4 +85,4 @@ private
|
|
70
85
|
raise
|
71
86
|
end
|
72
87
|
end
|
73
|
-
end
|
88
|
+
end
|
@@ -58,6 +58,25 @@ module MultiZip::Backend::Zipruby
|
|
58
58
|
true
|
59
59
|
end
|
60
60
|
|
61
|
+
def member_info(member_path, options = {})
|
62
|
+
read_operation do |zip|
|
63
|
+
member_location = zip.locate_name(member_path)
|
64
|
+
if member_location == -1
|
65
|
+
zip.close
|
66
|
+
member_not_found!(member_path)
|
67
|
+
end
|
68
|
+
|
69
|
+
member_stats = zip.get_stat(member_location)
|
70
|
+
|
71
|
+
{
|
72
|
+
path: member_stats.name,
|
73
|
+
size: member_stats.size.to_i,
|
74
|
+
type: member_stats.directory? ? :directory : :file,
|
75
|
+
original: member_stats
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
61
80
|
private
|
62
81
|
|
63
82
|
# NOTE: Zip::Archive#locate_name return values
|
@@ -91,4 +110,4 @@ private
|
|
91
110
|
raise
|
92
111
|
end
|
93
112
|
end
|
94
|
-
end
|
113
|
+
end
|
data/lib/multi_zip/errors.rb
CHANGED
data/lib/multi_zip/version.rb
CHANGED
@@ -200,6 +200,55 @@ shared_examples 'zip backend' do |backend_name|
|
|
200
200
|
end
|
201
201
|
end
|
202
202
|
|
203
|
+
describe '#member_info' do
|
204
|
+
context 'member found' do
|
205
|
+
archive_member_files.each do |member_file|
|
206
|
+
it "returns hash of member information" do
|
207
|
+
info = subject.member_info(member_file)
|
208
|
+
expect(info).to include(
|
209
|
+
{
|
210
|
+
path: member_file,
|
211
|
+
size: archive_member_size(member_file),
|
212
|
+
type: :file
|
213
|
+
}
|
214
|
+
)
|
215
|
+
|
216
|
+
expect(info[:original]).to_not be_nil
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context 'member not found' do
|
222
|
+
it_behaves_like 'raises MemberNotFoundError', :member_info, 'doesnt_exist'
|
223
|
+
end
|
224
|
+
|
225
|
+
context 'member is a directory' do
|
226
|
+
it "returns hash of member information" do
|
227
|
+
expect(
|
228
|
+
subject.member_info(archive_member_directories.first)
|
229
|
+
).to include(
|
230
|
+
{
|
231
|
+
path: archive_member_directories.first,
|
232
|
+
size: archive_member_size(archive_member_directories.first).to_i,
|
233
|
+
type: :directory
|
234
|
+
}
|
235
|
+
)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
it_behaves_like 'archive not found, raises ArchiveNotFoundError', :member_info, archive_member_files.first
|
240
|
+
|
241
|
+
it_behaves_like 'archive is not a file, raises ArchiveNotFoundError', :member_info, archive_member_files.first
|
242
|
+
|
243
|
+
context 'archive cannot be accessed due to permissions' do
|
244
|
+
it 'raises ArchiveNotAccessibleError'
|
245
|
+
end
|
246
|
+
|
247
|
+
context 'invalid or unreadable archive' do
|
248
|
+
it_behaves_like 'raises InvalidArchiveError', :member_info, archive_member_files.first
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
203
252
|
describe '#write_member' do
|
204
253
|
after { FileUtils.rm(filename) if File.exists?(filename) }
|
205
254
|
|
@@ -318,35 +367,51 @@ shared_examples 'zip backend' do |backend_name|
|
|
318
367
|
context 'archive exists' do
|
319
368
|
before do
|
320
369
|
FileUtils.cp(filename, temp_filename)
|
321
|
-
expect(
|
322
|
-
MultiZip.new(temp_filename).member_exists?(member_file_name)
|
323
|
-
).to be_truthy
|
324
370
|
end
|
325
371
|
|
326
|
-
|
327
|
-
|
328
|
-
end
|
329
|
-
|
330
|
-
context 'member removed successfully' do
|
331
|
-
it 'returns true' do
|
332
|
-
expect(result).to be_truthy
|
333
|
-
end
|
334
|
-
it 'removes the member from the file' do
|
372
|
+
context 'member found in archive' do
|
373
|
+
let!(:result) do
|
335
374
|
expect(
|
336
375
|
MultiZip.new(temp_filename).member_exists?(member_file_name)
|
337
|
-
).to
|
376
|
+
).to be_truthy
|
377
|
+
|
378
|
+
subject.remove_member(member_file_name)
|
338
379
|
end
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
expect(
|
380
|
+
|
381
|
+
context 'member removed successfully' do
|
382
|
+
it 'returns true' do
|
383
|
+
expect(result).to be_truthy
|
384
|
+
end
|
385
|
+
it 'removes the member from the file' do
|
386
|
+
expect(
|
387
|
+
MultiZip.new(temp_filename).member_exists?(member_file_name)
|
388
|
+
).to be_falsey
|
389
|
+
end
|
390
|
+
it 'does not remove any other members' do
|
391
|
+
zip = MultiZip.new(temp_filename)
|
392
|
+
(archive_member_files - [member_file_name]).each do |mfn|
|
393
|
+
expect(zip.member_exists?(mfn)).to be_truthy
|
394
|
+
end
|
343
395
|
end
|
344
396
|
end
|
397
|
+
|
398
|
+
context 'member not successfully removed' do
|
399
|
+
it 'raises MemberNotRemovedError'
|
400
|
+
it 'does not remove member from the archive'
|
401
|
+
end
|
345
402
|
end
|
346
403
|
|
347
|
-
context 'member not
|
348
|
-
|
349
|
-
|
404
|
+
context 'member not found in archive' do
|
405
|
+
before do
|
406
|
+
expect(
|
407
|
+
MultiZip.new(temp_filename).member_exists?('doesnt_exist')
|
408
|
+
).to be_falsey
|
409
|
+
end
|
410
|
+
it 'raises MemberNotFoundError' do
|
411
|
+
expect(
|
412
|
+
lambda { subject.remove_member('doesnt_exist') }
|
413
|
+
).to raise_error(MultiZip::MemberNotFoundError)
|
414
|
+
end
|
350
415
|
end
|
351
416
|
end
|
352
417
|
|
@@ -463,4 +528,3 @@ shared_examples 'archive is not a file, raises ArchiveNotFoundError' do |*args|
|
|
463
528
|
it_behaves_like 'raises ArchiveNotFoundError', *args
|
464
529
|
end
|
465
530
|
end
|
466
|
-
|
data/spec/lib/multi_zip_spec.rb
CHANGED
@@ -2,7 +2,8 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe MultiZip do
|
4
4
|
let(:filename) { archive_fixture_filename }
|
5
|
-
let(:subject) { MultiZip.new(filename) }
|
5
|
+
let(:subject) { MultiZip.new(filename, instance_options) }
|
6
|
+
let(:instance_options) { {} }
|
6
7
|
|
7
8
|
describe '.new' do
|
8
9
|
it 'accepts a block'
|
@@ -24,10 +25,11 @@ RSpec.describe MultiZip do
|
|
24
25
|
end
|
25
26
|
|
26
27
|
context 'unknown backend' do
|
28
|
+
let(:instance_options) { { :allow_no_backends => true } }
|
27
29
|
it 'raises exception' do
|
28
30
|
expect(
|
29
31
|
lambda { subject.backend = 'unsupported' }
|
30
|
-
).to raise_exception(MultiZip::
|
32
|
+
).to raise_exception(MultiZip::InvalidBackendError)
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
@@ -50,4 +52,4 @@ RSpec.describe MultiZip do
|
|
50
52
|
end
|
51
53
|
end
|
52
54
|
end
|
53
|
-
end
|
55
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'multi_zip'
|
2
2
|
|
3
3
|
# can't use pry on old rubies or on rubinius
|
4
|
-
if RUBY_VERSION.to_f >= 2.
|
4
|
+
if RUBY_VERSION.to_f >= 2.2 && RUBY_ENGINE == 'ruby'
|
5
5
|
require 'byebug'
|
6
6
|
end
|
7
7
|
|
@@ -76,28 +76,61 @@ def archive_member_directories
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def test_with_rubyzip?
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
79
|
+
test = if ENV['ONLY']
|
80
|
+
ENV['ONLY'] == 'rubyzip'
|
81
|
+
else
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
if test
|
86
|
+
# rubyzip requires ruby >= 1.9.2
|
87
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("1.9.2")
|
88
|
+
gem 'rubyzip'
|
89
|
+
return true
|
90
|
+
end
|
91
|
+
return false
|
83
92
|
end
|
84
|
-
false
|
85
93
|
rescue Gem::LoadError
|
86
94
|
false
|
87
95
|
end
|
88
96
|
|
89
97
|
def test_with_zipruby?
|
90
|
-
|
91
|
-
|
98
|
+
test = if ENV['ONLY']
|
99
|
+
ENV['ONLY'] == 'zipruby'
|
100
|
+
else
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
104
|
+
if test
|
105
|
+
gem 'zipruby'
|
106
|
+
return true
|
107
|
+
end
|
92
108
|
rescue Gem::LoadError
|
93
109
|
false
|
94
110
|
end
|
95
111
|
|
112
|
+
def test_with_archive_zip?
|
113
|
+
if ENV['ONLY']
|
114
|
+
ENV['ONLY'] =~ /archive/
|
115
|
+
else
|
116
|
+
true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_with_cli?
|
121
|
+
if ENV['ONLY']
|
122
|
+
ENV['ONLY'] = 'cli'
|
123
|
+
else
|
124
|
+
MultiZip::Backend::Cli.strategy_available?
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
96
128
|
def backends_to_test
|
97
129
|
@backends_to_test ||= [
|
98
130
|
(test_with_zipruby? ? :zipruby : nil),
|
99
131
|
(test_with_rubyzip? ? :rubyzip : nil),
|
100
|
-
:archive_zip
|
132
|
+
(test_with_archive_zip? ? :archive_zip : nil),
|
133
|
+
(test_with_cli? ? :cli : nil),
|
101
134
|
].compact
|
102
135
|
end
|
103
136
|
|
@@ -108,8 +141,13 @@ if excluded_backends.length > 0
|
|
108
141
|
warn "*** Backends that will not be tested: #{excluded_backends.map(&:to_s).join(', ')} ***"
|
109
142
|
end
|
110
143
|
|
111
|
-
|
112
|
-
|
144
|
+
puts "*** MultiZip::Backend::Cli.strategy_available?: #{MultiZip::Backend::Cli.strategy_available?.inspect}"
|
145
|
+
if MultiZip::Backend::Cli.strategy_available?
|
146
|
+
puts "*** MultiZip::Backend::Cli.strategy: #{MultiZip::Backend::Cli.strategy.inspect}"
|
147
|
+
end
|
148
|
+
|
149
|
+
BACKEND_CONSTANTS = Hash.new([])
|
150
|
+
BACKEND_CLASSES = Hash.new([])
|
113
151
|
|
114
152
|
def set_backend_class(lib, klass)
|
115
153
|
BACKEND_CONSTANTS[lib.to_sym] = klass.constants
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: multi_zip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Nielsen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-12-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -65,6 +65,7 @@ files:
|
|
65
65
|
- ".rspec"
|
66
66
|
- ".travis.yml"
|
67
67
|
- BACKEND_CONSTANTS.md
|
68
|
+
- CHANGELOG.md
|
68
69
|
- Gemfile
|
69
70
|
- Guardfile
|
70
71
|
- LICENSE.txt
|
@@ -73,6 +74,8 @@ files:
|
|
73
74
|
- TODO.md
|
74
75
|
- lib/multi_zip.rb
|
75
76
|
- lib/multi_zip/backend/archive_zip.rb
|
77
|
+
- lib/multi_zip/backend/cli.rb
|
78
|
+
- lib/multi_zip/backend/cli/info_zip.rb
|
76
79
|
- lib/multi_zip/backend/rubyzip.rb
|
77
80
|
- lib/multi_zip/backend/zipruby.rb
|
78
81
|
- lib/multi_zip/errors.rb
|
@@ -84,6 +87,7 @@ files:
|
|
84
87
|
- spec/fixtures/test.zip
|
85
88
|
- spec/fixtures/test/.gitkeep
|
86
89
|
- spec/lib/multi_zip/backend/archive_zip_spec.rb
|
90
|
+
- spec/lib/multi_zip/backend/cli_spec.rb
|
87
91
|
- spec/lib/multi_zip/backend/rubyzip_spec.rb
|
88
92
|
- spec/lib/multi_zip/backend/zipruby_spec.rb
|
89
93
|
- spec/lib/multi_zip_spec.rb
|
@@ -108,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
112
|
version: '0'
|
109
113
|
requirements: []
|
110
114
|
rubyforge_project:
|
111
|
-
rubygems_version: 2.
|
115
|
+
rubygems_version: 2.4.5.1
|
112
116
|
signing_key:
|
113
117
|
specification_version: 4
|
114
118
|
summary: Abstracts zipping and unzipping using whatever gems are installed, automatically.
|
@@ -119,6 +123,7 @@ test_files:
|
|
119
123
|
- spec/fixtures/test.zip
|
120
124
|
- spec/fixtures/test/.gitkeep
|
121
125
|
- spec/lib/multi_zip/backend/archive_zip_spec.rb
|
126
|
+
- spec/lib/multi_zip/backend/cli_spec.rb
|
122
127
|
- spec/lib/multi_zip/backend/rubyzip_spec.rb
|
123
128
|
- spec/lib/multi_zip/backend/zipruby_spec.rb
|
124
129
|
- spec/lib/multi_zip_spec.rb
|