ruby-lzws 1.0.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/AUTHORS +1 -0
- data/LICENSE +21 -0
- data/README.md +188 -0
- data/ext/extconf.rb +77 -0
- data/ext/lzws_ext/common.h +9 -0
- data/ext/lzws_ext/error.c +14 -0
- data/ext/lzws_ext/error.h +11 -0
- data/ext/lzws_ext/io.c +92 -0
- data/ext/lzws_ext/io.h +12 -0
- data/ext/lzws_ext/macro.h +15 -0
- data/ext/lzws_ext/main.c +42 -0
- data/ext/lzws_ext/option.c +22 -0
- data/ext/lzws_ext/option.h +60 -0
- data/ext/lzws_ext/stream/compressor.c +217 -0
- data/ext/lzws_ext/stream/compressor.h +27 -0
- data/ext/lzws_ext/stream/decompressor.c +206 -0
- data/ext/lzws_ext/stream/decompressor.h +26 -0
- data/ext/lzws_ext/string.c +78 -0
- data/ext/lzws_ext/string.h +12 -0
- data/lib/lzws/error.rb +20 -0
- data/lib/lzws/file.rb +56 -0
- data/lib/lzws/option.rb +66 -0
- data/lib/lzws/stream/abstract.rb +153 -0
- data/lib/lzws/stream/delegates.rb +36 -0
- data/lib/lzws/stream/raw/abstract.rb +46 -0
- data/lib/lzws/stream/raw/compressor.rb +100 -0
- data/lib/lzws/stream/raw/decompressor.rb +78 -0
- data/lib/lzws/stream/reader.rb +163 -0
- data/lib/lzws/stream/reader_helpers.rb +192 -0
- data/lib/lzws/stream/stat.rb +78 -0
- data/lib/lzws/stream/writer.rb +145 -0
- data/lib/lzws/stream/writer_helpers.rb +93 -0
- data/lib/lzws/string.rb +27 -0
- data/lib/lzws/validation.rb +36 -0
- data/lib/lzws/version.rb +6 -0
- data/lib/lzws.rb +4 -0
- metadata +163 -0
data/lib/lzws/option.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Ruby bindings for lzws library.
|
2
|
+
# Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
require_relative "error"
|
5
|
+
require_relative "validation"
|
6
|
+
|
7
|
+
module LZWS
|
8
|
+
module Option
|
9
|
+
# Default options will be compatible with UNIX compress.
|
10
|
+
|
11
|
+
LOWEST_MAX_CODE_BIT_LENGTH = 9
|
12
|
+
BIGGEST_MAX_CODE_BIT_LENGTH = 16
|
13
|
+
|
14
|
+
DECOMPRESSOR_DEFAULTS = {
|
15
|
+
:buffer_length => 0,
|
16
|
+
:without_magic_header => false,
|
17
|
+
:msb => false,
|
18
|
+
:unaligned_bit_groups => false,
|
19
|
+
:quiet => false
|
20
|
+
}
|
21
|
+
.freeze
|
22
|
+
|
23
|
+
COMPRESSOR_DEFAULTS = DECOMPRESSOR_DEFAULTS.merge(
|
24
|
+
:max_code_bit_length => BIGGEST_MAX_CODE_BIT_LENGTH,
|
25
|
+
:block_mode => true
|
26
|
+
)
|
27
|
+
.freeze
|
28
|
+
|
29
|
+
def self.get_compressor_options(options)
|
30
|
+
Validation.validate_hash options
|
31
|
+
|
32
|
+
options = COMPRESSOR_DEFAULTS.merge options
|
33
|
+
|
34
|
+
Validation.validate_not_negative_integer options[:buffer_length]
|
35
|
+
|
36
|
+
max_code_bit_length = options[:max_code_bit_length]
|
37
|
+
Validation.validate_positive_integer max_code_bit_length
|
38
|
+
|
39
|
+
raise ValidateError, "invalid max code bit length" if
|
40
|
+
max_code_bit_length < LOWEST_MAX_CODE_BIT_LENGTH ||
|
41
|
+
max_code_bit_length > BIGGEST_MAX_CODE_BIT_LENGTH
|
42
|
+
|
43
|
+
Validation.validate_bool options[:without_magic_header]
|
44
|
+
Validation.validate_bool options[:block_mode]
|
45
|
+
Validation.validate_bool options[:msb]
|
46
|
+
Validation.validate_bool options[:unaligned_bit_groups]
|
47
|
+
Validation.validate_bool options[:quiet]
|
48
|
+
|
49
|
+
options
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.get_decompressor_options(options)
|
53
|
+
Validation.validate_hash options
|
54
|
+
|
55
|
+
options = DECOMPRESSOR_DEFAULTS.merge options
|
56
|
+
|
57
|
+
Validation.validate_not_negative_integer options[:buffer_length]
|
58
|
+
Validation.validate_bool options[:without_magic_header]
|
59
|
+
Validation.validate_bool options[:msb]
|
60
|
+
Validation.validate_bool options[:unaligned_bit_groups]
|
61
|
+
Validation.validate_bool options[:quiet]
|
62
|
+
|
63
|
+
options
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# Ruby bindings for lzws 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 LZWS
|
10
|
+
module Stream
|
11
|
+
class Abstract
|
12
|
+
# LZWS 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 :pos
|
25
|
+
alias tell pos
|
26
|
+
|
27
|
+
def initialize(io, external_encoding: nil, internal_encoding: nil, transcode_options: {})
|
28
|
+
@raw_stream = create_raw_stream
|
29
|
+
|
30
|
+
Validation.validate_io io
|
31
|
+
@io = io
|
32
|
+
|
33
|
+
@stat = Stat.new @io.stat
|
34
|
+
|
35
|
+
set_encoding external_encoding, internal_encoding, transcode_options
|
36
|
+
reset_buffer
|
37
|
+
reset_io_advise
|
38
|
+
|
39
|
+
@pos = 0
|
40
|
+
end
|
41
|
+
|
42
|
+
# -- buffer --
|
43
|
+
|
44
|
+
protected def reset_buffer
|
45
|
+
@buffer = ::String.new :encoding => ::Encoding::BINARY
|
46
|
+
end
|
47
|
+
|
48
|
+
# -- advise --
|
49
|
+
|
50
|
+
protected def reset_io_advise
|
51
|
+
# Both compressor and decompressor need sequential io access.
|
52
|
+
@io.advise :sequential
|
53
|
+
rescue ::Errno::ESPIPE # rubocop:disable Lint/HandleExceptions
|
54
|
+
# ok
|
55
|
+
end
|
56
|
+
|
57
|
+
def advise
|
58
|
+
# Noop
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# -- encoding --
|
63
|
+
|
64
|
+
def set_encoding(*args)
|
65
|
+
external_encoding, internal_encoding, transcode_options = process_set_encoding_arguments(*args)
|
66
|
+
|
67
|
+
set_target_encoding :@external_encoding, external_encoding
|
68
|
+
set_target_encoding :@internal_encoding, internal_encoding
|
69
|
+
@transcode_options = transcode_options
|
70
|
+
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
protected def process_set_encoding_arguments(*args)
|
75
|
+
external_encoding = args[0]
|
76
|
+
|
77
|
+
unless external_encoding.nil? || external_encoding.is_a?(::Encoding)
|
78
|
+
Validation.validate_string external_encoding
|
79
|
+
|
80
|
+
# First argument can be "external_encoding:internal_encoding".
|
81
|
+
match = %r{(.+?):(.+)}.match external_encoding
|
82
|
+
|
83
|
+
unless match.nil?
|
84
|
+
external_encoding = match[0]
|
85
|
+
internal_encoding = match[1]
|
86
|
+
|
87
|
+
transcode_options = args[1]
|
88
|
+
Validation.validate_hash transcode_options unless transcode_options.nil?
|
89
|
+
|
90
|
+
return [external_encoding, internal_encoding, transcode_options]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
internal_encoding = args[1]
|
95
|
+
Validation.validate_string internal_encoding \
|
96
|
+
unless internal_encoding.nil? || internal_encoding.is_a?(::Encoding)
|
97
|
+
|
98
|
+
transcode_options = args[2]
|
99
|
+
Validation.validate_hash transcode_options unless transcode_options.nil?
|
100
|
+
|
101
|
+
[external_encoding, internal_encoding, transcode_options]
|
102
|
+
end
|
103
|
+
|
104
|
+
protected def set_target_encoding(name, value)
|
105
|
+
unless value.nil? || value.is_a?(::Encoding)
|
106
|
+
begin
|
107
|
+
value = ::Encoding.find value
|
108
|
+
rescue ::ArgumentError
|
109
|
+
raise ValidateError, "invalid #{name} encoding"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
instance_variable_set name, value
|
114
|
+
end
|
115
|
+
|
116
|
+
protected def target_encoding
|
117
|
+
return @internal_encoding unless @internal_encoding.nil?
|
118
|
+
|
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 lzws library.
|
2
|
+
# Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
module LZWS
|
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
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Ruby bindings for lzws library.
|
2
|
+
# Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
require "lzws_ext"
|
5
|
+
|
6
|
+
require_relative "../../error"
|
7
|
+
|
8
|
+
module LZWS
|
9
|
+
module Stream
|
10
|
+
module Raw
|
11
|
+
class Abstract
|
12
|
+
def initialize(native_stream)
|
13
|
+
@native_stream = native_stream
|
14
|
+
@is_closed = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def close(&writer)
|
18
|
+
return nil if @is_closed
|
19
|
+
|
20
|
+
flush(&writer)
|
21
|
+
|
22
|
+
@native_stream.close
|
23
|
+
@is_closed = true
|
24
|
+
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def closed?
|
29
|
+
@is_closed
|
30
|
+
end
|
31
|
+
|
32
|
+
protected def flush_destination_buffer(&writer)
|
33
|
+
result_bytesize = write_result(&writer)
|
34
|
+
raise NotEnoughDestinationError, "not enough destination" if result_bytesize == 0
|
35
|
+
end
|
36
|
+
|
37
|
+
protected def write_result(&_writer)
|
38
|
+
result = @native_stream.read_result
|
39
|
+
yield result
|
40
|
+
|
41
|
+
result.bytesize
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# Ruby bindings for lzws library.
|
2
|
+
# Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
require "lzws_ext"
|
5
|
+
|
6
|
+
require_relative "abstract"
|
7
|
+
require_relative "../../error"
|
8
|
+
require_relative "../../option"
|
9
|
+
require_relative "../../validation"
|
10
|
+
|
11
|
+
module LZWS
|
12
|
+
module Stream
|
13
|
+
module Raw
|
14
|
+
class Compressor < Abstract
|
15
|
+
def initialize(options = {})
|
16
|
+
options = Option.get_compressor_options options
|
17
|
+
native_stream = NativeCompressor.new options
|
18
|
+
|
19
|
+
super native_stream
|
20
|
+
|
21
|
+
@need_to_write_magic_header = !options[:without_magic_header]
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(source, &writer)
|
25
|
+
do_not_use_after_close
|
26
|
+
|
27
|
+
Validation.validate_string source
|
28
|
+
Validation.validate_proc writer
|
29
|
+
|
30
|
+
if @need_to_write_magic_header
|
31
|
+
write_magic_header(&writer)
|
32
|
+
@need_to_write_magic_header = false
|
33
|
+
end
|
34
|
+
|
35
|
+
total_bytes_written = 0
|
36
|
+
|
37
|
+
loop do
|
38
|
+
bytes_written, need_more_destination = @native_stream.write source
|
39
|
+
total_bytes_written += bytes_written
|
40
|
+
|
41
|
+
if need_more_destination
|
42
|
+
source = source.byteslice bytes_written, source.bytesize - bytes_written
|
43
|
+
flush_destination_buffer(&writer)
|
44
|
+
next
|
45
|
+
end
|
46
|
+
|
47
|
+
unless bytes_written == source.bytesize
|
48
|
+
# Compressor write should eat all provided "source" without remainder.
|
49
|
+
raise UnexpectedError, "unexpected error"
|
50
|
+
end
|
51
|
+
|
52
|
+
break
|
53
|
+
end
|
54
|
+
|
55
|
+
total_bytes_written
|
56
|
+
end
|
57
|
+
|
58
|
+
protected def write_magic_header(&writer)
|
59
|
+
loop do
|
60
|
+
need_more_destination = @native_stream.write_magic_header
|
61
|
+
|
62
|
+
if need_more_destination
|
63
|
+
flush_destination_buffer(&writer)
|
64
|
+
next
|
65
|
+
end
|
66
|
+
|
67
|
+
break
|
68
|
+
end
|
69
|
+
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def flush(&writer)
|
74
|
+
do_not_use_after_close
|
75
|
+
|
76
|
+
Validation.validate_proc writer
|
77
|
+
|
78
|
+
loop do
|
79
|
+
need_more_destination = @native_stream.flush
|
80
|
+
|
81
|
+
if need_more_destination
|
82
|
+
flush_destination_buffer(&writer)
|
83
|
+
next
|
84
|
+
end
|
85
|
+
|
86
|
+
break
|
87
|
+
end
|
88
|
+
|
89
|
+
write_result(&writer)
|
90
|
+
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
protected def do_not_use_after_close
|
95
|
+
raise UsedAfterCloseError, "compressor used after close" if @is_closed
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Ruby bindings for lzws library.
|
2
|
+
# Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
require "lzws_ext"
|
5
|
+
|
6
|
+
require_relative "abstract"
|
7
|
+
require_relative "../../error"
|
8
|
+
require_relative "../../option"
|
9
|
+
require_relative "../../validation"
|
10
|
+
|
11
|
+
module LZWS
|
12
|
+
module Stream
|
13
|
+
module Raw
|
14
|
+
class Decompressor < Abstract
|
15
|
+
def initialize(options = {})
|
16
|
+
options = Option.get_decompressor_options options
|
17
|
+
native_stream = NativeDecompressor.new options
|
18
|
+
|
19
|
+
super native_stream
|
20
|
+
|
21
|
+
@need_to_read_magic_header = !options[:without_magic_header]
|
22
|
+
end
|
23
|
+
|
24
|
+
def read(source, &writer)
|
25
|
+
do_not_use_after_close
|
26
|
+
|
27
|
+
Validation.validate_string source
|
28
|
+
Validation.validate_proc writer
|
29
|
+
|
30
|
+
total_bytes_read = 0
|
31
|
+
|
32
|
+
if @need_to_read_magic_header
|
33
|
+
bytes_read = @native_stream.read_magic_header source
|
34
|
+
if bytes_read == 0
|
35
|
+
# Decompressor is not able to read full magic header.
|
36
|
+
return 0
|
37
|
+
end
|
38
|
+
|
39
|
+
total_bytes_read += bytes_read
|
40
|
+
source = source.byteslice bytes_read, source.bytesize - bytes_read
|
41
|
+
|
42
|
+
@need_to_read_magic_header = false
|
43
|
+
end
|
44
|
+
|
45
|
+
loop do
|
46
|
+
bytes_read, need_more_destination = @native_stream.read source
|
47
|
+
total_bytes_read += bytes_read
|
48
|
+
|
49
|
+
if need_more_destination
|
50
|
+
source = source.byteslice bytes_read, source.bytesize - bytes_read
|
51
|
+
flush_destination_buffer(&writer)
|
52
|
+
next
|
53
|
+
end
|
54
|
+
|
55
|
+
break
|
56
|
+
end
|
57
|
+
|
58
|
+
# Please remember that "total_bytes_read" can not be equal to "source.bytesize".
|
59
|
+
total_bytes_read
|
60
|
+
end
|
61
|
+
|
62
|
+
def flush(&writer)
|
63
|
+
do_not_use_after_close
|
64
|
+
|
65
|
+
Validation.validate_proc writer
|
66
|
+
|
67
|
+
write_result(&writer)
|
68
|
+
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
protected def do_not_use_after_close
|
73
|
+
raise UsedAfterCloseError, "decompressor used after close" if @is_closed
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# Ruby bindings for lzws library.
|
2
|
+
# Copyright (c) 2019 AUTHORS, MIT License.
|
3
|
+
|
4
|
+
require_relative "abstract"
|
5
|
+
require_relative "raw/decompressor"
|
6
|
+
require_relative "reader_helpers"
|
7
|
+
require_relative "../validation"
|
8
|
+
|
9
|
+
module LZWS
|
10
|
+
module Stream
|
11
|
+
class Reader < Abstract
|
12
|
+
include ReaderHelpers
|
13
|
+
|
14
|
+
DEFAULT_IO_PORTION_BYTESIZE = 1 << 15 # 32 KB
|
15
|
+
|
16
|
+
attr_accessor :lineno
|
17
|
+
|
18
|
+
def initialize(source_io, options = {}, *args)
|
19
|
+
@options = options
|
20
|
+
|
21
|
+
super source_io, *args
|
22
|
+
|
23
|
+
io_portion_bytesize = @options[:io_portion_bytesize]
|
24
|
+
@options.delete :io_portion_bytesize
|
25
|
+
|
26
|
+
Validation.validate_positive_integer io_portion_bytesize unless io_portion_bytesize.nil?
|
27
|
+
@io_portion_bytesize = io_portion_bytesize || DEFAULT_IO_PORTION_BYTESIZE
|
28
|
+
|
29
|
+
reset_io_remainder
|
30
|
+
|
31
|
+
@lineno = 0
|
32
|
+
end
|
33
|
+
|
34
|
+
protected def create_raw_stream
|
35
|
+
Raw::Decompressor.new @options
|
36
|
+
end
|
37
|
+
|
38
|
+
protected def reset_io_remainder
|
39
|
+
@io_remainder = ::String.new :encoding => ::Encoding::BINARY
|
40
|
+
end
|
41
|
+
|
42
|
+
# -- synchronous --
|
43
|
+
|
44
|
+
def read(bytes_to_read = nil, out_buffer = nil)
|
45
|
+
Validation.validate_not_negative_integer bytes_to_read unless bytes_to_read.nil?
|
46
|
+
Validation.validate_string out_buffer unless out_buffer.nil?
|
47
|
+
|
48
|
+
return ::String.new :encoding => ::Encoding::BINARY if bytes_to_read == 0
|
49
|
+
|
50
|
+
unless bytes_to_read.nil?
|
51
|
+
return nil if eof?
|
52
|
+
|
53
|
+
read_more_from_buffer until @buffer.bytesize >= bytes_to_read || @io.eof?
|
54
|
+
|
55
|
+
return read_bytes_from_buffer bytes_to_read, out_buffer
|
56
|
+
end
|
57
|
+
|
58
|
+
read_more_from_buffer until @io.eof?
|
59
|
+
|
60
|
+
result = @buffer
|
61
|
+
reset_buffer
|
62
|
+
@pos += result.bytesize
|
63
|
+
|
64
|
+
result.force_encoding @external_encoding unless @external_encoding.nil?
|
65
|
+
result = transcode_to_internal result
|
66
|
+
result = out_buffer.replace result unless out_buffer.nil?
|
67
|
+
|
68
|
+
result
|
69
|
+
end
|
70
|
+
|
71
|
+
protected def read_more_from_buffer
|
72
|
+
io_data = @io.read @io_portion_bytesize
|
73
|
+
append_io_data_to_buffer io_data
|
74
|
+
end
|
75
|
+
|
76
|
+
def readpartial(bytes_to_read = nil, out_buffer = nil)
|
77
|
+
raise ::EOFError if eof?
|
78
|
+
|
79
|
+
readpartial_from_buffer until @buffer.bytesize >= bytes_to_read || @io.eof?
|
80
|
+
|
81
|
+
read_bytes_from_buffer bytes_to_read, out_buffer
|
82
|
+
end
|
83
|
+
|
84
|
+
protected def readpartial_from_buffer
|
85
|
+
io_data = @io.readpartial @io_portion_bytesize
|
86
|
+
append_io_data_to_buffer io_data
|
87
|
+
end
|
88
|
+
|
89
|
+
def rewind
|
90
|
+
raw_wrapper :close
|
91
|
+
|
92
|
+
reset_io_remainder
|
93
|
+
|
94
|
+
super
|
95
|
+
end
|
96
|
+
|
97
|
+
def close
|
98
|
+
raw_wrapper :close
|
99
|
+
|
100
|
+
super
|
101
|
+
end
|
102
|
+
|
103
|
+
# -- asynchronous --
|
104
|
+
|
105
|
+
def read_nonblock(bytes_to_read, out_buffer = nil, *options)
|
106
|
+
raise ::EOFError if eof?
|
107
|
+
|
108
|
+
read_more_from_buffer_nonblock(*options) until @buffer.bytesize >= bytes_to_read || @io.eof?
|
109
|
+
|
110
|
+
read_bytes_from_buffer bytes_to_read, out_buffer
|
111
|
+
end
|
112
|
+
|
113
|
+
protected def read_more_from_buffer_nonblock(*options)
|
114
|
+
io_data = @io.read_nonblock @io_portion_bytesize, *options
|
115
|
+
append_io_data_to_buffer io_data
|
116
|
+
end
|
117
|
+
|
118
|
+
# -- common --
|
119
|
+
|
120
|
+
def eof?
|
121
|
+
@io.eof? && @buffer.bytesize == 0
|
122
|
+
end
|
123
|
+
|
124
|
+
protected def read_bytes_from_buffer(bytes_to_read, out_buffer)
|
125
|
+
bytes_read = [@buffer.bytesize, bytes_to_read].min
|
126
|
+
|
127
|
+
# Result uses buffer binary encoding.
|
128
|
+
result = @buffer.byteslice 0, bytes_read
|
129
|
+
@buffer = @buffer.byteslice bytes_read, @buffer.bytesize - bytes_read
|
130
|
+
@pos += bytes_read
|
131
|
+
|
132
|
+
result = out_buffer.replace result unless out_buffer.nil?
|
133
|
+
result
|
134
|
+
end
|
135
|
+
|
136
|
+
protected def append_io_data_to_buffer(io_data)
|
137
|
+
io_portion = @io_remainder + io_data
|
138
|
+
bytes_read = raw_wrapper :read, io_portion
|
139
|
+
@io_remainder = io_portion.byteslice bytes_read, io_portion.bytesize - bytes_read
|
140
|
+
|
141
|
+
# We should just ignore case when "io.eof?" appears but "io_remainder" is not empty.
|
142
|
+
# Ancient compress implementations can write bytes from not initialized buffer parts to output.
|
143
|
+
raw_wrapper :flush if @io.eof?
|
144
|
+
end
|
145
|
+
|
146
|
+
protected def transcode_to_internal(data)
|
147
|
+
data = data.encode @internal_encoding, @transcode_options unless @internal_encoding.nil?
|
148
|
+
data
|
149
|
+
end
|
150
|
+
|
151
|
+
# We should be able to return data back to buffer.
|
152
|
+
# We won't use any transcode options because transcoded data should be backward compatible.
|
153
|
+
protected def transcode_to_external(data)
|
154
|
+
data = data.encode @external_encoding unless @external_encoding.nil?
|
155
|
+
data
|
156
|
+
end
|
157
|
+
|
158
|
+
protected def raw_wrapper(method_name, *args)
|
159
|
+
@raw_stream.send(method_name, *args) { |portion| @buffer << portion }
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|