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,219 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "English"
5
+
6
+ require_relative "../validation"
7
+
8
+ module ADSP
9
+ module Stream
10
+ # ADSP::Stream::ReaderHelpers module.
11
+ module ReaderHelpers
12
+ # Returns next byte.
13
+ def getbyte
14
+ read 1
15
+ end
16
+
17
+ # Yields each byte.
18
+ def each_byte(&block)
19
+ each_string method(:getbyte), &block
20
+ end
21
+
22
+ # Returns next byte.
23
+ # Raises +::EOFError+ when no data available.
24
+ def readbyte
25
+ readstring method(:getbyte)
26
+ end
27
+
28
+ # Pushes back +byte+.
29
+ def ungetbyte(byte)
30
+ Validation.validate_string byte
31
+
32
+ @buffer.prepend byte
33
+
34
+ nil
35
+ end
36
+
37
+ # -- char --
38
+
39
+ # Returns next char.
40
+ def getc
41
+ if @external_encoding.nil?
42
+ byte = getbyte
43
+ return nil if byte.nil?
44
+
45
+ return transcode_to_internal byte
46
+ end
47
+
48
+ char = ::String.new :encoding => ::Encoding::BINARY
49
+
50
+ # Read one byte until valid string will appear.
51
+ loop do
52
+ byte = getbyte
53
+ return nil if byte.nil?
54
+
55
+ char << byte
56
+
57
+ char.force_encoding @external_encoding
58
+ return transcode_to_internal char if char.valid_encoding?
59
+
60
+ char.force_encoding ::Encoding::BINARY
61
+ end
62
+ end
63
+
64
+ # Returns next char.
65
+ # Raises +::EOFError+ when no data available.
66
+ def readchar
67
+ readstring method(:getc)
68
+ end
69
+
70
+ # Yields each char.
71
+ def each_char(&block)
72
+ each_string method(:getc), &block
73
+ end
74
+
75
+ # Pushes back +char+.
76
+ def ungetc(char)
77
+ ungetstring char
78
+ end
79
+
80
+ # -- lines --
81
+
82
+ # Returns next line by +separator+.
83
+ # Line length is limited by +limit+.
84
+ def gets(separator = $OUTPUT_RECORD_SEPARATOR, limit = nil)
85
+ # Limit can be a first argument.
86
+ if separator.is_a? ::Numeric
87
+ limit = separator
88
+ separator = $OUTPUT_RECORD_SEPARATOR
89
+ end
90
+
91
+ line_ending =
92
+ if separator.nil?
93
+ nil
94
+ else
95
+ Validation.validate_string separator
96
+ ::String.new separator, :encoding => target_encoding
97
+ end
98
+
99
+ Validation.validate_positive_integer limit unless limit.nil?
100
+
101
+ line = ::String.new :encoding => target_encoding
102
+
103
+ loop do
104
+ char = getc
105
+
106
+ if char.nil?
107
+ return nil if line.empty?
108
+
109
+ break
110
+ end
111
+
112
+ line << char
113
+
114
+ break if
115
+ (!line_ending.nil? && line.end_with?(line_ending)) ||
116
+ (!limit.nil? && line.length >= limit)
117
+ end
118
+
119
+ @lineno += 1
120
+
121
+ line
122
+ end
123
+
124
+ # Returns next line.
125
+ # Raises +::EOFError+ when no data available.
126
+ def readline
127
+ readstring method(:gets)
128
+ end
129
+
130
+ # Returns all available lines.
131
+ def readlines
132
+ lines = []
133
+ each_line { |line| lines << line }
134
+
135
+ lines
136
+ end
137
+
138
+ # Yields each line.
139
+ def each_line(&block)
140
+ each_string method(:gets), &block
141
+ end
142
+
143
+ alias each each_line
144
+
145
+ # Pushes back +line+.
146
+ def ungetline(line)
147
+ ungetstring line
148
+
149
+ @lineno -= 1
150
+
151
+ nil
152
+ end
153
+
154
+ # -- common --
155
+
156
+ # Returns next string by +each_proc+.
157
+ # Raises +::EOFError+ when no data available.
158
+ protected def readstring(each_proc)
159
+ string = each_proc.call
160
+ raise ::EOFError if string.nil?
161
+
162
+ string
163
+ end
164
+
165
+ # Yields each string by +each_proc+.
166
+ protected def each_string(each_proc, &block)
167
+ return enum_for __method__, each_proc unless block.is_a? ::Proc
168
+
169
+ loop do
170
+ string = each_proc.call
171
+ break if string.nil?
172
+
173
+ yield string
174
+ end
175
+
176
+ nil
177
+ end
178
+
179
+ # Pushes back +string+.
180
+ protected def ungetstring(string)
181
+ Validation.validate_string string
182
+
183
+ string = ::String.new string, :encoding => @internal_encoding unless @internal_encoding.nil?
184
+ string = transcode_to_external string unless @external_encoding.nil?
185
+
186
+ string.force_encoding ::Encoding::BINARY
187
+ @buffer.prepend string
188
+
189
+ nil
190
+ end
191
+
192
+ # -- etc --
193
+
194
+ # Additional class methods for reader.
195
+ module ClassMethods
196
+ # Opens +file_path+ in binary mode, creates reader and yields it.
197
+ def open(file_path, *args, &block)
198
+ Validation.validate_string file_path
199
+ Validation.validate_proc block
200
+
201
+ ::File.open file_path, "rb" do |io|
202
+ reader = new io, *args
203
+
204
+ begin
205
+ yield reader
206
+ ensure
207
+ reader.close
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ # Extends target +klass+ with additional class methods.
214
+ def self.included(klass)
215
+ klass.extend ClassMethods
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,80 @@
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::Stat class.
9
+ class Stat
10
+ # Libraries like minitar tries to access stat to know whether stream is seekable.
11
+ # We need to mark stream as not directory, file, etc, because it is not seekable.
12
+
13
+ extend ::Forwardable
14
+
15
+ # List of methods returning false.
16
+ METHODS_RETURNING_FALSE = %i[
17
+ blockdev?
18
+ chardev?
19
+ directory?
20
+ executable?
21
+ executable_real?
22
+ file?
23
+ grpowned?
24
+ owned?
25
+ pipe?
26
+ setgid?
27
+ setuid?
28
+ socket?
29
+ sticky?
30
+ symlink?
31
+ zero?
32
+ ]
33
+ .freeze
34
+
35
+ # List of methods to be forwarded for native stream status info.
36
+ DELEGATES = %i[
37
+ <=>
38
+ atime
39
+ birthtime
40
+ blksize
41
+ blocks
42
+ ctime
43
+ dev
44
+ dev_major
45
+ dev_minor
46
+ ftype
47
+ gid
48
+ ino
49
+ inspect
50
+ mode
51
+ mtime
52
+ nlink
53
+ rdev
54
+ rdev_major
55
+ rdev_minor
56
+ readable?
57
+ readable_real?
58
+ size
59
+ size?
60
+ uid
61
+ world_readable?
62
+ world_writable?
63
+ writable?
64
+ writable_real?
65
+ ]
66
+ .freeze
67
+
68
+ # Initializes status info based on native stream +stat+.
69
+ def initialize(stat)
70
+ @stat = stat
71
+ end
72
+
73
+ METHODS_RETURNING_FALSE.each do |method_name|
74
+ define_method(method_name) { false }
75
+ end
76
+
77
+ def_delegators :@stat, *DELEGATES
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,206 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require_relative "abstract"
5
+ require_relative "raw/compressor"
6
+ require_relative "writer_helpers"
7
+
8
+ module ADSP
9
+ module Stream
10
+ # ADSP::Stream::Writer class.
11
+ class Writer < Abstract
12
+ include WriterHelpers
13
+
14
+ # Current raw stream class.
15
+ RawCompressor = Raw::Compressor
16
+
17
+ # Initializes stream using +destination_io+ native stream and +options+.
18
+ # Option: +:external_encoding+ encoding name for destination data.
19
+ # Option: +:internal_encoding+ encoding name for source data.
20
+ # Option: +:transcode_options+ transcode options for data.
21
+ def initialize(destination_io, options = {}, *args)
22
+ @options = options
23
+
24
+ super destination_io, *args
25
+ end
26
+
27
+ # Creates raw stream.
28
+ protected def create_raw_stream
29
+ self.class::RawCompressor.new @options
30
+ end
31
+
32
+ # -- synchronous --
33
+
34
+ # Writes +objects+ to stream.
35
+ def write(*objects)
36
+ validate_write
37
+
38
+ write_remaining_buffer
39
+
40
+ bytes_written = 0
41
+
42
+ objects.each do |object|
43
+ source = transcode object.to_s
44
+ bytes_written += raw_wrapper :write, source
45
+ end
46
+
47
+ @pos += bytes_written
48
+
49
+ bytes_written
50
+ end
51
+
52
+ # Flushes stream.
53
+ def flush
54
+ validate_write
55
+
56
+ finish :flush
57
+
58
+ @io.flush if @io.respond_to? :flush
59
+
60
+ self
61
+ end
62
+
63
+ # Resets stream.
64
+ def rewind
65
+ validate_write
66
+
67
+ finish :close
68
+
69
+ super
70
+ end
71
+
72
+ # Closes stream.
73
+ def close
74
+ validate_write
75
+
76
+ finish :close
77
+
78
+ super
79
+ end
80
+
81
+ # Finishes stream using +method_name+.
82
+ protected def finish(method_name)
83
+ write_remaining_buffer
84
+
85
+ raw_wrapper method_name
86
+ end
87
+
88
+ # Writes remaining buffer and resets it.
89
+ protected def write_remaining_buffer
90
+ return nil if @buffer.bytesize.zero?
91
+
92
+ @io.write @buffer
93
+
94
+ reset_buffer
95
+ end
96
+
97
+ # Wraps +method_name+ for raw stream.
98
+ protected def raw_wrapper(method_name, *args)
99
+ @raw_stream.send(method_name, *args) { |portion| @io.write portion }
100
+ end
101
+
102
+ # Validates native stream responsibility to +write+ method.
103
+ protected def validate_write
104
+ raise ValidateError, "io should be responsible to write" unless @io.respond_to? :write
105
+ end
106
+
107
+ # -- asynchronous --
108
+
109
+ # Writes +object+ nonblock.
110
+ # +options+ will be passed to native stream.
111
+ # Native stream +write_nonblock+ can raise +IO::WaitWritable+ error.
112
+ # After resolving this error user may provide same content again.
113
+ # It is not possible to revert accepted content after error.
114
+ # So we have to accept content after processing native stream +write_nonblock+.
115
+ # It means that first write nonblock won't call native stream +write_nonblock+.
116
+ def write_nonblock(object, *options)
117
+ validate_write_nonblock
118
+
119
+ return 0 unless write_remaining_buffer_nonblock(*options)
120
+
121
+ source = transcode object.to_s
122
+ bytes_written = raw_nonblock_wrapper :write, source
123
+ @pos += bytes_written
124
+
125
+ bytes_written
126
+ end
127
+
128
+ # Flushes stream nonblock.
129
+ # +options+ will be passed to native stream.
130
+ def flush_nonblock(*options)
131
+ validate_write_nonblock
132
+
133
+ return false unless finish_nonblock :flush, *options
134
+
135
+ @io.flush if @io.respond_to? :flush
136
+
137
+ true
138
+ end
139
+
140
+ # Resets stream nonblock.
141
+ # +options+ will be passed to native stream.
142
+ def rewind_nonblock(*options)
143
+ validate_write_nonblock
144
+
145
+ return false unless finish_nonblock :close, *options
146
+
147
+ method(:rewind).super_method.call
148
+
149
+ true
150
+ end
151
+
152
+ # Closes stream nonblock.
153
+ # +options+ will be passed to native stream.
154
+ def close_nonblock(*options)
155
+ validate_write_nonblock
156
+
157
+ return false unless finish_nonblock :close, *options
158
+
159
+ method(:close).super_method.call
160
+
161
+ true
162
+ end
163
+
164
+ # Finishes stream using +method_name+ nonblock.
165
+ # +options+ will be passed to native stream.
166
+ protected def finish_nonblock(method_name, *options)
167
+ return false unless write_remaining_buffer_nonblock(*options)
168
+
169
+ raw_nonblock_wrapper method_name
170
+
171
+ write_remaining_buffer_nonblock(*options)
172
+ end
173
+
174
+ # Writes remaining buffer nonblock.
175
+ # +options+ will be passed to native stream.
176
+ protected def write_remaining_buffer_nonblock(*options)
177
+ return true if @buffer.bytesize.zero?
178
+
179
+ bytes_written = @io.write_nonblock @buffer, *options
180
+ return false if bytes_written.zero?
181
+
182
+ @buffer = @buffer.byteslice bytes_written, @buffer.bytesize - bytes_written
183
+
184
+ @buffer.bytesize.zero?
185
+ end
186
+
187
+ # Wraps nonblock +method_name+ for raw stream.
188
+ protected def raw_nonblock_wrapper(method_name, *args)
189
+ @raw_stream.send(method_name, *args) { |portion| @buffer << portion }
190
+ end
191
+
192
+ # Validates native stream responsibility to +write_nonblock+ method.
193
+ protected def validate_write_nonblock
194
+ raise ValidateError, "io should be responsible to write nonblock" unless @io.respond_to? :write_nonblock
195
+ end
196
+
197
+ # -- common --
198
+
199
+ # Transcodes +data+ to external encoding.
200
+ protected def transcode(data)
201
+ data = data.encode @external_encoding, **@transcode_options unless @external_encoding.nil?
202
+ data
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,102 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require "English"
5
+
6
+ require_relative "../validation"
7
+
8
+ module ADSP
9
+ module Stream
10
+ # ADSP::Stream::WriterHelpers module.
11
+ module WriterHelpers
12
+ # Writes +object+ to stream.
13
+ def <<(object)
14
+ write object
15
+ end
16
+
17
+ # Writes +objects+ to stream.
18
+ # Uses +field_separator+ for each object.
19
+ # Uses +record_separator+ for group of objects.
20
+ def print(*objects, field_separator: $OUTPUT_FIELD_SEPARATOR, record_separator: $OUTPUT_RECORD_SEPARATOR)
21
+ objects.each do |object|
22
+ write object
23
+ write field_separator unless field_separator.nil?
24
+ end
25
+
26
+ write record_separator unless record_separator.nil?
27
+
28
+ nil
29
+ end
30
+
31
+ # Formats each argument and writes to stream.
32
+ def printf(*args)
33
+ write sprintf(*args)
34
+
35
+ nil
36
+ end
37
+
38
+ # Writes first char of +object+ to stream.
39
+ # Numeric object uses +encoding+ for providing first char.
40
+ def putc(object, encoding: ::Encoding::BINARY)
41
+ case object
42
+ when ::Numeric
43
+ write object.chr(encoding)
44
+ when ::String
45
+ write object[0]
46
+ else
47
+ raise ValidateError, "invalid object: \"#{object}\" for putc"
48
+ end
49
+
50
+ object
51
+ end
52
+
53
+ # Writes +objects+ to stream.
54
+ def puts(*objects)
55
+ objects.each do |object|
56
+ if object.is_a? ::Array
57
+ puts(*object)
58
+ next
59
+ end
60
+
61
+ source = object.to_s
62
+ newline = "\n".encode source.encoding
63
+
64
+ # Do not add newline if source ends with newline.
65
+ if source.end_with? newline
66
+ write source
67
+ else
68
+ write source + newline
69
+ end
70
+ end
71
+
72
+ nil
73
+ end
74
+
75
+ # -- etc --
76
+
77
+ # Additional class methods for writer.
78
+ module ClassMethods
79
+ # Opens +file_path+ in binary mode, creates writer and yields it.
80
+ def open(file_path, *args, &block)
81
+ Validation.validate_string file_path
82
+ Validation.validate_proc block
83
+
84
+ ::File.open file_path, "wb" do |io|
85
+ writer = new io, *args
86
+
87
+ begin
88
+ yield writer
89
+ ensure
90
+ writer.close
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ # Extends target +klass+ with additional class methods.
97
+ def self.included(klass)
98
+ klass.extend ClassMethods
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,58 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require_relative "error"
5
+ require_relative "option"
6
+ require_relative "validation"
7
+
8
+ module ADSP
9
+ # ADSP::String class.
10
+ class String
11
+ # Current option class.
12
+ Option = ADSP::Option
13
+
14
+ # Current buffer length names.
15
+ # It is a part of decompressor options.
16
+ BUFFER_LENGTH_NAMES = %i[destination_buffer_length].freeze
17
+
18
+ # Compresses +source+ string using +options+.
19
+ # Option: +:destination_buffer_length+ destination buffer length.
20
+ # Returns compressed string.
21
+ def self.compress(source, options = {})
22
+ Validation.validate_string source
23
+
24
+ options = self::Option.get_compressor_options options, BUFFER_LENGTH_NAMES
25
+
26
+ native_compress_string source, options
27
+ end
28
+
29
+ # :nocov:
30
+
31
+ # Internal method for compressing +source+ string using +options+.
32
+ def self.native_compress_string(source, options)
33
+ raise NotImplementedError
34
+ end
35
+
36
+ # :nocov:
37
+
38
+ # Decompresses +source+ string using +options+.
39
+ # Option: +:destination_buffer_length+ destination buffer length.
40
+ # Returns decompressed string.
41
+ def self.decompress(source, options = {})
42
+ Validation.validate_string source
43
+
44
+ options = self::Option.get_decompressor_options options, BUFFER_LENGTH_NAMES
45
+
46
+ native_decompress_string source, options
47
+ end
48
+
49
+ # :nocov:
50
+
51
+ # Internal method for decompressing +source+ string using +options+.
52
+ def self.native_decompress_string(source, options)
53
+ raise NotImplementedError
54
+ end
55
+
56
+ # :nocov:
57
+ end
58
+ end
@@ -0,0 +1,46 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require_relative "error"
5
+
6
+ module ADSP
7
+ # ADSP::Validation class.
8
+ class Validation
9
+ # Raises error when +value+ is not array.
10
+ def self.validate_array(value)
11
+ raise ValidateError, "invalid array" unless value.is_a? ::Array
12
+ end
13
+
14
+ # Raises error when +value+ is not hash.
15
+ def self.validate_hash(value)
16
+ raise ValidateError, "invalid hash" unless value.is_a? ::Hash
17
+ end
18
+
19
+ # Raises error when +value+ is not negative integer.
20
+ def self.validate_not_negative_integer(value)
21
+ raise ValidateError, "invalid not negative integer" unless value.is_a?(::Integer) && value >= 0
22
+ end
23
+
24
+ # Raises error when +value+ is not positive integer.
25
+ def self.validate_positive_integer(value)
26
+ raise ValidateError, "invalid positive integer" unless value.is_a?(::Integer) && value.positive?
27
+ end
28
+
29
+ # Raises error when +value+ is not proc.
30
+ def self.validate_proc(value)
31
+ unless value.is_a?(::Proc) || value.is_a?(::Method) || value.is_a?(::UnboundMethod)
32
+ raise ValidateError, "invalid proc"
33
+ end
34
+ end
35
+
36
+ # Raises error when +value+ is not string.
37
+ def self.validate_string(value)
38
+ raise ValidateError, "invalid string" unless value.is_a? ::String
39
+ end
40
+
41
+ # Raises error when +value+ is not symbol.
42
+ def self.validate_symbol(value)
43
+ raise ValidateError, "invalid symbol" unless value.is_a? ::Symbol
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,7 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ module ADSP
5
+ # Abstract data stream processor version.
6
+ VERSION = "1.0.0".freeze
7
+ end