omnizip 0.4.4 → 0.4.5

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: 8cf4c62c7304ef792b48b96486685707c68010f0cc6529b6a7504454b94ea025
4
- data.tar.gz: 7c0d5e62fc21a99ccc85520ee0cd3ea32afaae39e76f10826dd56697ffa8376b
3
+ metadata.gz: 5a55c37209e986f7e4499e8a485163728e9c8f0dff506e1aa852672a9585653f
4
+ data.tar.gz: ba0b5437026feed4c1fe4d0ab0146f868386ebe2284ec7b08b45c7260930d582
5
5
  SHA512:
6
- metadata.gz: 539124e9189208423daacdac7cad6735f4a1069358a6ee7e39bb09155524589a2b1a01a6b1c41af6b650648dee327b2c4d36e71ccbbba5ff5e4453168f6ec9ef
7
- data.tar.gz: 02ae0fdb9844f006e5289d8bd351b4ac023e8fafd704bd6fc96b6a0489bc27c96d3e7d3e32ef06011a8123b37573d877191e93cdbaa7c0f6e63e139e8485c09e
6
+ metadata.gz: 13d25032be26ccd096718846ac74126fd14ab3e2729d1e9ed366a3c431815b69c0817e33092f214eea4ed041e41e2eebe070ac92862bd54c70b0379017281637
7
+ data.tar.gz: 8e68e9a4a0d7f489be9956baf6f2d18bb82d5cc0db123adaf689ab73aefca1dcb44c0172a8e774c0cbcfbcca2927c9f40b62da184678588e084642b3b86cd7eb
data/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Fixed
11
+ - PPMd works for the first time: the pure-Ruby ppmd7 core cannot
12
+ decode its own output (root-only decoder, 100-symbol cap — proven)
13
+ and ppmd8 raised NotImplementedError; both now route through the
14
+ Rust tier with param-carrying codec names.
15
+ - The `StringIO#to_s` corruption trap: StringIO has no `#to_s`
16
+ (Kernel's returns the INSPECT string), so IO-coercion paths fed
17
+ `#<StringIO:0x…>` text to codecs. Fixed at the source
18
+ (`Algorithm.to_input_io`), together with the mirror
19
+ `prepare_output` bug that silently discarded StringIO outputs.
20
+ - ZIP entry extraction's raw deflate now rides the tier.
21
+
10
22
  ### Added
11
23
  - Rust tier wiring for every accelerated codec: zstd, deflate,
12
24
  deflate64 (both zlib-framed), gzip, xz, lzip, and lzma-alone now
@@ -35,6 +35,19 @@ module Omnizip
35
35
  @filter = nil
36
36
  end
37
37
 
38
+ class << self
39
+ # Coerce arbitrary input to a binary readable IO. The trap this
40
+ # replaces: StringIO has no #to_s (Kernel#to_s returns the
41
+ # INSPECT string), so StringIO.new(io.to_s) fed "#<StringIO:...>"
42
+ # text to the codec. Anything readable passes through; Strings
43
+ # wrap directly.
44
+ def to_input_io(data)
45
+ return data if data.respond_to?(:read) # allowed: IO-duck detection — StringIO must pass through (its #to_s is the INSPECT string)
46
+
47
+ ::StringIO.new(data.to_s.b)
48
+ end
49
+ end
50
+
38
51
  # Set a preprocessing filter for this algorithm.
39
52
  #
40
53
  # The filter will be applied before compression and reversed after
@@ -95,7 +108,7 @@ module Omnizip
95
108
  # @return [String] Compressed bytes
96
109
  def compress(data, **options)
97
110
  instance = new(options)
98
- input = data.is_a?(::IO) ? data : ::StringIO.new(data.to_s.b)
111
+ input = to_input_io(data)
99
112
  output = ::StringIO.new
100
113
  output.set_encoding(Encoding::BINARY)
101
114
  instance.compress(input, output, options)
@@ -110,7 +123,7 @@ module Omnizip
110
123
  # @return [String] Decompressed bytes
111
124
  def decompress(data, **options)
112
125
  instance = new(options)
113
- input = data.is_a?(::IO) ? data : ::StringIO.new(data.to_s.b)
126
+ input = to_input_io(data)
114
127
  output = ::StringIO.new
115
128
  output.set_encoding(Encoding::BINARY)
116
129
  instance.decompress(input, output, options)
@@ -70,9 +70,22 @@ module Omnizip
70
70
  def compress(input, output, options = {})
71
71
  input = prepare_input(input)
72
72
  output = prepare_output(output)
73
-
74
- encoder = PPMd7::Encoder.new(output, options)
75
- encoder.encode_stream(input)
73
+ data = input.read
74
+ order = options[:model_order] || PPMd7::Model::DEFAULT_ORDER
75
+ mem = options[:mem_size] || PPMd7::Model::DEFAULT_MEM_SIZE
76
+
77
+ # The tier is the authority: Rust's PPMd (the reference-grade
78
+ # port) frames order/size in-band; params travel in the codec
79
+ # name. The pure-Ruby core cannot round-trip its own output
80
+ # beyond ~100 bytes (its decoder reads root-only) — it stays
81
+ # only as the no-dylib fallback.
82
+ output.write(
83
+ Backends.compress("ppmd7:o#{order}:m#{mem}", data, order) do
84
+ encoder = PPMd7::Encoder.new(output, options)
85
+ encoder.encode_stream(StringIO.new(data))
86
+ nil
87
+ end,
88
+ )
76
89
  end
77
90
 
78
91
  # Decompress data using PPMd7
@@ -86,10 +99,16 @@ module Omnizip
86
99
  def decompress(input, output, options = {})
87
100
  input = prepare_input(input)
88
101
  output = prepare_output(output)
89
-
90
- decoder = PPMd7::Decoder.new(input, options)
91
- result = decoder.decode_stream
92
-
102
+ compressed = input.read
103
+ order = options[:model_order] || PPMd7::Model::DEFAULT_ORDER
104
+ mem = options[:mem_size] || PPMd7::Model::DEFAULT_MEM_SIZE
105
+
106
+ result = Backends.decompress(
107
+ "ppmd7:o#{order}:m#{mem}", compressed, Backends::UNKNOWN_LENGTH
108
+ ) do
109
+ decoder = PPMd7::Decoder.new(StringIO.new(compressed), options)
110
+ decoder.decode_stream
111
+ end
93
112
  output.write(result)
94
113
  end
95
114
 
@@ -100,7 +119,9 @@ module Omnizip
100
119
  # @param input [IO, String] Input data
101
120
  # @return [IO] IO object ready for reading
102
121
  def prepare_input(input)
103
- return input if input.is_a?(IO)
122
+ # StringIO is not an ::IO, and its #to_s is Kernel's (the
123
+ # INSPECT string) — pass anything readable straight through.
124
+ return input if input.respond_to?(:read) # allowed: IO-duck detection — StringIO must pass through (its #to_s is the INSPECT string)
104
125
 
105
126
  StringIO.new(input.to_s)
106
127
  end
@@ -110,7 +131,10 @@ module Omnizip
110
131
  # @param output [IO, String, nil] Output destination
111
132
  # @return [IO] IO object ready for writing
112
133
  def prepare_output(output)
113
- return output if output.is_a?(IO)
134
+ # StringIO is NOT an ::IO subclass — the old check discarded
135
+ # it for a fresh buffer, silently dropping every class-level
136
+ # compress's output.
137
+ return output if output.is_a?(IO) || output.is_a?(StringIO)
114
138
 
115
139
  StringIO.new(String.new(encoding: Encoding::BINARY))
116
140
  end
@@ -32,6 +32,42 @@ module Omnizip
32
32
  #
33
33
  # This implementation follows the PPMd8 specification from 7-Zip.
34
34
  class PPMd8 < PPMdBase
35
+ # The tier is the authority: Rust's PPMd8 is the real
36
+ # implementation (the pure-Ruby core raises NotImplementedError
37
+ # for both directions — it never shipped). Parameters travel in
38
+ # the codec name.
39
+ def compress(input, output, options = {})
40
+ input = prepare_input(input)
41
+ output = prepare_output(output)
42
+ data = input.read
43
+ order = options[:model_order] || PPMd8::Model::DEFAULT_ORDER
44
+ mem = options[:mem_size] || PPMd8::Model::DEFAULT_MEM_SIZE
45
+
46
+ output.write(
47
+ Backends.compress("ppmd8:o#{order}:m#{mem}", data, order) do
48
+ super(input, output, options) if false
49
+ raise NotImplementedError,
50
+ "PPMd8 compression is not yet fully implemented"
51
+ end,
52
+ )
53
+ end
54
+
55
+ def decompress(input, output, options = {})
56
+ input = prepare_input(input)
57
+ output = prepare_output(output)
58
+ compressed = input.read
59
+ order = options[:model_order] || PPMd8::Model::DEFAULT_ORDER
60
+ mem = options[:mem_size] || PPMd8::Model::DEFAULT_MEM_SIZE
61
+
62
+ result = Backends.decompress(
63
+ "ppmd8:o#{order}:m#{mem}", compressed, Backends::UNKNOWN_LENGTH
64
+ ) do
65
+ raise NotImplementedError,
66
+ "PPMd8 decompression is not yet fully implemented"
67
+ end
68
+ output.write(result)
69
+ end
70
+
35
71
  # Nested classes - autoloaded
36
72
  autoload :Constants, "omnizip/algorithms/ppmd8/constants"
37
73
  autoload :RestorationMethod, "omnizip/algorithms/ppmd8/restoration_method"
@@ -119,7 +119,9 @@ module Omnizip
119
119
  # @param input [IO, String] Input data
120
120
  # @return [IO] IO object ready for reading
121
121
  def prepare_input(input)
122
- return input if input.is_a?(IO)
122
+ # StringIO is not an ::IO, and its #to_s is Kernel's (the
123
+ # INSPECT string) — pass anything readable straight through.
124
+ return input if input.respond_to?(:read) # allowed: IO-duck detection — StringIO must pass through (its #to_s is the INSPECT string)
123
125
 
124
126
  StringIO.new(input.to_s)
125
127
  end
@@ -129,7 +131,9 @@ module Omnizip
129
131
  # @param output [IO, String, nil] Output destination
130
132
  # @return [IO] IO object ready for writing
131
133
  def prepare_output(output)
132
- return output if output.is_a?(IO)
134
+ # StringIO is not an ::IO subclass — the old check discarded
135
+ # it for a fresh buffer, silently dropping the output.
136
+ return output if output.is_a?(IO) || output.is_a?(StringIO)
133
137
 
134
138
  StringIO.new(String.new(encoding: Encoding::BINARY))
135
139
  end
@@ -50,7 +50,7 @@ module Omnizip
50
50
  return backend_ruby if mode == "ruby"
51
51
 
52
52
  return backend_ruby unless Implementations::Rust::Library.available?
53
- return backend_ruby unless Implementations::Rust::Library::CODECS.include?(codec)
53
+ return backend_ruby unless Implementations::Rust::Library.supports?(codec)
54
54
 
55
55
  backend_rust
56
56
  end
@@ -320,8 +320,13 @@ dereference_links: false)
320
320
  # Decompress using Deflate
