omnizip 0.3.31 → 0.3.33

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: eb1ed1142019a375dd71be87daa4c59bdd509640530a183e34b5db31841d9d1b
4
- data.tar.gz: a203011eea4196ef77193b642794c1a1eb5b870be4d1e9f66a1a233895d7c4b1
3
+ metadata.gz: 0f3064a982a38257ff4ef606d6de52925a377e2138a3bacc7cb60033a6ff1ac1
4
+ data.tar.gz: f24f28f6daac8df45542854f099c071d5b17f52deff99ae7f3bd5d009d6d7c0d
5
5
  SHA512:
6
- metadata.gz: 17955bf45c57c5a2a6d4117b66617590f9ae5c0527c3f268b79b9be8f353f10b38a51829b60b9bb77cfd374840f694b55b1f56e3777cb8bb629e04e4b3044e17
7
- data.tar.gz: 6396d10e54bfd95dcddff3e3475884f8240a3da8c83850156396c2e515b8ad19059094241260adac8684c202e64b5bf41dcf2507a1deda87dc44009661ea3e9d
6
+ metadata.gz: 7e08082742ca10c5156773d5d279c1e5c7fe6d035cbe06678e2c68509862d89880c1a7f3e40d4e7a69b433e2407546d3735846a7d53a3c139819fa271001b852
7
+ data.tar.gz: 8d8551cf4cd26ae177b36e5aa67f217323853535ffaed26e4825f537e4384e998629b6084240a2dd82eaad276396dd51b22ab84a4d65f6f9f98475965ca6096e
data/CHANGELOG.md CHANGED
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Fixed
11
+ - The convenience API (`compress_file`, `compress_directory`,
12
+ `extract_archive`, `list_archive`, `read_from_archive`,
13
+ `add_to_archive`, `remove_from_archive`) now routes archive
14
+ formats by extension: `.tar` writes/reads real TAR (previously a
15
+ mislabeled ZIP), `.7z` writes/reads real 7z via a new
16
+ `ArchiveHandlers::SevenZipHandler` (previously a mislabeled ZIP;
17
+ archives verify with `7zz`), and read-only formats (`.rar`,
18
+ `.iso`, `.cpio`) raise `UnsupportedFormatError` instead of
19
+ silently writing a ZIP under a foreign name. Extensionless
20
+ outputs keep the ZIP default; an explicit `format:` still wins.
21
+
22
+ ## [0.3.32] - 2026-08-27
23
+
24
+ ### Fixed
25
+ - The `omnizip` executable crashed on every invocation
26
+ (`uninitialized constant Omnizip::Cli`): the `Cli` autoload was
27
+ missing and the Thor command classes referenced
28
+ `Omnizip::Cli::Shared` mid-load, re-triggering the in-progress
29
+ autoload. The autoload exists now and Shared is defined ahead of
30
+ the command classes in `lib/omnizip/cli.rb` (its separate file is
31
+ gone). Verified: `omnizip version`, the documented
32
+ `compress/decompress output.lzma` round-trip (which also
33
+ interops with `xz --format=lzma` in both directions), and
34
+ `archive create backup.7z` producing an archive `7zz` validates.
35
+
36
+ ## [0.3.31] - 2026-08-27
37
+
10
38
  ### Fixed
11
39
  - `Omnizip.compress_file` now routes single-file compression by
12
40
  output extension: `.gz`, `.bz2`, `.xz`, `.lzma` and `.lz` produce
@@ -15,6 +43,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
43
  `compress_file('input.txt', 'output.lzma', algorithm: :lzma)`
16
44
  example previously emitted `PK`-magic data). Unknown extensions
17
45
  and explicit archive formats keep the previous behavior.
46
+ - CI: the Windows matrix no longer fails when choco's winrar
47
+ download 404s (RAR specs skip when it is absent).
18
48
 
19
49
  ## [0.3.30] - 2026-08-27
20
50
 
@@ -69,6 +69,7 @@ module Omnizip
69
69
  LAZY_LOAD_TRIGGERS = {
70
70
  zip: -> { Omnizip::ArchiveHandlers::ZipHandler },
71
71
  tar: -> { Omnizip::ArchiveHandlers::TarHandler },
72
+ seven_zip: -> { Omnizip::ArchiveHandlers::SevenZipHandler },
72
73
  }.freeze
