lib/file: add some doc and nitunits
[nit.git] / lib / standard / file.nit
index 102c8e3..8af3c70 100644 (file)
@@ -32,9 +32,9 @@ in "C Header" `{
        #include <errno.h>
 `}
 
-# File Abstract Stream
-abstract class FStream
-       super IOS
+# `Stream` used to interact with a File or FileDescriptor
+abstract class FileStream
+       super Stream
        # The path of the file.
        var path: nullable String = null
 
@@ -47,7 +47,22 @@ abstract class FStream
        # File descriptor of this file
        fun fd: Int do return _file.fileno
 
-       # Sets the buffering mode for the current FStream
+       redef fun close
+       do
+               if _file == null then return
+               if _file.address_is_null then
+                       if last_error != null then return
+                       last_error = new IOError("Cannot close unopened file")
+                       return
+               end
+               var i = _file.io_close
+               if i != 0 then
+                       last_error = new IOError("Close failed due to error {sys.errno.strerror}")
+               end
+               _file = null
+       end
+
+       # Sets the buffering mode for the current FileStream
        #
        # If the buf_size is <= 0, its value will be 512 by default
        #
@@ -58,20 +73,25 @@ abstract class FStream
        fun set_buffering_mode(buf_size, mode: Int) do
                if buf_size <= 0 then buf_size = 512
                if _file.set_buffering_type(buf_size, mode) != 0 then
-                       last_error = new IOError("Error while changing buffering type for FStream, returned error {sys.errno.strerror}")
+                       last_error = new IOError("Error while changing buffering type for FileStream, returned error {sys.errno.strerror}")
                end
        end
 end
 
-# File input stream
-class IFStream
-       super FStream
-       super BufferedIStream
-       super PollableIStream
+# `Stream` that can read from a File
+class FileReader
+       super FileStream
+       super BufferedReader
+       super PollableReader
        # Misc
 
        # Open the same file again.
        # The original path is reused, therefore the reopened file can be a different file.
+       #
+       #     var f = new FileReader.open("/etc/issue")
+       #     var l = f.read_line
+       #     f.reopen
+       #     assert l == f.read_line
        fun reopen
        do
                if not eof and not _file.address_is_null then close
@@ -89,8 +109,7 @@ class IFStream
 
        redef fun close
        do
-               if _file.address_is_null then return
-               var i = _file.io_close
+               super
                _buffer.clear
                end_reached = true
        end
@@ -110,6 +129,16 @@ class IFStream
        redef var end_reached: Bool = false
 
        # Open the file at `path` for reading.
+       #
+       #     var f = new FileReader.open("/etc/issue")
+       #     assert not f.end_reached
+       #     f.close
+       #
+       # In case of error, `last_error` is set
+       #
+       #     f = new FileReader.open("/fail/does not/exist")
+       #     assert f.end_reached
+       #     assert f.last_error != null
        init open(path: String)
        do
                self.path = path
@@ -121,6 +150,9 @@ class IFStream
                end
        end
 
+       # Creates a new File stream from a file descriptor
+       #
+       # This is a low-level method.
        init from_fd(fd: Int) do
                self.path = ""
                prepare_buffer(1)
@@ -132,10 +164,10 @@ class IFStream
        end
 end
 
-# File output stream
-class OFStream
-       super FStream
-       super OStream
+# `Stream` that can write to a File
+class FileWriter
+       super FileStream
+       super Writer
 
        redef fun write(s)
        do
@@ -149,21 +181,11 @@ class OFStream
                else
                        for i in s.substrings do write_native(i.to_cstring, i.length)
                end
-               _file.flush
        end
 
        redef fun close
        do
-               if _file.address_is_null then
-                       if last_error != null then return
-                       last_error = new IOError("Cannot close unopened write stream")
-                       _is_writable = false
-                       return
-               end
-               var i = _file.io_close
-               if i != 0 then
-                       last_error = new IOError("Close failed due to error {sys.errno.strerror}")
-               end
+               super
                _is_writable = false
        end
        redef var is_writable = false
@@ -232,8 +254,10 @@ private fun wipe_write: NativeString do return "w".to_cstring
 ###############################################################################
 
 # Standard input stream.
+#
+# The class of the default value of `sys.stdin`.
 class Stdin
-       super IFStream
+       super FileReader
 
        init do
                _file = new NativeFile.native_stdin
@@ -245,18 +269,23 @@ class Stdin
 end
 
 # Standard output stream.
+#
+# The class of the default value of `sys.stdout`.
 class Stdout
-       super OFStream
+       super FileWriter
        init do
                _file = new NativeFile.native_stdout
                path = "/dev/stdout"
                _is_writable = true
+               set_buffering_mode(256, sys.buffer_mode_line)
        end
 end
 
 # Standard error stream.
+#
+# The class of the default value of `sys.stderr`.
 class Stderr
-       super OFStream
+       super FileWriter
        init do
                _file = new NativeFile.native_stderr
                path = "/dev/stderr"
@@ -266,11 +295,11 @@ end
 
 ###############################################################################
 
-redef class Streamable
+redef class Writable
        # Like `write_to` but take care of creating the file
        fun write_to_file(filepath: String)
        do
