# Read a string until the end of the line.
#
- # The line terminator '\n', if any, is preserved in each line.
- # Use the method `Text::chomp` to safely remove it.
+ # The line terminator '\n' and '\r\n', if any, is removed in each line.
#
# ~~~
# var txt = "Hello\n\nWorld\n"
# var i = new StringIStream(txt)
- # assert i.read_line == "Hello\n"
- # assert i.read_line == "\n"
- # assert i.read_line == "World\n"
+ # assert i.read_line == "Hello"
+ # assert i.read_line == ""
+ # assert i.read_line == "World"
# assert i.eof
# ~~~
#
- # If `\n` is not present at the end of the result, it means that
- # a non-eol terminated last line was returned.
+ # Only LINE FEED (`\n`), CARRIAGE RETURN & LINE FEED (`\r\n`), and
+ # the end or file (EOF) is considered to delimit the end of lines.
+ # CARRIAGE RETURN (`\r`) alone is not used for the end of line.
#
# ~~~
- # var i2 = new StringIStream("hello")
- # assert not i2.eof
- # assert i2.read_line == "hello"
+ # var txt2 = "Hello\r\n\n\rWorld"
+ # var i2 = new StringIStream(txt2)
+ # assert i2.read_line == "Hello"
+ # assert i2.read_line == ""
+ # assert i2.read_line == "\rWorld"
# assert i2.eof
# ~~~
#
- # NOTE: Only LINE FEED (`\n`) is considered to delimit the end of lines.
+ # NOTE: Use `append_line_to` if the line terminator needs to be preserved.
fun read_line: String
do
if last_error != null then return ""
- assert not eof
+ if eof then return ""
var s = new FlatBuffer
append_line_to(s)
- return s.to_s
+ return s.to_s.chomp
end
# Read all the lines until the eof.
#
- # The line terminator '\n' is removed in each line,
+ # The line terminator '\n' and `\r\n` is removed in each line,
#
# ~~~
# var txt = "Hello\n\nWorld\n"
# This method is more efficient that splitting
# the result of `read_all`.
#
- # NOTE: Only LINE FEED (`\n`) is considered to delimit the end of lines.
+ # NOTE: SEE `read_line` for details.
fun read_lines: Array[String]
do
var res = new Array[String]
while not eof do
- res.add read_line.chomp
+ res.add read_line
end
return res
end
+ # Return an iterator that read each line.
+ #
+ # The line terminator '\n' and `\r\n` is removed in each line,
+ # The line are read with `read_line`. See this method for details.
+ #
+ # ~~~
+ # var txt = "Hello\n\nWorld\n"
+ # var i = new StringIStream(txt)
+ # assert i.each_line.to_a == ["Hello", "", "World"]
+ # ~~~
+ #
+ # Unlike `read_lines` that read all lines at the call, `each_line` is lazy.
+ # Therefore, the stream should no be closed until the end of the stream.
+ #
+ # ~~~
+ # i = new StringIStream(txt)
+ # var el = i.each_line
+ #
+ # assert el.item == "Hello"
+ # el.next
+ # assert el.item == ""
+ # el.next
+ #
+ # i.close
+ #
+ # assert not el.is_ok
+ # # closed before "world" is read
+ # ~~~
+ fun each_line: LineIterator do return new LineIterator(self)
+
# Read all the stream until the eof.
+ #
+ # The content of the file is returned verbatim.
+ #
+ # ~~~
+ # var txt = "Hello\n\nWorld\n"
+ # var i = new StringIStream(txt)
+ # assert i.read_all == txt
+ # ~~~
fun read_all: String
do
if last_error != null then return ""
# Read a string until the end of the line and append it to `s`.
#
- # SEE: `read_line` for details.
+ # Unlike `read_line` and other related methods,
+ # the line terminator '\n', if any, is preserved in each line.
+ # Use the method `Text::chomp` to safely remove it.
+ #
+ # ~~~
+ # var txt = "Hello\n\nWorld\n"
+ # var i = new StringIStream(txt)
+ # var b = new FlatBuffer
+ # i.append_line_to(b)
+ # assert b == "Hello\n"
+ # i.append_line_to(b)
+ # assert b == "Hello\n\n"
+ # i.append_line_to(b)
+ # assert b == txt
+ # assert i.eof
+ # ~~~
+ #
+ # If `\n` is not present at the end of the result, it means that
+ # a non-eol terminated last line was returned.
+ #
+ # ~~~
+ # var i2 = new StringIStream("hello")
+ # assert not i2.eof
+ # var b2 = new FlatBuffer
+ # i2.append_line_to(b2)
+ # assert b2 == "hello"
+ # assert i2.eof
+ # ~~~
+ #
+ # NOTE: The single character LINE FEED (`\n`) delimits the end of lines.
+ # Therefore CARRIAGE RETURN & LINE FEED (`\r\n`) is also recognized.
fun append_line_to(s: Buffer)
do
if last_error != null then return
fun eof: Bool is abstract
end
+# Iterator returned by `IStream::each_line`.
+# See the aforementioned method for details.
+class LineIterator
+ super Iterator[String]
+
+ # The original stream
+ var stream: IStream
+
+ redef fun is_ok
+ do
+ var res = not stream.eof
+ if not res and close_on_finish then stream.close
+ return res
+ end
+
+ redef fun item
+ do
+ var line = self.line
+ if line == null then
+ line = stream.read_line
+ end
+ self.line = line
+ return line
+ end
+
+ # The last line read (cache)
+ private var line: nullable String = null
+
+ redef fun next
+ do
+ # force the read
+ if line == null then item
+ # drop the line
+ line = null
+ end
+
+ # Close the stream when the stream is at the EOF.
+ #
+ # Default is false.
+ var close_on_finish = false is writable
+
+ redef fun finish
+ do
+ if close_on_finish then stream.close
+ end
+end
+
# IStream capable of declaring if readable without blocking
abstract class PollableIStream
super IStream
super IStream
redef fun read_char
do
- if last_error != null then return 0
- if eof then last_error = new IOError("Stream has reached eof")
- if _buffer_pos >= _buffer.length then
- fill_buffer
- end
- if _buffer_pos >= _buffer.length then
+ if last_error != null then return -1
+ if eof then
+ last_error = new IOError("Stream has reached eof")
return -1
end
var c = _buffer.chars[_buffer_pos]
if last_error != null then return ""
if _buffer.length == _buffer_pos then
if not eof then
- fill_buffer
return read(i)
end
return ""
var i = _buffer_pos
while i < _buffer.length and _buffer.chars[i] != '\n' do i += 1
+ var eol
+ if i < _buffer.length then
+ assert _buffer.chars[i] == '\n'
+ i += 1
+ eol = true
+ else
+ eol = false
+ end
+
# if there is something to append
if i > _buffer_pos then
# Enlarge the string (if needed)
s.add(_buffer.chars[j])
j += 1
end
+ _buffer_pos = i
+ else
+ assert end_reached
+ return
end
- if i < _buffer.length then
- # so \n is in _buffer[i]
- _buffer_pos = i + 1 # skip \n
+ if eol then
+ # so \n is found
return
else
# so \n is not found
- _buffer_pos = i
- if end_reached then
- return
- else
- fill_buffer
- end
+ if end_reached then return
+ fill_buffer
end
end
end
- redef fun eof do return _buffer_pos >= _buffer.length and end_reached
+ redef fun eof
+ do
+ if _buffer_pos < _buffer.length then return false
+ if end_reached then return true
+ fill_buffer
+ return _buffer_pos >= _buffer.length and end_reached
+ end
# The buffer
private var buffer: nullable FlatBuffer = null
source = ""
end
+ redef fun read_all do
+ var c = cursor
+ cursor = source.length
+ if c == 0 then return source
+ return source.substring_from(c)
+ end
+
redef fun eof do return cursor >= source.length
end