73
74
  private_constant :LAZY_LOAD_TRIGGERS
74
75
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ module ArchiveHandlers
5
+ # Adapter that exposes the canonical +create+/+extract_to+/+list+
6
+ # interface for the 7z format. Wraps +Omnizip::Formats::SevenZip+.
7
+ class SevenZipHandler
8
+ # Creates a .7z archive. The yielded proxy only supports
9
+ # +add(name, source_path)+ for files — directory entries are not
10
+ # part of the single-file convenience flow.
11
+ def create(path, &block)
12
+ Omnizip::Formats::SevenZip.create(path) do |writer|
13
+ block&.call(FileProxy.new(writer))
14
+ end
15
+ end
16
+
17
+ def extract_to(path, output_dir, **options)
18
+ Omnizip::Formats::SevenZip.open(path, options) do |reader|
19
+ reader.extract_all(output_dir)
20
+ end
21
+ end
22
+
23
+ def list(path, details: false, **options)
24
+ Omnizip::Formats::SevenZip.open(path, options) do |reader|
25
+ files = reader.list_files
26
+ return details ? files.map { |f| { name: f } } : files
27
+ end
28
+ end
29
+
30
+ def read_entry(_path, _entry_name)
31
+ raise NotImplementedError,
32
+ "read_from_archive for .7z is not wired; use " \
33
+ "Formats::SevenZip.open"
34
+ end
35
+
36
+ # Translates the generic +add(name, source_path)+ interface to
37
+ # the 7z writer's +add_file(file_path, archive_path)+.
38
+ class FileProxy
39
+ def initialize(writer)
40
+ @writer = writer
41
+ end
42
+
43
+ def add(name, source_path = nil)
44
+ if source_path.nil?
45
+ raise ArgumentError,
46
+ "directory entries are not supported in .7z " \
47
+ "convenience compression"
48
+ end
49
+
50
+ @writer.add_file(source_path, name)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ Omnizip::ArchiveHandler.register(:seven_zip,
58
+ Omnizip::ArchiveHandlers::SevenZipHandler.new)
@@ -7,5 +7,6 @@ module Omnizip
7
7
  module ArchiveHandlers
8
8
  autoload :ZipHandler, "omnizip/archive_handlers/zip_handler"
9
9
  autoload :TarHandler, "omnizip/archive_handlers/tar_handler"
10
+ autoload :SevenZipHandler, "omnizip/archive_handlers/seven_zip_handler"
10
11
  end
11
12
  end
data/lib/omnizip/cli.rb CHANGED
@@ -19,9 +19,38 @@
19
19
  require "thor"
20
20
 
21
21
  module Omnizip
22
+ # Shared helpers used by the Thor command groups defined below
23
+ # (ProfileCommands, ArchiveCommands, Cli). Defined here because the
24
+ # command classes include it at class-body time; a separate file
25
+ # under Omnizip::Cli would re-trigger this file's autoload
26
+ # mid-load.
27
+ module Shared
28
+ # Print a formatted error message and exit nonzero.
29
+ #
30
+ # @param error [StandardError] the error to display
31
+ def handle_error(error)
32
+ warn Omnizip::CliOutputFormatter.format_error(error)
33
+ exit 1
34
+ end
35
+
36
+ # Format a byte count with a binary-unit suffix.
37
+ #
38
+ # @param bytes [Integer] the byte count
39
+ # @return [String] human-readable size (e.g. "1.5 MB")
40
+ def format_bytes(bytes)
41
+ return "0 B" if bytes.zero?
42
+
43
+ units = %w[B KB MB GB TB]
44
+ exp = (Math.log(bytes) / Math.log(1024)).to_i
45
+ exp = [exp, units.size - 1].min
46
+
47
+ "%.1f %s" % [bytes.to_f / (1024**exp), units[exp]]
48
+ end
49
+ end
50
+
22
51
  # Profile commands subcommand group
23
52
  class ProfileCommands < Thor
24
- include Omnizip::Cli::Shared
53
+ include Shared
25
54
 
26
55
  class << self
27
56
  def exit_on_failure?
@@ -69,7 +98,7 @@ module Omnizip
69
98
 
70
99
  # Archive commands subcommand group
