Utility class to access file system services.

Usually created with Text::to_path.

Path objects does not necessarily represent existing files in a file system. They are sate-less objects that efficiently represent path information. They also provide an easy to use API on file-system services and are used to store their error status (see last_error)

Introduced properties

fun /(subpath: String): Path

core :: Path :: /

Correctly join self with subpath using the directory separator.
fun copy(dest: Path)

core :: Path :: copy

Copy content of file at path to dest.
init defaultinit(path: String)

core :: Path :: defaultinit

fun delete

core :: Path :: delete

Delete a file from the file system.
fun dir: Path

core :: Path :: dir

Return the directory part of the path.
protected fun dir=(dir: Path)

core :: Path :: dir=

Return the directory part of the path.
fun each_line: LineIterator

core :: Path :: each_line

Return an iterator on each line of the file
fun exists: Bool

core :: Path :: exists

Does the file at path exists?
fun filename: String

core :: Path :: filename

Short name of the file at to_s
protected fun filename=(filename: String)

core :: Path :: filename=

Short name of the file at to_s
fun files: Array[Path]

core :: Path :: files

Lists the files contained within the directory at path.
fun is_dir: Bool

core :: Path :: is_dir

Is self the path to an existing directory ?
fun last_error: nullable IOError

core :: Path :: last_error

Last error produced by I/O operations.
fun last_error=(last_error: nullable IOError)

core :: Path :: last_error=

Last error produced by I/O operations.
fun open_ro: FileReader

core :: Path :: open_ro

Open this file for reading.
fun open_wo: FileWriter

core :: Path :: open_wo

Open this file for writing
fun read_all: String

core :: Path :: read_all

Read all the content of the file as a string.
fun read_all_bytes: Bytes

core :: Path :: read_all_bytes

Read all the content on the file as a raw sequence of bytes.
fun read_lines: Array[String]

core :: Path :: read_lines

Read all the lines of the file
fun rmdir

core :: Path :: rmdir

Recursively delete a directory and all of its content
fun simplified: Path

core :: Path :: simplified

The path simplified by removing useless ., removing //, and resolving ..
protected fun simplified=(simplified: Path)

core :: Path :: simplified=

The path simplified by removing useless ., removing //, and resolving ..
fun stat: nullable FileStat

core :: Path :: stat

Information on the file at self following symbolic links

Redefined properties

redef fun ==(other: nullable Object): Bool

core $ Path :: ==

Have self and other the same value?
redef type SELF: Path

core $ Path :: SELF

Type of this instance, automatically specialized in every class
redef fun hash: Int

core $ Path :: hash

The hash code of the object.
redef fun to_s: String

core $ Path :: to_s

Path to this file

All properties

fun !=(other: nullable Object): Bool

core :: Object :: !=

Have self and other different values?
fun /(subpath: String): Path

core :: Path :: /

Correctly join self with subpath using the directory separator.
fun ==(other: nullable Object): Bool

core :: Object :: ==

Have self and other the same value?
type CLASS: Class[SELF]

core :: Object :: CLASS

The type of the class of self.
type SELF: Object

core :: Object :: SELF

Type of this instance, automatically specialized in every class
protected fun class_factory(name: String): CLASS

core :: Object :: class_factory

Implementation used by get_class to create the specific class.
fun class_name: String

core :: Object :: class_name

The class name of the object.
fun copy(dest: Path)

core :: Path :: copy

Copy content of file at path to dest.
init defaultinit(path: String)

core :: Path :: defaultinit

fun delete

core :: Path :: delete

Delete a file from the file system.
fun dir: Path

core :: Path :: dir

Return the directory part of the path.
protected fun dir=(dir: Path)

core :: Path :: dir=

Return the directory part of the path.
fun each_line: LineIterator

core :: Path :: each_line

Return an iterator on each line of the file
fun exists: Bool

core :: Path :: exists

Does the file at path exists?
fun filename: String

core :: Path :: filename

Short name of the file at to_s
protected fun filename=(filename: String)

core :: Path :: filename=

Short name of the file at to_s
fun files: Array[Path]

core :: Path :: files

Lists the files contained within the directory at path.
fun get_class: CLASS