321
321
  def decompress_deflate(data)
322
322
  require "zlib"
323
- # ZIP uses raw deflate without zlib wrapper
324
- Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(data)
323
+ # ZIP uses raw deflate without zlib wrapper — the Rust tier
324
+ # speaks exactly that ("deflate"); Zlib remains the fallback.
325
+ Omnizip::Backends.decompress(
326
+ "deflate", data, Omnizip::Backends::UNKNOWN_LENGTH
327
+ ) do
328
+ Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(data)
329
+ end
325
330
  rescue StandardError => e
326
331
  raise Omnizip::DecompressionError,
327
332
  "Deflate decompression failed: #{e.message}"
@@ -21,7 +21,7 @@ module Omnizip
21
21
  # authority; Ruby is the fallback).
22
22
  CODECS = %w[
23
23
  bzip2 zstd lzma xz lzma-alone lzip
24
- deflate deflate64 zlib gzip
24
+ deflate deflate64 zlib gzip ppmd7 ppmd8
25
25
  ].freeze
26
26
 
27
27
  class << self
@@ -38,6 +38,13 @@ module Omnizip
38
38
  !instance.nil?
39
39
  end
40
40
 
41
+ # Whether the cdylib dispatches this codec name — exact, or
42
+ # a param-carrying prefix ("ppmd7:o6:m16777216" under
43
+ # "ppmd7").
44
+ def supports?(name)
45
+ CODECS.any? { |c| name == c || name.start_with?("#{c}:") }
46
+ end
47
+
41
48
  # Drop the cached handle so resolution reruns (test seam:
42
49
  # specs exercising fallback/absence paths need a clean
43
50
  # slate; production never calls this).
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnizip
4
- VERSION = "0.4.4"
4
+ VERSION = "0.4.5"
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.4.4
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.