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,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