zipr 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 59ff464e79a2fcb648409fa45f1732713f282b34
4
+ data.tar.gz: 3fad749fa16e837f55419322237e945da88e6156
5
+ SHA512:
6
+ metadata.gz: c16b81553150ba9eb0953136387c2fbd9f6d31e57f630a3501005a7eb0dd4d80bc77fbfbdaa8124ccc4e538cfc429361c7ab5cf77b1d82e1018bf0b09e17977c
7
+ data.tar.gz: 548670732186cf43020dd5f47a5d147b6868669da7be05da055967d76ffd1b55c32e23921fcdb40f12d7d9b2a92e325c535944ace9cff2fef02d4bdaf9e6f45f
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,30 @@
1
+ #
2
+ # Author:: Alex Munoz (<amunoz951@gmail.com>)
3
+ # Copyright:: Copyright (c) 2020 Alex Munoz
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ require 'zip' # rubyzip gem
19
+ require 'digest'
20
+ require 'easy_io'
21
+ require 'json'
22
+ require 'tmpdir'
23
+ require 'fileutils'
24
+ require 'seven_zip_ruby_am'
25
+ require 'os'
26
+
27
+ require_relative 'zipr/config'
28
+ require_relative 'zipr/archive'
29
+ require_relative 'zipr/helper'
30
+ require_relative 'zipr/sfx'
Binary file
Binary file
@@ -0,0 +1,462 @@
1
+ module Zipr
2
+ class Archive
3
+ @cache_path = "#{Zipr.config['paths']['cache']}/zipr"
4
+ attr_accessor :checksum_path, :options, :checksums, :mode
5
+
6
+ #
7
+ # Description:
8
+ # Create an instance of the class.
9
+ # Allows usage of block form.
10
+ # Returns:
11
+ # When used without a block, returns the instance.
12
+ # Parameters:
13
+ # options:
14
+ # :archive_type - The type of archive - :seven_zip or :zip - Can be omitted if the archive exists or using default. Default: :zip
15
+ # :exclude_files - Array of files to be excluded from archiving/extracting - Can be relative or exact paths.
16
+ # :exclude_unless_missing - Array of files to be excluded from archiving/extracting only if they already exist - Can be relative or exact paths.
17
+ # :password - the archive password - currently :seven_zip is the only supported archive_type for encrypted archives.
18
+ # :silent - [true/false] No info messages if flagged
19
+ # checksums: A hash of checksums of the archived files. If you checked one of the determine_files methods for idempotency first, pass the result to this parameter to avoid duplicate processing.
20
+ # mode:
21
+ # :idempotent - Does not add/extract files to/from the archive if they already exist and are the exact same file, verified by checksum.
22
+ # :overwrite - Adds/Extracts all eligible files to/from the archive even if the checksums match.
23
+ # :if_missing - never overwrites any files, only adds/extracts to/from the archive if the relative path does not exist.
24
+ # checksum_file: (optional) The path to the initial checksum file. Usually only needed if the archive is not kept on the disk and we need to see if extracted files have changed.
25
+ def self.open(path, options: {}, checksums: {}, mode: :idempotent, checksum_file: nil, &block)
26
+ archive_instance = new(path, options: options, checksums: checksums, mode: mode, checksum_file: checksum_file)
27
+ block.nil? ? archive_instance : yield(archive_instance)
28
+ end
29
+
30
+ #
31
+ # Description:
32
+ # Add files to an existing archive or create one if it does not exist in a single line. No additional IO handling necessary.
33
+ # Returns:
34
+ # Array of 2 objects: String path to the checksum file and the hash of known checksums for archive files.
35
+ # Parameters:
36
+ # path: The full path to the archive being added to/created.
37
+ # see comment of method 'add' below for :source_folder and :files_to_add parameter information.
38
+ # see comment of method 'open' above for remaining parameter information.
39
+ def self.add(path, source_folder, files_to_add: nil, options: {}, checksums: {}, mode: :idempotent, checksum_file: nil)
40
+ archive = new(path, options: options, checksums: checksums, mode: mode, checksum_file: checksum_file)
41
+ archive.add(source_folder, files_to_add: files_to_add)
42
+ end
43
+
44
+ #
45
+ # Description:
46
+ # Extract files from an archive in a single line. No additional IO handling necessary.
47
+ # Returns:
48
+ # Array of 2 objects: String path to the checksum file and the hash of known checksums for archive files.
49
+ # Parameters:
50
+ # path: The full path to the archive being extracted.
51
+ # see comment of method 'extract' for :destination_folder and :files_to_extract parameter information.
52
+ # see comment of method 'self.open' for remaining parameter information.
53
+ def self.extract(path, destination_folder, files_to_extract: nil, options: {}, checksums: nil, mode: :idempotent, checksum_file: nil)
54
+ archive = new(path, options: options, checksums: checksums, mode: mode, checksum_file: checksum_file)
55
+ archive.extract(destination_folder, files_to_extract: files_to_extract)
56
+ end
57
+
58
+ #
59
+ # Description:
60
+ # Determines what files should be added to an archive based on the options and mode provided in a single line.
61
+ # Can be useful to perform actions on changing files before they are added or to determine idempotency state (like with Chef/Puppet)
62
+ # Returns:
63
+ # Array of 2 objects: an array of files to be added (or the :all symbol) and a hash of the known file checksums in the archive
64
+ # Parameters:
65
+ # path: The full path to the archive being created/examined.
66
+ # see comment of method 'determine_files_to_add' for :source_folder and :files_to_check
67
+ # see comment of method 'open' for remaining parameter information.
68
+ def self.determine_files_to_add(path, source_folder, files_to_check: nil, options: {}, checksums: {}, mode: :idempotent, checksum_file: nil)
69
+ archive = new(path, options: options, checksums: checksums, mode: mode, checksum_file: checksum_file)
70
+ files_to_add = archive.determine_files_to_add(source_folder, files_to_check: files_to_check)
71
+ [files_to_add, archive.checksums]
72
+ end
73
+
74
+ #
75
+ # Description:
76
+ # Determines what files should be extracted based on the options and mode provided.
77
+ # Can be useful to perform actions on changing files before they are extracted or to determine idempotency state (like with Chef/Puppet).
78
+ # Returns:
79
+ # Array of 2 objects: an array of files to be extracted (or the :all symbol) and a hash of the known file checksums in the archive.
80
+ # Parameters:
81
+ # path: The full path to the archive being extracted/examined.
82
+ # see comment of method 'determine_files_to_extract' for :destination_folder and :files_to_check parameter information.
83
+ # see comment of method 'open' for remaining parameter information.
84
+ def self.determine_files_to_extract(path, destination_folder, files_to_check: nil, options: {}, checksums: {}, mode: :idempotent, checksum_file: nil)
85
+ archive = new(path, options: options, checksums: checksums, mode: mode, checksum_file: checksum_file)
86
+ files_to_extract = archive.determine_files_to_extract(destination_folder, files_to_check: files_to_check)
87
+ [files_to_extract, archive.checksums]
88
+ end
89
+
90
+ #
91
+ # Description:
92
+ # Initializes the Archive.
93
+ # Parameters:
94
+ # path: The path to the existing archive or where the archive will be created.
95
+ # see comment of method 'open' for remaining parameter information.
96
+ def initialize(path, options: nil, checksums: nil, mode: nil, checksum_file: nil)
97
+ @path = path
98
+ archive_checksum = ::File.exist?(path) ? ::Digest::SHA256.file(path).hexdigest : 'does_not_exist'
99
+ @checksum_path = checksum_file || "#{@cache_path}/checksums/#{::File.basename(path)}-#{archive_checksum}.txt"
100
+ _assign_common_accessors(options: options, checksums: checksums, mode: mode)
101
+ @options[:archive_type] ||= (::File.exist?(@path) ? detect_archive_type : :zip)
102
+ end
103
+
104
+ #
105
+ # Description:
106
+ # Extract files from an archive
107
+ # Returns:
108
+ # Array of 2 objects: String path to the checksum file and the hash of known checksums for archive files.
109
+ # Parameters:
110
+ # path: The full path to the archive being extracted.
111
+ # destination_folder: The path where files will be extracted to.
112
+ # files_to_extract: An array of files to be extracted from the archive. Relative paths should be used. If omitted, extracts all contents of the archive.
113
+ def extract(destination_folder, files_to_extract: nil)
114
+ raise "Unable to extract #{@path}! The file does not exist!" unless ::File.file?(@path)
115
+ @options[:overwrite] = @mode != :if_missing
116
+ files_to_extract = determine_files_to_extract(destination_folder, files_to_check: files_to_extract)
117
+
118
+ case @options[:archive_type]
119
+ when :zip
120
+ _extract_zip(destination_folder, files_to_extract)
121
+ when :seven_zip
122
+ _extract_seven_zip(destination_folder, files_to_extract)
123
+ end
124
+
125
+ EasyIO.logger.info "Extracting #{::File.basename(@path)} finished." unless @options[:silent]
126
+ [@checksum_path, update_checksum_file]
127
+ end
128
+
129
+ #
130
+ # Description:
131
+ # Add files to an existing archive or create one if it does not exist
132
+ # Returns:
133
+ # Array of 2 objects: String path to the checksum file and the hash of known checksums for archive files.
134
+ # Parameters:
135
+ # source_folder: The path from which relative archive paths will be derived.
136
+ # files_to_add: An array of files to be added to the archive. Relative or full paths accepted. If omitted, takes all files and folders in the source_folder.
137
+ def add(source_folder, files_to_add: nil)
138
+ files_to_add = determine_files_to_add(source_folder, files_to_check: files_to_add)
139
+ FileUtils.mkdir_p(::File.dirname(@path)) unless ::File.directory?(::File.dirname(@path))
140
+
141
+ case @options[:archive_type]
142
+ when :zip
143
+ _add_to_zip(source_folder, files_to_add)
144
+ when :seven_zip
145
+ _add_to_seven_zip(source_folder, files_to_add)
146
+ else
147
+ raise "':#{@options[:archive_type]}' is not a supported archive type!"
148
+ end
149
+ raise "Failed to create archive at #{@path}!" unless ::File.file?(@path)
150
+ EasyIO.logger.info "Archiving to #{::File.basename(@path)} finished." unless @options[:silent]
151
+ archive_checksums = @options[:sfx] ? @checksums : update_checksum_file # Don't update the checksum file yet if it's an SFX
152
+ [@checksum_path, archive_checksums]
153
+ end
154
+
155
+ #
156
+ # Description:
157
+ # Writes the archive's checksum file.
158
+ # Returns:
159
+ # A hash of all known checksums for the archive.
160
+ def update_checksum_file
161
+ archive_path = @options[:sfx] ? @sfx_path : @path
162
+ archive_checksum = ::Digest::SHA256.file(archive_path).hexdigest
163
+ @checksums['archive_checksum'] = archive_checksum
164
+ @checksum_path = "#{@cache_path}/checksums/#{::File.basename(archive_path)}-#{archive_checksum}.txt"
165
+ FileUtils.mkdir_p(::File.dirname(checksum_path)) unless ::File.directory?(::File.dirname(checksum_path))
166
+ ::File.write(checksum_path, @checksums.to_json)
167
+ @checksums
168
+ end
169
+
170
+ # Description:
171
+ # Loads the known checksums for this archive from the checksum file.
172
+ # Returns:
173
+ # A hash of all known checksums for the archive.
174
+ def load_checksum_file
175
+ @checksums = if ::File.exist?(@checksum_path) # Read the checksums file if it hasn't been read yet
176
+ file_content = ::File.read(@checksum_path)
177
+ JSON.parse(file_content)
178
+ else
179
+ {}
180
+ end
181
+ end
182
+
183
+ #
184
+ # Description:
185
+ # Read a file inside a zip file without extracting it to the filesystem.
186
+ # Currently only supports :zip files
187
+ # Returns:
188
+ # A string containing the file contents.
189
+ # Parameters:
190
+ # relative_path: The path inside the archive for the file to view.
191
+ def view_file(relative_path)
192
+ raise 'Reading files inside a 7zip archive is not yet supported!' if @options[:archive_type] == :seven_zip
193
+ EasyIO.logger.debug "Reading #{@path} // #{relative_path}..."
194
+ ::Zip::File.open(@path).read(relative_path)
195
+ end
196
+
197
+ #
198
+ # Description:
199
+ # Determines what files should be extracted based on the options and mode provided.
200
+ # Returns:
201
+ # An array of files to be extracted (or the :all symbol).
202
+ # Parameters:
203
+ # destination_folder: Where the files would be extracted to.
204
+ # files_to_check: Array of files intended to be extracted from an archive. Should be relative names/paths with or without asterisk wildcards or a regular expression.
205
+ # default: All files and folders in the archive.
206
+ def determine_files_to_extract(destination_folder, files_to_check: nil)
207
+ files_to_check ||= :all # defaults to :all files
208
+
209
+ unless ::File.exist?(@path)
210
+ # If the archive doesn't exist but checksums were provided, check for files to extract based off of checksums
211
+ return @checksums.select { |entry_name, checksum| _extract_file?(entry_name, checksum == 'directory', destination_folder, files_to_check) }.keys if @checksums.nil? || @checksums.empty?
212
+ # If the archive doesn't exist and no checksums were found, extract all files_to_check or :all files
213
+ return files_to_check || :all
214
+ end
215
+
216
+ files_to_extract = case @options[:archive_type]
217
+ when :zip
218
+ _determine_zip_files_to_extract(destination_folder, files_to_check)
219
+ when :seven_zip
220
+ _determine_seven_zip_files_to_extract(destination_folder, files_to_check)
221
+ else
222
+ raise "':#{@options[:archive_type]}' is not a supported archive type!"
223
+ end
224
+
225
+ EasyIO.logger.debug "Files to extract: #{files_to_extract.empty? ? 'none' : JSON.pretty_generate(files_to_extract)}"
226
+ files_to_extract
227
+ end
228
+
229
+ #
230
+ # Description:
231
+ # Determines what files should be added to an archive based on the options and mode provided.
232
+ # Returns:
233
+ # An array of files to be added (or the :all symbol).
234
+ # Parameters:
235
+ # source_folder: The filesystem directory where files are being added from.
236
+ # files_to_check: Array of files intended to be added to an archive. Can be exact names/paths or names/paths with wildcards (glob style).
237
+ # default: All files and folders under the source_folder.
238
+ def determine_files_to_add(source_folder, files_to_check: nil)
239
+ files_to_check ||= Dir.glob("#{source_folder}/**/*".tr('\\', '/')) if files_to_check.nil?
240
+ files_to_add = []
241
+ files_to_check.each do |target_search|
242
+ files = Dir.glob(Zipr.prepend_source_folder(source_folder, target_search))
243
+ files.each do |source_file|
244
+ relative_path = Zipr.slice_source_folder(source_folder, source_file)
245
+ exists_in_zip = !!@checksums[relative_path]
246
+ next if @mode == :if_missing && exists_in_zip
247
+ next if _excluded_file?(relative_path, exists_in_zip: exists_in_zip) || _excluded_file?(source_file, exists_in_zip: exists_in_zip)
248
+ next if @mode == :idempotent && ::File.file?(source_file) && @checksums[relative_path] == Digest::SHA256.file(source_file).hexdigest
249
+ next if ::File.directory?(source_file) && @checksums[relative_path] == 'directory'
250
+ EasyIO.logger.debug "'#{relative_path}' would be added to archive..."
251
+ files_to_add.push(source_file)
252
+ end
253
+ end
254
+ files_to_add
255
+ end
256
+
257
+ #
258
+ # Description:
259
+ # Detects what kind of archive the existing file is.
260
+ # Returns:
261
+ # The type of archive detected.
262
+ # Parameters:
263
+ # archive_types: (optional) The array of archive_types to try. Be default, tries all archive types.
264
+ def detect_archive_type(archive_types = supported_archive_types)
265
+ try_archive_type = archive_types.shift
266
+ case try_archive_type
267
+ when :zip
268
+ ::Zip::File.open(@path) { |_archive_file| } # Test if the file can be opened as a zip file
269
+ when :seven_zip
270
+ SevenZipRuby::Reader.open_file(@path) { |_archive_file| } # Test if the file can be opened as a 7z file
271
+ else
272
+ raise "Archive type for #{try_archive_type} not implemented! Add it to Zipr::Archive.detect_archive_type definition."
273
+ end
274
+ @archive_type = try_archive_type
275
+ rescue ::Zip::Error, StandardError
276
+ remaining_message = archive_types.empty? ? 'No remaining types to attempt!' : "Attempting remaining types (#{archive_types.join(', ')})..."
277
+ EasyIO.logger.debug "Archive does not appear to be a #{try_archive_type}. #{remaining_message}"
278
+ return detect_archive_type(archive_types) unless archive_types.empty? # Try the remaining archive types unless none are left
279
+ raise "Archive type for #{@path} could not be detected! Ensure it is in the list of supported types (#{supported_archive_types.join(', ')})."
280
+ end
281
+
282
+ #
283
+ # Description:
284
+ # Defines all supported archive types.
285
+ # Returns:
286
+ # Array of supported archive types.
287
+ def supported_archive_types
288
+ [:zip, :seven_zip]
289
+ end
290
+
291
+ #
292
+ # Description:
293
+ # More readably access the archive_type option.
294
+ # Returns:
295
+ # A symbol representing the archive type of the instance.
296
+ def archive_type
297
+ @options[:archive_type]
298
+ end
299
+
300
+ private
301
+
302
+ def _extract_zip(destination_folder, files_to_extract)
303
+ ::Zip::File.open(@path) do |archive_items|
304
+ EasyIO.logger.info "Extracting to #{destination_folder}..." unless @options[:silent]
305
+ archive_items.each do |archive_item|
306
+ extract_item_lambda = ->(destination_path) { archive_item.extract(destination_path) { :overwrite } }
307
+ _extract_item(destination_folder, files_to_extract, archive_item.name, archive_item.ftype == :directory, extract_item_lambda)
308
+ end
309
+ end
310
+ end
311
+
312
+ def _extract_seven_zip(destination_folder, files_to_extract)
313
+ SevenZipRuby::Reader.open_file(@path, @options) do |seven_zip_archive|
314
+ EasyIO.logger.info "Extracting to #{destination_folder}..." unless @options[:silent]
315
+ seven_zip_archive.entries.each do |archive_item|
316
+ extract_item_lambda = ->(_destination_path) { seven_zip_archive.extract(archive_item.index, destination_folder) }
317
+ _extract_item(destination_folder, files_to_extract, archive_item.path, archive_item.directory?, extract_item_lambda)
318
+ end
319
+ end
320
+ end
321
+
322
+ def _extract_item(destination_folder, files_to_extract, archive_entry_name, is_directory, extract_item_lambda)
323
+ destination_path = ::File.join(destination_folder.tr('\\', '/'), archive_entry_name)
324
+ return unless files_to_extract.nil? || files_to_extract == :all || files_to_extract.include?(archive_entry_name)
325
+ if ::File.exist?(destination_path)
326
+ return unless @options[:overwrite] # skip extract if the file exists and overwrite is false
327
+ FileUtils.rm(destination_path)
328
+ end
329
+ if is_directory
330
+ FileUtils.mkdir_p(destination_path)
331
+ @checksums[archive_entry_name.tr('\\', '/')] = 'directory'
332
+ return
333
+ end
334
+
335
+ full_destination_folder = ::File.dirname(destination_path)
336
+ FileUtils.mkdir_p(full_destination_folder) unless ::File.directory?(full_destination_folder)
337
+ EasyIO.logger.info "Extracting #{archive_entry_name}..." unless @options[:silent]
338
+ extract_item_lambda.call(destination_path)
339
+ @checksums[archive_entry_name.tr('\\', '/')] = Digest::SHA256.file(destination_path).hexdigest
340
+ end
341
+
342
+ def _add_to_zip(source_folder, files_to_add)
343
+ if files_to_add.empty?
344
+ EasyIO.logger.info 'Skipping adding of files to archive. No files have changed.'
345
+ return
346
+ end
347
+ ::Zip::File.open(@path, ::Zip::File::CREATE) do |zip_archive|
348
+ EasyIO.logger.info "Adding to #{@path}..." unless @options[:silent]
349
+ files_to_add.each do |source_file|
350
+ add_directory_lambda = ->(relative_path) { zip_archive.mkdir(relative_path) }
351
+ add_file_lambda = ->(relative_path) { zip_archive.add(relative_path, source_file) { :overwrite } }
352
+ _add_item(source_file, add_directory_lambda, add_file_lambda, source_folder)
353
+ end
354
+ end
355
+ end
356
+
357
+ def _add_to_seven_zip(source_folder, files_to_add)
358
+ if files_to_add.empty?
359
+ EasyIO.logger.info 'Skipping adding of files to archive. No files have changed.'
360
+ return
361
+ end
362
+ params = @options.reject { |k, _v| k == :sfx }
363
+ SevenZipRuby::Writer.open_file("#{@path}.tmp", params) do |seven_zip_archive|
364
+ EasyIO.logger.info "Adding to #{@path}..." unless @options[:silent]
365
+ _keep_unchanged_seven_zip_files(seven_zip_archive, source_folder, files_to_add) unless @mode == :overwrite
366
+ files_to_add.each do |source_file|
367
+ add_directory_lambda = ->(relative_path) { seven_zip_archive.mkdir(relative_path) }
368
+ add_file_lambda = ->(relative_path) { seven_zip_archive.add_file(source_file, as: relative_path) }
369
+ _add_item(source_file, add_directory_lambda, add_file_lambda, source_folder)
370
+ end
371
+ end
372
+ FileUtils.mv("#{@path}.tmp", @path, force: true)
373
+ end
374
+
375
+ def _keep_unchanged_seven_zip_files(seven_zip_archive, source_folder, files_to_add)
376
+ existing_archive = @options[:sfx] ? @sfx_path : @path
377
+ # TODO: Ensure it's not duplicating kept items
378
+ if ::File.exist?(existing_archive) # If the archive already exists, save it's existing content first
379
+ EasyIO.logger.debug "Reading existing 7z archive #{existing_archive}..."
380
+ files_to_add_relative_paths = files_to_add.map { |file| Zipr.slice_source_folder(source_folder, file) }
381
+ SevenZipRuby::Reader.open_file(existing_archive, @options) do |existing_seven_zip_archive|
382
+ items_to_keep = existing_seven_zip_archive.entries.reject do |archive_item| # Don't keep (reject) entries that we'll be adding later
383
+ reject_entry = files_to_add_relative_paths.include?(archive_item.path)
384
+ @checksums.delete(archive_item.path) if reject_entry # Delete from @checksums if it's being rejected
385
+ reject_entry
386
+ end
387
+ items_to_keep.each do |archive_item|
388
+ EasyIO.logger.debug "Keeping #{archive_item.path}..."
389
+ if archive_item.directory?
390
+ seven_zip_archive.mkdir(archive_item.path)
391
+ next
392
+ end
393
+ seven_zip_archive.add_data(existing_seven_zip_archive.extract_data(archive_item.index), archive_item.path)
394
+ end
395
+ end
396
+ end
397
+ end
398
+
399
+ def _add_item(source_file, add_directory_lambda, add_file_lambda, source_folder)
400
+ relative_path = source_file.tr('\\', '/')
401
+ relative_path.slice!(source_folder.tr('\\', '/'))
402
+ relative_path = relative_path.reverse.chomp('/').reverse
403
+ EasyIO.logger.info "Adding #{relative_path}..." unless @options[:silent]
404
+ if ::File.directory?(source_file)
405
+ add_directory_lambda.call(relative_path)
406
+ archive_item_checksum = 'directory'
407
+ else
408
+ add_file_lambda.call(relative_path)
409
+ archive_item_checksum = Digest::SHA256.file(source_file).hexdigest
410
+ end
411
+
412
+ @checksums[relative_path] = archive_item_checksum
413
+ end
414
+
415
+ def _determine_zip_files_to_extract(destination_folder, files_to_check)
416
+ files_to_extract = []
417
+ ::Zip::File.open(@path) do |archive_items|
418
+ files_to_extract = archive_items.select { |archive_item| _extract_file?(archive_item.name, archive_item.ftype == :directory, destination_folder, files_to_check) }.map(&:name)
419
+ end
420
+ files_to_extract
421
+ end
422
+
423
+ def _determine_seven_zip_files_to_extract(destination_folder, files_to_check)
424
+ files_to_extract = []
425
+ SevenZipRuby::Reader.open_file(@path, @options) do |seven_zip_archive|
426
+ files_to_extract = seven_zip_archive.entries.select { |archive_item| _extract_file?(archive_item.path, archive_item.directory?, destination_folder, files_to_check) }.map(&:path)
427
+ end
428
+ files_to_extract
429
+ end
430
+
431
+ def _extract_file?(archive_entry_name, is_a_directory, destination_folder, files_to_check)
432
+ destination_path = "#{destination_folder}/#{archive_entry_name}"
433
+ return false unless files_to_check == :all || files_to_check.include?(archive_entry_name) # Make sure the file is in the whitelist if it was provided
434
+ return false if ::File.directory?(destination_path) && is_a_directory # Archive item is a directory and the destination directory exists
435
+ return false if @mode == :if_missing && ::File.exist?(destination_path) # File exists and we're not overwriting existing files due to the mode
436
+ return false if _excluded_file?(archive_entry_name, destination_path: destination_path) # File is excluded in :options
437
+ return false if @mode == :idempotent && ::File.file?(destination_path) && ::Digest::SHA256.file(destination_path).hexdigest == @checksums[archive_entry_name] # Checksum of destination file matches checksum in archive
438
+ true
439
+ end
440
+
441
+ def _excluded_file?(file_path, destination_path: '', exists_in_zip: false)
442
+ @options[:exclude_files] ||= []
443
+ @options[:exclude_unless_missing] ||= []
444
+ return true if @options[:exclude_files].any? { |e| file_path.tr('\\', '/') =~ /^#{Zipr.wildcard_to_regex(e.tr('\\', '/'))}$/i }
445
+ return true if ::File.exist?(destination_path) && @options[:exclude_unless_missing].any? { |e| file_path.tr('\\', '/') =~ /^#{Zipr.wildcard_to_regex(e.tr('\\', '/'))}$/i }
446
+ return true if exists_in_zip && @options[:exclude_unless_missing].any? { |e| file_path.tr('\\', '/') =~ /^#{Zipr.wildcard_to_regex(e.tr('\\', '/'))}$/i }
447
+ false
448
+ end
449
+
450
+ def _assign_common_accessors(options: nil, checksums: nil, mode: nil)
451
+ @options = options || @options || {}
452
+ @checksums = checksums || @checksums || {}
453
+ @mode = mode || @mode || :idempotent
454
+
455
+ archive_changed = ::File.exist?(@path) && @checksums['archive_checksum'] != Digest::SHA256.file(@path).hexdigest
456
+ outdated_checksums = @checksums.empty? || archive_changed
457
+ load_checksum_file if outdated_checksums # Read the checksums file if it hasn't been read yet
458
+ end
459
+
460
+ # TODO: Add delete method to remove something from an archive
461
+ end
462
+ end
@@ -0,0 +1,15 @@
1
+ module Zipr
2
+ module_function
3
+
4
+ def config
5
+ @config ||= EasyJSON.config(defaults: defaults)
6
+ end
7
+
8
+ def defaults
9
+ {
10
+ 'paths' => {
11
+ 'cache' => Dir.tmpdir,
12
+ },
13
+ }
14
+ end
15
+ end
@@ -0,0 +1,53 @@
1
+ module Zipr
2
+ module_function
3
+
4
+ def seven_zip_executable_path
5
+ path = node['seven_zip']['home']
6
+ EasyIO.logger.debug "7-zip home: '#{path}'" unless path.nil?
7
+ path ||= seven_zip_exe_from_registry if OS.windows?
8
+ EasyIO.logger.debug "7-zip path: '#{path}'"
9
+ ::File.join(path, OS.windows? ? '7z.exe' : '7z')
10
+ end
11
+
12
+ def seven_zip_exe_from_registry
13
+ key_path = 'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\7zFM.exe'
14
+ return nil unless EasyIO::Registry.key_exists?(key_path)
15
+ # Read path from recommended Windows App Paths registry location
16
+ # docs: https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
17
+ EasyIO::Registry.read(key_path, 'Path')
18
+ end
19
+
20
+ # returns results of all files found in the array of files, including files found by wildcard, as relative paths.
21
+ def flattened_paths(source_folder, files)
22
+ return files if source_folder.nil? || source_folder.empty?
23
+ result = []
24
+ files.each do |entry|
25
+ standardized_entry = "#{source_folder.tr('\\', '/')}/#{slice_source_folder(source_folder, entry)}"
26
+ files_found = Dir.glob(standardized_entry)
27
+ if files_found.empty?
28
+ result.push(entry)
29
+ else
30
+ result += files_found.map { |e| slice_source_folder(source_folder, e) }
31
+ end
32
+ end
33
+ result
34
+ end
35
+
36
+ def wildcard_to_regex(entry)
37
+ entry.gsub(/([^\.\]])\*/, '\1.*') # convert any asterisk wildcard not preceded by a period or square bracket to .*
38
+ .sub(/^\*/, '.*') # convert a string that starts with an asterisk to .* (not preceded by anything)
39
+ end
40
+
41
+ def prepend_source_folder(source_folder, entry)
42
+ return entry.tr('\\', '/') if source_folder.nil? || source_folder.empty? || entry.tr('\\', '/').start_with?(source_folder.tr('\\', '/'))
43
+ "#{source_folder.tr('\\', '/')}/#{entry.tr('\\', '/')}"
44
+ end
45
+
46
+ def slice_source_folder(source_folder, entry)
47
+ entry.tr('\\', '/').sub(source_folder.tr('\\', '/'), '').reverse.chomp('/').reverse
48
+ end
49
+
50
+ def checksums_folder
51
+ "#{@cache_path}/zipr/archive_checksums"
52
+ end
53
+ end
@@ -0,0 +1,113 @@
1
+ module Zipr
2
+ class SFX < Zipr::Archive
3
+ attr_accessor :info_file
4
+ attr_reader :sfx_path
5
+ attr_reader :sfx_cache_path
6
+
7
+ #
8
+ # Description:
9
+ # Create an SFX archive in one line. No additional IO handling necessary.
10
+ # Returns:
11
+ # Array of 2 objects: String path to the checksum file and the hash of known checksums for archive files.
12
+ # Parameters:
13
+ # path: The full path to the SFX archive being created.
14
+ # see comment of method 'create' for :source_folder, :files_to_add, and :info_hash parameter information.
15
+ # see comment of method 'initialize' for :temp_subfolder parameter information.
16
+ # see comment of Zipr::Archive.open for remaining parameter information.
17
+ def self.create(path, source_folder, files_to_add: nil, info_hash: nil, temp_subfolder: nil, options: nil, checksums: nil, mode: nil)
18
+ sfx_archive = new(path, temp_subfolder: temp_subfolder, options: options, checksums: checksums, mode: mode) # Create new SFX instance
19
+ sfx_archive.create(source_folder, files_to_add: files_to_add, info_hash: info_hash)
20
+ end
21
+
22
+ #
23
+ # Description:
24
+ # Initializes the Archive.
25
+ # Parameters:
26
+ # path: The path to the existing archive or where the archive will be created.
27
+ # see comment of method 'open' for remaining parameter information.
28
+ def initialize(path, temp_subfolder: nil, checksum_file: nil, options: nil, checksums: nil, mode: nil)
29
+ basename = ::File.basename(path).sub(/\.[^\.]+$/, '')
30
+ mode ||= :overwrite
31
+ temp_subfolder ||= basename
32
+ invalid_basename = basename.empty? || ::File.directory?(path)
33
+ if temp_subfolder.empty? || invalid_basename
34
+ @skip_cleanup = true
35
+ raise "SFX path was not complete! Ensure the path includes a filename.\n path: #{path}" if invalid_basename
36
+ raise 'Subfolder provided to initialize Zipr::SFX archive was empty!'
37
+ end
38
+
39
+ @sfx_path = path
40
+ @sfx_cache_path = "#{Zipr.config['paths']['cache']}/zipr/SFX/#{temp_subfolder}"
41
+ @path = "#{@sfx_cache_path}/#{basename}.sfx.7z" # Inherited class uses @path for the 7z file
42
+ path_checksum = ::File.exist?(path) ? ::Digest::SHA256.file(path).hexdigest : 'does_not_exist'
43
+ @checksum_path = checksum_file || "#{@cache_path}/checksums/#{::File.basename(path)}-#{path_checksum}.txt"
44
+ _assign_common_accessors(options: options, checksums: checksums, mode: mode)
45
+ @options[:sfx] = true
46
+ @options[:archive_type] = :seven_zip
47
+ end
48
+
49
+ # Description:
50
+ # Generate an info file for the SFX used for executing a command/file after extraction
51
+ # Parameters:
52
+ # info_hash: (optional)
53
+ # Title: Title for messages
54
+ # BeginPrompt: Message to prompt user before launching RunProgram or ExecuteFile
55
+ # Progress: Value can be "yes" or "no". Default value is "yes".
56
+ # RunProgram: Command for executing. Default value is "setup.exe". Use double backslashes for directories.
57
+ # InstallPath: Directory where files will be extracted and run from. Default value is ".\\" - Ensure the directory ends with a double slash.
58
+ # Delete: Directory to delete after launched executable exits. Use double backslashes.
59
+ # ExecuteFile: Name of file for executing
60
+ # ExecuteParameters: Parameters for "ExecuteFile"
61
+ def generate_info_file(info_hash = nil)
62
+ info_hash ||= {}
63
+ @info_file = <<-EOS.strip
64
+ ;!@Install@!UTF-8!
65
+ #{info_hash.map { |k, v| "#{k}=\"#{v}\"" }.join("\n")}
66
+ ;!@InstallEnd@!
67
+ EOS
68
+ ::File.open("#{sfx_cache_path}/sfx_info.txt", 'wb:UTF-8') { |file| file.write(@info_file) }
69
+ end
70
+
71
+ #
72
+ # Description:
73
+ # Create the SFX package
74
+ # Returns:
75
+ # Array of 2 objects: String path to the checksum file and the hash of known checksums for archive files.
76
+ # Parameters:
77
+ # source_folder: The path from which relative archive paths will be derived.
78
+ # files_to_add: An array of files to be added to the SFX archive. Relative or full paths accepted. Wildcards accepted. If omitted, takes all files and folders in the source_folder.
79
+ # info_hash: (optional) A hash containing the installer config info file. See comments for method 'generate_info_file' for more information.
80
+ def create(source_folder, files_to_add: nil, info_hash: nil)
81
+ changed_files = determine_files_to_add(source_folder, files_to_check: files_to_add)
82
+ if changed_files.empty? && ::File.exist?(@sfx_path)
83
+ EasyIO.logger.info 'No files in the SFX have changed. Skipping SFX creation.'
84
+ return [@checksum_path, @checksums]
85
+ end
86
+ EasyIO.logger.info "Creating SFX archive: #{sfx_path}"
87
+ add(source_folder, files_to_add: files_to_add)
88
+ use_info_file = !(info_hash.nil? || info_hash.empty?)
89
+ generate_info_file(info_hash) if use_info_file
90
+ sfx_module = "#{__dir__}/#{use_info_file ? '7zsd_All.sfx' : '7zS2.sfx'}"
91
+ sfx_components = [sfx_module] # Start with the module
92
+ sfx_components.push("#{sfx_cache_path}/sfx_info.txt") if ::File.exist?("#{sfx_cache_path}/sfx_info.txt") # Add the info file if it exists
93
+ sfx_components.push(@path) # Add the temporary .7z archive
94
+ ::File.open(sfx_path, 'wb') do |sfx_archive|
95
+ sfx_components.each do |file|
96
+ EasyIO.logger.debug "Adding #{file} to SFX"
97
+ sfx_archive.write(::File.open(file, 'rb').read)
98
+ end
99
+ end
100
+ EasyIO.logger.info 'SFX created successfully.'
101
+ [@checksum_path, update_checksum_file]
102
+ ensure
103
+ cleanup unless @skip_cleanup
104
+ end
105
+
106
+ #
107
+ # Description:
108
+ # Deletes temporary files/folders used while creating this SFX
109
+ def cleanup
110
+ FileUtils.rm_rf(sfx_cache_path)
111
+ end
112
+ end
113
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zipr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Alex Munoz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: easy_io
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rubyzip
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: seven_zip_ruby_am
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.2'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 1.2.5.4
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '1.2'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 1.2.5.4
75
+ - !ruby/object:Gem::Dependency
76
+ name: os
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '1'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rspec
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '3'
103
+ description:
104
+ email:
105
+ - amunoz951@gmail.com
106
+ executables: []
107
+ extensions: []
108
+ extra_rdoc_files: []
109
+ files:
110
+ - LICENSE
111
+ - lib/zipr.rb
112
+ - lib/zipr/7zS2.sfx
113
+ - lib/zipr/7zsd_All.sfx
114
+ - lib/zipr/archive.rb
115
+ - lib/zipr/config.rb
116
+ - lib/zipr/helper.rb
117
+ - lib/zipr/sfx.rb
118
+ homepage: https://github.com/amunoz951/zipr
119
+ licenses:
120
+ - Apache-2.0
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '2.3'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 2.5.2.3
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Ruby library for easily extracting and creating 7zip and zip archives idempotently.
142
+ test_files: []