struct

Path

Inherits Comparable / Struct / Value / Object

A Path represents a filesystem path and allows path-handling operations such as querying its components as well as semantic manipulations.

A path is hierarchical and composed of a sequence of directory and file name elements separated by a special separator or delimiter. A root component, that identifies a file system hierarchy, may also be present. The name element that is farthest from the root of the directory hierarchy is the name of a file or directory. The other name elements are directory names. A Path can represent a root, a root and a sequence of names, or simply one or more name elements. A Path is considered to be an empty path if it consists solely of one name element that is empty or equal to ".". Accessing a file using an empty path is equivalent to accessing the default directory of the process.

Examples

Path["foo/bar/baz.cr"].parent    # => Path["foo/bar"]
Path["foo/bar/baz.cr"].basename  # => "baz.cr"
Path["./foo/../bar"].normalize   # => Path["bar"]
Path["~/bin"].expand(home: true) # => Path["/home/crystal/bin"]

For now, its methods are purely lexical, there is no direct filesystem access.

Path handling comes in different kinds depending on operating system:

  • Path.posix() creates a new POSIX path
  • Path.windows() creates a new Windows path
  • Path.new() means Path.posix on POSIX platforms and Path.windows() on Windows platforms.
# On POSIX system:
Path.new("foo", "bar", "baz.cr") == Path.posix("foo/bar/baz.cr")
# On Windows system:
Path.new("foo", "bar", "baz.cr") == Path.windows("foo\\bar\\baz.cr")

