lib/file: some methods return a nullable Error on error
[nit.git] / lib / standard / file.nit
index 8af3c70..42eca14 100644 (file)
@@ -42,7 +42,17 @@ abstract class FileStream
        private var file: nullable NativeFile = null
 
        # The status of a file. see POSIX stat(2).
-       fun file_stat: NativeFileStat do return _file.file_stat
+       #
+       #     var f = new FileReader.open("/etc/issue")
+       #     assert f.file_stat.is_file
+       #
+       # Return null in case of error
+       fun file_stat: nullable FileStat
+       do
+               var stat = _file.file_stat
+               if stat.address_is_null then return null
+               return new FileStat(stat)
+       end
 
        # File descriptor of this file
        fun fd: Int do return _file.fileno
@@ -330,6 +340,10 @@ class Path
        #
        # Returns `null` if there is no file at `self`.
        #
+       #     assert "/etc/".to_path.stat.is_dir
+       #     assert "/etc/issue".to_path.stat.is_file
+       #     assert "/fail/does not/exist".to_path.stat == null
+       #
        # ~~~
        # var p = "/tmp/".to_path
        # var stat = p.stat
@@ -481,7 +495,7 @@ class Path
                end
 
                # Delete the directory itself
-               if ok then path.to_cstring.rmdir
+               if ok then ok = path.to_cstring.rmdir and ok
 
                return ok
        end
@@ -523,6 +537,11 @@ class FileStat
                return stat.atime
        end
 
+       # Returns the last access time
+       #
+       # alias for `last_access_time`
+       fun atime: Int do return last_access_time
+
        # Returns the last modification time in seconds since Epoch
        fun last_modification_time: Int
        do
@@ -530,6 +549,12 @@ class FileStat
                return stat.mtime
        end
 
+       # Returns the last modification time
+       #
+       # alias for `last_modification_time`
+       fun mtime: Int do return last_modification_time
+
+
        # Size of the file at `path`
        fun size: Int
        do
@@ -537,13 +562,16 @@ class FileStat
                return stat.size
        end
 
-       # Is this a regular file and not a device file, pipe, socket, etc.?
+       # Is self a regular file and not a device file, pipe, socket, etc.?
        fun is_file: Bool
        do
                assert not finalized
                return stat.is_reg
        end
 
+       # Alias for `is_file`
+       fun is_reg: Bool do return is_file
+
        # Is this a directory?
        fun is_dir: Bool
        do
@@ -567,6 +595,11 @@ class FileStat
                return stat.ctime
        end
 
+       # Returns the last status change time
+       #
+       # alias for `last_status_change_time`
+       fun ctime: Int do return last_status_change_time
+
        # Returns the permission bits of file
        fun mode: Int
        do
@@ -613,10 +646,20 @@ redef class String
        fun file_exists: Bool do return to_cstring.file_exists
 
        # The status of a file. see POSIX stat(2).
-       fun file_stat: NativeFileStat do return to_cstring.file_stat
+       fun file_stat: nullable FileStat
+       do
+               var stat = to_cstring.file_stat
+               if stat.address_is_null then return null
+               return new FileStat(stat)
+       end
 
        # The status of a file or of a symlink. see POSIX lstat(2).
-       fun file_lstat: NativeFileStat do return to_cstring.file_lstat
+       fun file_lstat: nullable FileStat
+       do
+               var stat = to_cstring.file_lstat
+               if stat.address_is_null then return null
+               return new FileStat(stat)
+       end
 
        # Remove a file, return true if success
        fun file_delete: Bool do return to_cstring.file_delete
@@ -860,28 +903,47 @@ redef class String
        end
 
        # Create a directory (and all intermediate directories if needed)
-       fun mkdir
+       #
+       # Return an error object in case of error.
+       #
+       #    assert "/etc/".mkdir != null
+       fun mkdir: nullable Error
        do
                var dirs = self.split_with("/")
                var path = new FlatBuffer
-               if dirs.is_empty then return
+               if dirs.is_empty then return null
                if dirs[0].is_empty then
                        # it was a starting /
                        path.add('/')
                end
+               var error: nullable Error = null
                for d in dirs do
                        if d.is_empty then continue
                        path.append(d)
                        path.add('/')
-                       path.to_s.to_cstring.file_mkdir
+                       var res = path.to_s.to_cstring.file_mkdir
+                       if not res and error == null then
+                               error = new IOError("Cannot create directory `{path}`: {sys.errno.strerror}")
+                       end
                end
