Class: IO::Stream::Generic

Inherits:
Object
  • Object
show all
Defined in:
lib/io/stream/generic.rb

Direct Known Subclasses

Buffered

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(block_size: BLOCK_SIZE, maximum_read_size: MAXIMUM_READ_SIZE) ⇒ Generic

Returns a new instance of Generic.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/io/stream/generic.rb', line 21

def initialize(block_size: BLOCK_SIZE, maximum_read_size: MAXIMUM_READ_SIZE)
	@eof = false
	
	@writing = ::Thread::Mutex.new
	
	@block_size = block_size
	@maximum_read_size = maximum_read_size
	
	@read_buffer = StringBuffer.new
	@write_buffer = StringBuffer.new
	@drain_buffer = StringBuffer.new
	
	# Used as destination buffer for underlying reads.
	@input_buffer = StringBuffer.new
end

Instance Attribute Details

#block_sizeObject

Returns the value of attribute block_size.



37
38
39
# File 'lib/io/stream/generic.rb', line 37

def block_size
  @block_size
end

Instance Method Details

#<<(string) ⇒ Object

Writes ‘string` to the stream and returns self.



163
164
165
166
167
# File 'lib/io/stream/generic.rb', line 163

def <<(string)
	write(string)
	
	return self
end

#closeObject

Best effort to flush any unwritten data, and then close the underling IO.



189
190
191
192
193
194
195
196
197
198
199
# File 'lib/io/stream/generic.rb', line 189

def close
	return if closed?
	
	begin
		flush
	rescue
		# We really can't do anything here unless we want #close to raise exceptions.
	ensure
		sysclose
	end
end

#close_readObject



181
182
# File 'lib/io/stream/generic.rb', line 181

def close_read
end

#close_writeObject



184
185
186
# File 'lib/io/stream/generic.rb', line 184

def close_write
	flush
end

#closed?Boolean

Returns:

  • (Boolean)


177
178
179
# File 'lib/io/stream/generic.rb', line 177

def closed?
	false
end

#eof!Object

Raises:

  • (EOFError)


215
216
217
218
219
220
# File 'lib/io/stream/generic.rb', line 215

def eof!
	@read_buffer.clear
	@eof = true
	
	raise EOFError
end

#eof?Boolean

Determins if the stream has consumed all available data. May block if the stream is not readable. See #readable? for a non-blocking alternative.

Returns:

  • (Boolean)


205
206
207
208
209
210
211
212
213
# File 'lib/io/stream/generic.rb', line 205

def eof?
	if !@read_buffer.empty?
		return false
	elsif @eof
		return true
	else
		return !self.fill_read_buffer
	end
end

#flushObject

Flushes buffered data to the stream.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/io/stream/generic.rb', line 132

def flush
	return if @write_buffer.empty?
	
	@writing.synchronize do
		# Flip the write buffer and drain buffer:
		@write_buffer, @drain_buffer = @drain_buffer, @write_buffer
		
		begin
			syswrite(@drain_buffer)
		ensure
			# If the write operation fails, we still need to clear this buffer, and the data is essentially lost.
			@drain_buffer.clear
		end
	end
end

#gets(separator = $/, **options) ⇒ Object



127
128
129
# File 'lib/io/stream/generic.rb', line 127

def gets(separator = $/, **options)
	read_until(separator, **options)
end

#peek(size = nil) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/io/stream/generic.rb', line 110

def peek(size = nil)
	if size
		until @eof or @read_buffer.bytesize >= size
			# Compute the amount of data we need to read from the underlying stream:
			read_size = size - @read_buffer.bytesize
			
			# Don't read less than @block_size to avoid lots of small reads:
			fill_read_buffer(read_size > @block_size ? read_size : @block_size)
		end
		return @read_buffer[..([size, @read_buffer.size].min - 1)]
	end
	until (block_given? && yield(@read_buffer)) or @eof
		fill_read_buffer
	end
	return @read_buffer
end

#puts(*arguments, separator: $/) ⇒ Object



169
170
171
172
173
174
175
# File 'lib/io/stream/generic.rb', line 169

def puts(*arguments, separator: $/)
	arguments.each do |argument|
		@write_buffer << argument << separator
	end
	
	flush
end

#read(size = nil) ⇒ Object

Reads ‘size` bytes from the stream. If size is not specified, read until end of file.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/io/stream/generic.rb', line 40

