omnizip 0.3.30 → 0.3.32

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: 20b634d20f6abd7d5c44314d94b89e5008b1ad34e4c0efa14a01ef72a71c0723
4
- data.tar.gz: 58d5380b5f2a98f92361395a55738047313f66c90bb9e662a56a47ee20333f11
3
+ metadata.gz: a20cd2785f3068dabfe29b7cb3e9b88503b151839ea076fd4d4c727708e5a440
4
+ data.tar.gz: 7db78180133b7b7d3e62dff82588036c73dabc80c1957d892ffbff96fd83e657
5
5
  SHA512:
6
- metadata.gz: efcdcc1f1f3cf2774a6c955531f5a6747f4bad4464e0521f27e89d6b5c97b31bedcd1e3e2315f421bb7bea29a4aa73ca4f75dcee8eec702516e8c2b5349fb115
7
- data.tar.gz: 96d041e3847fa402dbf86cfa3f86b7ba7b9b12f754471e10a8c60de8e966decbb0cfeb3711f71cfb905bf7acfea62a30128a1fb573f4f52e8e8e29b37fecc00b
6
+ metadata.gz: 601948e2be2303a5c82573728118fe4fdb6bd123ce983f6f92776373e3215e96c1b29ab02217356353a8978335af54233093ede1aeb9aa8665a57f1f189a9525
7
+ data.tar.gz: ac68a261028b8a92a948bb0f93caed9997e5e3d13c885962d1857104c65f16d2da2eba38996aac90fb0a17da57d9fa8b7e1c806956e22d65d36765ebcf9f84c7
data/CHANGELOG.md CHANGED
@@ -7,8 +7,33 @@ 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 `omnizip` executable crashed on every invocation
12
+ (`uninitialized constant Omnizip::Cli`): the `Cli` autoload was
13
+ missing and the Thor command classes referenced
14
+ `Omnizip::Cli::Shared` mid-load, re-triggering the in-progress
15
+ autoload. The autoload exists now and Shared is defined ahead of
16
+ the command classes in `lib/omnizip/cli.rb` (its separate file is
17
+ gone). Verified: `omnizip version`, the documented
18
+ `compress/decompress output.lzma` round-trip (which also
19
+ interops with `xz --format=lzma` in both directions), and
20
+ `archive create backup.7z` producing an archive `7zz` validates.
21
+
10
22
  ## [0.3.31] - 2026-08-27
11
23
 
24
+ ### Fixed
25
+ - `Omnizip.compress_file` now routes single-file compression by
26
+ output extension: `.gz`, `.bz2`, `.xz`, `.lzma` and `.lz` produce
27
+ the matching format instead of silently writing a ZIP under a
28
+ foreign extension (the documented
29
+ `compress_file('input.txt', 'output.lzma', algorithm: :lzma)`
30
+ example previously emitted `PK`-magic data). Unknown extensions
31
+ and explicit archive formats keep the previous behavior.
32
+ - CI: the Windows matrix no longer fails when choco's winrar
33
+ download 404s (RAR specs skip when it is absent).
34
+
35
+ ## [0.3.30] - 2026-08-27
36
+
12
37
  ### Changed
13
38
  - Re-enabled LZMA2 in the 7-Zip header-encryption and split-archive