+               return error
        end
 
        # Delete a directory and all of its content, return `true` on success
        #
        # Does not go through symbolic links and may get stuck in a cycle if there
        # is a cycle in the filesystem.
-       fun rmdir: Bool do return to_path.rmdir
+       #
+       # Return an error object in case of error.
+       #
+       #    assert "/fail/does not/exist".rmdir != null
+       fun rmdir: nullable Error
+       do
+               var res = to_path.rmdir
+               if res then return null
+               var error = new IOError("Cannot change remove `{self}`: {sys.errno.strerror}")
+               return error
+       end
 
        # Change the current working directory
        #
@@ -890,8 +952,18 @@ redef class String
        #     "..".chdir
        #     assert getcwd == "/"
        #
-       # TODO: errno
-       fun chdir do to_cstring.file_chdir
+       # Return an error object in case of error.
+       #
+       #     assert "/etc".chdir == null
+       #     assert "/fail/does no/exist".chdir != null
+       #     assert getcwd == "/etc" # unchanger
+       fun chdir: nullable Error
+       do
+               var res = to_cstring.file_chdir
+               if res then return null
+               var error = new IOError("Cannot change directory to `{self}`: {sys.errno.strerror}")
+               return error
+       end
 
        # Return right-most extension (without the dot)
        #
@@ -921,7 +993,17 @@ redef class String
                end
        end
 
-       # returns files contained within the directory represented by self
+       # Returns entries contained within the directory represented by self.
+       #
+       #     var files = "/etc".files
+       #     assert files.has("issue")
+       #
+       # Returns an empty array in case of error
+       #
+       #     files = "/etc/issue".files
+       #     assert files.is_empty
+       #
+       # TODO find a better way to handle errors and to give them back to the user.
        fun files: Array[String] is extern import Array[String], Array[String].add, NativeString.to_s, String.to_cstring `{
                char *dir_path;
                DIR *dir;
@@ -929,8 +1011,11 @@ redef class String
                dir_path = String_to_cstring( recv );
                if ((dir = opendir(dir_path)) == NULL)
                {
-                       perror( dir_path );
-                       exit( 1 );
+                       //perror( dir_path );
+                       //exit( 1 );
+                       Array_of_String results;
+                       results = new_Array_of_String();
+                       return results;
                }
                else
                {
@@ -966,14 +1051,14 @@ redef class NativeString
                return stat_element;
        `}
        private fun file_mkdir: Bool is extern "string_NativeString_NativeString_file_mkdir_0"
-       private fun rmdir: Bool `{ return rmdir(recv); `}
+       private fun rmdir: Bool `{ return !rmdir(recv); `}
        private fun file_delete: Bool is extern "string_NativeString_NativeString_file_delete_0"
-       private fun file_chdir is extern "string_NativeString_NativeString_file_chdir_0"
+       private fun file_chdir: Bool is extern "string_NativeString_NativeString_file_chdir_0"
        private fun file_realpath: NativeString is extern "file_NativeString_realpath"
 end
 
 # This class is system dependent ... must reify the vfs
-extern class NativeFileStat `{ struct stat * `}
+private extern class NativeFileStat `{ struct stat * `}
        # Returns the permission bits of file
        fun mode: Int is extern "file_FileStat_FileStat_mode_0"
        # Returns the last access time
@@ -1117,30 +1202,30 @@ redef class Sys
 end
 
 # Print `objects` on the standard output (`stdout`).
-protected fun printn(objects: Object...)
+fun printn(objects: Object...)
 do
        sys.stdout.write(objects.to_s)
 end
 
 # Print an `object` on the standard output (`stdout`) and add a newline.
-protected fun print(object: Object)
+fun print(object: Object)
 do
        sys.stdout.write(object.to_s)
        sys.stdout.write("\n")
 end
 
 # Read a character from the standard input (`stdin`).
-protected fun getc: Char
+fun getc: Char
 do
        return sys.stdin.read_char.ascii
 end
 
 # Read a line from the standard input (`stdin`).
-protected fun gets: String
+fun gets: String
 do
        return sys.stdin.read_line
 end
 
 # Return the working (current) directory
-protected fun getcwd: String do return file_getcwd.to_s
+fun getcwd: String do return file_getcwd.to_s
 private fun file_getcwd: NativeString is extern "string_NativeString_NativeString_file_getcwd_0"