core :: Object :: get_class

The meta-object representing the dynamic type of self.
fun hash: Int

core :: Object :: hash

The hash code of the object.
init init

core :: Object :: init

fun inspect: String

core :: Object :: inspect

Developer readable representation of self.
protected fun inspect_head: String

core :: Object :: inspect_head

Return "CLASSNAME:#OBJECTID".
fun is_dir: Bool

core :: Path :: is_dir

Is self the path to an existing directory ?
intern fun is_same_instance(other: nullable Object): Bool

core :: Object :: is_same_instance

Return true if self and other are the same instance (i.e. same identity).
fun is_same_serialized(other: nullable Object): Bool

core :: Object :: is_same_serialized

Is self the same as other in a serialization context?
intern fun is_same_type(other: Object): Bool

core :: Object :: is_same_type

Return true if self and other have the same dynamic type.
fun last_error: nullable IOError

core :: Path :: last_error

Last error produced by I/O operations.
fun last_error=(last_error: nullable IOError)

core :: Path :: last_error=

Last error produced by I/O operations.
intern fun object_id: Int

core :: Object :: object_id

An internal hash code for the object based on its identity.
fun open_ro: FileReader

core :: Path :: open_ro

Open this file for reading.
fun open_wo: FileWriter

core :: Path :: open_wo

Open this file for writing
fun output

core :: Object :: output

Display self on stdout (debug only).
intern fun output_class_name

core :: Object :: output_class_name

Display class name on stdout (debug only).
fun read_all: String

core :: Path :: read_all

Read all the content of the file as a string.
fun read_all_bytes: Bytes

core :: Path :: read_all_bytes

Read all the content on the file as a raw sequence of bytes.
fun read_lines: Array[String]

core :: Path :: read_lines

Read all the lines of the file
fun rmdir

core :: Path :: rmdir

Recursively delete a directory and all of its content
fun serialization_hash: Int

core :: Object :: serialization_hash

Hash value use for serialization
fun simplified: Path

core :: Path :: simplified

The path simplified by removing useless ., removing //, and resolving ..
protected fun simplified=(simplified: Path)

core :: Path :: simplified=

The path simplified by removing useless ., removing //, and resolving ..
fun stat: nullable FileStat

core :: Path :: stat

Information on the file at self following symbolic links
intern fun sys: Sys

core :: Object :: sys

Return the global sys object, the only instance of the Sys class.
abstract fun to_jvalue(env: JniEnv): JValue

core :: Object :: to_jvalue

fun to_s: String

core :: Object :: to_s

User readable representation of self.
package_diagram core::Path Path core::Object Object core::Path->core::Object

Parents

interface Object

core :: Object

The root of the class hierarchy.

Class definitions

