lib/std: rewrite `Path::rmdir`, `Path::files` and `Path::delete` to handle errors
[nit.git] / lib / standard / stream.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # This file is free software, which comes along with NIT. This software is
4 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
5 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
7 # is kept unaltered, and a notification of the changes is added.
8 # You are allowed to redistribute it and sell it, alone or is a part of
9 # another product.
10
11 # Input and output streams of characters
12 module stream
13
14 intrude import text::ropes
15 import error
16 intrude import bytes
17
18 in "C" `{
19 #include <unistd.h>
20 #include <string.h>
21 #include <signal.h>
22 `}
23
24 # Any kind of error that could be produced by an operation on Streams
25 class IOError
26 super Error
27 end
28
29 # Any kind of stream to read/write/both to or from a source
30 abstract class Stream
31 # Error produced by the file stream
32 #
33 # var ifs = new FileReader.open("donotmakethisfile.binx")
34 # ifs.read_all
35 # ifs.close
36 # assert ifs.last_error != null
37 var last_error: nullable IOError = null
38
39 # close the stream
40 fun close is abstract
41 end
42
43 # A `Stream` that can be read from
44 abstract class Reader
45 super Stream
46 # Reads a character. Returns `null` on EOF or timeout
47 fun read_char: nullable Char is abstract
48
49 # Reads a byte. Returns `null` on EOF or timeout
50 fun read_byte: nullable Byte is abstract
51
52 # Reads a String of at most `i` length
53 fun read(i: Int): String do return read_bytes(i).to_s
54
55 # Read at most i bytes
56 fun read_bytes(i: Int): Bytes
57 do
58 if last_error != null then return new Bytes.empty
59 var s = new NativeString(i)
60 var buf = new Bytes(s, 0, 0)
61 while i > 0 and not eof do
62 var c = read_byte
63 if c != null then
64 buf.add c
65 i -= 1
66 end
67 end
68 return buf
69 end
70
71 # Read a string until the end of the line.
72 #
73 # The line terminator '\n' and '\r\n', if any, is removed in each line.
74 #
75 # ~~~
76 # var txt = "Hello\n\nWorld\n"
77 # var i = new StringReader(txt)
78 # assert i.read_line == "Hello"
79 # assert i.read_line == ""
80 # assert i.read_line == "World"
81 # assert i.eof
82 # ~~~
83 #
84 # Only LINE FEED (`\n`), CARRIAGE RETURN & LINE FEED (`\r\n`), and
85 # the end or file (EOF) is considered to delimit the end of lines.
86 # CARRIAGE RETURN (`\r`) alone is not used for the end of line.
87 #
88 # ~~~
89 # var txt2 = "Hello\r\n\n\rWorld"
90 # var i2 = new StringReader(txt2)
91 # assert i2.read_line == "Hello"
92 # assert i2.read_line == ""
93 # assert i2.read_line == "\rWorld"
94 # assert i2.eof
95 # ~~~
96 #
97 # NOTE: Use `append_line_to` if the line terminator needs to be preserved.
98 fun read_line: String
99 do
100 if last_error != null then return ""
101 if eof then return ""
102 var s = new FlatBuffer
103 append_line_to(s)
104 return s.to_s.chomp
105 end
106
107 # Read all the lines until the eof.
108 #
109 # The line terminator '\n' and `\r\n` is removed in each line,
110 #
111 # ~~~
112 # var txt = "Hello\n\nWorld\n"
113 # var i = new StringReader(txt)
114 # assert i.read_lines == ["Hello", "", "World"]
115 # ~~~
116 #
117 # This method is more efficient that splitting
118 # the result of `read_all`.
119 #
120 # NOTE: SEE `read_line` for details.
121 fun read_lines: Array[String]
122 do
123 var res = new Array[String]
124 while not eof do
125 res.add read_line
126 end
127 return res
128 end
129
130 # Return an iterator that read each line.
131 #
132 # The line terminator '\n' and `\r\n` is removed in each line,
133 # The line are read with `read_line`. See this method for details.
134 #
135 # ~~~
136 # var txt = "Hello\n\nWorld\n"
137 # var i = new StringReader(txt)
138 # assert i.each_line.to_a == ["Hello", "", "World"]
139 # ~~~
140 #
141 # Unlike `read_lines` that read all lines at the call, `each_line` is lazy.
142 # Therefore, the stream should no be closed until the end of the stream.
143 #
144 # ~~~
145 # i = new StringReader(txt)
146 # var el = i.each_line
147 #
148 # assert el.item == "Hello"
149 # el.next
150 # assert el.item == ""
151 # el.next
152 #
153 # i.close
154 #
155 # assert not el.is_ok
156 # # closed before "world" is read
157 # ~~~
158 fun each_line: LineIterator do return new LineIterator(self)
159
160 # Read all the stream until the eof.
161 #
162 # The content of the file is returned as a String.
163 #
164 # ~~~
165 # var txt = "Hello\n\nWorld\n"
166 # var i = new StringReader(txt)
167 # assert i.read_all == txt
168 # ~~~
169 fun read_all: String do return read_all_bytes.to_s
170
171 # Read all the stream until the eof.
172 #
173 # The content of the file is returned verbatim.
174 fun read_all_bytes: Bytes
175 do
176 if last_error != null then return new Bytes.empty
177 var s = new Bytes.empty
178 while not eof do
179 var c = read_byte
180 if c != null then s.add(c)
181 end
182 return s
183 end
184
185 # Read a string until the end of the line and append it to `s`.
186 #
187 # Unlike `read_line` and other related methods,
188 # the line terminator '\n', if any, is preserved in each line.
189 # Use the method `Text::chomp` to safely remove it.
190 #
191 # ~~~
192 # var txt = "Hello\n\nWorld\n"
193 # var i = new StringReader(txt)
194 # var b = new FlatBuffer
195 # i.append_line_to(b)
196 # assert b == "Hello\n"
197 # i.append_line_to(b)
198 # assert b == "Hello\n\n"
199 # i.append_line_to(b)
200 # assert b == txt
201 # assert i.eof
202 # ~~~
203 #
204 # If `\n` is not present at the end of the result, it means that
205 # a non-eol terminated last line was returned.
206 #
207 # ~~~
208 # var i2 = new StringReader("hello")
209 # assert not i2.eof
210 # var b2 = new FlatBuffer
211 # i2.append_line_to(b2)
212 # assert b2 == "hello"
213 # assert i2.eof
214 # ~~~
215 #
216 # NOTE: The single character LINE FEED (`\n`) delimits the end of lines.
217 # Therefore CARRIAGE RETURN & LINE FEED (`\r\n`) is also recognized.
218 fun append_line_to(s: Buffer)
219 do
220 if last_error != null then return
221 loop
222 var x = read_char
223 if x == null then
224 if eof then return
225 else
226 s.chars.push(x)
227 if x == '\n' then return
228 end
229 end
230 end
231
232 # Is there something to read.
233 # This function returns 'false' if there is something to read.
234 fun eof: Bool is abstract
235
236 # Read the next sequence of non whitespace characters.
237 #
238 # Leading whitespace characters are skipped.
239 # The first whitespace character that follows the result is consumed.
240 #
241 # An empty string is returned if the end of the file or an error is encounter.
242 #
243 # ~~~
244 # var w = new StringReader(" Hello, \n\t World!")
245 # assert w.read_word == "Hello,"
246 # assert w.read_char == '\n'.ascii
247 # assert w.read_word == "World!"
248 # assert w.read_word == ""
249 # ~~~
250 #
251 # `Char::is_whitespace` determines what is a whitespace.
252 fun read_word: String
253 do
254 var buf = new FlatBuffer
255 var c = read_nonwhitespace
256 if c != null then
257 buf.add(c)
258 while not eof do
259 c = read_char
260 if c == null then break
261 if c.is_whitespace then break
262 buf.add(c)
263 end
264 end
265 var res = buf.to_s
266 return res
267 end
268
269 # Skip whitespace characters (if any) then return the following non-whitespace character.
270 #
271 # Returns the code point of the character.
272 # Returns `null` on end of file or error.
273 #
274 # In fact, this method works like `read_char` except it skips whitespace.
275 #
276 # ~~~
277 # var w = new StringReader(" \nab\tc")
278 # assert w.read_nonwhitespace == 'a'
279 # assert w.read_nonwhitespace == 'b'
280 # assert w.read_nonwhitespace == 'c'
281 # assert w.read_nonwhitespace == null
282 # ~~~
283 #
284 # `Char::is_whitespace` determines what is a whitespace.
285 fun read_nonwhitespace: nullable Char
286 do
287 var c: nullable Char = null
288 while not eof do
289 c = read_char
290 if c == null or not c.is_whitespace then break
291 end
292 return c
293 end
294 end
295
296 # Iterator returned by `Reader::each_line`.
297 # See the aforementioned method for details.
298 class LineIterator
299 super Iterator[String]
300
301 # The original stream
302 var stream: Reader
303
304 redef fun is_ok
305 do
306 var res = not stream.eof
307 if not res and close_on_finish then stream.close
308 return res
309 end
310
311 redef fun item
312 do
313 var line = self.line
314 if line == null then
315 line = stream.read_line
316 end
317 self.line = line
318 return line
319 end
320
321 # The last line read (cache)
322 private var line: nullable String = null
323
324 redef fun next
325 do
326 # force the read
327 if line == null then item
328 # drop the line
329 line = null
330 end
331
332 # Close the stream when the stream is at the EOF.
333 #
334 # Default is false.
335 var close_on_finish = false is writable
336
337 redef fun finish
338 do
339 if close_on_finish then stream.close
340 end
341 end
342
343 # `Reader` capable of declaring if readable without blocking
344 abstract class PollableReader
345 super Reader
346
347 # Is there something to read? (without blocking)
348 fun poll_in: Bool is abstract
349
350 end
351
352 # A `Stream` that can be written to
353 abstract class Writer
354 super Stream
355
356 # Writes bytes from `s`
357 fun write_bytes(s: Bytes) is abstract
358
359 # write a string
360 fun write(s: Text) is abstract
361
362 # Write a single byte
363 fun write_byte(value: Byte) is abstract
364
365 # Can the stream be used to write
366 fun is_writable: Bool is abstract
367 end
368
369 # Things that can be efficienlty written to a `Writer`
370 #
371 # The point of this interface is to allow the instance to be efficiently
372 # written into a `Writer`.
373 #
374 # Ready-to-save documents usually provide this interface.
375 interface Writable
376 # Write itself to a `stream`
377 # The specific logic it let to the concrete subclasses
378 fun write_to(stream: Writer) is abstract
379
380 # Like `write_to` but return a new String (may be quite large)
381 #
382 # This funtionality is anectodical, since the point
383 # of streamable object to to be efficienlty written to a
384 # stream without having to allocate and concatenate strings
385 fun write_to_string: String
386 do
387 var stream = new StringWriter
388 write_to(stream)
389 return stream.to_s
390 end
391 end
392
393 redef class Text
394 super Writable
395 redef fun write_to(stream) do stream.write(self)
396 end
397
398 # Input streams with a buffered input for efficiency purposes
399 abstract class BufferedReader
400 super Reader
401 redef fun read_char
402 do
403 if last_error != null then return null
404 if eof then
405 last_error = new IOError("Stream has reached eof")
406 return null
407 end
408 # TODO: Fix when supporting UTF-8
409 var c = _buffer[_buffer_pos].to_i.ascii
410 _buffer_pos += 1
411 return c
412 end
413
414 redef fun read_byte
415 do
416 if last_error != null then return null
417 if eof then
418 last_error = new IOError("Stream has reached eof")
419 return null
420 end
421 var c = _buffer[_buffer_pos]
422 _buffer_pos += 1
423 return c
424 end
425
426 fun buffer_reset do
427 _buffer_length = 0
428 _buffer_pos = 0
429 end
430
431 # Peeks up to `n` bytes in the buffer
432 #
433 # The operation does not consume the buffer
434 #
435 # ~~~nitish
436 # var x = new FileReader.open("File.txt")
437 # assert x.peek(5) == x.read(5)
438 # ~~~
439 fun peek(i: Int): Bytes do
440 if eof then return new Bytes.empty
441 var remsp = _buffer_length - _buffer_pos
442 if i <= remsp then
443 var bf = new Bytes.with_capacity(i)
444 bf.append_ns_from(_buffer, i, _buffer_pos)
445 return bf
446 end
447 var bf = new Bytes.with_capacity(i)
448 bf.append_ns_from(_buffer, remsp, _buffer_pos)
449 _buffer_pos = _buffer_length
450 read_intern(i - bf.length, bf)
451 remsp = _buffer_length - _buffer_pos
452 var full_len = bf.length + remsp
453 if full_len > _buffer_capacity then
454 var c = _buffer_capacity
455 while c < full_len do c = c * 2 + 2
456 _buffer_capacity = c
457 end
458 var nns = new NativeString(_buffer_capacity)
459 bf.items.copy_to(nns, bf.length, 0, 0)
460 _buffer.copy_to(nns, remsp, _buffer_pos, bf.length)
461 _buffer = nns
462 _buffer_pos = 0
463 _buffer_length = full_len
464 return bf
465 end
466
467 redef fun read_bytes(i)
468 do
469 if last_error != null then return new Bytes.empty
470 var buf = new Bytes.with_capacity(i)
471 read_intern(i, buf)
472 return buf
473 end
474
475 # Fills `buf` with at most `i` bytes read from `self`
476 private fun read_intern(i: Int, buf: Bytes): Int do
477 if eof then return 0
478 var p = _buffer_pos
479 var bufsp = _buffer_length - p
480 if bufsp >= i then
481 _buffer_pos += i
482 buf.append_ns_from(_buffer, i, p)
483 return i
484 end
485 _buffer_pos = _buffer_length
486 var readln = _buffer_length - p
487 buf.append_ns_from(_buffer, readln, p)
488 var rd = read_intern(i - readln, buf)
489 return rd + readln
490 end
491
492 redef fun read_all_bytes
493 do
494 if last_error != null then return new Bytes.empty
495 var s = new Bytes.with_capacity(10)
496 while not eof do
497 var j = _buffer_pos
498 var k = _buffer_length
499 while j < k do
500 s.add(_buffer[j])
501 j += 1
502 end
503 _buffer_pos = j
504 fill_buffer
505 end
506 return s
507 end
508
509 redef fun append_line_to(s)
510 do
511 loop
512 # First phase: look for a '\n'
513 var i = _buffer_pos
514 while i < _buffer_length and _buffer[i] != 0xAu8 do
515 i += 1
516 end
517
518 var eol
519 if i < _buffer_length then
520 assert _buffer[i] == 0xAu8
521 i += 1
522 eol = true
523 else
524 eol = false
525 end
526
527 # if there is something to append
528 if i > _buffer_pos then
529 # Enlarge the string (if needed)
530 s.enlarge(s.length + i - _buffer_pos)
531
532 # Copy from the buffer to the string
533 var j = _buffer_pos
534 while j < i do
535 s.bytes.add(_buffer[j])
536 j += 1
537 end
538 _buffer_pos = i
539 else
540 assert end_reached
541 return
542 end
543
544 if eol then
545 # so \n is found
546 return
547 else
548 # so \n is not found
549 if end_reached then return
550 fill_buffer
551 end
552 end
553 end
554
555 redef fun eof
556 do
557 if _buffer_pos < _buffer_length then return false
558 if end_reached then return true
559 fill_buffer
560 return _buffer_pos >= _buffer_length and end_reached
561 end
562
563 # The buffer
564 private var buffer: NativeString = new NativeString(0)
565
566 # The current position in the buffer
567 private var buffer_pos = 0
568
569 # Length of the current buffer (i.e. nuber of bytes in the buffer)
570 private var buffer_length = 0
571
572 # Capacity of the buffer
573 private var buffer_capacity = 0
574
575 # Fill the buffer
576 protected fun fill_buffer is abstract
577
578 # Has the last fill_buffer reached the end
579 protected fun end_reached: Bool is abstract
580
581 # Allocate a `_buffer` for a given `capacity`.
582 protected fun prepare_buffer(capacity: Int)
583 do
584 _buffer = new NativeString(capacity)
585 _buffer_pos = 0 # need to read
586 _buffer_length = 0
587 _buffer_capacity = capacity
588 end
589 end
590
591 # A `Stream` that can be written to and read from
592 abstract class Duplex
593 super Reader
594 super Writer
595 end
596
597 # `Stream` that can be used to write to a `String`
598 #
599 # Mainly used for compatibility with Writer type and tests.
600 class StringWriter
601 super Writer
602
603 private var content = new Array[String]
604 redef fun to_s do return content.plain_to_s
605 redef fun is_writable do return not closed
606
607 redef fun write_bytes(b) do
608 content.add(b.to_s)
609 end
610
611 redef fun write(str)
612 do
613 assert not closed
614 content.add(str.to_s)
615 end
616
617 # Is the stream closed?
618 protected var closed = false
619
620 redef fun close do closed = true
621 end
622
623 # `Stream` used to read from a `String`
624 #
625 # Mainly used for compatibility with Reader type and tests.
626 class StringReader
627 super Reader
628
629 # The string to read from.
630 var source: String
631
632 # The current position in the string (bytewise).
633 private var cursor: Int = 0
634
635 redef fun read_char do
636 if cursor < source.length then
637 # Fix when supporting UTF-8
638 var c = source[cursor]
639 cursor += 1
640 return c
641 else
642 return null
643 end
644 end
645
646 redef fun read_byte do
647 if cursor < source.length then
648 var c = source.bytes[cursor]
649 cursor += 1
650 return c
651 else
652 return null
653 end
654 end
655
656 redef fun close do
657 source = ""
658 end
659
660 redef fun read_all_bytes do
661 var nslen = source.length - cursor
662 var nns = new NativeString(nslen)
663 source.copy_to_native(nns, nslen, cursor, 0)
664 return new Bytes(nns, nslen, nslen)
665 end
666
667 redef fun eof do return cursor >= source.length
668 end