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.



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

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.



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

def block_size
  @block_size
end

Instance Method Details

#<<(string) ⇒ Object

Writes ‘string` to the stream and returns self.



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

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

#closeObject

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



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

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



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

def close_read
end

#close_writeObject



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

def close_write
	flush
end

#closed?Boolean

Returns:

  • (Boolean)


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

def closed?
	false
end

#eof!Object

Raises:

  • (EOFError)


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

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)


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

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.



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

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



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

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

#peek(size = nil) ⇒ Object



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

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



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

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.



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

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)


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

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.



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

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.



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

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)


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

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



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

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.



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

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