core $ Path
# Utility class to access file system services.
#
# Usually created with `Text::to_path`.
#
# `Path` objects does not necessarily represent existing files in a file system.
# They are sate-less objects that efficiently represent path information.
# They also provide an easy to use API on file-system services and are used to store their error status (see `last_error`)
class Path

	private var path: String

	# Path to this file
	redef fun to_s do return path

	# Short name of the file at `to_s`
	#
	# ~~~
	# var path = "/tmp/somefile".to_path
	# assert path.filename == "somefile"
	# ~~~
	#
	# The result does not depend of the file system, thus is cached for efficiency.
	var filename: String = path.basename is lazy

	# The path simplified by removing useless `.`, removing `//`, and resolving `..`
	#
	# ~~~
	# var path = "somedir/./tmp/../somefile".to_path
	# assert path.simplified.to_s == "somedir/somefile"
	# ~~~
	#
	# See `String:simplify_path` for details.
	#
	# The result does not depend of the file system, thus is cached for efficiency.
	var simplified: Path is lazy do
		var res = path.simplify_path.to_path
		res.simplified = res
		return res
	end

	# Return the directory part of the path.
	#
	# ~~~
	# var path = "/foo/bar/baz".to_path
	# assert path.dir.to_s == "/foo/bar"
	# assert path.dir.dir.to_s == "/foo"
	# assert path.dir.dir.dir.to_s == "/"
	# ~~~
	#
	# See `String:dirname` for details.
	#
	# The result does not depend of the file system, thus is cached for efficiency.
	var dir: Path is lazy do
		return path.dirname.to_path
	end

	# Last error produced by I/O operations.
	#
	# ~~~
	# var path = "/does/not/exists".to_path
	# assert path.last_error == null
	# path.read_all
	# assert path.last_error != null
	# ~~~
	#
	# Since `Path` objects are stateless, `last_error` is reset on most operations and reflect its status.
	var last_error: nullable IOError = null is writable

	# Does the file at `path` exists?
	#
	# If the file does not exists, `last_error` is set to the information.
	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`.
	# `last_error` is updated to contains the error information on error, and null on success.
	#
	#     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"
	# else
	#     print p.last_error.to_s
	# end
	# ~~~
	fun stat: nullable FileStat
	do
		var stat = path.to_cstring.file_stat
		if stat.address_is_null then
			last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}")
			return null
		end
		last_error = 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
			last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}")
			return null
		end
		last_error = null
		return new FileStat(stat)
	end

	# Delete a file from the file system.
	#
	# `last_error` is updated to contains the error information on error, and null on success.
	fun delete
	do
		var res = path.to_cstring.file_delete
		if not res then
			last_error = new IOError("Cannot delete `{path}`: {sys.errno.strerror}")
		else
			last_error = null
		end
	end

	# Copy content of file at `path` to `dest`.
	#
	# `last_error` is updated to contains the error information on error, and null on success.
	fun copy(dest: Path)
	do
		last_error = null
		var input = open_ro
		var output = dest.open_wo

		var buffer = new CString(4096)
		while not input.eof do
			var read = input.read_bytes_to_cstring(buffer, 4096)
			output.write_bytes_from_cstring(buffer, read)
		end

		input.close
		output.close
		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

	# Correctly join `self` with `subpath` using the directory separator.
	#
	# Using a standard "{self}/{path}" does not work in the following cases:
	#
	# * `self` is empty.
	# * `path` starts with `'/'`.
	#
	# This method ensures that the join is valid.
	#
	#     var hello = "hello".to_path
	#     assert (hello/"world").to_s   == "hello/world"
	#     assert ("hel/lo".to_path / "wor/ld").to_s == "hel/lo/wor/ld"
	#     assert ("".to_path / "world").to_s == "world"
	#     assert (hello / "/world").to_s  == "/world"
	#     assert ("hello/".to_path / "world").to_s  == "hello/world"
	fun /(subpath: String): Path do return new Path(path / subpath)

	# 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
			if name == "." or name == ".." then continue
			res.add self / name
		end
		d.closedir

		return res
	end

	# Is `self` the path to an existing directory ?
	#
	# ~~~nit
	# assert ".".to_path.is_dir
	# assert not "/etc/issue".to_path.is_dir
	# assert not "/should/not/exist".to_path.is_dir
	# ~~~
	fun is_dir: Bool do
		var st = stat
		if st == null then return false
		return st.is_dir
	end

	# Recursively 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 with the first encountered error, or null on success.
	# The method does not stop on the first error and tries to remove the most files and directories.
	#
	# ~~~
	# var path = "/does/not/exists/".to_path
	# path.rmdir
	# assert path.last_error != null
	#
	# path = "/tmp/path/to/create".to_path
	# path.to_s.mkdir
	# assert path.exists
	# path.rmdir
	# assert path.last_error == null
	# ~~~
	fun rmdir
	do
		var first_error = null
		for file in self.files do
			var stat = file.link_stat
			if stat == null then
				if first_error == null then first_error = file.last_error
				continue
			end
			if stat.is_dir then
				# Recursively rmdir
				file.rmdir
			else
				file.delete
			end
			if first_error == null then first_error = file.last_error
		end

		# Delete the directory itself if things are fine
		if first_error == null then
			if not path.to_cstring.rmdir then
				first_error = new IOError("Cannot remove `{self}`: {sys.errno.strerror}")
			end
		end
		self.last_error = first_error
	end

	redef fun ==(other) do return other isa Path and simplified.path == other.simplified.path
	redef fun hash do return simplified.path.hash
end
lib/core/file.nit:375,1--758,3