def read(size = nil)
	return String.new(encoding: Encoding::BINARY) if size == 0
	
	if size
		until @eof or @read_buffer.bytesize >= size
			# Compute the amount of data we need to read from the underlying stream:
			read_size = size - @read_buffer.bytesize
			
			# Don't read less than @block_size to avoid lots of small reads:
			fill_read_buffer(read_size > @block_size ? read_size : @block_size)
		end
	else
		until @eof
			fill_read_buffer
		end
	end
	
	return consume_read_buffer(size)
end

#read_exactly(size, exception: EOFError) ⇒ Object

Raises:

  • (exception)


71
72
73
74
75
76
77
78
79
80
81
# File 'lib/io/stream/generic.rb', line 71

def read_exactly(size, exception: EOFError)
	if buffer = read(size)
		if buffer.bytesize != size
			raise exception, "could not read enough data"
		end
		
		return buffer
	end
	
	raise exception, "encountered eof while reading data"
end

#read_partial(size = nil) ⇒ Object

Read at most ‘size` bytes from the stream. Will avoid reading from the underlying stream if possible.



61
62
63
64
65
66
67
68
69
# File 'lib/io/stream/generic.rb', line 61

def read_partial(size = nil)
	return String.new(encoding: Encoding::BINARY) if size == 0

	if !@eof and @read_buffer.empty?
		fill_read_buffer
	end
	
	return consume_read_buffer(size)
end

#read_until(pattern, offset = 0, chomp: true) ⇒ String

Efficiently read data from the stream until encountering pattern.

Parameters:

  • pattern (String)

    The pattern to match.

Returns:

  • (String)

    The contents of the stream up until the pattern, which is consumed but not returned.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/io/stream/generic.rb', line 91

def read_until(pattern, offset = 0, chomp: true)
	# We don't want to split on the pattern, so we subtract the size of the pattern.
	split_offset = pattern.bytesize - 1
	
	until index = @read_buffer.index(pattern, offset)
		offset = @read_buffer.bytesize - split_offset
		
		offset = 0 if offset < 0
		
		return unless fill_read_buffer
	end
	
	@read_buffer.freeze
	matched = @read_buffer.byteslice(0, index+(chomp ? 0 : pattern.bytesize))
	@read_buffer = @read_buffer.byteslice(index+pattern.bytesize, @read_buffer.bytesize)
	
	return matched
end

#readable?Boolean

Whether there is a chance that a read operation will succeed or not.

Returns:

  • (Boolean)


224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/io/stream/generic.rb', line 224

def readable?
	# If we are at the end of the file, we can't read any more data:
	if @eof
		return false
	end
	
	# If the read buffer is not empty, we can read more data:
	if !@read_buffer.empty?
		return true
	end
	
	# If the underlying stream is readable, we can read more data:
	return !closed?
end

#readpartial(size = nil) ⇒ Object

This is a compatibility shim for existing code that uses ‘readpartial`.



84
85
86
# File 'lib/io/stream/generic.rb', line 84

def readpartial(size = nil)
	read_partial(size) or raise EOFError, "Encountered eof while reading data!"
end

#write(string) ⇒ Object

Writes ‘string` to the buffer. When the buffer is full or #sync is true the buffer is flushed to the underlying `io`.

Parameters:

  • string

    the string to write to the buffer.

Returns:

  • the number of bytes appended to the buffer.



152
153
154
155
156
157
158
159
160
# File 'lib/io/stream/generic.rb', line 152

def write(string)
	@write_buffer << string
	
	if @write_buffer.bytesize >= @block_size
		flush
	end
	
	return string.bytesize
end