omnizip 0.3.51 → 0.3.52

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b268c5b315ddcfff3347f6843e30e7ca180b1b3bae14b21a7a721bd2ccf3f6e
4
- data.tar.gz: 2d150420264453af6e1ba5114b32a31b1a67c12a020f41435a037747dec2bfde
3
+ metadata.gz: 8002bf03f99715929f352dfb92a91a10d02f6dac7152006a6cb87ca20b3647ae
4
+ data.tar.gz: b095258b0b0af358ae9c0a21482a6f8b29de1620bb2162d64dd8794f29eb8f59
5
5
  SHA512:
6
- metadata.gz: '053812e04bf42b6fe5335d57a5183c90b19a3ed8b414ee57ac9a83831a04f15e7225ecc79d29bc42e78e692bf7fe6f60b88ba234a3fc92eb0e781e7a357acd14'
7
- data.tar.gz: dd2e32bd6ea8732be35a2f6c7325ed23b922860c051caf7666df93edd7f7da1342bdc1f6c9928346a404a7a1c707c509bd74d1b281ddd07cdc4a591c67e8a05d
6
+ metadata.gz: 527be0108ee0fa63b6e7ebafdeaafd05430a17c08e0d59b0f731db7a3b76401da96daec205947c7c3b5baa07aba886515959513ef3d4b31438889761219e958d
7
+ data.tar.gz: e1f8665f545fc81a4459cbdffbb9e5d5aca219a177a5b2d2ac3bd47c589a2ceb11b02d8cfdc748ac39381417f12682bf53cf14105dcbcd3d04bd59b77d7f9e87
data/CHANGELOG.md CHANGED
@@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.51] - 2026-09-01
11
+
12
+ ### Changed
13
+ - Profile resolution lives in one place: `Omnizip::Profile` gains
14
+ `resolve`, `apply`, and `first_file_among`, and the convenience
15
+ methods plus the `archive create` command collapse onto them. The
16
+ two prior copies had drifted — the command only accepted `"auto"`
17
+ strings and could not pass a `CompressionProfile` through.
18
+ - Both direct converter strategies (ZIP↔7z) now extract through
19
+ `Omnizip.extract_archive` and write through a shared
20
+ `ConversionStrategy#repack_tree` on the Archive facade — they were
21
+ the last library code instantiating format readers/writers
22
+ directly, and the 7z→ZIP path required `#open` against the
23
+ reader-always-usable invariant.
24
+ - `create_rar` routes through the RAR handler (unrar-verified) with
25
+ the generic writer block interface; its stale "requires RAR
26
+ license" docstring is gone. `decompress_file`'s three-arm case
27
+ with magic `:xz`/`:zstandard` markers becomes a
28
+ `SINGLE_FILE_DECOMPRESSORS` table mirroring the compressor table.
29
+
30
+ ### Fixed
31
+ - macOS CI failed at setup from 2026-09-01: Homebrew disabled the
32
+ `rar` cask (Gatekeeper check), breaking `brew install rar`. The
33
+ workflow now installs it separately and non-fatally, like the
34
+ Windows branch already did for winrar; RAR interop specs skip
35
+ when unrar is absent.
36
+
10
37
  ## [0.3.50] - 2026-08-31
11
38
 
12
39
  ### Added
@@ -31,7 +31,7 @@ module Omnizip
31
31
  entries.map do |e|
32
32
  { name: e.name, size: e.size, directory: e.is_dir,
33
33
  compressed_size: (e.compressed_size if e.compressed_size&.positive?),
34
- mtime: e.mtime }
34
+ mtime: e.mtime, crc: e.crc }
35
35
  end
36
36
  else
37
37
  entries.map(&:name)
@@ -44,19 +44,27 @@ module Omnizip
44
44
  ].join("\n")
45
45
  end
46
46
 
47
- # Format file size in human-readable format.
47
+ # Format file size in human-readable format. This is THE byte
48
+ # formatter — commands, the CLI, and display-oriented models
49
+ # delegate here instead of carrying private copies.
48
50
  #