The main differences between Windows and POSIX paths:

  • POSIX paths use forward slash (/) as only path separator, Windows paths use backslash (\) as default separator but also recognize forward slashes.
  • POSIX paths are generally case-sensitive, Windows paths case-insensitive (see #<=>).
  • A POSIX path is absolute if it begins with a forward slash (/). A Windows path is absolute if it starts with both a drive and a root (see #absolute?).
Path.posix("/foo/./bar").normalize   # => Path.posix("/foo/bar")
Path.windows("/foo/./bar").normalize # => Path.windows("\\foo\\bar")

Path.posix("/foo").absolute?   # => true
Path.windows("/foo").absolute? # => false

Path.posix("foo") == Path.posix("FOO")     # => false
Path.windows("foo") == Path.windows("FOO") # => true

Constants

SEPARATORS = separators(Kind.native)

The file/directory separator characters of the current platform. {'/'} on POSIX, {'\\', '/'} on Windows.

Constructors

[](parts : Enumerable) : Path

Creates a new Path of native kind.

When compiling for a windows target, this is equal to Path.windows(), otherwise Path.posix is used.

Source
[](name : String | Path, *parts) : Path

Creates a new Path of native kind.

When compiling for a windows target, this is equal to Path.windows(), otherwise Path.posix is used.

Source
from_json_object_key?(key : String) : Path
Source
home

Returns the path of the home directory of the current user.

Source
new(name : String = "") : Path

Creates a new Path of native kind.

When compiling for a windows target, this is equal to Path.windows(), otherwise Path.posix is used.

Source
new(path : Path) : Path

Creates a new Path of native kind.

When compiling for a windows target, this is equal to Path.windows(), otherwise Path.posix is used.

Source
new(parts : Enumerable) : Path

Creates a new Path of native kind.

When compiling for a windows target, this is equal to Path.windows(), otherwise Path.posix is used.

Source
new(pull : JSON::PullParser) : self
Source
new(name : String | Path, *parts : String | Path) : Path

Creates a new Path of native kind.

When compiling for a windows target, this is equal to Path.windows(), otherwise Path.posix is used.

Source
posix(name : String = "") : Path

Creates a new Path of POSIX kind.

Source
posix(path : Path) : Path

Creates a new Path of POSIX kind.

Source
posix(parts : Enumerable) : Path

Creates a new Path of POSIX kind.

Source
posix(name : String | Path, *parts : String | Path) : Path

Creates a new Path of POSIX kind.

Source
windows(name : String = "") : Path

Creates a new Path of Windows kind.

Source
windows(path : Path) : Path

Creates a new Path of Windows kind.

Source
windows(parts : Enumerable) : Path

Creates a new Path of Windows kind.

Source
windows(name : String | Path, *parts : String | Path) : Path

Creates a new Path of Windows kind.

Source

Instance methods

/(part : Path | String) : Path

Appends the given part to this path and returns the joined path.

Path["foo"] / "bar" / "baz"     # => Path["foo/bar/baz"]
Path["foo/"] / Path["/bar/baz"] # => Path["foo/bar/baz"]

See join(part) for details.

Source
<=>(other : Path)

Compares this path to other.

The comparison is performed strictly lexically: foo and ./foo are not treated as equal. Nor are paths of different kind. To compare paths semantically, they need to be normalized and converted to the same kind.

Path["foo"] <=> Path["foo"]               # => 0
Path["foo"] <=> Path["./foo"]             # => 1
Path["foo"] <=> Path["foo/"]              # => -1
Path.posix("foo") <=> Path.windows("foo") # => -1

Comparison is case-sensitive for POSIX paths and case-insensitive for Windows paths.

Path.posix("foo") <=> Path.posix("FOO")     # => 1
Path.windows("foo") <=> Path.windows("FOO") # => 0
Source
==(other : self)

Returns true if this path is considered equivalent to other.

The comparison is performed strictly lexically: foo and ./foo are not treated as equal. Nor are paths of different kind. To compare paths semantically, they need to be normalized and converted to the same kind.

Path["foo"] == Path["foo"]               # => true
Path["foo"] == Path["./foo"]             # => false
Path["foo"] == Path["foo/"]              # => false
Path.posix("foo") == Path.windows("foo") # => false

Comparison is case-sensitive for POSIX paths and case-insensitive for Windows paths.

Path.posix("foo") == Path.posix("FOO")     # => false
Path.windows("foo") == Path.windows("FOO") # => true
Source
absolute?

Returns true if this path is absolute.

A POSIX path is absolute if it begins with a forward slash (/).

A Windows path is absolute if it begins with a drive letter (C:), a UNC share (\\server\share), or a root local device path (\\., \\?), which is then followed by a root path separator. Drive-relative paths (C:foo), rooted paths (\foo), and root local device paths (\\.) are not absolute.

Source
anchor

Returns the concatenation of #drive and #root.

Path["/etc/"].anchor                           # => Path["/"]
Path.windows("C:Program Files").anchor         # => Path.windows("C:")
Path.windows("C:\\Program Files").anchor       # => Path.windows("C:\\")
Path.windows("\\\\host\\share\\folder").anchor # => Path.windows("\\\\host\\share\\")
Path.windows("\\\\.\\NUL").anchor              # => Path.windows("\\\\.\\")
Path.windows("//?").anchor                     # => Path.windows("//?")
Source
basename(suffix : String | Nil = nil) : String

Returns the last component of this path.

If suffix is given, it is stripped from the end.

In case the last component is the empty string (i.e. the path has a trailing separator), the second to last component is returned. For a path that only consists of an anchor, or an empty path, the base name is equivalent to the full path.

Path["/foo/bar/file.cr"].basename # => "file.cr"
Path["/foo/bar/"].basename        # => "bar"
Path["/foo/bar/."].basename       # => "."
Path["/"].basename                # => "/"
Path[""].basename                 # => ""
Source
dirname

Returns all components of this path except the last one.

Path["/foo/bar/file.cr"].dirname # => "/foo/bar"
Source
drive

Returns a path representing the drive component or nil if this path does not contain a drive.

See #anchor for the combination of drive and #root.

Path.windows("C:\\Program Files").drive       # => Path.windows("C:")
Path.windows("\\\\host\\share\\folder").drive # => Path.windows("\\\\host\\share")
Path.windows("\\\\.\\NUL").drive              # => Path.windows("\\\\.")
Path.windows("//?").drive                     # => Path.windows("//?")

NOTE: Drives are only available for Windows paths. It can be a drive letter (C:), a UNC share (\\host\share), or a root local device path (\\., \\?).

Source
drive_and_root

Returns a tuple of #drive and #root as strings.

Source
each_parent

Yields each parent of this path beginning with the topmost parent.

Path["foo/bar/file.cr"].each_parent { |parent| puts parent }
# Path["."]
# Path["foo"]
# Path["foo/bar"]
Source
each_part

Yields each component of this path as a String.

Path.new("foo/bar/").each_part # yields: "foo", "bar"

See #parts for more examples.

Source
each_part

Returns an iterator over all components of this path.

parts = Path.new("foo/bar/").each_part
parts.next # => "foo"
parts.next # => "bar"
parts.next # => Iterator::Stop::INSTANCE

See #parts for more examples.

Source
ends_with_separator?
Source
expand(base : Path | String = Dir.current, *, home : Path | String | Bool = false, expand_base = true) : Path

Converts this path to an absolute path. Relative paths are referenced from the current working directory of the process (Dir.current) unless base is given, in which case it will be used as the reference path.

Path["foo"].expand                 # => Path["/current/path/foo"]
Path["~/foo"].expand(home: "/bar") # => Path["/bar/foo"]
Path["baz"].expand("/foo/bar")     # => Path["/foo/bar/baz"]

home specifies the home directory which ~ will expand to. "~" is expanded to the value passed to home. If it is false (default), home is not expanded. If true, it is expanded to the user's home directory (Path.home).

If expand_base is true, base itself will be expanded in Dir.current if it is not an absolute path. This guarantees the method returns an absolute path (assuming that Dir.current is absolute).

Source
extension

Returns the extension of this path, or an empty string if it has no extension.

Path["foo.cr"].extension     # => ".cr"
Path["foo"].extension        # => ""
Path["foo.tar.gz"].extension # => ".gz"
Source
hash(hasher)

See Object#hash(hasher)

Source
inspect(io : IO)

Inspects this path to io.

Source
join(parts : Enumerable) : Path

Appends the given parts to this path and returns the joined path.

Path["foo"].join("bar", "baz")           # => Path["foo/bar/baz"]
Path["foo/"].join(Path["/bar/", "/baz"]) # => Path["foo/bar/baz"]
Path["/foo/"].join("/bar/", "/baz/")     # => Path["/foo/bar/baz/"]

Non-matching paths are implicitly converted to this path's kind.

Path.posix("foo/bar").join(Path.windows("baz\\baq")) # => Path.posix("foo/bar/baz/baq")
Path.windows("foo\\bar").join(Path.posix("baz/baq")) # => Path.windows("foo\\bar\\baz/baq")

See join(part) for details.

Source
join(part) : Path

Appends the given part to this path and returns the joined path.

Path["foo"].join("bar")     # => Path["foo/bar"]
Path["foo/"].join("/bar")   # => Path["foo/bar"]
Path["/foo/"].join("/bar/") # => Path["/foo/bar/"]

Joining an empty string ("") appends a trailing path separator. In case the path already ends with a trailing separator, no additional separator is added.

Path["a/b"].join("")   # => Path["a/b/"]
Path["a/b/"].join("")  # => Path["a/b/"]
Path["a/b/"].join("c") # => Path["a/b/c"]
Source
join(*parts) : Path

Appends the given parts to this path and returns the joined path.

Path["foo"].join("bar", "baz")       # => Path["foo/bar/baz"]
Path["foo/"].join("/bar/", "/baz")   # => Path["foo/bar/baz"]
Path["/foo/"].join("/bar/", "/baz/") # => Path["/foo/bar/baz/"]

See join(part) for details.

Source
kind
Source
native?

Returns true if this is a native path for the target platform.

Source
normalize(*, remove_final_separator : Bool = true) : Path

Removes redundant elements from this path and returns the shortest equivalent path by purely lexical processing. It applies the following rules iteratively until no further processing can be done:

  1. Replace multiple slashes with a single slash.
  2. Eliminate each . path name element (the current directory).
  3. Eliminate each .. path name element (the parent directory) preceded by a non-.. element along with the latter.
  4. Eliminate .. elements that begin a rooted path: that is, replace "/.." by "/" at the beginning of a path.

If the path turns to be empty, the current directory (".") is returned.

The returned path ends in a slash if it is the root ("/", \, or C:\), or if this path is a Windows local device path that also ends in a slash. This trailing slash is significant; \\.\C: refers to the volume C:, on which most I/O functions fail, whereas \\.\C:\ refers to the root directory of said volume.

See also Rob Pike: Lexical File Names in Plan 9 or Getting Dot-Dot Right

Source
parent

Returns the parent path of this path.

If the path is empty, it returns ".". If the path is rooted and in the top-most hierarchy, the root path is returned.

Path["foo/bar/file.cr"].parent # => Path["foo/bar"]
Path["foo"].parent             # => Path["."]
Path["/foo"].parent            # => Path["/"]
Path["/"].parent               # => Path["/"]
Path[""].parent                # => Path["."]
Path["foo/bar/."].parent       # => Path["foo/bar"]
Source
parents

Returns all parent paths of this path beginning with the topmost path.

Path["foo/bar/file.cr"].parents # => [Path["."], Path["foo"], Path["foo/bar"]]
Source
parts

Returns the components of this path as an Array(String).

Path.new("foo/bar/").parts                   # => ["foo", "bar"]
Path.new("/Users/foo/bar.cr").parts          # => ["/", "Users", "foo", "bar.cr"]
Path.windows("C:\\Users\\foo\\bar.cr").parts # => ["C:\\", "Users", "foo", "bar.cr"]
Path.posix("C:\\Users\\foo\\bar.cr").parts   # => ["C:\\Users\\foo\\bar.cr"]
Source
posix?

Returns true if this is a POSIX path.

Source
relative?

Returns true if this path is relative.

See #absolute?

Source
relative_to(base : Path | String) : Path

Same as #relative_to but returns self if self can't be expressed as relative path to base.

Source
relative_to?(base : Path) : Path | Nil

Returns a relative path that is lexically equivalent to self when joined to base with an intervening separator.

The returned path is in normalized form.

That means with normalized paths base.join(target.relative_to(base)) is equivalent to target.

Returns nil if self cannot be expressed as relative to base or if knowing the current working directory would be necessary to resolve it. The latter can be avoided by expanding the paths first.

For Windows paths, the drive and the root must be identical; relative paths between different path types are not supported, even if they would resolve to the same roots (e.g. \\.\C:\foo and C:\foo are not equivalent, nor are \\?\UNC\server\share\foo and \\server\share\foo).

Source
relative_to?(base : String) : Path | Nil

Returns a relative path that is lexically equivalent to self when joined to base with an intervening separator.

The returned path is in normalized form.

That means with normalized paths base.join(target.relative_to(base)) is equivalent to target.

Returns nil if self cannot be expressed as relative to base or if knowing the current working directory would be necessary to resolve it. The latter can be avoided by expanding the paths first.

For Windows paths, the drive and the root must be identical; relative paths between different path types are not supported, even if they would resolve to the same roots (e.g. \\.\C:\foo and C:\foo are not equivalent, nor are \\?\UNC\server\share\foo and \\server\share\foo).

Source
root

Returns the root path component of this path or nil if it is not rooted.

See #anchor for the combination of #drive and root.

Path["/etc/"].root                           # => Path["/"]
Path.windows("C:Program Files").root         # => nil
Path.windows("C:\\Program Files").root       # => Path.windows("\\")
Path.windows("\\\\host\\share\\folder").root # => Path.windows("\\")
Path.windows("//./NUL").root                 # => Path.windows("/")
Path.windows("\\\\?").root                   # => nil
Source
sibling(name : Path | String) : Path

Resolves path name in this path's parent directory.

Raises Path::Error if #parent is nil.

Source
stem

Returns the last component of this path without the extension.

This is equivalent to self.basename(self.extension).

Path["file.cr"].stem     # => "file"
Path["file.tar.gz"].stem # => "file.tar"
Path["foo/file.cr"].stem # => "file"
Source
to_json(json : JSON::Builder) : Nil
Source
to_json_object_key
Source
to_kind(kind, *, mappings : Bool = true) : Path

Converts this path to the given kind.

See #to_windows and #to_posix for details.

  • #to_native converts to the native path semantics.
Source
to_native

Converts this path to a native path.

  • #to_kind performs a configurable conversion.
Source
to_posix(*, mappings : Bool = true) : Path

Converts this path to a POSIX path.

It returns a new instance with Kind::POSIX and all occurrences of Windows' backslash file separators (\\) replaced by forward slash (/). If #posix? is true, this is a no-op.

Path.windows("foo/bar\\baz").to_posix # => Path.posix("foo/bar/baz")
Path.posix("foo/bar\\baz").to_posix   # => Path.posix("foo/bar\\baz")

When mappings is true (default), replacements for forbidden characters in Windows paths are substituted by the original characters when converting to a POSIX path. Originals are calculated by subtracting 0xF000 from the replacement codepoint. For example, the U+F05C becomes U+005C, the backslash character.

Path.windows("foo\uF05Cbar").to_posix(mappings: true)  # => Path.posix("foo\\bar")
Path.windows("foo\uF05Cbar").to_posix(mappings: false) # => Path.posix("foo\uF05Cbar")
  • #to_windows performs the inverse conversion.
  • #to_kind performs a configurable conversion.
Source
to_s(io : IO)

Appends the string representation of this path to io.

Source
to_s

Returns the string representation of this path.

Source
to_uri

Returns a new URI with file scheme from this path.

A URI can only be created with an absolute path. Raises Path::Error if this path is not absolute.

Source
to_windows(*, mappings : Bool = true) : Path

Converts this path to a Windows path.

This creates a new instance with the same string representation but with Kind::WINDOWS. If #windows? is true, this is a no-op.

Path.posix("foo/bar").to_windows   # => Path.windows("foo/bar")
Path.windows("foo/bar").to_windows # => Path.windows("foo/bar")

When mappings is true (default), forbidden characters in Windows paths are substituted by replacement characters when converting from a POSIX path. Replacements are calculated by adding 0xF000 to their codepoint. For example, the backslash character U+005C becomes U+F05C.

Path.posix("foo\\bar").to_windows(mappings: true)  # => Path.windows("foo\uF05Cbar")
Path.posix("foo\\bar").to_windows(mappings: false) # => Path.windows("foo\\bar")
  • #to_posix performs the inverse conversion.
  • #to_kind performs a configurable conversion.
Source
to_yaml(yaml : YAML::Nodes::Builder) : Nil
Source
windows?

Returns true if this is a Windows path.

Source

Nested types