multi_zip 0.1.4 → 0.1.6
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 +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
|