brotli 0.6.0 → 0.7.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 +4 -4
- data/.github/workflows/main.yml +1 -1
- data/Dockerfile +38 -0
- data/README.md +50 -1
- data/Rakefile +17 -0
- data/ext/brotli/brotli.c +116 -12
- data/ext/brotli/brotli.h +3 -0
- data/ext/brotli/buffer.c +6 -5
- data/ext/brotli/extconf.rb +9 -0
- data/lib/brotli/version.rb +1 -1
- data/test/brotli_test.rb +69 -3
- data/test/brotli_writer_test.rb +57 -0
- metadata +4 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3722424560d3f3491399e46108213ae088a809ca502fe2c9f8a83d5c9383434
|
4
|
+
data.tar.gz: 4584239a0597b0c2e5cf47ba0d6c8a96e0198cc8af7c7b217f8bf1312bab56d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca8b9c2bfae436c8b19d657948e0a2f2a3137d05ef9af070f2de4625ffcbe9cb7dd2e6eb011c8e475ea16a74ac5825b9dc074ab95b809e68ce722a06ce72798d
|
7
|
+
data.tar.gz: b5864a5aeb5630e8ad9b491621d89aa4c12f65131a261e49b0499d35cd5b32d43288ddbccb10cfe2ab7201d140e1ae311a0cbb1328cc0a010fad4ad775db8f2d
|
data/.github/workflows/main.yml
CHANGED
data/Dockerfile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
ARG GCC_VERSION=14
|
2
|
+
FROM gcc:${GCC_VERSION}
|
3
|
+
|
4
|
+
ARG USE_SYSTEM_BROTLI=true
|
5
|
+
|
6
|
+
# Install Ruby and development dependencies
|
7
|
+
RUN apt-get update && apt-get install -y \
|
8
|
+
git \
|
9
|
+
libbrotli-dev \
|
10
|
+
pkg-config \
|
11
|
+
ruby \
|
12
|
+
ruby-dev \
|
13
|
+
&& rm -rf /var/lib/apt/lists/*
|
14
|
+
|
15
|
+
# Verify GCC version
|
16
|
+
RUN gcc --version
|
17
|
+
|
18
|
+
# Set working directory
|
19
|
+
WORKDIR /app
|
20
|
+
|
21
|
+
# Copy source code
|
22
|
+
COPY . .
|
23
|
+
|
24
|
+
# Install bundler and dependencies
|
25
|
+
RUN gem install bundler
|
26
|
+
RUN bundle install
|
27
|
+
|
28
|
+
# Build and test
|
29
|
+
RUN if [ "${USE_SYSTEM_BROTLI}" = "true" ]; then \
|
30
|
+
echo "Building with system Brotli" && \
|
31
|
+
bundle exec rake compile; \
|
32
|
+
else \
|
33
|
+
echo "Building with vendor Brotli" && \
|
34
|
+
bundle exec rake compile -- --enable-vendor; \
|
35
|
+
fi
|
36
|
+
RUN bundle exec rake test
|
37
|
+
|
38
|
+
CMD ["bash"]
|
data/README.md
CHANGED
@@ -25,13 +25,62 @@ Or install it yourself as:
|
|
25
25
|
|
26
26
|
## Usage
|
27
27
|
|
28
|
+
### Basic Compression/Decompression
|
29
|
+
|
28
30
|
```ruby
|
29
31
|
require 'brotli'
|
30
32
|
compressed = Brotli.deflate(string)
|
31
33
|
decompressed = Brotli.inflate(compressed)
|
32
34
|
```
|
33
35
|
|
34
|
-
|
36
|
+
### Custom Dictionary Support
|
37
|
+
|
38
|
+
Brotli supports using custom dictionaries to improve compression ratio when you have repetitive data patterns:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
# Using a dictionary that contains common patterns in your data
|
42
|
+
dictionary = "common patterns in my data"
|
43
|
+
data = "This text contains common patterns in my data multiple times"
|
44
|
+
|
45
|
+
# Compress with dictionary
|
46
|
+
compressed = Brotli.deflate(data, dictionary: dictionary)
|
47
|
+
|
48
|
+
# Decompress with the same dictionary
|
49
|
+
decompressed = Brotli.inflate(compressed, dictionary: dictionary)
|
50
|
+
```
|
51
|
+
|
52
|
+
### Compression Options
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# Combine dictionary with other compression options
|
56
|
+
compressed = Brotli.deflate(data,
|
57
|
+
dictionary: dictionary,
|
58
|
+
quality: 11, # 0-11, higher = better compression but slower
|
59
|
+
mode: :text, # :generic (default), :text, or :font
|
60
|
+
lgwin: 22, # window size (10-24)
|
61
|
+
lgblock: 0 # block size (0 or 16-24)
|
62
|
+
)
|
63
|
+
```
|
64
|
+
|
65
|
+
### Streaming Compression with Writer
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
# Basic usage
|
69
|
+
File.open('output.br', 'wb') do |file|
|
70
|
+
writer = Brotli::Writer.new(file)
|
71
|
+
writer.write(data)
|
72
|
+
writer.close
|
73
|
+
end
|
74
|
+
|
75
|
+
# With dictionary
|
76
|
+
File.open('output.br', 'wb') do |file|
|
77
|
+
writer = Brotli::Writer.new(file, dictionary: dictionary)
|
78
|
+
writer.write(data)
|
79
|
+
writer.close
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
See test/brotli_test.rb for more examples.
|
35
84
|
|
36
85
|
## Development
|
37
86
|
|
data/Rakefile
CHANGED
@@ -23,3 +23,20 @@ end
|
|
23
23
|
task :build => :compile
|
24
24
|
task :test => :compile
|
25
25
|
task :default => :test
|
26
|
+
|
27
|
+
task :docker do
|
28
|
+
gcc_versions = ["14", "15"]
|
29
|
+
brotli_configs = [true, false]
|
30
|
+
gcc_versions.product(brotli_configs).each do |gcc_version, use_system_brotli|
|
31
|
+
command = "docker build "\
|
32
|
+
"--progress=plain "\
|
33
|
+
"--build-arg GCC_VERSION=#{gcc_version} "\
|
34
|
+
"--build-arg USE_SYSTEM_BROTLI=#{use_system_brotli} "\
|
35
|
+
"-t brotli:#{gcc_version}#{use_system_brotli ? "-use_system_brotli" : ""} ."
|
36
|
+
puts "Running: #{command}"
|
37
|
+
system command
|
38
|
+
unless $?.exited?
|
39
|
+
raise "Docker build failed for GCC version #{gcc_version} with use_system_brotli=#{use_system_brotli}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/ext/brotli/brotli.c
CHANGED
@@ -28,6 +28,8 @@ typedef struct {
|
|
28
28
|
BrotliDecoderState* s;
|
29
29
|
buffer_t* buffer;
|
30
30
|
BrotliDecoderResult r;
|
31
|
+
uint8_t* dict;
|
32
|
+
size_t dict_len;
|
31
33
|
} brotli_inflate_args_t;
|
32
34
|
|
33
35
|
static void*
|
@@ -44,6 +46,14 @@ brotli_inflate_no_gvl(void *arg)
|
|
44
46
|
buffer_t* buffer = args->buffer;
|
45
47
|
BrotliDecoderState* s = args->s;
|
46
48
|
|
49
|
+
#ifdef HAVE_BROTLIDECODERATTACHDICTIONARY
|
50
|
+
/* Attach dictionary if provided */
|
51
|
+
if (args->dict && args->dict_len > 0) {
|
52
|
+
BrotliDecoderAttachDictionary(s, BROTLI_SHARED_DICTIONARY_RAW,
|
53
|
+
args->dict_len, args->dict);
|
54
|
+
}
|
55
|
+
#endif
|
56
|
+
|
47
57
|
for (;;) {
|
48
58
|
r = BrotliDecoderDecompressStream(s,
|
49
59
|
&available_in, &next_in,
|
@@ -71,17 +81,25 @@ brotli_inflate_no_gvl(void *arg)
|
|
71
81
|
static ID id_read;
|
72
82
|
|
73
83
|
static VALUE
|
74
|
-
brotli_inflate(VALUE
|
84
|
+
brotli_inflate(int argc, VALUE *argv, VALUE self)
|
75
85
|
{
|
76
|
-
VALUE value = Qnil;
|
86
|
+
VALUE str = Qnil, opts = Qnil, value = Qnil, dict = Qnil;
|
77
87
|
brotli_inflate_args_t args;
|
78
88
|
|
89
|
+
rb_scan_args(argc, argv, "11", &str, &opts);
|
90
|
+
|
79
91
|
if (rb_respond_to(str, id_read)) {
|
80
92
|
str = rb_funcall(str, id_read, 0, 0);
|
81
93
|
}
|
82
94
|
|
83
95
|
StringValue(str);
|
84
96
|
|
97
|
+
/* Extract dictionary from options if provided */
|
98
|
+
if (!NIL_P(opts)) {
|
99
|
+
Check_Type(opts, T_HASH);
|
100
|
+
dict = rb_hash_aref(opts, CSTR2SYM("dictionary"));
|
101
|
+
}
|
102
|
+
|
85
103
|
args.str = (uint8_t*)RSTRING_PTR(str);
|
86
104
|
args.len = (size_t)RSTRING_LEN(str);
|
87
105
|
args.buffer = create_buffer(BUFSIZ);
|
@@ -90,6 +108,20 @@ brotli_inflate(VALUE self, VALUE str)
|
|
90
108
|
NULL);
|
91
109
|
args.r = BROTLI_DECODER_RESULT_ERROR;
|
92
110
|
|
111
|
+
/* Set dictionary parameters */
|
112
|
+
if (!NIL_P(dict)) {
|
113
|
+
#ifdef HAVE_BROTLIDECODERATTACHDICTIONARY
|
114
|
+
StringValue(dict);
|
115
|
+
args.dict = (uint8_t*)RSTRING_PTR(dict);
|
116
|
+
args.dict_len = (size_t)RSTRING_LEN(dict);
|
117
|
+
#else
|
118
|
+
rb_raise(rb_eBrotli, "Dictionary support not available in this build");
|
119
|
+
#endif
|
120
|
+
} else {
|
121
|
+
args.dict = NULL;
|
122
|
+
args.dict_len = 0;
|
123
|
+
}
|
124
|
+
|
93
125
|
#ifdef HAVE_RUBY_THREAD_H
|
94
126
|
rb_thread_call_without_gvl(brotli_inflate_no_gvl, (void *)&args, NULL, NULL);
|
95
127
|
#else
|
@@ -205,6 +237,8 @@ typedef struct {
|
|
205
237
|
BrotliEncoderState* s;
|
206
238
|
buffer_t* buffer;
|
207
239
|
BROTLI_BOOL finished;
|
240
|
+
uint8_t *dict;
|
241
|
+
size_t dict_len;
|
208
242
|
} brotli_deflate_args_t;
|
209
243
|
|
210
244
|
static void*
|
@@ -221,6 +255,19 @@ brotli_deflate_no_gvl(void *arg)
|
|
221
255
|
buffer_t* buffer = args->buffer;
|
222
256
|
BrotliEncoderState* s = args->s;
|
223
257
|
|
258
|
+
#if defined(HAVE_BROTLIENCODERPREPAREDICTIONARY) && defined(HAVE_BROTLIENCODERATTACHPREPAREDDICTIONARY)
|
259
|
+
/* Attach dictionary if provided */
|
260
|
+
if (args->dict && args->dict_len > 0) {
|
261
|
+
BrotliEncoderPreparedDictionary* dict = BrotliEncoderPrepareDictionary(
|
262
|
+
BROTLI_SHARED_DICTIONARY_RAW, args->dict_len, args->dict,
|
263
|
+
BROTLI_MAX_QUALITY, brotli_alloc, brotli_free, NULL);
|
264
|
+
if (dict) {
|
265
|
+
BrotliEncoderAttachPreparedDictionary(s, dict);
|
266
|
+
/* Note: dict is owned by encoder after attach, no need to free */
|
267
|
+
}
|
268
|
+
}
|
269
|
+
#endif
|
270
|
+
|
224
271
|
for (;;) {
|
225
272
|
r = BrotliEncoderCompressStream(s,
|
226
273
|
BROTLI_OPERATION_FINISH,
|
@@ -247,7 +294,7 @@ brotli_deflate_no_gvl(void *arg)
|
|
247
294
|
static VALUE
|
248
295
|
brotli_deflate(int argc, VALUE *argv, VALUE self)
|
249
296
|
{
|
250
|
-
VALUE str = Qnil, opts = Qnil, value = Qnil;
|
297
|
+
VALUE str = Qnil, opts = Qnil, value = Qnil, dict = Qnil;
|
251
298
|
brotli_deflate_args_t args;
|
252
299
|
|
253
300
|
rb_scan_args(argc, argv, "11", &str, &opts);
|
@@ -257,14 +304,35 @@ brotli_deflate(int argc, VALUE *argv, VALUE self)
|
|
257
304
|
}
|
258
305
|
StringValue(str);
|
259
306
|
|
307
|
+
/* Extract dictionary from options if provided */
|
308
|
+
if (!NIL_P(opts)) {
|
309
|
+
Check_Type(opts, T_HASH);
|
310
|
+
dict = rb_hash_aref(opts, CSTR2SYM("dictionary"));
|
311
|
+
}
|
312
|
+
|
260
313
|
args.str = (uint8_t*)RSTRING_PTR(str);
|
261
314
|
args.len = (size_t)RSTRING_LEN(str);
|
262
315
|
args.s = brotli_deflate_parse_options(
|
263
316
|
BrotliEncoderCreateInstance(brotli_alloc, brotli_free, NULL),
|
264
317
|
opts);
|
265
|
-
|
318
|
+
size_t max_compressed_size = BrotliEncoderMaxCompressedSize(args.len);
|
319
|
+
args.buffer = create_buffer(max_compressed_size);
|
266
320
|
args.finished = BROTLI_FALSE;
|
267
321
|
|
322
|
+
/* Set dictionary parameters */
|
323
|
+
if (!NIL_P(dict)) {
|
324
|
+
#if defined(HAVE_BROTLIENCODERPREPAREDICTIONARY) && defined(HAVE_BROTLIENCODERATTACHPREPAREDDICTIONARY)
|
325
|
+
StringValue(dict);
|
326
|
+
args.dict = (uint8_t*)RSTRING_PTR(dict);
|
327
|
+
args.dict_len = (size_t)RSTRING_LEN(dict);
|
328
|
+
#else
|
329
|
+
rb_raise(rb_eBrotli, "Dictionary support not available in this build");
|
330
|
+
#endif
|
331
|
+
} else {
|
332
|
+
args.dict = NULL;
|
333
|
+
args.dict_len = 0;
|
334
|
+
}
|
335
|
+
|
268
336
|
#ifdef HAVE_RUBY_THREAD_H
|
269
337
|
rb_thread_call_without_gvl(brotli_deflate_no_gvl, (void *)&args, NULL, NULL);
|
270
338
|
#else
|
@@ -372,6 +440,7 @@ static VALUE rb_writer_alloc(VALUE klass) {
|
|
372
440
|
static VALUE rb_writer_initialize(int argc, VALUE* argv, VALUE self) {
|
373
441
|
VALUE io = Qnil;
|
374
442
|
VALUE opts = Qnil;
|
443
|
+
VALUE dict = Qnil;
|
375
444
|
rb_scan_args(argc, argv, "11", &io, &opts);
|
376
445
|
if (NIL_P(io)) {
|
377
446
|
rb_raise(rb_eArgError, "io should not be nil");
|
@@ -382,6 +451,41 @@ static VALUE rb_writer_initialize(int argc, VALUE* argv, VALUE self) {
|
|
382
451
|
TypedData_Get_Struct(self, struct brotli, &brotli_data_type, br);
|
383
452
|
brotli_deflate_parse_options(br->state, opts);
|
384
453
|
br->io = io;
|
454
|
+
|
455
|
+
#if defined(HAVE_BROTLIENCODERPREPAREDICTIONARY) && defined(HAVE_BROTLIENCODERATTACHPREPAREDDICTIONARY)
|
456
|
+
/* Extract and attach dictionary if provided */
|
457
|
+
if (!NIL_P(opts)) {
|
458
|
+
Check_Type(opts, T_HASH);
|
459
|
+
dict = rb_hash_aref(opts, CSTR2SYM("dictionary"));
|
460
|
+
if (!NIL_P(dict)) {
|
461
|
+
StringValue(dict);
|
462
|
+
BrotliEncoderPreparedDictionary* prepared_dict = BrotliEncoderPrepareDictionary(
|
463
|
+
BROTLI_SHARED_DICTIONARY_RAW,
|
464
|
+
(size_t)RSTRING_LEN(dict),
|
465
|
+
(uint8_t*)RSTRING_PTR(dict),
|
466
|
+
BROTLI_MAX_QUALITY,
|
467
|
+
brotli_alloc,
|
468
|
+
brotli_free,
|
469
|
+
NULL);
|
470
|
+
if (prepared_dict) {
|
471
|
+
BrotliEncoderAttachPreparedDictionary(br->state, prepared_dict);
|
472
|
+
/* Note: dict is owned by encoder after attach, no need to free */
|
473
|
+
} else {
|
474
|
+
rb_raise(rb_eBrotli, "Failed to prepare dictionary for compression");
|
475
|
+
}
|
476
|
+
}
|
477
|
+
}
|
478
|
+
#else
|
479
|
+
/* Check if dictionary is requested but not supported */
|
480
|
+
if (!NIL_P(opts)) {
|
481
|
+
Check_Type(opts, T_HASH);
|
482
|
+
dict = rb_hash_aref(opts, CSTR2SYM("dictionary"));
|
483
|
+
if (!NIL_P(dict)) {
|
484
|
+
rb_raise(rb_eBrotli, "Dictionary support not available in this build");
|
485
|
+
}
|
486
|
+
}
|
487
|
+
#endif
|
488
|
+
|
385
489
|
return self;
|
386
490
|
}
|
387
491
|
|
@@ -475,9 +579,9 @@ Init_brotli(void)
|
|
475
579
|
rb_mBrotli = rb_define_module("Brotli");
|
476
580
|
rb_eBrotli = rb_define_class_under(rb_mBrotli, "Error", rb_eStandardError);
|
477
581
|
rb_global_variable(&rb_eBrotli);
|
478
|
-
rb_define_singleton_method(rb_mBrotli, "deflate",
|
479
|
-
rb_define_singleton_method(rb_mBrotli, "inflate",
|
480
|
-
rb_define_singleton_method(rb_mBrotli, "version",
|
582
|
+
rb_define_singleton_method(rb_mBrotli, "deflate", brotli_deflate, -1);
|
583
|
+
rb_define_singleton_method(rb_mBrotli, "inflate", brotli_inflate, -1);
|
584
|
+
rb_define_singleton_method(rb_mBrotli, "version", brotli_version, 0);
|
481
585
|
id_read = rb_intern("read");
|
482
586
|
// Brotli::Writer
|
483
587
|
id_write = rb_intern("write");
|
@@ -485,9 +589,9 @@ Init_brotli(void)
|
|
485
589
|
id_close = rb_intern("close");
|
486
590
|
rb_Writer = rb_define_class_under(rb_mBrotli, "Writer", rb_cObject);
|
487
591
|
rb_define_alloc_func(rb_Writer, rb_writer_alloc);
|
488
|
-
rb_define_method(rb_Writer, "initialize",
|
489
|
-
rb_define_method(rb_Writer, "write",
|
490
|
-
rb_define_method(rb_Writer, "finish",
|
491
|
-
rb_define_method(rb_Writer, "flush",
|
492
|
-
rb_define_method(rb_Writer, "close",
|
592
|
+
rb_define_method(rb_Writer, "initialize", rb_writer_initialize, -1);
|
593
|
+
rb_define_method(rb_Writer, "write", rb_writer_write, 1);
|
594
|
+
rb_define_method(rb_Writer, "finish", rb_writer_finish, 0);
|
595
|
+
rb_define_method(rb_Writer, "flush", rb_writer_flush, 0);
|
596
|
+
rb_define_method(rb_Writer, "close", rb_writer_close, 0);
|
493
597
|
}
|
data/ext/brotli/brotli.h
CHANGED
data/ext/brotli/buffer.c
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
#include "buffer.h"
|
2
|
+
#include "ruby/ruby.h"
|
2
3
|
#define INITIAL (1024)
|
3
4
|
|
4
5
|
buffer_t*
|
5
6
|
create_buffer(const size_t initial) {
|
6
|
-
buffer_t *buffer =
|
7
|
+
buffer_t *buffer = ruby_xmalloc(sizeof(buffer_t));
|
7
8
|
if (buffer == NULL) {
|
8
9
|
return NULL;
|
9
10
|
}
|
@@ -12,7 +13,7 @@ create_buffer(const size_t initial) {
|
|
12
13
|
buffer->expand_count = 0;
|
13
14
|
buffer->expand_ratio = 130;
|
14
15
|
buffer->size = (size_t) (initial > 0 ? initial : INITIAL);
|
15
|
-
buffer->ptr =
|
16
|
+
buffer->ptr = ruby_xmalloc(buffer->size);
|
16
17
|
if (buffer->ptr == NULL) {
|
17
18
|
delete_buffer(buffer);
|
18
19
|
return NULL;
|
@@ -24,17 +25,17 @@ create_buffer(const size_t initial) {
|
|
24
25
|
void
|
25
26
|
delete_buffer(buffer_t* buffer) {
|
26
27
|
if (buffer->ptr != NULL) {
|
27
|
-
|
28
|
+
ruby_xfree(buffer->ptr);
|
28
29
|
buffer->ptr = NULL;
|
29
30
|
}
|
30
|
-
|
31
|
+
ruby_xfree(buffer);
|
31
32
|
}
|
32
33
|
|
33
34
|
static
|
34
35
|
buffer_t*
|
35
36
|
expand_buffer(buffer_t* const buffer, const size_t need) {
|
36
37
|
size_t size = need * buffer->expand_ratio / 100;
|
37
|
-
buffer->ptr =
|
38
|
+
buffer->ptr = ruby_xrealloc(buffer->ptr, size);
|
38
39
|
buffer->size = size;
|
39
40
|
buffer->expand_count += 1;
|
40
41
|
return buffer;
|
data/ext/brotli/extconf.rb
CHANGED
@@ -13,9 +13,18 @@ have_dev_pkg = [
|
|
13
13
|
pkg_config("libbrotlienc")
|
14
14
|
].all? { |e| e }
|
15
15
|
|
16
|
+
have_header("brotli/shared_dictionary.h")
|
17
|
+
have_func("BrotliEncoderPrepareDictionary", "brotli/encode.h")
|
18
|
+
have_func("BrotliEncoderAttachPreparedDictionary", "brotli/encode.h")
|
19
|
+
have_func("BrotliDecoderAttachDictionary", "brotli/decode.h")
|
20
|
+
|
16
21
|
if enable_config("vendor")
|
17
22
|
have_dev_pkg = false
|
18
23
|
Logging::message "Use vendor brotli\n"
|
24
|
+
$defs << "-DHAVE_BROTLI_SHARED_DICTIONARY_H"
|
25
|
+
$defs << "-DHAVE_BROTLIENCODERPREPAREDICTIONARY"
|
26
|
+
$defs << "-DHAVE_BROTLIENCODERATTACHPREPAREDDICTIONARY"
|
27
|
+
$defs << "-DHAVE_BROTLIDECODERATTACHDICTIONARY"
|
19
28
|
end
|
20
29
|
|
21
30
|
$CPPFLAGS << " -DOS_MACOSX" if RbConfig::CONFIG["host_os"] =~ /darwin|mac os/
|
data/lib/brotli/version.rb
CHANGED
data/test/brotli_test.rb
CHANGED
@@ -21,9 +21,10 @@ class BrotliTest < Test::Unit::TestCase
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
test ".version" do
|
25
|
+
assert do
|
26
|
+
puts "Brotli version: #{Brotli.version}"
|
27
|
+
["1.1.0", "1.0.9"].include? Brotli.version
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
@@ -100,6 +101,71 @@ class BrotliTest < Test::Unit::TestCase
|
|
100
101
|
end
|
101
102
|
end
|
102
103
|
|
104
|
+
sub_test_case "dictionary support" do
|
105
|
+
def setup
|
106
|
+
omit "Dictionary tests are skipped" if Brotli.version < "1.1.0"
|
107
|
+
end
|
108
|
+
|
109
|
+
def dictionary_data
|
110
|
+
"The quick brown fox jumps over the lazy dog"
|
111
|
+
end
|
112
|
+
|
113
|
+
def repetitive_data
|
114
|
+
dictionary_data * 10
|
115
|
+
end
|
116
|
+
|
117
|
+
test "deflate with dictionary produces smaller output" do
|
118
|
+
compressed_no_dict = Brotli.deflate(repetitive_data)
|
119
|
+
compressed_with_dict = Brotli.deflate(repetitive_data, dictionary: dictionary_data)
|
120
|
+
|
121
|
+
assert compressed_with_dict.bytesize < compressed_no_dict.bytesize
|
122
|
+
end
|
123
|
+
|
124
|
+
test "inflate with matching dictionary works" do
|
125
|
+
compressed = Brotli.deflate(repetitive_data, dictionary: dictionary_data)
|
126
|
+
decompressed = Brotli.inflate(compressed, dictionary: dictionary_data)
|
127
|
+
|
128
|
+
assert_equal repetitive_data, decompressed
|
129
|
+
end
|
130
|
+
|
131
|
+
test "inflate without dictionary fails on dictionary-compressed data" do
|
132
|
+
compressed = Brotli.deflate(repetitive_data, dictionary: dictionary_data)
|
133
|
+
|
134
|
+
assert_raise Brotli::Error do
|
135
|
+
Brotli.inflate(compressed)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
test "inflate with wrong dictionary fails" do
|
140
|
+
compressed = Brotli.deflate(repetitive_data, dictionary: dictionary_data)
|
141
|
+
wrong_dict = "A completely different dictionary"
|
142
|
+
|
143
|
+
assert_raise Brotli::Error do
|
144
|
+
Brotli.inflate(compressed, dictionary: wrong_dict)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
test "deflate and inflate work with binary dictionary" do
|
149
|
+
binary_dict = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09" * 10
|
150
|
+
data = "Test data " * 100 + binary_dict
|
151
|
+
|
152
|
+
compressed = Brotli.deflate(data, dictionary: binary_dict)
|
153
|
+
decompressed = Brotli.inflate(compressed, dictionary: binary_dict)
|
154
|
+
|
155
|
+
assert_equal data, decompressed
|
156
|
+
end
|
157
|
+
|
158
|
+
test "deflate with dictionary and other options" do
|
159
|
+
compressed = Brotli.deflate(repetitive_data,
|
160
|
+
dictionary: dictionary_data,
|
161
|
+
quality: 11,
|
162
|
+
mode: :text)
|
163
|
+
decompressed = Brotli.inflate(compressed, dictionary: dictionary_data)
|
164
|
+
|
165
|
+
assert_equal repetitive_data, decompressed
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
103
169
|
sub_test_case "Ractor safe" do
|
104
170
|
test "able to invoke non-main ractor" do
|
105
171
|
unless defined? ::Ractor
|
data/test/brotli_writer_test.rb
CHANGED
@@ -33,4 +33,61 @@ class BrotliWriterTest < Test::Unit::TestCase
|
|
33
33
|
Brotli::Writer.new nil
|
34
34
|
end
|
35
35
|
end
|
36
|
+
|
37
|
+
test "works with dictionary" do
|
38
|
+
omit_if(Brotli.version < "1.1.0", "Dictionary tests are skipped")
|
39
|
+
|
40
|
+
dictionary = "The quick brown fox jumps over the lazy dog"
|
41
|
+
data = dictionary * 10
|
42
|
+
|
43
|
+
writer = Brotli::Writer.new @tempfile, dictionary: dictionary
|
44
|
+
assert_equal data.bytesize, writer.write(data)
|
45
|
+
assert_equal @tempfile, writer.close
|
46
|
+
|
47
|
+
@tempfile.open
|
48
|
+
compressed = @tempfile.read
|
49
|
+
decompressed = Brotli.inflate(compressed, dictionary: dictionary)
|
50
|
+
assert_equal data, decompressed
|
51
|
+
end
|
52
|
+
|
53
|
+
test "writer with dictionary produces smaller output" do
|
54
|
+
omit_if(Brotli.version < "1.1.0", "Dictionary tests are skipped")
|
55
|
+
|
56
|
+
dictionary = "The quick brown fox jumps over the lazy dog"
|
57
|
+
data = dictionary * 10
|
58
|
+
|
59
|
+
# Without dictionary
|
60
|
+
@tempfile.rewind
|
61
|
+
writer_no_dict = Brotli::Writer.new @tempfile
|
62
|
+
writer_no_dict.write(data)
|
63
|
+
writer_no_dict.close
|
64
|
+
size_no_dict = @tempfile.size
|
65
|
+
|
66
|
+
# With dictionary
|
67
|
+
tempfile2 = Tempfile.new
|
68
|
+
writer_with_dict = Brotli::Writer.new tempfile2, dictionary: dictionary
|
69
|
+
writer_with_dict.write(data)
|
70
|
+
writer_with_dict.close
|
71
|
+
size_with_dict = tempfile2.size
|
72
|
+
|
73
|
+
assert size_with_dict < size_no_dict
|
74
|
+
|
75
|
+
tempfile2.close!
|
76
|
+
end
|
77
|
+
|
78
|
+
test "writer with dictionary and other options" do
|
79
|
+
omit_if(Brotli.version < "1.1.0", "Dictionary tests are skipped")
|
80
|
+
|
81
|
+
dictionary = "compression dictionary"
|
82
|
+
data = "test data " * 100 + dictionary * 5
|
83
|
+
|
84
|
+
writer = Brotli::Writer.new @tempfile, dictionary: dictionary, quality: 11, mode: :text
|
85
|
+
writer.write(data)
|
86
|
+
writer.finish
|
87
|
+
writer.close
|
88
|
+
|
89
|
+
@tempfile.open
|
90
|
+
decompressed = Brotli.inflate(@tempfile.read, dictionary: dictionary)
|
91
|
+
assert_equal data, decompressed
|
92
|
+
end
|
36
93
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brotli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- miyucy
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: Brotli compressor/decompressor
|
14
13
|
email:
|
@@ -23,6 +22,7 @@ files:
|
|
23
22
|
- ".gitignore"
|
24
23
|
- ".gitmodules"
|
25
24
|
- ".rspec"
|
25
|
+
- Dockerfile
|
26
26
|
- Gemfile
|
27
27
|
- LICENSE.txt
|
28
28
|
- README.md
|
@@ -140,7 +140,6 @@ homepage: https://github.com/miyucy/brotli
|
|
140
140
|
licenses:
|
141
141
|
- MIT
|
142
142
|
metadata: {}
|
143
|
-
post_install_message:
|
144
143
|
rdoc_options: []
|
145
144
|
require_paths:
|
146
145
|
- lib
|
@@ -155,8 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
154
|
- !ruby/object:Gem::Version
|
156
155
|
version: '0'
|
157
156
|
requirements: []
|
158
|
-
rubygems_version: 3.
|
159
|
-
signing_key:
|
157
|
+
rubygems_version: 3.6.7
|
160
158
|
specification_version: 4
|
161
159
|
summary: Brotli compressor/decompressor
|
162
160
|
test_files:
|