71
100
  class ArchiveCommands < Thor
72
- include Omnizip::Cli::Shared
101
+ include Shared
73
102
 
74
103
  class << self
75
104
  def exit_on_failure?
@@ -298,7 +327,7 @@ module Omnizip
298
327
  # Provides Thor-based CLI commands for compressing and decompressing
299
328
  # files using various compression algorithms.
300
329
  class Cli < Thor
301
- include Omnizip::Cli::Shared
330
+ include Shared
302
331
 
303
332
  class << self
304
333
  def exit_on_failure?
@@ -40,7 +40,7 @@ module Omnizip
40
40
  return output_path
41
41
  end
42
42
 
43
- Omnizip::ArchiveHandler.for(format).create(output_path) do |archive|
43
+ Omnizip::ArchiveHandler.for(resolve_archive_format(output_path, format)).create(output_path) do |archive|
44
44
  archive.add(::File.basename(input_path), input_path)
45
45
  end
46
46
 
@@ -69,7 +69,7 @@ module Omnizip
69
69
  apply_profile(first_file, options)
70
70
  end
71
71
 
72
- Omnizip::ArchiveHandler.for(format).create(output_path) do |archive|
72
+ Omnizip::ArchiveHandler.for(resolve_archive_format(output_path, format)).create(output_path) do |archive|
73
73
  add_directory_contents(archive, input_dir, "", recursive: recursive)
74
74
  end
75
75
 
@@ -87,7 +87,7 @@ module Omnizip
87
87
  def extract_archive(archive_path, output_dir, format: DEFAULT_FORMAT,
88
88
  overwrite: false, **options)
89
89
  require_archive!(archive_path)
90
- Omnizip::ArchiveHandler.for(format)
90
+ Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format))
91
91
  .extract_to(archive_path, output_dir,
92
92
  overwrite: overwrite, **options)
93
93
  end
@@ -102,7 +102,7 @@ module Omnizip
102
102
  def list_archive(archive_path, format: DEFAULT_FORMAT, details: false,
103
103
  **options)
104
104
  require_archive!(archive_path)
105
- Omnizip::ArchiveHandler.for(format)
105
+ Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format))
106
106
  .list(archive_path, details: details, **options)
107
107
  end
108
108
 
@@ -114,7 +114,7 @@ module Omnizip
114
114
  # @return [String] Entry contents
115
115
  def read_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT)
116
116
  require_archive!(archive_path)
117
- Omnizip::ArchiveHandler.for(format).read_entry(archive_path, entry_name)
117
+ Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format)).read_entry(archive_path, entry_name)
118
118
  end
119
119
 
120
120
  # Add a file to an existing archive.
@@ -131,7 +131,7 @@ module Omnizip
131
131
  raise Errno::ENOENT, "Source file not found: #{source_path}"
132
132
  end
133
133
 
134
- handler = Omnizip::ArchiveHandler.for(format)
134
+ handler = Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format))
135
135
  # allowed: handler is a registered duck; missing add_entry raises below
136
136
  if handler.respond_to?(:add_entry)
137
137
  handler.add_entry(archive_path, entry_name, source_path)
@@ -151,7 +151,7 @@ module Omnizip
151
151
  # @return [String] +archive_path+
152
152
  def remove_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT)
153
153
  require_archive!(archive_path)
154
- handler = Omnizip::ArchiveHandler.for(format)
154
+ handler = Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format))
155
155
  # allowed: handler is a registered duck; missing remove_entry raises below
156
156
  unless handler.respond_to?(:remove_entry)
157
157
  raise Omnizip::UnsupportedFormatError,
@@ -172,6 +172,20 @@ module Omnizip
172
172
  # Extension -> single-file compressor map. Each lambda takes
173
173
  # (input_path, output_path, options). These formats are streams,
174
174
  # not archives, so they bypass ArchiveHandler.