49
- # @param bytes [Integer] Size in bytes
50
- # @return [String] Formatted size
51
+ # @param bytes [Integer, nil] Size in bytes
52
+ # @return [String] Formatted size ("512 B", "1.5 KB", "2.0 MB")
51
53
  def format_size(bytes)
54
+ return "0 B" if bytes.nil? || bytes.zero?
55
+
52
56
  units = %w[B KB MB GB TB]
53
- return "0 B" if bytes.zero?
57
+ size = bytes.to_f
58
+ unit_index = 0
59
+
60
+ while size >= 1024.0 && unit_index < units.size - 1
61
+ size /= 1024.0
62
+ unit_index += 1
63
+ end
54
64
 
55
- exp = (Math.log(bytes) / Math.log(1024)).floor
56
- exp = [exp, units.length - 1].min
65
+ return format("%d B", bytes) if unit_index.zero?
57
66
 
58
- size = (bytes.to_f / (1024**exp)).round(2)
59
- "#{size} #{units[exp]}"
67
+ format("%.1f %s", size, units[unit_index])
60
68
  end
61
69
 
62
70
  # Format error message for display.
data/lib/omnizip/cli.rb CHANGED
@@ -60,13 +60,7 @@ module Omnizip
60
60
  # @param bytes [Integer] the byte count
61
61
  # @return [String] human-readable size (e.g. "1.5 MB")
62
62
  def format_bytes(bytes)
63
- return "0 B" if bytes.zero?
64
-
65
- units = %w[B KB MB GB TB]
66
- exp = (Math.log(bytes) / Math.log(1024)).to_i
67
- exp = [exp, units.size - 1].min
68
-
69
- "%.1f %s" % [bytes.to_f / (1024**exp), units[exp]]
63
+ Omnizip::CliOutputFormatter.format_size(bytes)
70
64
  end
71
65
  end
72
66
 
@@ -201,16 +201,7 @@ module Omnizip
201
201
  end
202
202
 
203
203
  def format_bytes(bytes)
204
- units = %w[B KB MB GB]
205
- size = bytes.to_f
206
- unit_idx = 0
207
-
208
- while size >= 1024 && unit_idx < units.length - 1
209
- size /= 1024.0
210
- unit_idx += 1
211
- end
212
-
213
- format("%.2f %s", size, units[unit_idx])
204
+ Omnizip::CliOutputFormatter.format_size(bytes)
214
205
  end
215
206
 
216
207
  def parse_volume_size(size_str)
@@ -135,17 +135,10 @@ module Omnizip
135
135
  end
136
136
 
137
137
  def extract_with_patterns(archive_file, output_dir, verbose)
138
- # Determine archive type and open appropriately
139
- archive = case File.extname(archive_file).downcase
140
- when ".zip"
141
- Omnizip::Zip::File.open(archive_file)
142
- when ".rar"
143
- Formats::Rar::Reader.new(archive_file).open
144
- when ".tar"
145
- Formats::Tar::Reader.new(archive_file).read
146
- else
147
- Formats::SevenZip::Reader.new(archive_file).open
148
- end
138
+ # Route through the facade: the extension routes pick each
139
+ # format's reader, and the reader session holds no open
140
+ # resources to close
141
+ archive = Omnizip::Archive.open(archive_file)
149
142
 
150
143
  # Build filter chain
151
144
  filter = Extraction::FilterChain.new
@@ -193,8 +186,6 @@ module Omnizip
193
186
  end
194
187
 
195
188
  extracted.size
196
- ensure
197
- archive.close if archive.is_a?(Omnizip::Zip::File)
198
189
  end
199
190
 
200
191
  def extract_gzip(archive_file, output_dir, verbose)
@@ -157,24 +157,7 @@ module Omnizip
157
157
  end
158
158
 
159
159
  def format_bytes(bytes)
160
- return "0 B" if bytes.zero?
161
-
162
- units = %w[B KB MB GB]
163
- size = bytes.to_f
164
- unit_idx = 0
165
-
166
- while size >= 1024 && unit_idx < units.length - 1
167
- size /= 1024.0
168
- unit_idx += 1
169
- end
170
-
171
- if size < 10
172
- format("%.2f %s", size, units[unit_idx])
173
- elsif size < 100
174
- format("%.1f %s", size, units[unit_idx])
175
- else
176
- format("%.0f %s", size, units[unit_idx])
177
- end
160
+ Omnizip::CliOutputFormatter.format_size(bytes)
178
161
  end
