adsp 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/AUTHORS +1 -0
  3. data/LICENSE +21 -0
  4. data/README.md +300 -0
  5. data/lib/adsp/error.rb +14 -0
  6. data/lib/adsp/file.rb +76 -0
  7. data/lib/adsp/option.rb +51 -0
  8. data/lib/adsp/stream/abstract.rb +206 -0
  9. data/lib/adsp/stream/delegates.rb +39 -0
  10. data/lib/adsp/stream/raw/abstract.rb +69 -0
  11. data/lib/adsp/stream/raw/compressor.rb +110 -0
  12. data/lib/adsp/stream/raw/decompressor.rb +80 -0
  13. data/lib/adsp/stream/raw/native_compressor.rb +58 -0
  14. data/lib/adsp/stream/raw/native_decompressor.rb +44 -0
  15. data/lib/adsp/stream/reader.rb +234 -0
  16. data/lib/adsp/stream/reader_helpers.rb +219 -0
  17. data/lib/adsp/stream/stat.rb +80 -0
  18. data/lib/adsp/stream/writer.rb +206 -0
  19. data/lib/adsp/stream/writer_helpers.rb +102 -0
  20. data/lib/adsp/string.rb +58 -0
  21. data/lib/adsp/validation.rb +46 -0
  22. data/lib/adsp/version.rb +7 -0
  23. data/lib/adsp.rb +8 -0
  24. data/test/common.rb +108 -0
  25. data/test/coverage_helper.rb +18 -0
  26. data/test/file.test.rb +120 -0
  27. data/test/minitest.rb +20 -0
  28. data/test/mock/common.rb +57 -0
  29. data/test/mock/file.rb +60 -0
  30. data/test/mock/stream/raw/compressor.rb +20 -0
  31. data/test/mock/stream/raw/decompressor.rb +20 -0
  32. data/test/mock/stream/raw/native_compressor.rb +82 -0
  33. data/test/mock/stream/raw/native_decompressor.rb +70 -0
  34. data/test/mock/stream/reader.rb +18 -0
  35. data/test/mock/stream/writer.rb +18 -0
  36. data/test/mock/string.rb +44 -0
  37. data/test/option.rb +66 -0
  38. data/test/stream/abstract.rb +125 -0
  39. data/test/stream/minitar.test.rb +50 -0
  40. data/test/stream/raw/abstract.rb +45 -0
  41. data/test/stream/raw/compressor.test.rb +166 -0
  42. data/test/stream/raw/decompressor.test.rb +166 -0
  43. data/test/stream/reader.test.rb +643 -0
  44. data/test/stream/reader_helpers.test.rb +421 -0
  45. data/test/stream/writer.test.rb +610 -0
  46. data/test/stream/writer_helpers.test.rb +267 -0
  47. data/test/string.test.rb +95 -0
  48. data/test/validation.rb +71 -0
  49. data/test/version.test.rb +18 -0
  50. metadata +274 -0
