ruby-brs 1.0.1

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.
@@ -0,0 +1,274 @@
1
+ // Ruby bindings for brotli library.
2
+ // Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ #include "brs_ext/string.h"
5
+
6
+ #include <brotli/decode.h>
7
+ #include <brotli/encode.h>
8
+ #include <stdint.h>
9
+ #include <stdlib.h>
10
+
11
+ #include "brs_ext/buffer.h"
12
+ #include "brs_ext/common.h"
13
+ #include "brs_ext/error.h"
14
+ #include "brs_ext/macro.h"
15
+ #include "brs_ext/option.h"
16
+ #include "ruby.h"
17
+
18
+ // -- buffer --
19
+
20
+ static inline VALUE create_buffer(VALUE length)
21
+ {
22
+ return rb_str_new(NULL, NUM2UINT(length));
23
+ }
24
+
25
+ #define CREATE_BUFFER(buffer, length, exception) \
26
+ VALUE buffer = rb_protect(create_buffer, UINT2NUM(length), &exception);
27
+
28
+ static inline VALUE resize_buffer(VALUE args)
29
+ {
30
+ VALUE buffer = rb_ary_entry(args, 0);
31
+ VALUE length = rb_ary_entry(args, 1);
32
+ return rb_str_resize(buffer, NUM2UINT(length));
33
+ }
34
+
35
+ #define RESIZE_BUFFER(buffer, length, exception) \
36
+ VALUE resize_buffer_args = rb_ary_new_from_args(2, buffer, UINT2NUM(length)); \
37
+ buffer = rb_protect(resize_buffer, resize_buffer_args, &exception); \
38
+ RB_GC_GUARD(resize_buffer_args);
39
+
40
+ static inline brs_ext_result_t increase_destination_buffer(
41
+ VALUE destination_value, size_t destination_length,
42
+ size_t* remaining_destination_buffer_length_ptr, size_t destination_buffer_length)
43
+ {
44
+ if (*remaining_destination_buffer_length_ptr == destination_buffer_length) {
45
+ // We want to write more data at once, than buffer has.
46
+ return BRS_EXT_ERROR_NOT_ENOUGH_DESTINATION_BUFFER;
47
+ }
48
+
49
+ int exception;
50
+
51
+ RESIZE_BUFFER(destination_value, destination_length + destination_buffer_length, exception);
52
+ if (exception != 0) {
53
+ return BRS_EXT_ERROR_ALLOCATE_FAILED;
54
+ }
55
+
56
+ *remaining_destination_buffer_length_ptr = destination_buffer_length;
57
+
58
+ return 0;
59
+ }
60
+
61
+ // -- utils --
62
+
63
+ #define GET_SOURCE_DATA(source_value) \
64
+ Check_Type(source_value, T_STRING); \
65
+ \
66
+ const char* source = RSTRING_PTR(source_value); \
67
+ size_t source_length = RSTRING_LEN(source_value); \
68
+ const uint8_t* remaining_source = (const uint8_t*)source; \
69
+ size_t remaining_source_length = source_length;
70
+
71
+ // -- compress --
72
+
73
+ static inline brs_ext_result_t compress(
74
+ BrotliEncoderState* state_ptr,
75
+ const uint8_t* remaining_source, size_t remaining_source_length,
76
+ VALUE destination_value, size_t destination_buffer_length)
77
+ {
78
+ brs_ext_result_t ext_result;
79
+
80
+ size_t destination_length = 0;
81
+ size_t remaining_destination_buffer_length = destination_buffer_length;
82
+
83
+ while (true) {
84
+ uint8_t* remaining_destination_buffer = (uint8_t*)RSTRING_PTR(destination_value) + destination_length;
85
+ size_t prev_remaining_destination_buffer_length = remaining_destination_buffer_length;
86
+
87
+ BROTLI_BOOL result = BrotliEncoderCompressStream(
88
+ state_ptr,
89
+ BROTLI_OPERATION_FINISH,
90
+ &remaining_source_length, &remaining_source,
91
+ &remaining_destination_buffer_length, &remaining_destination_buffer,
92
+ NULL);
93
+
94
+ if (!result) {
95
+ return BRS_EXT_ERROR_UNEXPECTED;
96
+ }
97
+
98
+ destination_length += prev_remaining_destination_buffer_length - remaining_destination_buffer_length;
99
+
100
+ if (BrotliEncoderHasMoreOutput(state_ptr) || !BrotliEncoderIsFinished(state_ptr)) {
101
+ ext_result = increase_destination_buffer(
102
+ destination_value, destination_length,
103
+ &remaining_destination_buffer_length, destination_buffer_length);
104
+
105
+ if (ext_result != 0) {
106
+ return ext_result;
107
+ }
108
+
109
+ continue;
110
+ }
111
+
112
+ break;
113
+ }
114
+
115
+ int exception;
116
+
117
+ RESIZE_BUFFER(destination_value, destination_length, exception);
118
+ if (exception != 0) {
119
+ return BRS_EXT_ERROR_ALLOCATE_FAILED;
120
+ }
121
+
122
+ return 0;
123
+ }
124
+
125
+ VALUE brs_ext_compress_string(VALUE BRS_EXT_UNUSED(self), VALUE source_value, VALUE options)
126
+ {
127
+ GET_SOURCE_DATA(source_value);
128
+ Check_Type(options, T_HASH);
129
+ BRS_EXT_GET_COMPRESSOR_OPTIONS(options);
130
+ BRS_EXT_GET_BUFFER_LENGTH_OPTION(options, destination_buffer_length);
131
+
132
+ BrotliEncoderState* state_ptr = BrotliEncoderCreateInstance(NULL, NULL, NULL);
133
+ if (state_ptr == NULL) {
134
+ brs_ext_raise_error(BRS_EXT_ERROR_ALLOCATE_FAILED);
135
+ }
136
+
137
+ brs_ext_result_t ext_result = brs_ext_set_compressor_options(state_ptr, &compressor_options);
138
+ if (ext_result != 0) {
139
+ BrotliEncoderDestroyInstance(state_ptr);
140
+ brs_ext_raise_error(ext_result);
141
+ }
142
+
143
+ if (destination_buffer_length == 0) {
144
+ destination_buffer_length = BRS_DEFAULT_DESTINATION_BUFFER_LENGTH_FOR_COMPRESSOR;
145
+ }
146
+
147
+ int exception;
148
+
149
+ CREATE_BUFFER(destination_value, destination_buffer_length, exception);
150
+ if (exception != 0) {
151
+ BrotliEncoderDestroyInstance(state_ptr);
152
+ brs_ext_raise_error(BRS_EXT_ERROR_ALLOCATE_FAILED);
153
+ }
154
+
155
+ ext_result = compress(
156
+ state_ptr,
157
+ remaining_source, remaining_source_length,
158
+ destination_value, destination_buffer_length);
159
+
160
+ BrotliEncoderDestroyInstance(state_ptr);
161
+
162
+ if (ext_result != 0) {
163
+ brs_ext_raise_error(ext_result);
164
+ }
165
+
166
+ return destination_value;
167
+ }
168
+
169
+ // -- decompress --
170
+
171
+ static inline brs_ext_result_t decompress(
172
+ BrotliDecoderState* state_ptr,
173
+ const uint8_t* remaining_source, size_t remaining_source_length,
174
+ VALUE destination_value, size_t destination_buffer_length)
175
+ {
176
+ brs_ext_result_t ext_result;
177
+
178
+ size_t destination_length = 0;
179
+ size_t remaining_destination_buffer_length = destination_buffer_length;
180
+
181
+ while (true) {
182
+ uint8_t* remaining_destination_buffer = (uint8_t*)RSTRING_PTR(destination_value) + destination_length;
183
+ size_t prev_remaining_destination_buffer_length = remaining_destination_buffer_length;
184
+
185
+ BrotliDecoderResult result = BrotliDecoderDecompressStream(
186
+ state_ptr,
187
+ &remaining_source_length, &remaining_source,
188
+ &remaining_destination_buffer_length, &remaining_destination_buffer,
189
+ NULL);
190
+
191
+ if (
192
+ result != BROTLI_DECODER_RESULT_SUCCESS &&
193
+ result != BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT &&
194
+ result != BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
195
+ BrotliDecoderErrorCode error_code = BrotliDecoderGetErrorCode(state_ptr);
196
+ return brs_ext_get_decompressor_error(error_code);
197
+ }
198
+
199
+ destination_length += prev_remaining_destination_buffer_length - remaining_destination_buffer_length;
200
+
201
+ if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
202
+ ext_result = increase_destination_buffer(
203
+ destination_value, destination_length,
204
+ &remaining_destination_buffer_length, destination_buffer_length);
205
+
206
+ if (ext_result != 0) {
207
+ return ext_result;
208
+ }
209
+
210
+ continue;
211
+ }
212
+
213
+ break;
214
+ }
215
+
216
+ int exception;
217
+
218
+ RESIZE_BUFFER(destination_value, destination_length, exception);
219
+ if (exception != 0) {
220
+ brs_ext_raise_error(BRS_EXT_ERROR_ALLOCATE_FAILED);
221
+ }
222
+
223
+ return 0;
224
+ }
225
+
226
+ VALUE brs_ext_decompress_string(VALUE BRS_EXT_UNUSED(self), VALUE source_value, VALUE options)
227
+ {
228
+ GET_SOURCE_DATA(source_value);
229
+ Check_Type(options, T_HASH);
230
+ BRS_EXT_GET_DECOMPRESSOR_OPTIONS(options);
231
+ BRS_EXT_GET_BUFFER_LENGTH_OPTION(options, destination_buffer_length);
232
+
233
+ BrotliDecoderState* state_ptr = BrotliDecoderCreateInstance(NULL, NULL, NULL);
234
+ if (state_ptr == NULL) {
235
+ brs_ext_raise_error(BRS_EXT_ERROR_ALLOCATE_FAILED);
236
+ }
237
+
238
+ brs_ext_result_t ext_result = brs_ext_set_decompressor_options(state_ptr, &decompressor_options);
239
+ if (ext_result != 0) {
240
+ BrotliDecoderDestroyInstance(state_ptr);
241
+ brs_ext_raise_error(ext_result);
242
+ }
243
+
244
+ if (destination_buffer_length == 0) {
245
+ destination_buffer_length = BRS_DEFAULT_DESTINATION_BUFFER_LENGTH_FOR_DECOMPRESSOR;
246
+ }
247
+
248
+ int exception;
249
+
250
+ CREATE_BUFFER(destination_value, destination_buffer_length, exception);
251
+ if (exception != 0) {
252
+ BrotliDecoderDestroyInstance(state_ptr);
253
+ brs_ext_raise_error(BRS_EXT_ERROR_ALLOCATE_FAILED);
254
+ }
255
+
256
+ ext_result = decompress(
257
+ state_ptr,
258
+ remaining_source, remaining_source_length,
259
+ destination_value, destination_buffer_length);
260
+
261
+ BrotliDecoderDestroyInstance(state_ptr);
262
+
263
+ if (ext_result != 0) {
264
+ brs_ext_raise_error(ext_result);
265
+ }
266
+
267
+ return destination_value;
268
+ }
269
+
270
+ void brs_ext_string_exports(VALUE root_module)
271
+ {
272
+ rb_define_module_function(root_module, "_native_compress_string", RUBY_METHOD_FUNC(brs_ext_compress_string), 2);
273
+ rb_define_module_function(root_module, "_native_decompress_string", RUBY_METHOD_FUNC(brs_ext_decompress_string), 2);
274
+ }
@@ -0,0 +1,14 @@
1
+ // Ruby bindings for brotli library.
2
+ // Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ #if !defined(BRS_EXT_STRING_H)
5
+ #define BRS_EXT_STRING_H
6
+
7
+ #include "ruby.h"
8
+
9
+ VALUE brs_ext_compress_string(VALUE self, VALUE source, VALUE options);
10
+ VALUE brs_ext_decompress_string(VALUE self, VALUE source, VALUE options);
11
+
12
+ void brs_ext_string_exports(VALUE root_module);
13
+
14
+ #endif // BRS_EXT_STRING_H
data/ext/extconf.rb ADDED
@@ -0,0 +1,66 @@
1
+ # Ruby bindings for brotli library.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require "mkmf"
5
+
6
+ def require_header(name)
7
+ abort "Can't find #{name} header" unless find_header name
8
+ end
9
+
10
+ require_header "brotli/encode.h"
11
+ require_header "brotli/decode.h"
12
+
13
+ def require_library(name, functions)
14
+ functions.each do |function|
15
+ abort "Can't find #{name} library and #{function} function" unless find_library name, function
16
+ end
17
+ end
18
+
19
+ encoder_functions = %w[
20
+ BrotliEncoderCreateInstance
21
+ BrotliEncoderSetParameter
22
+ BrotliEncoderCompressStream
23
+ BrotliEncoderHasMoreOutput
24
+ BrotliEncoderIsFinished
25
+ BrotliEncoderDestroyInstance
26
+ ]
27
+ .freeze
28
+
29
+ require_library "brotlienc", encoder_functions
30
+
31
+ decoder_functions = %w[
32
+ BrotliDecoderCreateInstance
33
+ BrotliDecoderSetParameter
34
+ BrotliDecoderDecompressStream
35
+ BrotliDecoderGetErrorCode
36
+ BrotliDecoderDestroyInstance
37
+ ]
38
+ .freeze
39
+
40
+ require_library "brotlidec", decoder_functions
41
+
42
+ extension_name = "brs_ext".freeze
43
+ dir_config extension_name
44
+
45
+ sources = %w[
46
+ stream/compressor
47
+ stream/decompressor
48
+ buffer
49
+ error
50
+ io
51
+ main
52
+ option
53
+ string
54
+ ]
55
+ .freeze
56
+
57
+ # rubocop:disable Style/GlobalVars
58
+ $srcs = sources
59
+ .map { |name| "src/#{extension_name}/#{name}.c" }
60
+ .freeze
61
+
62
+ $CFLAGS << " -Wno-declaration-after-statement"
63
+ $VPATH << "$(srcdir)/#{extension_name}:$(srcdir)/#{extension_name}/stream"
64
+ # rubocop:enable Style/GlobalVars
65
+
66
+ create_makefile extension_name
data/lib/brs/error.rb ADDED
@@ -0,0 +1,21 @@
1
+ # Ruby bindings for brotli library.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ module BRS
5
+ class BaseError < ::StandardError; end
6
+
7
+ class AllocateError < BaseError; end
8
+ class ValidateError < BaseError; end
9
+
10
+ class UsedAfterCloseError < BaseError; end
11
+ class NotEnoughSourceBufferError < BaseError; end
12
+ class NotEnoughDestinationBufferError < BaseError; end
13
+ class NotEnoughDestinationError < BaseError; end
14
+ class DecompressorCorruptedSourceError < BaseError; end
15
+
16
+ class AccessIOError < BaseError; end
17
+ class ReadIOError < BaseError; end
18
+ class WriteIOError < BaseError; end
19
+
20
+ class UnexpectedError < BaseError; end
21
+ end
data/lib/brs/file.rb ADDED
@@ -0,0 +1,44 @@
1
+ # Ruby bindings for brotli library.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require "brs_ext"
5
+
6
+ require_relative "error"
7
+ require_relative "option"
8
+ require_relative "validation"
9
+
10
+ module BRS
11
+ module File
12
+ BUFFER_LENGTH_NAMES = %i[source_buffer_length destination_buffer_length].freeze
13
+
14
+ def self.compress(source, destination, options = {})
15
+ Validation.validate_string source
16
+ Validation.validate_string destination
17
+
18
+ options = Option.get_compressor_options options, BUFFER_LENGTH_NAMES
19
+
20
+ open_files(source, destination) do |source_io, destination_io|
21
+ BRS._native_compress_io source_io, destination_io, options
22
+ end
23
+ end
24
+
25
+ def self.decompress(source, destination, options = {})
26
+ Validation.validate_string source
27
+ Validation.validate_string destination
28
+
29
+ options = Option.get_decompressor_options options, BUFFER_LENGTH_NAMES
30
+
31
+ open_files(source, destination) do |source_io, destination_io|
32
+ BRS._native_decompress_io source_io, destination_io, options
33
+ end
34
+ end
35
+
36
+ private_class_method def self.open_files(source, destination, &_block)
37
+ ::File.open source, "rb" do |source_io|
38
+ ::File.open destination, "wb" do |destination_io|
39
+ yield source_io, destination_io
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
data/lib/brs/option.rb ADDED
@@ -0,0 +1,80 @@
1
+ # Ruby bindings for brotli library.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require_relative "error"
5
+ require_relative "validation"
6
+
7
+ module BRS
8
+ module Option
9
+ DEFAULT_BUFFER_LENGTH = 0
10
+
11
+ COMPRESSOR_DEFAULTS = {
12
+ :mode => nil,
13
+ :quality => nil,
14
+ :lgwin => nil,
15
+ :lgblock => nil,
16
+ :disable_literal_context_modeling => nil,
17
+ :size_hint => nil,
18
+ :large_window => nil
19
+ }
20
+ .freeze
21
+
22
+ DECOMPRESSOR_DEFAULTS = {
23
+ :disable_ring_buffer_reallocation => nil,
24
+ :large_window => nil
25
+ }
26
+ .freeze
27
+
28
+ def self.get_compressor_options(options, buffer_length_names)
29
+ Validation.validate_hash options
30
+
31
+ buffer_length_defaults = buffer_length_names.each_with_object({}) { |name, defaults| defaults[name] = DEFAULT_BUFFER_LENGTH }
32
+ options = COMPRESSOR_DEFAULTS.merge(buffer_length_defaults).merge options
33
+
34
+ buffer_length_names.each { |name| Validation.validate_not_negative_integer options[name] }
35
+
36
+ mode = options[:mode]
37
+ unless mode.nil?
38
+ Validation.validate_symbol mode
39
+ raise ValidateError, "invalid mode" unless MODES.include? mode
40
+ end
41
+
42
+ quality = options[:quality]
43
+ Validation.validate_not_negative_integer quality unless quality.nil?
44
+
45
+ lgwin = options[:lgwin]
46
+ Validation.validate_not_negative_integer lgwin unless lgwin.nil?
47
+
48
+ lgblock = options[:lgblock]
49
+ Validation.validate_not_negative_integer lgblock unless lgblock.nil?
50
+
51
+ disable_literal_context_modeling = options[:disable_literal_context_modeling]
52
+ Validation.validate_bool disable_literal_context_modeling unless disable_literal_context_modeling.nil?
53
+
54
+ size_hint = options[:size_hint]
55
+ Validation.validate_not_negative_integer size_hint unless size_hint.nil?
56
+
57
+ large_window = options[:large_window]
58
+ Validation.validate_bool large_window unless large_window.nil?
59
+
60
+ options
61
+ end
62
+
63
+ def self.get_decompressor_options(options, buffer_length_names)
64
+ Validation.validate_hash options
65
+
66
+ buffer_length_defaults = buffer_length_names.each_with_object({}) { |name, defaults| defaults[name] = DEFAULT_BUFFER_LENGTH }
67
+ options = DECOMPRESSOR_DEFAULTS.merge(buffer_length_defaults).merge options
68
+
69
+ buffer_length_names.each { |name| Validation.validate_not_negative_integer options[name] }
70
+
71
+ disable_ring_buffer_reallocation = options[:disable_ring_buffer_reallocation]
72
+ Validation.validate_bool disable_ring_buffer_reallocation unless disable_ring_buffer_reallocation.nil?
73
+
74
+ large_window = options[:large_window]
75
+ Validation.validate_bool large_window unless large_window.nil?
76
+
77
+ options
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,153 @@
1
+ # Ruby bindings for brotli library.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require_relative "delegates"
5
+ require_relative "stat"
6
+ require_relative "../error"
7
+ require_relative "../validation"
8
+
9
+ module BRS
10
+ module Stream
11
+ class Abstract
12
+ # Native stream is not seekable by design.
13
+ # Related methods like "seek" and "pos=" can't be implemented.
14
+
15
+ # It is not possible to maintain correspondance between bytes consumed from source and bytes written to destination by design.
16
+ # We will consume all source bytes and maintain buffer with remaining destination data.
17
+
18
+ include Delegates
19
+
20
+ attr_reader :io
21
+ attr_reader :stat
22
+ attr_reader :external_encoding
23
+ attr_reader :internal_encoding
24
+ attr_reader :transcode_options
25
+ attr_reader :pos
26
+ alias tell pos
27
+
28
+ def initialize(io, external_encoding: nil, internal_encoding: nil, transcode_options: {})
29
+ @raw_stream = create_raw_stream
30
+
31
+ Validation.validate_io io
32
+ @io = io
33
+
34
+ @stat = Stat.new @io.stat
35
+
36
+ set_encoding external_encoding, internal_encoding, transcode_options
37
+ reset_buffer
38
+ reset_io_advise
39
+
40
+ @pos = 0
41
+ end
42
+
43
+ # -- buffer --
44
+
45
+ protected def reset_buffer
46
+ @buffer = ::String.new :encoding => ::Encoding::BINARY
47
+ end
48
+
49
+ # -- advise --
50
+
51
+ protected def reset_io_advise
52
+ # Both compressor and decompressor need sequential io access.
53
+ @io.advise :sequential
54
+ rescue ::Errno::ESPIPE # rubocop:disable Lint/HandleExceptions
55
+ # ok
56
+ end
57
+
58
+ def advise
59
+ # Noop
60
+ nil
61
+ end
62
+
63
+ # -- encoding --
64
+
65
+ def set_encoding(*args)
66
+ external_encoding, internal_encoding, transcode_options = process_set_encoding_arguments(*args)
67
+
68
+ set_target_encoding :@external_encoding, external_encoding
69
+ set_target_encoding :@internal_encoding, internal_encoding
70
+ @transcode_options = transcode_options
71
+
72
+ self
73
+ end
74
+
75
+ protected def process_set_encoding_arguments(*args)
76
+ external_encoding = args[0]
77
+
78
+ unless external_encoding.nil? || external_encoding.is_a?(::Encoding)
79
+ Validation.validate_string external_encoding
80
+
81
+ # First argument can be "external_encoding:internal_encoding".
82
+ match = %r{(.+?):(.+)}.match external_encoding
83
+
84
+ unless match.nil?
85
+ external_encoding = match[0]
86
+ internal_encoding = match[1]
87
+
88
+ transcode_options = args[1]
89
+ Validation.validate_hash transcode_options unless transcode_options.nil?
90
+
91
+ return [external_encoding, internal_encoding, transcode_options]
92
+ end
93
+ end
94
+
95
+ internal_encoding = args[1]
96
+ Validation.validate_string internal_encoding \
97
+ unless internal_encoding.nil? || internal_encoding.is_a?(::Encoding)
98
+
99
+ transcode_options = args[2]
100
+ Validation.validate_hash transcode_options unless transcode_options.nil?
101
+
102
+ [external_encoding, internal_encoding, transcode_options]
103
+ end
104
+
105
+ protected def set_target_encoding(name, value)
106
+ unless value.nil? || value.is_a?(::Encoding)
107
+ begin
108
+ value = ::Encoding.find value
109
+ rescue ::ArgumentError
110
+ raise ValidateError, "invalid #{name} encoding"
111
+ end
112
+ end
113
+
114
+ instance_variable_set name, value
115
+ end
116
+
117
+ protected def target_encoding
118
+ return @internal_encoding unless @internal_encoding.nil?
119
+ return @external_encoding unless @external_encoding.nil?
120
+
121
+ ::Encoding::BINARY
122
+ end
123
+
124
+ # -- etc --
125
+
126
+ def rewind
127
+ @raw_stream = create_raw_stream
128
+
129
+ @io.rewind
130
+ reset_buffer
131
+ reset_io_advise
132
+
133
+ @pos = 0
134
+
135
+ 0
136
+ end
137
+
138
+ def close
139
+ @io.close
140
+
141
+ nil
142
+ end
143
+
144
+ def closed?
145
+ @raw_stream.closed? && @io.closed?
146
+ end
147
+
148
+ def to_io
149
+ self
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,36 @@
1
+ # Ruby bindings for brotli library.
2
+ # Copyright (c) 2019 AUTHORS, MIT License.
3
+
4
+ require "forwardable"
5
+
6
+ module BRS
7
+ module Stream
8
+ module Delegates
9
+ DELEGATES = %i[
10
+ autoclose=
11
+ autoclose?
12
+ binmode
13
+ binmode?
14
+ close_on_exec=
15
+ close_on_exec?
16
+ fcntl
17
+ fdatasync
18
+ fileno
19
+ fsync
20
+ ioctl
21
+ isatty
22
+ pid
23
+ sync
24
+ sync=
25
+ to_i
26
+ tty?
27
+ ]
28
+ .freeze
29
+
30
+ def self.included(klass)
31
+ klass.extend ::Forwardable
32
+ klass.def_delegators :@io, *DELEGATES
33
+ end
34
+ end
35
+ end
36
+ end