179
162
 
180
163
  # Helper methods to handle different entry types. Each delegates
@@ -54,7 +54,8 @@ module Omnizip
54
54
  %i[comment set_mtime chmod set_attribute].none? { |key| options[key] }
55
55
  end
56
56
 
57
- # Read-only view for 7z archives; the edit path is zip-only
57
+ # Read-only view for 7z archives through the handler registry;
58
+ # the edit path is zip-only
58
59
  def show_seven_zip_metadata(archive_path, pattern)
59
60
  if options[:comment] || options[:set_mtime] ||
60
61
  options[:chmod] || options[:set_attribute]
@@ -62,37 +63,37 @@ module Omnizip
62
63
  "Metadata editing is only supported for ZIP archives"
63
64
  end
64
65
 
65
- reader = Omnizip::Formats::SevenZip::Reader.new(archive_path)
66
- reader.open
66
+ entries = Omnizip.list_archive(archive_path, details: true)
67
67
 
68
68
  if pattern
69
- entries = reader.list_files.select do |e|
70
- File.fnmatch(pattern, e.name)
69
+ entries = entries.select do |entry|
70
+ File.fnmatch(pattern, entry[:name])
71
71
  end
72
72
  if entries.empty?
73
73
  warn "No entries match pattern: #{pattern}"
74
74
  return
75
75
  end
76
76
 
77
- entries.each do |entry|
78
- puts "Entry: #{entry.name}"
79
- puts " Type: #{entry.is_dir ? 'Directory' : 'File'}"
80
- puts " Size: #{format_size(entry.size)}"
81
- # Per-entry packed sizes are not tracked by the parser
82
- if entry.compressed_size&.positive?
83
- puts " Compressed: #{format_size(entry.compressed_size)}"
84
- end
85
- puts " Modified: #{entry.mtime}" if entry.mtime
86
- puts " CRC32: 0x#{entry.crc.to_s(16).upcase}" if entry.crc
87
- end
77
+ entries.each { |entry| show_seven_zip_entry(entry) }
88
78
  else
89
- entries = reader.list_files
90
- files = entries.reject(&:is_dir)
79
+ files = entries.reject { |entry| entry[:directory] }
91
80
  puts "Archive: #{archive_path}"
92
81
  puts "Entries: #{entries.size} " \
93
82
  "(#{files.size} files, #{entries.size - files.size} dirs)"
94
- puts "Total size: #{format_size(files.sum(&:size))}"
83
+ puts "Total size: #{format_size(files.sum { |entry| entry[:size] })}"
84
+ end
85
+ end
86
+
87
+ def show_seven_zip_entry(entry)
88
+ puts "Entry: #{entry[:name]}"
89
+ puts " Type: #{entry[:directory] ? 'Directory' : 'File'}"
90
+ puts " Size: #{format_size(entry[:size])}"
91
+ # Per-entry packed sizes are not tracked by the parser
92
+ if entry[:compressed_size]&.positive?
93
+ puts " Compressed: #{format_size(entry[:compressed_size])}"
95
94
  end
95
+ puts " Modified: #{entry[:mtime]}" if entry[:mtime]
96
+ puts " CRC32: 0x#{entry[:crc].to_s(16).upcase}" if entry[:crc]
96
97
  end
97
98
 
98
99
  def show_metadata(archive, pattern)
@@ -199,13 +200,7 @@ module Omnizip
199
200
  end
200
201
 
201
202
  def format_size(bytes)
202
- return "0 B" if bytes.zero?
203
-
204
- units = %w[B KB MB GB TB]
205
- exp = (Math.log(bytes) / Math.log(1024)).to_i
206
- exp = [exp, units.size - 1].min
207
-
208
- "%.1f %s" % [bytes.to_f / (1024**exp), units[exp]]
203
+ Omnizip::CliOutputFormatter.format_size(bytes)
209
204
  end
