deflate-ruby 0.1.0
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 +7 -0
- data/CLAUDE.md +138 -0
- data/LICENSE.txt +21 -0
- data/README.md +117 -0
- data/ext/deflate_ruby/deflate_ruby.c +301 -0
- data/ext/deflate_ruby/extconf.rb +34 -0
- data/ext/deflate_ruby/libdeflate/CMakeLists.txt +270 -0
- data/ext/deflate_ruby/libdeflate/COPYING +22 -0
- data/ext/deflate_ruby/libdeflate/NEWS.md +494 -0
- data/ext/deflate_ruby/libdeflate/README.md +228 -0
- data/ext/deflate_ruby/libdeflate/common_defs.h +747 -0
- data/ext/deflate_ruby/libdeflate/lib/adler32.c +162 -0
- data/ext/deflate_ruby/libdeflate/lib/arm/adler32_impl.h +358 -0
- data/ext/deflate_ruby/libdeflate/lib/arm/cpu_features.c +230 -0
- data/ext/deflate_ruby/libdeflate/lib/arm/cpu_features.h +214 -0
- data/ext/deflate_ruby/libdeflate/lib/arm/crc32_impl.h +600 -0
- data/ext/deflate_ruby/libdeflate/lib/arm/crc32_pmull_helpers.h +156 -0
- data/ext/deflate_ruby/libdeflate/lib/arm/crc32_pmull_wide.h +226 -0
- data/ext/deflate_ruby/libdeflate/lib/arm/matchfinder_impl.h +78 -0
- data/ext/deflate_ruby/libdeflate/lib/bt_matchfinder.h +342 -0
- data/ext/deflate_ruby/libdeflate/lib/cpu_features_common.h +93 -0
- data/ext/deflate_ruby/libdeflate/lib/crc32.c +262 -0
- data/ext/deflate_ruby/libdeflate/lib/crc32_multipliers.h +377 -0
- data/ext/deflate_ruby/libdeflate/lib/crc32_tables.h +587 -0
- data/ext/deflate_ruby/libdeflate/lib/decompress_template.h +777 -0
- data/ext/deflate_ruby/libdeflate/lib/deflate_compress.c +4129 -0
- data/ext/deflate_ruby/libdeflate/lib/deflate_compress.h +15 -0
- data/ext/deflate_ruby/libdeflate/lib/deflate_constants.h +56 -0
- data/ext/deflate_ruby/libdeflate/lib/deflate_decompress.c +1208 -0
- data/ext/deflate_ruby/libdeflate/lib/gzip_compress.c +90 -0
- data/ext/deflate_ruby/libdeflate/lib/gzip_constants.h +45 -0
- data/ext/deflate_ruby/libdeflate/lib/gzip_decompress.c +144 -0
- data/ext/deflate_ruby/libdeflate/lib/hc_matchfinder.h +401 -0
- data/ext/deflate_ruby/libdeflate/lib/ht_matchfinder.h +234 -0
- data/ext/deflate_ruby/libdeflate/lib/lib_common.h +106 -0
- data/ext/deflate_ruby/libdeflate/lib/matchfinder_common.h +224 -0
- data/ext/deflate_ruby/libdeflate/lib/riscv/matchfinder_impl.h +97 -0
- data/ext/deflate_ruby/libdeflate/lib/utils.c +141 -0
- data/ext/deflate_ruby/libdeflate/lib/x86/adler32_impl.h +134 -0
- data/ext/deflate_ruby/libdeflate/lib/x86/adler32_template.h +518 -0
- data/ext/deflate_ruby/libdeflate/lib/x86/cpu_features.c +183 -0
- data/ext/deflate_ruby/libdeflate/lib/x86/cpu_features.h +169 -0
- data/ext/deflate_ruby/libdeflate/lib/x86/crc32_impl.h +160 -0
- data/ext/deflate_ruby/libdeflate/lib/x86/crc32_pclmul_template.h +495 -0
- data/ext/deflate_ruby/libdeflate/lib/x86/decompress_impl.h +57 -0
- data/ext/deflate_ruby/libdeflate/lib/x86/matchfinder_impl.h +122 -0
- data/ext/deflate_ruby/libdeflate/lib/zlib_compress.c +82 -0
- data/ext/deflate_ruby/libdeflate/lib/zlib_constants.h +21 -0
- data/ext/deflate_ruby/libdeflate/lib/zlib_decompress.c +104 -0
- data/ext/deflate_ruby/libdeflate/libdeflate-config.cmake.in +3 -0
- data/ext/deflate_ruby/libdeflate/libdeflate.h +411 -0
- data/ext/deflate_ruby/libdeflate/libdeflate.pc.in +18 -0
- data/ext/deflate_ruby/libdeflate/programs/CMakeLists.txt +105 -0
- data/ext/deflate_ruby/libdeflate/programs/benchmark.c +696 -0
- data/ext/deflate_ruby/libdeflate/programs/checksum.c +218 -0
- data/ext/deflate_ruby/libdeflate/programs/config.h.in +19 -0
- data/ext/deflate_ruby/libdeflate/programs/gzip.c +688 -0
- data/ext/deflate_ruby/libdeflate/programs/prog_util.c +521 -0
- data/ext/deflate_ruby/libdeflate/programs/prog_util.h +225 -0
- data/ext/deflate_ruby/libdeflate/programs/test_checksums.c +200 -0
- data/ext/deflate_ruby/libdeflate/programs/test_custom_malloc.c +155 -0
- data/ext/deflate_ruby/libdeflate/programs/test_incomplete_codes.c +385 -0
- data/ext/deflate_ruby/libdeflate/programs/test_invalid_streams.c +130 -0
- data/ext/deflate_ruby/libdeflate/programs/test_litrunlen_overflow.c +72 -0
- data/ext/deflate_ruby/libdeflate/programs/test_overread.c +95 -0
- data/ext/deflate_ruby/libdeflate/programs/test_slow_decompression.c +472 -0
- data/ext/deflate_ruby/libdeflate/programs/test_trailing_bytes.c +151 -0
- data/ext/deflate_ruby/libdeflate/programs/test_util.c +237 -0
- data/ext/deflate_ruby/libdeflate/programs/test_util.h +61 -0
- data/ext/deflate_ruby/libdeflate/programs/tgetopt.c +118 -0
- data/ext/deflate_ruby/libdeflate/scripts/android_build.sh +118 -0
- data/ext/deflate_ruby/libdeflate/scripts/android_tests.sh +69 -0
- data/ext/deflate_ruby/libdeflate/scripts/benchmark.sh +10 -0
- data/ext/deflate_ruby/libdeflate/scripts/checksum.sh +10 -0
- data/ext/deflate_ruby/libdeflate/scripts/checksum_benchmarks.sh +253 -0
- data/ext/deflate_ruby/libdeflate/scripts/cmake-helper.sh +17 -0
- data/ext/deflate_ruby/libdeflate/scripts/deflate_benchmarks.sh +119 -0
- data/ext/deflate_ruby/libdeflate/scripts/exec_tests.sh +38 -0
- data/ext/deflate_ruby/libdeflate/scripts/gen-release-archives.sh +37 -0
- data/ext/deflate_ruby/libdeflate/scripts/gen_bitreverse_tab.py +19 -0
- data/ext/deflate_ruby/libdeflate/scripts/gen_crc32_multipliers.c +199 -0
- data/ext/deflate_ruby/libdeflate/scripts/gen_crc32_tables.c +105 -0
- data/ext/deflate_ruby/libdeflate/scripts/gen_default_litlen_costs.py +44 -0
- data/ext/deflate_ruby/libdeflate/scripts/gen_offset_slot_map.py +29 -0
- data/ext/deflate_ruby/libdeflate/scripts/gzip_tests.sh +523 -0
- data/ext/deflate_ruby/libdeflate/scripts/libFuzzer/deflate_compress/corpus/0 +0 -0
- data/ext/deflate_ruby/libdeflate/scripts/libFuzzer/deflate_compress/fuzz.c +95 -0
- data/ext/deflate_ruby/libdeflate/scripts/libFuzzer/deflate_decompress/corpus/0 +3 -0
- data/ext/deflate_ruby/libdeflate/scripts/libFuzzer/deflate_decompress/fuzz.c +62 -0
- data/ext/deflate_ruby/libdeflate/scripts/libFuzzer/fuzz.sh +108 -0
- data/ext/deflate_ruby/libdeflate/scripts/libFuzzer/gzip_decompress/corpus/0 +0 -0
- data/ext/deflate_ruby/libdeflate/scripts/libFuzzer/gzip_decompress/fuzz.c +19 -0
- data/ext/deflate_ruby/libdeflate/scripts/libFuzzer/zlib_decompress/corpus/0 +3 -0
- data/ext/deflate_ruby/libdeflate/scripts/libFuzzer/zlib_decompress/fuzz.c +19 -0
- data/ext/deflate_ruby/libdeflate/scripts/run_tests.sh +416 -0
- data/ext/deflate_ruby/libdeflate/scripts/toolchain-i686-w64-mingw32.cmake +8 -0
- data/ext/deflate_ruby/libdeflate/scripts/toolchain-x86_64-w64-mingw32.cmake +8 -0
- data/lib/deflate_ruby/version.rb +5 -0
- data/lib/deflate_ruby.rb +71 -0
- metadata +191 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 68bc8b3a391756a9e043136b9b124086964806fcbb5683810181f4fcd19442ab
|
|
4
|
+
data.tar.gz: 17442db0cf381e96a52c0c198efdbf762b360c7b5decb552e82d1e8b72f0adec
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 46dbf853b6d7a0632253f167a2f792a69cc85c6623f8eaa77f4f96f6153c7f42af13e4f8c7efb3675245f2303899c80825e12aa3d94cdfbd03ace89d646bc9f8
|
|
7
|
+
data.tar.gz: 00fffef0a4c970467a3ffd9501f843af740d280f181af56255defb0189c82f5f0552133a3b3f27f54e322bfff0cbe860ab31771028c469f9d4ed58f23ba34a08
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
DeflateRuby is a Ruby gem that wraps the libdeflate C library to provide fast compression and decompression for DEFLATE, zlib, and gzip formats. The gem is built as a C extension that directly integrates libdeflate source files.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
### Core Components
|
|
12
|
+
|
|
13
|
+
1. **C Extension** (`ext/deflate_ruby/`)
|
|
14
|
+
- `deflate_ruby.c`: Main C extension wrapper implementing Ruby bindings for libdeflate
|
|
15
|
+
- `extconf.rb`: Extension configuration that compiles libdeflate source files along with the wrapper
|
|
16
|
+
- `libdeflate/`: Complete libdeflate source tree (v1.21) including all platform-specific optimizations
|
|
17
|
+
|
|
18
|
+
2. **Ruby Library** (`lib/deflate_ruby/`)
|
|
19
|
+
- `deflate_ruby.rb`: High-level Ruby API providing file-based compression/decompression methods
|
|
20
|
+
- `version.rb`: Gem version constant
|
|
21
|
+
- `deflate_ruby.so`: Compiled C extension (generated during build)
|
|
22
|
+
|
|
23
|
+
3. **Test Suite** (`test/`)
|
|
24
|
+
- `deflate_test.rb`: Tests for raw DEFLATE format
|
|
25
|
+
- `zlib_test.rb`: Tests for zlib format including stdlib interoperability
|
|
26
|
+
- `gzip_test.rb`: Tests for gzip format including stdlib interoperability
|
|
27
|
+
|
|
28
|
+
### Key Design Decisions
|
|
29
|
+
|
|
30
|
+
- **Embedded libdeflate**: The libdeflate source is included directly in `ext/deflate_ruby/libdeflate/` and compiled as part of the extension. This ensures consistent behavior across platforms and avoids external dependencies.
|
|
31
|
+
|
|
32
|
+
- **Buffer Management**: Decompression uses an adaptive buffer strategy starting at 100x the compressed size and doubling up to 10 attempts. This handles highly compressible data (like repetitive text) that can expand significantly.
|
|
33
|
+
|
|
34
|
+
- **Dual API**: The gem provides both string-based methods (e.g., `deflate_compress(data)`) and file-based methods (e.g., `deflate_compress_file(input, output)`) for flexibility.
|
|
35
|
+
|
|
36
|
+
## Common Development Commands
|
|
37
|
+
|
|
38
|
+
### Building the Extension
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Initial setup
|
|
42
|
+
cd ext/deflate_ruby
|
|
43
|
+
ruby extconf.rb
|
|
44
|
+
make
|
|
45
|
+
|
|
46
|
+
# Copy compiled extension to lib directory
|
|
47
|
+
cp deflate_ruby.so ../../lib/deflate_ruby/
|
|
48
|
+
|
|
49
|
+
# Or use rake
|
|
50
|
+
cd ../..
|
|
51
|
+
rake compile
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Running Tests
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Run all tests
|
|
58
|
+
rake test
|
|
59
|
+
|
|
60
|
+
# Run specific test file
|
|
61
|
+
ruby -Ilib:test test/deflate_test.rb
|
|
62
|
+
ruby -Ilib:test test/zlib_test.rb
|
|
63
|
+
ruby -Ilib:test test/gzip_test.rb
|
|
64
|
+
|
|
65
|
+
# Run single test method
|
|
66
|
+
ruby -Ilib:test test/deflate_test.rb -n test_deflate_compress_decompress_roundtrip
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Cleaning Build Artifacts
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd ext/deflate_ruby
|
|
73
|
+
make clean
|
|
74
|
+
|
|
75
|
+
# Or use rake
|
|
76
|
+
cd ../..
|
|
77
|
+
rake clean
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Important Implementation Details
|
|
81
|
+
|
|
82
|
+
### C Extension (ext/deflate_ruby/deflate_ruby.c)
|
|
83
|
+
|
|
84
|
+
- **Error Handling**: Uses custom `DeflateRuby::Error` exception class for all compression/decompression errors
|
|
85
|
+
- **Memory Management**: All libdeflate compressor/decompressor objects are properly freed after use
|
|
86
|
+
- **Compression Levels**: Supports levels 1-12 (libdeflate extends beyond standard 1-9)
|
|
87
|
+
- **Buffer Allocation**: Uses Ruby's string buffer API (`rb_str_buf_new`, `rb_str_resize`) for efficient memory handling
|
|
88
|
+
|
|
89
|
+
### Extension Configuration (ext/deflate_ruby/extconf.rb)
|
|
90
|
+
|
|
91
|
+
- Compiles all libdeflate source files from `lib/*.c` and architecture-specific subdirectories (`lib/x86/*.c`, `lib/arm/*.c`)
|
|
92
|
+
- Uses `-O2` optimization and `-std=c99` for C99 compatibility
|
|
93
|
+
- Sets up include paths to find `libdeflate.h`
|
|
94
|
+
|
|
95
|
+
### Test Data Characteristics
|
|
96
|
+
|
|
97
|
+
- Tests use moderately large data (1000x repetitions) to see meaningful compression level differences
|
|
98
|
+
- For gzip tests, binary data is prepended to text to make compression level differences more apparent
|
|
99
|
+
- Compression level tests compare level 1 vs level 6 (not higher levels) as very high levels can sometimes produce larger output on small, highly compressible data
|
|
100
|
+
|
|
101
|
+
## libdeflate Integration
|
|
102
|
+
|
|
103
|
+
The libdeflate library (https://github.com/ebiggers/libdeflate) is a heavily optimized compression library that:
|
|
104
|
+
|
|
105
|
+
- Provides faster compression/decompression than zlib for whole-buffer operations
|
|
106
|
+
- Includes CPU-specific optimizations (x86 SSE/AVX, ARM NEON)
|
|
107
|
+
- Supports DEFLATE, zlib, and gzip formats
|
|
108
|
+
- Is designed for non-streaming (whole-buffer) use cases
|
|
109
|
+
|
|
110
|
+
**Version**: Currently using libdeflate v1.21
|
|
111
|
+
|
|
112
|
+
**Updating libdeflate**: To update to a new version:
|
|
113
|
+
1. Download new release from https://github.com/ebiggers/libdeflate/releases
|
|
114
|
+
2. Extract to `ext/deflate_ruby/libdeflate/`
|
|
115
|
+
3. Test thoroughly as API may have changed
|
|
116
|
+
4. Update version reference in this file
|
|
117
|
+
|
|
118
|
+
## Testing Philosophy
|
|
119
|
+
|
|
120
|
+
This gem follows test-driven development (TDD):
|
|
121
|
+
|
|
122
|
+
1. Tests were written first, defining expected behavior
|
|
123
|
+
2. Implementation follows the test specifications
|
|
124
|
+
3. All three formats (deflate, zlib, gzip) have comprehensive test coverage including:
|
|
125
|
+
- Round-trip compression/decompression
|
|
126
|
+
- Multiple compression levels
|
|
127
|
+
- Empty data handling
|
|
128
|
+
- Invalid data error handling
|
|
129
|
+
- Binary data support
|
|
130
|
+
- Interoperability with Ruby's stdlib Zlib
|
|
131
|
+
|
|
132
|
+
## Compression Format Notes
|
|
133
|
+
|
|
134
|
+
- **DEFLATE**: Raw compressed stream with no wrapper or checksum
|
|
135
|
+
- **zlib**: DEFLATE stream with zlib header and Adler-32 checksum trailer
|
|
136
|
+
- **gzip**: DEFLATE stream with gzip header (includes magic bytes 0x1f 0x8b) and CRC32 checksum trailer
|
|
137
|
+
|
|
138
|
+
The gem's zlib and gzip implementations are fully interoperable with Ruby's standard library and command-line tools.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# DeflateRuby
|
|
2
|
+
|
|
3
|
+
Fast compression and decompression for Ruby using [libdeflate](https://github.com/ebiggers/libdeflate).
|
|
4
|
+
|
|
5
|
+
DeflateRuby provides Ruby bindings for libdeflate, a heavily optimized library for DEFLATE, zlib, and gzip compression and decompression. It's significantly faster than Ruby's built-in Zlib for whole-buffer compression/decompression operations.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
### From RubyGems.org
|
|
10
|
+
|
|
11
|
+
Install the gem directly:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
gem install deflate-ruby
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or add this line to your application's Gemfile:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
gem 'deflate-ruby'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
And then execute:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
bundle install
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### From Source
|
|
30
|
+
|
|
31
|
+
To build and install from source:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/deflate-ruby/deflate-ruby.git
|
|
35
|
+
cd deflate-ruby
|
|
36
|
+
gem build deflate-ruby.gemspec
|
|
37
|
+
gem install deflate-ruby-0.1.0.gem
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
DeflateRuby supports three compression formats: DEFLATE (raw), zlib, and gzip.
|
|
43
|
+
|
|
44
|
+
### String Compression/Decompression
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
require 'deflate_ruby'
|
|
48
|
+
|
|
49
|
+
data = "Hello, World!" * 100
|
|
50
|
+
|
|
51
|
+
# DEFLATE
|
|
52
|
+
compressed = DeflateRuby.deflate_compress(data, 6) # level 1-12
|
|
53
|
+
decompressed = DeflateRuby.deflate_decompress(compressed)
|
|
54
|
+
|
|
55
|
+
# Zlib
|
|
56
|
+
compressed = DeflateRuby.zlib_compress(data, 6)
|
|
57
|
+
decompressed = DeflateRuby.zlib_decompress(compressed)
|
|
58
|
+
|
|
59
|
+
# Gzip
|
|
60
|
+
compressed = DeflateRuby.gzip_compress(data, 6)
|
|
61
|
+
decompressed = DeflateRuby.gzip_decompress(compressed)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### File Compression/Decompression
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
# DEFLATE
|
|
68
|
+
DeflateRuby.deflate_compress_file("input.txt", "output.deflate", level: 9)
|
|
69
|
+
DeflateRuby.deflate_decompress_file("output.deflate", "decompressed.txt")
|
|
70
|
+
|
|
71
|
+
# Zlib
|
|
72
|
+
DeflateRuby.zlib_compress_file("input.txt", "output.zlib", level: 6)
|
|
73
|
+
DeflateRuby.zlib_decompress_file("output.zlib", "decompressed.txt")
|
|
74
|
+
|
|
75
|
+
# Gzip
|
|
76
|
+
DeflateRuby.gzip_compress_file("input.txt", "output.gz", level: 6)
|
|
77
|
+
DeflateRuby.gzip_decompress_file("output.gz", "decompressed.txt")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Compression Levels
|
|
81
|
+
|
|
82
|
+
DeflateRuby supports compression levels from 1 to 12:
|
|
83
|
+
- Levels 1-9: Standard compression levels (1 = fastest, 9 = best compression)
|
|
84
|
+
- Levels 10-12: Enhanced compression using minimum-cost-path algorithms
|
|
85
|
+
|
|
86
|
+
Default level is 6, which provides a good balance between speed and compression ratio.
|
|
87
|
+
|
|
88
|
+
## Compatibility
|
|
89
|
+
|
|
90
|
+
DeflateRuby's zlib and gzip formats are fully compatible with Ruby's built-in Zlib library and standard command-line tools like `gzip`.
|
|
91
|
+
|
|
92
|
+
## Performance
|
|
93
|
+
|
|
94
|
+
libdeflate is optimized for modern CPUs and can be significantly faster than zlib, especially for compression. It's designed for whole-buffer operations (not streaming), making it ideal for:
|
|
95
|
+
|
|
96
|
+
- Compressing/decompressing entire files
|
|
97
|
+
- Working with in-memory data
|
|
98
|
+
- Batch compression operations
|
|
99
|
+
- High-throughput scenarios
|
|
100
|
+
|
|
101
|
+
## Development
|
|
102
|
+
|
|
103
|
+
After checking out the repo, run:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
bundle install
|
|
107
|
+
rake compile
|
|
108
|
+
rake test
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
|
114
|
+
|
|
115
|
+
## Credits
|
|
116
|
+
|
|
117
|
+
This gem wraps [libdeflate](https://github.com/ebiggers/libdeflate) by Eric Biggers.
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#include <ruby.h>
|
|
2
|
+
#include "libdeflate.h"
|
|
3
|
+
#include <string.h>
|
|
4
|
+
|
|
5
|
+
static VALUE rb_mDeflateRuby;
|
|
6
|
+
static VALUE rb_eDeflateError;
|
|
7
|
+
|
|
8
|
+
// Helper function to raise errors
|
|
9
|
+
static void raise_deflate_error(const char *message) {
|
|
10
|
+
rb_raise(rb_eDeflateError, "%s", message);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// DEFLATE compression
|
|
14
|
+
static VALUE rb_deflate_compress(int argc, VALUE *argv, VALUE self) {
|
|
15
|
+
VALUE data, level_val;
|
|
16
|
+
rb_scan_args(argc, argv, "11", &data, &level_val);
|
|
17
|
+
|
|
18
|
+
Check_Type(data, T_STRING);
|
|
19
|
+
|
|
20
|
+
int level = NIL_P(level_val) ? 6 : NUM2INT(level_val);
|
|
21
|
+
if (level < 1 || level > 12) {
|
|
22
|
+
rb_raise(rb_eArgError, "compression level must be between 1 and 12");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const char *in_data = RSTRING_PTR(data);
|
|
26
|
+
size_t in_size = RSTRING_LEN(data);
|
|
27
|
+
|
|
28
|
+
struct libdeflate_compressor *compressor = libdeflate_alloc_compressor(level);
|
|
29
|
+
if (!compressor) {
|
|
30
|
+
raise_deflate_error("Failed to allocate compressor");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
size_t max_out_size = libdeflate_deflate_compress_bound(compressor, in_size);
|
|
34
|
+
VALUE out_str = rb_str_buf_new(max_out_size);
|
|
35
|
+
|
|
36
|
+
size_t actual_size = libdeflate_deflate_compress(
|
|
37
|
+
compressor,
|
|
38
|
+
in_data,
|
|
39
|
+
in_size,
|
|
40
|
+
RSTRING_PTR(out_str),
|
|
41
|
+
max_out_size
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
libdeflate_free_compressor(compressor);
|
|
45
|
+
|
|
46
|
+
if (actual_size == 0) {
|
|
47
|
+
raise_deflate_error("Compression failed");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
rb_str_set_len(out_str, actual_size);
|
|
51
|
+
return out_str;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// DEFLATE decompression
|
|
55
|
+
static VALUE rb_deflate_decompress(VALUE self, VALUE data) {
|
|
56
|
+
Check_Type(data, T_STRING);
|
|
57
|
+
|
|
58
|
+
const char *in_data = RSTRING_PTR(data);
|
|
59
|
+
size_t in_size = RSTRING_LEN(data);
|
|
60
|
+
|
|
61
|
+
struct libdeflate_decompressor *decompressor = libdeflate_alloc_decompressor();
|
|
62
|
+
if (!decompressor) {
|
|
63
|
+
raise_deflate_error("Failed to allocate decompressor");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Start with a reasonable buffer size and grow if needed
|
|
67
|
+
size_t out_size = in_size * 100; // Start larger for highly compressible data
|
|
68
|
+
VALUE out_str = rb_str_buf_new(out_size);
|
|
69
|
+
size_t actual_size;
|
|
70
|
+
|
|
71
|
+
enum libdeflate_result result;
|
|
72
|
+
|
|
73
|
+
// Try decompression, increase buffer if needed
|
|
74
|
+
for (int attempts = 0; attempts < 10; attempts++) {
|
|
75
|
+
result = libdeflate_deflate_decompress(
|
|
76
|
+
decompressor,
|
|
77
|
+
in_data,
|
|
78
|
+
in_size,
|
|
79
|
+
RSTRING_PTR(out_str),
|
|
80
|
+
out_size,
|
|
81
|
+
&actual_size
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (result == LIBDEFLATE_SUCCESS) {
|
|
85
|
+
break;
|
|
86
|
+
} else if (result == LIBDEFLATE_INSUFFICIENT_SPACE) {
|
|
87
|
+
out_size *= 2;
|
|
88
|
+
rb_str_resize(out_str, out_size);
|
|
89
|
+
} else {
|
|
90
|
+
libdeflate_free_decompressor(decompressor);
|
|
91
|
+
raise_deflate_error("Decompression failed: invalid or corrupted data");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
libdeflate_free_decompressor(decompressor);
|
|
96
|
+
|
|
97
|
+
if (result != LIBDEFLATE_SUCCESS) {
|
|
98
|
+
raise_deflate_error("Decompression failed: output buffer too small");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
rb_str_set_len(out_str, actual_size);
|
|
102
|
+
return out_str;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ZLIB compression
|
|
106
|
+
static VALUE rb_zlib_compress(int argc, VALUE *argv, VALUE self) {
|
|
107
|
+
VALUE data, level_val;
|
|
108
|
+
rb_scan_args(argc, argv, "11", &data, &level_val);
|
|
109
|
+
|
|
110
|
+
Check_Type(data, T_STRING);
|
|
111
|
+
|
|
112
|
+
int level = NIL_P(level_val) ? 6 : NUM2INT(level_val);
|
|
113
|
+
if (level < 1 || level > 12) {
|
|
114
|
+
rb_raise(rb_eArgError, "compression level must be between 1 and 12");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const char *in_data = RSTRING_PTR(data);
|
|
118
|
+
size_t in_size = RSTRING_LEN(data);
|
|
119
|
+
|
|
120
|
+
struct libdeflate_compressor *compressor = libdeflate_alloc_compressor(level);
|
|
121
|
+
if (!compressor) {
|
|
122
|
+
raise_deflate_error("Failed to allocate compressor");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
size_t max_out_size = libdeflate_zlib_compress_bound(compressor, in_size);
|
|
126
|
+
VALUE out_str = rb_str_buf_new(max_out_size);
|
|
127
|
+
|
|
128
|
+
size_t actual_size = libdeflate_zlib_compress(
|
|
129
|
+
compressor,
|
|
130
|
+
in_data,
|
|
131
|
+
in_size,
|
|
132
|
+
RSTRING_PTR(out_str),
|
|
133
|
+
max_out_size
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
libdeflate_free_compressor(compressor);
|
|
137
|
+
|
|
138
|
+
if (actual_size == 0) {
|
|
139
|
+
raise_deflate_error("Compression failed");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
rb_str_set_len(out_str, actual_size);
|
|
143
|
+
return out_str;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ZLIB decompression
|
|
147
|
+
static VALUE rb_zlib_decompress(VALUE self, VALUE data) {
|
|
148
|
+
Check_Type(data, T_STRING);
|
|
149
|
+
|
|
150
|
+
const char *in_data = RSTRING_PTR(data);
|
|
151
|
+
size_t in_size = RSTRING_LEN(data);
|
|
152
|
+
|
|
153
|
+
struct libdeflate_decompressor *decompressor = libdeflate_alloc_decompressor();
|
|
154
|
+
if (!decompressor) {
|
|
155
|
+
raise_deflate_error("Failed to allocate decompressor");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
size_t out_size = in_size * 100; // Start larger for highly compressible data
|
|
159
|
+
VALUE out_str = rb_str_buf_new(out_size);
|
|
160
|
+
size_t actual_size;
|
|
161
|
+
|
|
162
|
+
enum libdeflate_result result;
|
|
163
|
+
|
|
164
|
+
for (int attempts = 0; attempts < 10; attempts++) {
|
|
165
|
+
result = libdeflate_zlib_decompress(
|
|
166
|
+
decompressor,
|
|
167
|
+
in_data,
|
|
168
|
+
in_size,
|
|
169
|
+
RSTRING_PTR(out_str),
|
|
170
|
+
out_size,
|
|
171
|
+
&actual_size
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (result == LIBDEFLATE_SUCCESS) {
|
|
175
|
+
break;
|
|
176
|
+
} else if (result == LIBDEFLATE_INSUFFICIENT_SPACE) {
|
|
177
|
+
out_size *= 2;
|
|
178
|
+
rb_str_resize(out_str, out_size);
|
|
179
|
+
} else {
|
|
180
|
+
libdeflate_free_decompressor(decompressor);
|
|
181
|
+
raise_deflate_error("Decompression failed: invalid or corrupted zlib data");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
libdeflate_free_decompressor(decompressor);
|
|
186
|
+
|
|
187
|
+
if (result != LIBDEFLATE_SUCCESS) {
|
|
188
|
+
raise_deflate_error("Decompression failed: output buffer too small");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
rb_str_set_len(out_str, actual_size);
|
|
192
|
+
return out_str;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// GZIP compression
|
|
196
|
+
static VALUE rb_gzip_compress(int argc, VALUE *argv, VALUE self) {
|
|
197
|
+
VALUE data, level_val;
|
|
198
|
+
rb_scan_args(argc, argv, "11", &data, &level_val);
|
|
199
|
+
|
|
200
|
+
Check_Type(data, T_STRING);
|
|
201
|
+
|
|
202
|
+
int level = NIL_P(level_val) ? 6 : NUM2INT(level_val);
|
|
203
|
+
if (level < 1 || level > 12) {
|
|
204
|
+
rb_raise(rb_eArgError, "compression level must be between 1 and 12");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const char *in_data = RSTRING_PTR(data);
|
|
208
|
+
size_t in_size = RSTRING_LEN(data);
|
|
209
|
+
|
|
210
|
+
struct libdeflate_compressor *compressor = libdeflate_alloc_compressor(level);
|
|
211
|
+
if (!compressor) {
|
|
212
|
+
raise_deflate_error("Failed to allocate compressor");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
size_t max_out_size = libdeflate_gzip_compress_bound(compressor, in_size);
|
|
216
|
+
VALUE out_str = rb_str_buf_new(max_out_size);
|
|
217
|
+
|
|
218
|
+
size_t actual_size = libdeflate_gzip_compress(
|
|
219
|
+
compressor,
|
|
220
|
+
in_data,
|
|
221
|
+
in_size,
|
|
222
|
+
RSTRING_PTR(out_str),
|
|
223
|
+
max_out_size
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
libdeflate_free_compressor(compressor);
|
|
227
|
+
|
|
228
|
+
if (actual_size == 0) {
|
|
229
|
+
raise_deflate_error("Compression failed");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
rb_str_set_len(out_str, actual_size);
|
|
233
|
+
return out_str;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// GZIP decompression
|
|
237
|
+
static VALUE rb_gzip_decompress(VALUE self, VALUE data) {
|
|
238
|
+
Check_Type(data, T_STRING);
|
|
239
|
+
|
|
240
|
+
const char *in_data = RSTRING_PTR(data);
|
|
241
|
+
size_t in_size = RSTRING_LEN(data);
|
|
242
|
+
|
|
243
|
+
struct libdeflate_decompressor *decompressor = libdeflate_alloc_decompressor();
|
|
244
|
+
if (!decompressor) {
|
|
245
|
+
raise_deflate_error("Failed to allocate decompressor");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
size_t out_size = in_size * 100; // Start larger for highly compressible data
|
|
249
|
+
VALUE out_str = rb_str_buf_new(out_size);
|
|
250
|
+
size_t actual_size;
|
|
251
|
+
|
|
252
|
+
enum libdeflate_result result;
|
|
253
|
+
|
|
254
|
+
for (int attempts = 0; attempts < 10; attempts++) {
|
|
255
|
+
result = libdeflate_gzip_decompress(
|
|
256
|
+
decompressor,
|
|
257
|
+
in_data,
|
|
258
|
+
in_size,
|
|
259
|
+
RSTRING_PTR(out_str),
|
|
260
|
+
out_size,
|
|
261
|
+
&actual_size
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
if (result == LIBDEFLATE_SUCCESS) {
|
|
265
|
+
break;
|
|
266
|
+
} else if (result == LIBDEFLATE_INSUFFICIENT_SPACE) {
|
|
267
|
+
out_size *= 2;
|
|
268
|
+
rb_str_resize(out_str, out_size);
|
|
269
|
+
} else {
|
|
270
|
+
libdeflate_free_decompressor(decompressor);
|
|
271
|
+
raise_deflate_error("Decompression failed: invalid or corrupted gzip data");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
libdeflate_free_decompressor(decompressor);
|
|
276
|
+
|
|
277
|
+
if (result != LIBDEFLATE_SUCCESS) {
|
|
278
|
+
raise_deflate_error("Decompression failed: output buffer too small");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
rb_str_set_len(out_str, actual_size);
|
|
282
|
+
return out_str;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Initialize the extension
|
|
286
|
+
void Init_deflate_ruby(void) {
|
|
287
|
+
rb_mDeflateRuby = rb_define_module("DeflateRuby");
|
|
288
|
+
rb_eDeflateError = rb_define_class_under(rb_mDeflateRuby, "Error", rb_eStandardError);
|
|
289
|
+
|
|
290
|
+
// DEFLATE methods
|
|
291
|
+
rb_define_module_function(rb_mDeflateRuby, "deflate_compress", rb_deflate_compress, -1);
|
|
292
|
+
rb_define_module_function(rb_mDeflateRuby, "deflate_decompress", rb_deflate_decompress, 1);
|
|
293
|
+
|
|
294
|
+
// ZLIB methods
|
|
295
|
+
rb_define_module_function(rb_mDeflateRuby, "zlib_compress", rb_zlib_compress, -1);
|
|
296
|
+
rb_define_module_function(rb_mDeflateRuby, "zlib_decompress", rb_zlib_decompress, 1);
|
|
297
|
+
|
|
298
|
+
// GZIP methods
|
|
299
|
+
rb_define_module_function(rb_mDeflateRuby, "gzip_compress", rb_gzip_compress, -1);
|
|
300
|
+
rb_define_module_function(rb_mDeflateRuby, "gzip_decompress", rb_gzip_decompress, 1);
|
|
301
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mkmf"
|
|
4
|
+
|
|
5
|
+
# Add libdeflate source directory to the include path
|
|
6
|
+
$INCFLAGS << " -I$(srcdir)/libdeflate"
|
|
7
|
+
$CFLAGS << " -O2 -std=c99"
|
|
8
|
+
|
|
9
|
+
# Define source files to compile
|
|
10
|
+
libdeflate_sources = %w[
|
|
11
|
+
libdeflate/lib/deflate_compress.c
|
|
12
|
+
libdeflate/lib/deflate_decompress.c
|
|
13
|
+
libdeflate/lib/zlib_compress.c
|
|
14
|
+
libdeflate/lib/zlib_decompress.c
|
|
15
|
+
libdeflate/lib/gzip_compress.c
|
|
16
|
+
libdeflate/lib/gzip_decompress.c
|
|
17
|
+
libdeflate/lib/adler32.c
|
|
18
|
+
libdeflate/lib/crc32.c
|
|
19
|
+
libdeflate/lib/utils.c
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
# Add CPU architecture-specific files
|
|
23
|
+
arch_dirs = Dir.glob("libdeflate/lib/*/").select { |d| File.directory?(d) }
|
|
24
|
+
arch_dirs.each do |dir|
|
|
25
|
+
Dir.glob("#{dir}*.c").each do |source|
|
|
26
|
+
libdeflate_sources << source
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Set object files for libdeflate
|
|
31
|
+
$objs = libdeflate_sources.map { |src| src.sub(/\.c$/, ".o") }
|
|
32
|
+
$objs << "deflate_ruby.o"
|
|
33
|
+
|
|
34
|
+
create_makefile("deflate_ruby/deflate_ruby")
|