-               var stream = new OFStream.open(filepath)
+               var stream = new FileWriter.open(filepath)
                write_to(stream)
                stream.close
        end
@@ -327,8 +356,6 @@ class Path
        end
 
        # Delete a file from the file system, return `true` on success
-       #
-       # Require: `exists`
        fun delete: Bool do return path.to_cstring.file_delete
 
        # Copy content of file at `path` to `dest`
@@ -351,21 +378,80 @@ class Path
        # Open this file for reading
        #
        # Require: `exists and not link_stat.is_dir`
-       fun open_ro: IFStream
+       fun open_ro: FileReader
        do
                # TODO manage streams error when they are merged
-               return new IFStream.open(path)
+               return new FileReader.open(path)
        end
 
        # Open this file for writing
        #
        # Require: `not exists or not stat.is_dir`
-       fun open_wo: OFStream
+       fun open_wo: FileWriter
        do
                # TODO manage streams error when they are merged
-               return new OFStream.open(path)
+               return new FileWriter.open(path)
        end
 
+       # Read all the content of the file
+       #
+       # ~~~
+       # var content = "/etc/issue".to_path.read_all
+       # print content
+       # ~~~
+       #
+       # See `Reader::read_all` for details.
+       fun read_all: String
+       do
+               var s = open_ro
+               var res = s.read_all
+               s.close
+               return res
+       end
+
+       # Read all the lines of the file
+       #
+       # ~~~
+       # var lines = "/etc/passwd".to_path.read_lines
+       #
+       # print "{lines.length} users"
+       #
+       # for l in lines do
+       #     var fields = l.split(":")
+       #     print "name={fields[0]} uid={fields[2]}"
+       # end
+       # ~~~
+       #
+       # See `Reader::read_lines` for details.
+       fun read_lines: Array[String]
+       do
+               var s = open_ro
+               var res = s.read_lines
+               s.close
+               return res
+       end
+
+       # Return an iterator on each line of the file
+       #
+       # ~~~
+       # for l in "/etc/passwd".to_path.each_line do
+       #     var fields = l.split(":")
+       #     print "name={fields[0]} uid={fields[2]}"
+       # end
+       # ~~~
+       #
+       # Note: the stream is automatically closed at the end of the file (see `LineIterator::close_on_finish`)
+       #
+       # See `Reader::each_line` for details.
+       fun each_line: LineIterator
+       do
+               var s = open_ro
+               var res = s.each_line
+               res.close_on_finish = true
+               return res
+       end
+
+
        # Lists the name of the files contained within the directory at `path`
        #
        # Require: `exists and is_dir`
@@ -613,11 +699,12 @@ redef class String
        end
 
        # Simplify a file path by remove useless ".", removing "//", and resolving ".."
-       # ".." are not resolved if they start the path
-       # starting "/" is not removed
-       # trainling "/" is removed
        #
-       # Note that the method only wonrk on the string:
+       # * ".." are not resolved if they start the path
+       # * starting "/" is not removed
+       # * trailing "/" is removed
+       #
+       # Note that the method only work on the string:
        #
        #  * no I/O access is performed
        #  * the validity of the path is not checked
@@ -653,7 +740,6 @@ redef class String
        # Using a standard "{self}/{path}" does not work in the following cases:
        #
        # * `self` is empty.
-       # * `path` ends with `'/'`.
        # * `path` starts with `'/'`.
        #
        # This method ensures that the join is valid.
@@ -936,18 +1022,14 @@ end
 
 redef class Sys
 
-       init do
-               if stdout isa FStream then stdout.as(FStream).set_buffering_mode(256, buffer_mode_line)
-       end
-
        # Standard input
-       var stdin: PollableIStream = new Stdin is protected writable
+       var stdin: PollableReader = new Stdin is protected writable, lazy
 
        # Standard output
-       var stdout: OStream = new Stdout is protected writable
+       var stdout: Writer = new Stdout is protected writable, lazy
 
        # Standard output for errors
-       var stderr: OStream = new Stderr is protected writable
+       var stderr: Writer = new Stderr is protected writable, lazy
 
        # Enumeration for buffer mode full (flushes when buffer is full)
        fun buffer_mode_full: Int is extern "file_Sys_Sys_buffer_mode_full_0"
@@ -958,15 +1040,15 @@ redef class Sys
 
        # returns first available stream to read or write to
        # return null on interruption (possibly a signal)
-       protected fun poll( streams : Sequence[FStream] ) : nullable FStream
+       protected fun poll( streams : Sequence[FileStream] ) : nullable FileStream
        do
                var in_fds = new Array[Int]
                var out_fds = new Array[Int]
-               var fd_to_stream = new HashMap[Int,FStream]
+               var fd_to_stream = new HashMap[Int,FileStream]
                for s in streams do
                        var fd = s.fd
-                       if s isa IFStream then in_fds.add( fd )
-                       if s isa OFStream then out_fds.add( fd )
+                       if s isa FileReader then in_fds.add( fd )
+                       if s isa FileWriter then out_fds.add( fd )
 
                        fd_to_stream[fd] = s
                end