+redef class Writable
+ # Like `write_to` but take care of creating the file
+ fun write_to_file(filepath: String)
+ do
+ var stream = new FileWriter.open(filepath)
+ write_to(stream)
+ stream.close
+ end
+end
+
+# Utility class to access file system services
+#
+# Usually created with `Text::to_path`.
+class Path
+
+ private var path: String
+
+ # Path to this file
+ redef fun to_s do return path
+
+ # Name of the file name at `to_s`
+ #
+ # ~~~
+ # var path = "/tmp/somefile".to_path
+ # assert path.filename == "somefile"
+ # ~~~
+ var filename: String = path.basename("") is lazy
+
+ # Does the file at `path` exists?
+ fun exists: Bool do return stat != null
+
+ # Information on the file at `self` following symbolic links
+ #
+ # 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
+ # if stat != null then # Does `p` exist?
+ # print "It's size is {stat.size}"
+ # if stat.is_dir then print "It's a directory"
+ # end
+ # ~~~
+ fun stat: nullable FileStat
+ do
+ var stat = path.to_cstring.file_stat
+ if stat.address_is_null then return null
+ return new FileStat(stat)
+ end
+
+ # Information on the file or link at `self`
+ #
+ # Do not follow symbolic links.
+ fun link_stat: nullable FileStat
+ do
+ var stat = path.to_cstring.file_lstat
+ if stat.address_is_null then return null
+ return new FileStat(stat)
+ end
+
+ # Delete a file from the file system, return `true` on success
+ fun delete: Bool do return path.to_cstring.file_delete
+
+ # Copy content of file at `path` to `dest`
+ #
+ # Require: `exists`
+ fun copy(dest: Path)
+ do
+ var input = open_ro
+ var output = dest.open_wo
+
+ while not input.eof do
+ var buffer = input.read(1024)
+ output.write buffer
+ end
+
+ input.close
+ output.close
+ end
+
+ # Open this file for reading
+ #
+ # Require: `exists and not link_stat.is_dir`
+ fun open_ro: FileReader
+ do
+ # TODO manage streams error when they are merged
+ return new FileReader.open(path)
+ end
+
+ # Open this file for writing
+ #
+ # Require: `not exists or not stat.is_dir`
+ fun open_wo: FileWriter
+ do
+ # TODO manage streams error when they are merged
+ 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`
+ fun files: Array[Path]
+ do
+ var files = new Array[Path]
+ for filename in path.files do
+ files.add new Path(path / filename)
+ end
+ return files
+ 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 file system.
+ fun rmdir: Bool
+ do
+ var ok = true
+ for file in self.files do
+ var stat = file.link_stat
+ if stat.is_dir then
+ ok = file.rmdir and ok
+ else
+ ok = file.delete and ok
+ end
+ end
+
+ # Delete the directory itself
+ if ok then ok = path.to_cstring.rmdir and ok
+
+ return ok
+ 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
+ end
+
+ # Is this a UNIX socket
+ fun is_sock: Bool
+ do
+ assert not finalized
+ return stat.is_sock
+ end
+end
+
+redef class Text
+ # Access file system related services on the path at `self`
+ fun to_path: Path do return new Path(to_s)
+end
+