@@ -0,0 +1,39 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "forwardable"
5
+
6
+ module ADSP
7
+ module Stream
8
+ # ADSP::Stream::Delegates class.
9
+ module Delegates
10
+ # List of methods to be forwarded for native stream.
11
+ DELEGATES = %i[
12
+ autoclose=
13
+ autoclose?
14
+ binmode
15
+ binmode?
16
+ close_on_exec=
17
+ close_on_exec?
18
+ fcntl
19
+ fdatasync
20
+ fileno
21
+ fsync
22
+ ioctl
23
+ isatty
24
+ pid
25
+ sync
26
+ sync=
27
+ to_i
28
+ tty?
29
+ ]
30
+ .freeze
31
+
32
+ # Defines delegates for native stream after module included.
33
+ def self.included(klass)
34
+ klass.extend ::Forwardable
35
+ klass.def_delegators :@io, *DELEGATES
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,69 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require_relative "../../error"
5
+ require_relative "../../validation"
6
+
7
+ module ADSP
8
+ module Stream
9
+ module Raw
10
+ # ADSP::Stream::Raw::Abstract class.
11
+ class Abstract
12
+ # Initializes raw stream using +native_stream+.
13
+ def initialize(native_stream)
14
+ @native_stream = native_stream
15
+ @is_closed = false
16
+ end
17
+
18
+ # -- write --
19
+
20
+ # Flushes raw stream and writes next result using +writer+ proc.
21
+ def flush(&writer)
22
+ do_not_use_after_close
23
+
24
+ Validation.validate_proc writer
25
+
26
+ write_result(&writer)
27
+
28
+ nil
29
+ end
30
+
31
+ # Writes next result using +writer+ proc and frees destination buffer.
32
+ protected def more_destination(&writer)
33
+ result_bytesize = write_result(&writer)
34
+ raise NotEnoughDestinationError, "not enough destination" if result_bytesize.zero?
35
+ end
36
+
37
+ # Writes next result using block.
38
+ protected def write_result(&_writer)
39
+ result = @native_stream.read_result
40
+ yield result
41
+
42
+ result.bytesize
43
+ end
44
+
45
+ # -- close --
46
+
47
+ # Raises error when raw stream is closed.
48
+ protected def do_not_use_after_close
49
+ raise UsedAfterCloseError, "used after close" if closed?
50
+ end
51
+
52
+ # Writes next result using +writer+ proc and closes raw stream.
53
+ def close(&writer)
54
+ write_result(&writer)
55
+
56
+ @native_stream.close
57
+ @is_closed = true
58
+
59
+ nil
60
+ end
61
+
62
+ # Returns whether raw stream is closed.
63
+ def closed?
64
+ @is_closed
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,110 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require_relative "abstract"
5
+ require_relative "native_compressor"
6
+ require_relative "../../error"
7
+ require_relative "../../option"
8
+ require_relative "../../validation"
9
+
10
+ module ADSP
11
+ module Stream
12
+ module Raw
13
+ # ADSP::Stream::Raw::Compressor class.
14
+ class Compressor < Abstract
15
+ # Current native compressor class.
16
+ NativeCompressor = Raw::NativeCompressor
17
+
18
+ # Current option class.
19
+ Option = ADSP::Option
20
+
21
+ # Current buffer length names.
22
+ # It is a part of compressor options.
23
+ BUFFER_LENGTH_NAMES = %i[destination_buffer_length].freeze
24
+
25
+ # Initializes compressor.
26
+ # Option: +:destination_buffer_length+ destination buffer length.
27
+ def initialize(options = {})
28
+ options = self.class::Option.get_compressor_options options, BUFFER_LENGTH_NAMES
29
+ native_stream = self.class::NativeCompressor.new options
30
+
31
+ super native_stream
32
+ end
33
+
34
+ # Writes +source+ string, writes result using +writer+ proc.
35
+ # Returns amount of bytes written from +source+.
36
+ def write(source, &writer)
37
+ do_not_use_after_close
38
+
39
+ Validation.validate_string source
40
+ Validation.validate_proc writer
41
+
42
+ total_bytes_written = 0
43
+
44
+ loop do
45
+ bytes_written, need_more_destination = @native_stream.write source
46
+ total_bytes_written += bytes_written
47
+
48
+ if need_more_destination
49
+ source = source.byteslice bytes_written, source.bytesize - bytes_written
50
+ more_destination(&writer)
51
+ next
52
+ end
53
+
54
+ unless bytes_written == source.bytesize
55
+ # Compressor write should eat all provided "source" without remainder.
56
+ # :nocov:
57
+ raise UnexpectedError, "unexpected error"
58
+ # :nocov:
59
+ end
60
+
61
+ break
62
+ end
63
+
64
+ total_bytes_written
65
+ end
66
+
67
+ # Flushes compressor, writes result using +writer+ proc and closes compressor.
68
+ def flush(&writer)
69
+ do_not_use_after_close
70
+
71
+ Validation.validate_proc writer
72
+
73
+ loop do
74
+ need_more_destination = @native_stream.flush
75
+
76
+ if need_more_destination
77
+ more_destination(&writer)
78
+ next
79
+ end
80
+
81
+ break
82
+ end
83
+
84
+ super
85
+ end
86
+
87
+ # Finishes compressor, writes result using +writer+ proc and closes compressor.
88
+ # Raises +UsedAfterCloseError+ when used after close.
89
+ def close(&writer)
90
+ return nil if closed?
91
+
92
+ Validation.validate_proc writer
93
+
94
+ loop do
95
+ need_more_destination = @native_stream.finish
96
+
97
+ if need_more_destination
98
+ more_destination(&writer)
99
+ next
100
+ end
101
+
102
+ break
103
+ end
104
+
105
+ super
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,80 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require_relative "abstract"
5
+ require_relative "native_decompressor"
6
+ require_relative "../../option"
7
+ require_relative "../../validation"
8
+
9
+ module ADSP
10
+ module Stream
11
+ module Raw
12
+ # ADSP::Stream::Raw::Decompressor class.
13
+ class Decompressor < Abstract
14
+ # Current native decompressor class.
15
+ NativeDecompressor = Raw::NativeDecompressor
16
+
17
+ # Current option class.
18
+ Option = ADSP::Option
19
+
20
+ # Current buffer length names.
21
+ # It is a part of decompressor options.
22
+ BUFFER_LENGTH_NAMES = %i[destination_buffer_length].freeze
23
+
24
+ # Initializes decompressor.
25
+ # Option: +:destination_buffer_length+ destination buffer length.
26
+ def initialize(options = {})
27
+ options = self.class::Option.get_decompressor_options options, BUFFER_LENGTH_NAMES
28
+ native_stream = self.class::NativeDecompressor.new options
29
+
30
+ super native_stream
31
+ end
32
+
33
+ # Reads +source+ string, writes result using +writer+ proc.
34
+ # Returns amount of bytes read from +source+.
35
+ def read(source, &writer)
36
+ do_not_use_after_close
37
+
38
+ Validation.validate_string source
39
+ Validation.validate_proc writer
40
+
41
+ total_bytes_read = 0
42
+
43
+ loop do
44
+ bytes_read, need_more_destination = @native_stream.read source
45
+ total_bytes_read += bytes_read
46
+
47
+ if need_more_destination
48
+ source = source.byteslice bytes_read, source.bytesize - bytes_read
49
+ more_destination(&writer)
50
+ next
51
+ end
52
+
53
+ break
54
+ end
55
+
56
+ total_bytes_read
57
+ end
58
+
59
+ # Flushes decompressor, writes result using +writer+ proc and closes decompressor.
60
+ def flush(&writer)
61
+ do_not_use_after_close
62
+
63
+ Validation.validate_proc writer
64
+
65
+ super
66
+ end
67
+
68
+ # Writes result using +writer+ proc and closes decompressor.
69
+ # Raises +UsedAfterCloseError+ when used after close.
70
+ def close(&writer)
71
+ return nil if closed?
72
+
73
+ Validation.validate_proc writer
74
+
75
+ super
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,58 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require_relative "../../error"
5
+
6
+ module ADSP
7
+ module Stream
8
+ module Raw
9
+ # ADSP::Stream::Raw::NativeCompressor class.
10
+ class NativeCompressor
11
+ # :nocov:
12
+
13
+ # Initializes native compressor.
14
+ # Option: +:destination_buffer_length+ destination buffer length.
15
+ def initialize(options)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ # Writes part of +source+ string.
20
+ # Returns array of 2 values:
21
+ # 1. number of bytes written from +source+.
22
+ # 2. boolean that can be named as "need more destination".
23
+ # User needs to call +read_result+ if "need more destination" is true.
24
+ def write(source)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ # Provides next part of unread result.
29
+ # Returns empty string if there is no unread result.
30
+ def read_result
31
+ raise NotImplementedError
32
+ end
33
+
34
+ # Flushes internal buffers and prepares result for +read_result+.
35
+ # Returns boolean that can be named as "need more destination".
36
+ # User needs to call +read_result+ if "need more destination" is true.
37
+ def flush
38
+ raise NotImplementedError
39
+ end
40
+
41
+ # Finishes compressor and prepares result for +read_result+.
42
+ # Returns boolean that can be named as "need more destination".
43
+ # User needs to call +read_result+ if "need more destination" is true.
44
+ def finish
45
+ raise NotImplementedError
46
+ end
47
+
48
+ # Closes compressor and cleans up internal resources.
49
+ # Raises +UsedAfterCloseError+ when used after close.
50
+ def close
51
+ raise NotImplementedError
52
+ end
53
+
54
+ # :nocov:
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,44 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require_relative "../../error"
5
+
6
+ module ADSP
7
+ module Stream
8
+ module Raw
9
+ # ADSP::Stream::Raw::NativeDecompressor class.
10
+ class NativeDecompressor
11
+ # :nocov:
12
+
13
+ # Initializes native decompressor.
14
+ # Option: +:destination_buffer_length+ destination buffer length.
15
+ def initialize(options)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ # Reads part of +source+ string.
20
+ # Returns array of 2 values:
21
+ # 1. number of bytes read from +source+.
22
+ # 2. boolean that can be named as "need more destination".
23
+ # User needs to call +read_result+ if "need more destination" is true.
24
+ def read(source)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ # Provides next part of unread result.
29
+ # Returns empty string if there is no unread result.
30
+ def read_result
31
+ raise NotImplementedError
32
+ end
33
+
34
+ # Closes decompressor and cleans up internal resources.
35
+ # Raises +UsedAfterCloseError+ when used after close.
36
+ def close
37
+ raise NotImplementedError
38
+ end
39
+
40
+ # :nocov:
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,234 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 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 ADSP
10
+ module Stream
11
+ # ADSP::Stream::Reader class.
12
+ class Reader < Abstract
13
+ include ReaderHelpers
14
+
15
+ # Default source buffer length.
16
+ DEFAULT_SOURCE_BUFFER_LENGTH = 1 << 18 # 256 KB
17
+
18
+ # Current raw stream class.
19
+ RawDecompressor = Raw::Decompressor
20
+
21
+ # Current line for source data.
22
+ attr_accessor :lineno
23
+
24
+ # Initializes stream using +source_io+ native stream and +options+.
25
+ # Option: +:external_encoding+ encoding name for destination data.
26
+ # Option: +:internal_encoding+ encoding name for source data.
27
+ # Option: +:transcode_options+ transcode options for data.
28
+ def initialize(source_io, options = {}, *args)
29
+ @options = options
30
+
31
+ super source_io, *args
32
+
33
+ initialize_source_buffer_length
34
+ reset_io_remainder
35
+ reset_need_to_flush
36
+
37
+ @lineno = 0
38
+ end
39
+
40
+ # Creates raw stream.
41
+ protected def create_raw_stream
42
+ self.class::RawDecompressor.new @options
43
+ end
44
+
45
+ # Initializes source buffer length.
46
+ protected def initialize_source_buffer_length
47
+ source_buffer_length = @options[:source_buffer_length]
48
+ Validation.validate_not_negative_integer source_buffer_length unless source_buffer_length.nil?
49
+
50
+ if source_buffer_length.nil? || source_buffer_length.zero?
51
+ source_buffer_length = self.class::DEFAULT_SOURCE_BUFFER_LENGTH
52
+ end
53
+
54
+ @source_buffer_length = source_buffer_length
55
+ end
56
+
57
+ # Resets io remainder.
58
+ protected def reset_io_remainder
59
+ @io_remainder = ::String.new :encoding => ::Encoding::BINARY
60
+ end
61
+
62
+ # Resets need to flush flag.
63
+ protected def reset_need_to_flush
64
+ @need_to_flush = false
65
+ end
66
+
67
+ # -- synchronous --
68
+
69
+ # Reads +bytes_to_read+ bytes from stream.
70
+ # If +out_buffer+ is defined than it will be used as output destination.
71
+ def read(bytes_to_read = nil, out_buffer = nil)
72
+ Validation.validate_not_negative_integer bytes_to_read unless bytes_to_read.nil?
73
+ Validation.validate_string out_buffer unless out_buffer.nil?
74
+
75
+ raise ValidateError, "io should be responsible to read and eof" unless
76
+ @io.respond_to?(:read) && @io.respond_to?(:eof?)
77
+
78
+ unless bytes_to_read.nil?
79
+ return ::String.new :encoding => ::Encoding::BINARY if bytes_to_read.zero?
80
+ return nil if eof?
81
+
82
+ append_io_data @io.read(@source_buffer_length) while @buffer.bytesize < bytes_to_read && !@io.eof?
83
+ flush_io_data if @buffer.bytesize < bytes_to_read
84
+
85
+ return read_bytes_from_buffer bytes_to_read, out_buffer
86
+ end
87
+
88
+ append_io_data @io.read(@source_buffer_length) until @io.eof?
89
+ flush_io_data
90
+
91
+ read_buffer out_buffer
92
+ end
93
+
94
+ # Resets stream.
95
+ def rewind
96
+ raw_wrapper :close
97
+
98
+ reset_io_remainder
99
+ reset_need_to_flush
100
+
101
+ super
102
+ end
103
+
104
+ # Closes stream.
105
+ def close
106
+ raw_wrapper :close
107
+
108
+ super
109
+ end
110
+
111
+ # Returns whether we are at the end of stream.
112
+ def eof?
113
+ raise ValidateError, "io should be responsible to eof" unless @io.respond_to? :eof?
114
+
115
+ empty? && @io.eof?
116
+ end
117
+
118
+ # -- asynchronous --
119
+
120
+ # Reads +bytes_to_read+ bytes from stream.
121
+ # If +out_buffer+ is defined than it will be used as output destination.
122
+ # Raises +::EOFError+ when no data available.
123
+ def readpartial(bytes_to_read, out_buffer = nil)
124
+ raise ValidateError, "io should be responsible to readpartial" unless @io.respond_to? :readpartial
125
+
126
+ read_more_nonblock(bytes_to_read, out_buffer) { @io.readpartial @source_buffer_length }
127
+ end
128
+
129
+ # Reads +bytes_to_read+ bytes from stream.
130
+ # If +out_buffer+ is defined than it will be used as output destination.
131
+ # +options+ will be passed to native stream.
132
+ def read_nonblock(bytes_to_read, out_buffer = nil, *options)
133
+ raise ValidateError, "io should be responsible to read nonblock" unless @io.respond_to? :read_nonblock
134
+
135
+ read_more_nonblock(bytes_to_read, out_buffer) { @io.read_nonblock(@source_buffer_length, *options) }
136
+ end
137
+
138
+ # Reads +bytes_to_read+ bytes from stream.
139
+ # If +out_buffer+ is defined than it will be used as output destination.
140
+ protected def read_more_nonblock(bytes_to_read, out_buffer, &_block)
141
+ Validation.validate_not_negative_integer bytes_to_read
142
+ Validation.validate_string out_buffer unless out_buffer.nil?
143
+
144
+ return ::String.new :encoding => ::Encoding::BINARY if bytes_to_read.zero?
145
+
146
+ io_provided_eof_error = false
147
+
148
+ if @buffer.bytesize < bytes_to_read
149
+ begin
150
+ append_io_data yield
151
+ rescue ::EOFError
152
+ io_provided_eof_error = true
153
+ end
154
+ end
155
+
156
+ flush_io_data if @buffer.bytesize < bytes_to_read
157
+ raise ::EOFError if empty? && io_provided_eof_error
158
+
159
+ read_bytes_from_buffer bytes_to_read, out_buffer
160
+ end
161
+
162
+ # -- common --
163
+
164
+ # Appends +io_data+ from native stream to internal storage.
165
+ protected def append_io_data(io_data)
166
+ io_portion = @io_remainder + io_data
167
+ bytes_read = raw_wrapper :read, io_portion
168
+ @io_remainder = io_portion.byteslice bytes_read, io_portion.bytesize - bytes_read
169
+
170
+ # Even empty io data may require flush.
171
+ @need_to_flush = true
172
+ end
173
+
174
+ # Triggers flush method for native stream.
175
+ protected def flush_io_data
176
+ raw_wrapper :flush
177
+
178
+ @need_to_flush = false
179
+ end
180
+
181
+ # Returns whether stream is empty.
182
+ protected def empty?
183
+ !@need_to_flush && @buffer.bytesize.zero?
184
+ end
185
+
186
+ # Reads +bytes_to_read+ bytes from buffer.
187
+ # If +out_buffer+ is defined than it will be used as output destination.
188
+ protected def read_bytes_from_buffer(bytes_to_read, out_buffer)
189
+ bytes_read = [@buffer.bytesize, bytes_to_read].min
190
+
191
+ # Result uses buffer binary encoding.
192
+ result = @buffer.byteslice 0, bytes_read
193
+ @buffer = @buffer.byteslice bytes_read, @buffer.bytesize - bytes_read
194
+ @pos += bytes_read
195
+
196
+ result = out_buffer.replace result unless out_buffer.nil?
197
+ result
198
+ end
199
+
200
+ # Reads data from buffer.
201
+ # If +out_buffer+ is defined than it will be used as output destination.
202
+ protected def read_buffer(out_buffer)
203
+ result = @buffer
204
+ reset_buffer
205
+ @pos += result.bytesize
206
+
207
+ result.force_encoding @external_encoding unless @external_encoding.nil?
208
+ result = transcode_to_internal result
209
+
210
+ result = out_buffer.replace result unless out_buffer.nil?
211
+ result
212
+ end
213
+
214
+ # Transcodes +data+ to internal encoding.
215
+ protected def transcode_to_internal(data)
216
+ data = data.encode @internal_encoding, **@transcode_options unless @internal_encoding.nil?
217
+ data
218
+ end
219
+
220
+ # Transcodes +data+ to external encoding.
221
+ # We should be able to return data back to buffer.
222
+ # We won't use any transcode options because transcoded data should be backward compatible.
223
+ protected def transcode_to_external(data)
224
+ data = data.encode @external_encoding unless @external_encoding.nil?
225
+ data
226
+ end
227
+
228
+ # Wraps +method_name+ for raw stream.
229
+ protected def raw_wrapper(method_name, *args)
230
+ @raw_stream.send(method_name, *args) { |portion| @buffer << portion }
231
+ end
232
+ end
233
+ end
234
+ end