+ last_error = input.last_error or else output.last_error
+ end
+
+ # Open this file for reading.
+ #
+ # ~~~
+ # var file = "/etc/issue".to_path.open_ro
+ # print file.read_line
+ # file.close
+ # ~~~
+ #
+ # Note that it is the user's responsibility to close the stream.
+ # Therefore, for simple use case, look at `read_all` or `each_line`.
+ #
+ # ENSURE `last_error == result.last_error`
+ fun open_ro: FileReader
+ do
+ var res = new FileReader.open(path)
+ last_error = res.last_error
+ return res
+ end
+
+ # Open this file for writing
+ #
+ # ~~~
+ # var file = "bla.log".to_path.open_wo
+ # file.write "Blabla\n"
+ # file.close
+ # ~~~
+ #
+ # Note that it is the user's responsibility to close the stream.
+ # Therefore, for simple use case, look at `Writable::write_to_file`.
+ #
+ # ENSURE `last_error == result.last_error`
+ fun open_wo: FileWriter
+ do
+ var res = new FileWriter.open(path)
+ last_error = res.last_error
+ return res
+ end
+
+ # Read all the content of the file as a string.
+ #
+ # ~~~
+ # var content = "/etc/issue".to_path.read_all
+ # print content
+ # ~~~
+ #
+ # `last_error` is updated to contains the error information on error, and null on success.
+ # In case of error, the result might be empty or truncated.
+ #
+ # See `Reader::read_all` for details.
+ fun read_all: String do return read_all_bytes.to_s
+
+ # Read all the content on the file as a raw sequence of bytes.
+ #
+ # ~~~
+ # var content = "/etc/issue".to_path.read_all_bytes
+ # print content.to_s
+ # ~~~
+ #
+ # `last_error` is updated to contains the error information on error, and null on success.
+ # In case of error, the result might be empty or truncated.
+ fun read_all_bytes: Bytes
+ do
+ var s = open_ro
+ var res = s.read_all_bytes
+ s.close
+ last_error = s.last_error
+ 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
+ # ~~~
+ #
+ # `last_error` is updated to contains the error information on error, and null on success.
+ # In case of error, the result might be empty or truncated.
+ #
+ # See `Reader::read_lines` for details.
+ fun read_lines: Array[String]
+ do
+ var s = open_ro
+ var res = s.read_lines
+ s.close
+ last_error = s.last_error
+ 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`)
+ #
+ # `last_error` is updated to contains the error information on error, and null on success.
+ #
+ # 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
+ last_error = s.last_error
+ return res
+ end
+
+
+ # Lists the files contained within the directory at `path`.
+ #
+ # var files = "/etc".to_path.files
+ # assert files.has("/etc/issue".to_path)
+ #
+ # `last_error` is updated to contains the error information on error, and null on success.
+ # In case of error, the result might be empty or truncated.
+ #
+ # var path = "/etc/issue".to_path
+ # files = path.files
+ # assert files.is_empty
+ # assert path.last_error != null
+ fun files: Array[Path]
+ do
+ last_error = null
+ var res = new Array[Path]
+ var d = new NativeDir.opendir(path.to_cstring)
+ if d.address_is_null then
+ last_error = new IOError("Cannot list directory `{path}`: {sys.errno.strerror}")
+ return res
+ end
+
+ loop
+ var de = d.readdir
+ if de.address_is_null then
+ # readdir cannot fail, so null means end of list
+ break
+ end
+ var name = de.to_s_with_copy
+ if name == "." or name == ".." then continue
+ res.add new Path(path / name)
+ end
+ d.closedir
+
+ return res
+ end
+
+ # Delete a directory and all of its content
+ #
+ # Does not go through symbolic links and may get stuck in a cycle if there
+ # is a cycle in the file system.
+ #
+ # `last_error` is updated to contains the error information on error, and null on success.
+ # The method does not stop on the first error and try to remove most file and directories.
+ #
+ # ~~~
+ # var path = "/does/not/exists/".to_path
+ # path.rmdir
+ # assert path.last_error != null
+ # ~~~
+ fun rmdir
+ do
+ last_error = null
+ for file in self.files do
+ var stat = file.link_stat
+ if stat == null then
+ last_error = file.last_error
+ continue
+ end
+ if stat.is_dir then
+ # Recursively rmdir
+ file.rmdir
+ else
+ file.delete
+ end
+ if last_error == null then last_error = file.last_error
+ end
+
+ # Delete the directory itself if things are fine
+ if last_error == null then
+ if path.to_cstring.rmdir then
+ last_error = new IOError("Cannot remove `{self}`: {sys.errno.strerror}")
+ end
+ end
+ end
+
+ redef fun ==(other) do return other isa Path and path.simplify_path == other.path.simplify_path
+ redef fun hash do return path.simplify_path.hash
+end
+
+# Information on a file
+#
+# Created by `Path::stat` and `Path::link_stat`.
+#
+# The information within this class is gathered when the instance is initialized
+# it will not be updated if the targeted file is modified.
+class FileStat
+ super Finalizable
+
+ # TODO private init
+
+ # The low-level status of a file
+ #
+ # See: POSIX stat(2)
+ private var stat: NativeFileStat
+
+ private var finalized = false
+
+ redef fun finalize
+ do
+ if not finalized then
+ stat.free
+ finalized = true
+ end
+ end
+
+ # Returns the last access time in seconds since Epoch
+ fun last_access_time: Int
+ do
+ assert not finalized
+ 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
+ assert not finalized
+ 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
+ assert not finalized
+ return stat.size
+ end
+
+ # 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
+ assert not finalized
+ return stat.is_dir
+ end
+
+ # Is this a symbolic link?
+ fun is_link: Bool
+ do
+ assert not finalized
+ return stat.is_lnk
+ end
+
+ # FIXME Make the following POSIX only? or implement in some other way on Windows
+
+ # Returns the last status change time in seconds since Epoch
+ fun last_status_change_time: Int
+ do
+ assert not finalized
+ 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
+ assert not finalized
+ return stat.mode
+ end
+
+ # Is this a character device?
+ fun is_chr: Bool
+ do
+ assert not finalized
+ return stat.is_chr
+ end
+
+ # Is this a block device?
+ fun is_blk: Bool
+ do
+ assert not finalized
+ return stat.is_blk
+ end
+
+ # Is this a FIFO pipe?
+ fun is_fifo: Bool
+ do
+ assert not finalized
+ return stat.is_fifo