adsp 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.
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