14
39
  specs (the multi-chunk encoder landed in 0.3.19; the specs had
@@ -32,7 +32,7 @@ as absolute rules:
32
32
  | 10 | `10-format-detector-ocp.md` | medium | **DONE** |
33
33
  | 11 | `11-convenience-decoupling.md` | medium | **DONE** (ArchiveHandler dispatcher + ZipHandler + TarHandler) |
34
34
  | 12 | `12-thread-safety.md` | covered by 01 | n/a |
35
- | 13 | `13-lutaml-model-migration.md` | medium | **PARTIAL** — CompressionOptions and AlgorithmMetadata migrated; ConversionOptions pending lutaml symbol-type support |
35
+ | 13 | `13-lutaml-model-migration.md` | medium | **DONE** — six models migrated; follow-ups 2+3 landed in 0.3.28; 1 and 4 decided (deferred rename / migration declined with reasons) |
36
36
  | 14 | `14-add-missing-specs.md` | high | **DONE** |
37
37
 
38
38
  ## Anti-pattern counts (lib/)
@@ -129,18 +129,30 @@ is the intended end state, not unfinished work.
129
129
 
130
130
  ## Follow-up work
131
131
 
132
- 1. **Rename the 17.** If they are reports and projections, `to_h` is
133
- the wrong name that is the real defect. Suggested groupings:
134
- `to_report` for the computed ones, `to_tag_map` / `to_attributes`
135
- for projections, `to_xml_attributes` / `to_fields` for wire-format
136
- builders. Each rename is itself a public-API break.
137
- 2. **Fix `apply` in `CompressionOptions` and `AlgorithmMetadata`.**
138
- Both iterate `self.class.attributes.each do |attr| ... attr.name`,
139
- but `attributes` is a Hash, so the block gets a two-element Array
140
- and `attr.name` raises `NoMethodError`. Neither has a caller or a
141
- spec today, which is why it went unnoticed. Fix is `each_key`, plus
142
- a spec per class.
143
- 3. **Extract the shared `apply` loop** once (2) has landed — three
144
- classes now carry a similar body.
145
- 4. **Consider `FilterConfig` and the profile classes** for a proper
146
- migration; both were judged too high-blast-radius to bundle here.
132
+ 1. **Rename the 17 DECIDED: deferred to a deliberate major/minor
133
+ release.** The names below remain the plan if that release
134
+ happens: `to_report` for the computed ones, `to_tag_map` /
135
+ `to_attributes` for projections, `to_xml_attributes` / `to_fields`
136
+ for wire-format builders. Each rename is a public-API break
137
+ (every one of the 17 is a public class method), so it cannot ship
138
+ silently in a patch; the 17 staying is the intended end state for
139
+ now, per "Why 17 stay" above.
140
+ 2. **DONE (0.3.28, PR #42).** `apply` fixed in both classes all
141
+ three options models now share the tested
142
+ `Models::AttributeApply` concern, which also delivers (3).
143
+ 3. **DONE (0.3.28, PR #42)** see (2).
144
+ 4. **Considered decided against, with reasons.**
145
+ - `FilterConfig` is a behavior-heavy domain object, not a
146
+ serialization model: `filter_instance` reaches into
147
+ `FilterRegistry`, `id_for_format`/`bcj?`/`delta?` are domain
148
+ queries, `properties` defaults to a binary String (lutaml's
149
+ `:string` is UTF-8-oriented, so the attribute would need a
150
+ custom type), and initialization accepts two alias keys
151
+ (`:name`/`:name_sym`). Its `to_h` is a projection with no
152
+ round-trip consumer. Migration would fight the framework to
153
+ achieve nothing the class needs.
154
+ - The profile classes are abstract domain objects: subclasses
155
+ override `suitable_for?`, `apply_to` mutates an options object,
156
+ five validators run at construction, and `to_h` coexists with
157
+ `to_s`/`inspect` as display projections. No wire format round
158
+ trips them. Same verdict.
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?
@@ -31,6 +31,15 @@ module Omnizip
31
31
  return Omnizip::Chunked.compress_file(input_path, output_path, **options)
32
32
  end
33
33
 
34
+ # Single-file compression formats route by output extension
35
+ # (unless an archive format was requested explicitly): writing
36
+ # output.lzma through the archive path silently produced a ZIP
37
+ # under a foreign extension.
38
+ if format == DEFAULT_FORMAT && (handler = single_file_handler(output_path))
39
+ handler.call(input_path, output_path, options)
40
+ return output_path
41
+ end
42
+
34
43
  Omnizip::ArchiveHandler.for(format).create(output_path) do |archive|
35
44
  archive.add(::File.basename(input_path), input_path)
36
45
  end
@@ -160,6 +169,43 @@ module Omnizip
160
169
  Omnizip::Formats::Rar.create(archive_path, options, &block)
161
170
  end
162
171
 
172
+ # Extension -> single-file compressor map. Each lambda takes
173
+ # (input_path, output_path, options). These formats are streams,
174
+ # not archives, so they bypass ArchiveHandler.
175
+ SINGLE_FILE_COMPRESSORS = {
176
+ ".gz" => lambda do |input, output, options|
177
+ Omnizip::Formats::Gzip.compress(input, output, options)
178
+ end,
179
+ ".bz2" => lambda do |input, output, options|
180
+ Omnizip::Formats::Bzip2File.compress(input, output, options)
181
+ end,
182
+ ".xz" => lambda do |input, output, options|
183
+ Omnizip::Formats::Xz.create(::File.binread(input), output, options)
184
+ end,
185
+ ".lzma" => lambda do |input, output, options|
186
+ ::File.open(input, "rb") do |input_io|
187
+ ::File.open(output, "wb") do |output_io|
188
+ Omnizip::Formats::LzmaAlone.compress_stream(input_io, output_io,
189
+ options)
190
+ end
191
+ end
192
+ end,
193
+ ".lz" => lambda do |input, output, options|
194
+ ::File.open(input, "rb") do |input_io|
195
+ ::File.open(output, "wb") do |output_io|
196
+ Omnizip::Formats::Lzip.compress_stream(input_io, output_io,
197
+ options)
198
+ end
199
+ end
200
+ end,
201
+ }.freeze
202
+
203
+ # The single-file compressor matching the output extension, or nil.
204
+ def single_file_handler(output_path)
205
+ ext = ::File.extname(output_path).downcase
206
+ SINGLE_FILE_COMPRESSORS[ext]
207
+ end
208
+
163
209
  private
164
210
 
165
211
  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.30"
4
+ VERSION = "0.3.32"
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.30
4
+ version: 0.3.32
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -339,7 +339,6 @@ files:
339
339
  - lib/omnizip/chunked/writer.rb
340
340
  - lib/omnizip/cli.rb
341
341
  - lib/omnizip/cli/output_formatter.rb
342
- - lib/omnizip/cli/shared.rb
343
342
  - lib/omnizip/commands.rb
344
343
  - lib/omnizip/commands/.keep
345
344
  - 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