diff --git a/nsv/reader.py b/nsv/reader.py index 76e0345..6f8d0d8 100644 --- a/nsv/reader.py +++ b/nsv/reader.py @@ -1,23 +1,26 @@ class Reader: def __init__(self, file_obj): self._file_obj = file_obj + self._line_parts = [] + self._row_buffer = [] def __iter__(self): return self def __next__(self): - acc = [] for line in self._file_obj: + if line[-1] != '\n': # missing newline = EOF mid-line + self._line_parts.append(line) + continue + if self._line_parts: + self._line_parts.append(line) + line = ''.join(self._line_parts) + self._line_parts = [] if line == '\n': - return acc - if line[-1] == '\n': # so as not to chop if missing newline at EOF - line = line[:-1] - acc.append(Reader.unescape(line)) # bruh - # at the end of the file - if acc: - return acc - else: # an empty row would self-report in the cycle body - raise StopIteration + row, self._row_buffer = self._row_buffer, [] + return row + self._row_buffer.append(Reader.unescape(line[:-1])) # bruh + raise StopIteration @staticmethod def unescape(s: str) -> str: diff --git a/tests/test_incremental.py b/tests/test_incremental.py index 8ee769d..ed3d9ab 100644 --- a/tests/test_incremental.py +++ b/tests/test_incremental.py @@ -1,4 +1,5 @@ import unittest +import io import os import tempfile import nsv @@ -17,13 +18,43 @@ def test_incremental_reading(self): second = next(reader) self.assertEqual(second, ["d", "e", "f"]) - # third = next(reader) - # self.assertEqual(third, ["last1", "last2"]) - # Should be at end of the file with self.assertRaises(StopIteration): next(reader) + def test_incomplete_trailing_row_buffered(self): + """A resumable Reader buffers an incomplete trailing row instead of emitting it.""" + reader = nsv.Reader(io.StringIO('a\nb\n\nc\nd')) + self.assertEqual(next(reader), ['a', 'b']) + with self.assertRaises(StopIteration): + next(reader) + + reader = nsv.Reader(io.StringIO('a\nb\n\nc\nd\n')) + self.assertEqual(next(reader), ['a', 'b']) + with self.assertRaises(StopIteration): + next(reader) + + def test_reading_resumes_after_eof(self): + """Reading continues across EOF once more data is appended (tailing).""" + with tempfile.TemporaryDirectory() as output_dir: + file_path = os.path.join(output_dir, 'tail.nsv') + with open(file_path, 'w') as f: + f.write('a\nb') + f.flush() + + with open(file_path, 'r') as rf: + reader = nsv.Reader(rf) + with self.assertRaises(StopIteration): + next(reader) + + f.write('c\nd\n\n') + f.flush() + self.assertEqual(next(reader), ['a', 'bc', 'd']) + + def test_load_emits_incomplete_trailing_row(self): + """Non-resumable loads keeps emitting the incomplete tail.""" + self.assertEqual(nsv.loads('a\nb\n\nc\nd'), [['a', 'b'], ['c', 'd']]) + def test_incremental_writing(self): """Test writing elements incrementally.""" data = [["field1", "field2"], ["value1", "value2"], ["last1", "last2"]]