210
205
  end
211
206
  end
@@ -107,13 +107,7 @@ module Omnizip
107
107
  # @param bytes [Integer] Size in bytes
108
108
  # @return [String] Formatted size
109
109
  def format_size(bytes)
110
- if bytes < 1024
111
- "#{bytes} B"
112
- elsif bytes < 1024 * 1024
113
- "#{(bytes / 1024.0).round(1)} KB"
114
- else
115
- "#{(bytes / (1024.0 * 1024)).round(1)} MB"
116
- end
110
+ Omnizip::CliOutputFormatter.format_size(bytes)
117
111
  end
118
112
  end
119
113
  end
@@ -33,7 +33,10 @@ module Omnizip
33
33
  FileUtils.mkdir_p(dest)
34
34
  extracted = []
35
35
 
36
- entries_to_extract = list_matches
36
+ # Directory entries carry no content to extract
37
+ entries_to_extract = list_matches.reject do |entry|
38
+ entry_filename(entry).end_with?("/")
39
+ end
37
40
  total = entries_to_extract.size
38
41
  current = 0
39
42
 
@@ -162,7 +165,7 @@ module Omnizip
162
165
  entry.get_input_stream.read
163
166
  # allowed: some archives read entry content from the archive
164
167
  elsif @archive.respond_to?(:read)
165
- @archive.read(entry)
168
+ @archive.read(entry_filename(entry))
166
169
  else
167
170
  raise Error, "Cannot read entry content"
168
171
  end
@@ -191,10 +194,19 @@ module Omnizip
191
194
 
192
195
  # Get filename from entry
193
196
  #
194
- # @param entry [#entry_name] Entry (Omnizip::Entry or compatible)
197
+ # @param entry [#entry_name, #name] Entry (legacy Omnizip::Entry,
198
+ # Archive::Entry from a reader session, or compatible)
195
199
  # @return [String] Filename
196
200
  def entry_filename(entry)
197
- entry.entry_name || entry.to_s
201
+ # allowed: entries come from a caller-supplied archive
202
+ if entry.respond_to?(:entry_name)
203
+ entry.entry_name
204
+ # allowed: reader-session entries expose name, not entry_name
205
+ elsif entry.respond_to?(:name)
206
+ entry.name
207
+ else
208
+ entry.to_s
209
+ end
198
210
  end
199
211
 
200
212
  # Update progress tracker
@@ -122,13 +122,7 @@ module Omnizip
122
122
  end
123
123
 
124
124
  def format_size(bytes)
125
- return "0 B" if bytes.zero?
126
-
127
- units = %w[B KB MB GB TB]
128
- exp = (Math.log(bytes) / Math.log(1024)).to_i
129
- exp = [exp, units.size - 1].min
130
-
131
- "%.1f %s" % [bytes.to_f / (1024**exp), units[exp]]
125
+ Omnizip::CliOutputFormatter.format_size(bytes)
132
126
  end
133
127
  end
134
128
  end
@@ -161,40 +161,6 @@ progress: nil)
161
161
  Digest::MD5.digest("#{Time.now.to_f}#{rand}")
162
162
  end
163
163
 
164
- # Analyze file and calculate hashes
165
- #
166
- # @param file_path [String] Path to file
167
- # @return [FileInfo] File information
168
- def analyze_file(file_path)
169
- File.open(file_path, "rb") do |io|
170
- file_size = io.size
171
-
172
- # Calculate hash of first 16KB
173
- first_16k = io.read(16384) || ""
174
- hash_16k = Digest::MD5.digest(first_16k)
175
-
176
- # Calculate full file hash
177
- io.rewind
178
- hash_full = Digest::MD5.file(file_path).digest
179
-
180
- # Generate file ID
181
- file_id = Digest::MD5.digest("#{File.basename(file_path)}#{file_size}")
182
-
183
- # Read file blocks
184
- io.rewind
185
- blocks = read_file_blocks(io)
186
-
187
- FileInfo.new(
188
- path: file_path,
189
- file_id: file_id,
190
- hash_16k: hash_16k,
191
- hash_full: hash_full,
192
- size: file_size,
193
- blocks: blocks,
194
- )
195
- end
196
- end
197
-
198
164
  # Read file data into blocks