175
+ # Extensions naming archive formats with a registered handler;
176
+ # routed when the caller did not pass an explicit +format:+.
177
+ ARCHIVE_FORMAT_EXTENSIONS = {
178
+ ".zip" => :zip,
179
+ ".tar" => :tar,
180
+ ".7z" => :seven_zip,
181
+ }.freeze
182
+
183
+ # Extensions naming real formats this gem can READ but not write
184
+ # through the convenience API. Writing them a ZIP under a foreign
185
+ # name was silent corruption; failing truthfully is the only
186
+ # honest behavior.
187
+ READ_ONLY_FORMAT_EXTENSIONS = [".rar", ".iso", ".cpio"].freeze
188
+
175
189
  SINGLE_FILE_COMPRESSORS = {
176
190
  ".gz" => lambda do |input, output, options|
177
191
  Omnizip::Formats::Gzip.compress(input, output, options)
@@ -206,6 +220,26 @@ module Omnizip
206
220
  SINGLE_FILE_COMPRESSORS[ext]
207
221
  end
208
222
 
223
+ # Archive format for a path: an explicit +format+ wins unless it
224
+ # is the default; otherwise the extension routes. Known-but-
225
+ # unwritable extensions raise instead of receiving a mislabeled
226
+ # ZIP.
227
+ def resolve_archive_format(path, format)
228
+ return format unless format == DEFAULT_FORMAT
229
+
230
+ ext = ::File.extname(path).downcase
231
+ routed = ARCHIVE_FORMAT_EXTENSIONS[ext]
232
+ return routed if routed
233
+
234
+ if READ_ONLY_FORMAT_EXTENSIONS.include?(ext)
235
+ raise Omnizip::UnsupportedFormatError,
236
+ "#{ext} archives cannot be written by the convenience " \
237
+ "API (read-only support); use the format-specific reader"
238
+ end
239
+
240
+ DEFAULT_FORMAT
241
+ end
242
+
209
243
  private
210
244
 
211
245
  def require_archive!(archive_path)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnizip
4
- VERSION = "0.3.31"
4
+ VERSION = "0.3.33"
5
5
  end
data/lib/omnizip.rb CHANGED
@@ -67,6 +67,7 @@ module Omnizip
67
67
  autoload :Profiler, "omnizip/profiler"
68
68
  autoload :Commands, "omnizip/commands"
69
69
  autoload :CliOutputFormatter, "omnizip/cli/output_formatter"
70
+ autoload :Cli, "omnizip/cli"
70
71
 
71
72
  # Implementation namespaces. Each namespace file declares autoloads
72
73
  # for its concrete classes. The classes self-register with their
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.31
4
+ version: 0.3.33
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -321,6 +321,7 @@ files:
321
321
  - lib/omnizip/algorithms/zstandard/xxhash.rb
322
322
  - lib/omnizip/archive_handler.rb
323
323
  - lib/omnizip/archive_handlers.rb
324
+ - lib/omnizip/archive_handlers/seven_zip_handler.rb
324
325
  - lib/omnizip/archive_handlers/tar_handler.rb
325
326
  - lib/omnizip/archive_handlers/zip_handler.rb
326
327
  - lib/omnizip/buffer.rb
@@ -339,7 +340,6 @@ files:
339
340
  - lib/omnizip/chunked/writer.rb
340
341
  - lib/omnizip/cli.rb
341
342
  - lib/omnizip/cli/output_formatter.rb
342
- - lib/omnizip/cli/shared.rb
343
343
  - lib/omnizip/commands.rb
344
344
  - lib/omnizip/commands/.keep
345
345
  - lib/omnizip/commands/archive_create_command.rb
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Omnizip
4
- class Cli
5
- # Shared helpers used by the Thor command groups defined in
6
- # lib/omnizip/cli.rb (ProfileCommands, ArchiveCommands, Cli).
7
- module Shared
8
- # Print a formatted error message and exit nonzero.
9
- #
10
- # @param error [StandardError] the error to display
11
- def handle_error(error)
12
- warn Omnizip::CliOutputFormatter.format_error(error)
13
- exit 1
14
- end
15
-
16
- # Format a byte count with a binary-unit suffix.
17
- #
18
- # @param bytes [Integer] the byte count
19
- # @return [String] human-readable size (e.g. "1.5 MB")
20
- def format_bytes(bytes)
21
- return "0 B" if bytes.zero?
22
-
23
- units = %w[B KB MB GB TB]
24
- exp = (Math.log(bytes) / Math.log(1024)).to_i
25
- exp = [exp, units.size - 1].min
26
-
27
- "%.1f %s" % [bytes.to_f / (1024**exp), units[exp]]
28
- end
29
- end
30
- end
31
- end