199
165
  #
200
166
  # @param io [IO] File IO object
@@ -69,6 +69,11 @@ module Omnizip
69
69
  # @param par2_file [String] Path to .par2 index file
70
70
  # @raise [ArgumentError] if file doesn't exist
71
71
  def initialize(par2_file)
72
+ if par2_file.nil?
73
+ raise ArgumentError,
74
+ "PAR2 file is nil — no index .par2 was produced for this set"
75
+ end
76
+
72
77
  raise ArgumentError, "PAR2 file not found: #{par2_file}" unless
73
78
  File.exist?(par2_file)
74
79
 
@@ -145,18 +145,7 @@ module Omnizip
145
145
  end
146
146
 
147
147
  def format_bytes(bytes)
148
- return "0 B" if bytes.nil? || bytes.zero?
149
-
150
- units = %w[B KB MB GB]
151
- size = bytes.to_f
152
- unit_index = 0
153
-
154
- while size >= 1024.0 && unit_index < units.size - 1
155
- size /= 1024.0
156
- unit_index += 1
157
- end
158
-
159
- format("%.2f %s", size, units[unit_index])
148
+ Omnizip::CliOutputFormatter.format_size(bytes)
160
149
  end
161
150
 
162
151
  def truncate(string, max_length)
@@ -186,18 +186,7 @@ module Omnizip
186
186
  end
187
187
 
188
188
  def format_bytes(bytes)
189
- return "0 B" if bytes.zero?
190
-
191
- units = %w[B KB MB GB]
192
- size = bytes.to_f
193
- unit_index = 0
194
-
195
- while size >= 1024.0 && unit_index < units.size - 1
196
- size /= 1024.0
197
- unit_index += 1
198
- end
199
-
200
- format("%.2f %s", size, units[unit_index])
189
+ Omnizip::CliOutputFormatter.format_size(bytes)
201
190
  end
202
191
  end
203
192
  end
@@ -79,37 +79,9 @@ module Omnizip
79
79
  private
80
80
 
81
81
  def extract_to_temp(temp_dir)
82
- # Detect archive format and extract
83
- case detect_format
84
- when :zip
85
- extract_zip(temp_dir)
86
- when :seven_zip
87
- extract_7z(temp_dir)
88
- else
89
- raise Omnizip::UnsupportedFormatError,
90
- "Unknown archive format: #{@archive_path}"
91
- end
92
- end
93
-
94
- def extract_zip(dest)
95
- Omnizip::Zip::File.open(@archive_path) do |zip|
96
- zip.each do |entry|
97
- entry_path = File.join(dest, entry.name)
98
-
99
- if entry.directory?
100
- FileUtils.mkdir_p(entry_path)
101
- else
102
- FileUtils.mkdir_p(File.dirname(entry_path))
103
- zip.extract(entry, entry_path)
104
- end
105
- end
106
- end
107
- end
108
-
109
- def extract_7z(dest)
110
- # Placeholder for 7z extraction
111
- # Would use Omnizip::SevenZip::File when available
112
- raise NotImplementedError, "7z extraction not yet implemented"
82
+ # Every routed format extracts through the handler registry,
83
+ # same as the convenience API
84
+ Omnizip.extract_archive(@archive_path, temp_dir)
113
85
  end
114
86
 
115
87
  def move_to_destination(temp_dir)
@@ -147,18 +119,6 @@ module Omnizip
147
119
  end
148
120
  count
149
121
  end
150
-
151
- def detect_format
152
- File.open(@archive_path, "rb") do |f|
153
- magic = f.read(4)
154
- case magic
155
- when "PK\x03\x04"
156
- :zip
157
- when "7z\xBC\xAF"
158
- :seven_zip
159
- end
160
- end
161
- end
162
122
  end
163
123
  end
164
124
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnizip
4
- VERSION = "0.3.51"
4
+ VERSION = "0.3.52"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omnizip
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.51
4
+ version: 0.3.52
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.