# struct Path

- Path
- Struct
- Value
- Object

##
Overview

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. 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 a drive letter and root (`C:\`).

    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

##
Included Modules

- Comparable(Path)

##
Defined in:

##
Constant Summary

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.

- .[](name : String, *parts) : Path

  Creates a new `Path` of native kind.

- .home : Path

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

- .new(name : String = "") : Path

  Creates a new `Path` of native kind.

- .new(parts : Enumerable) : Path

  Creates a new `Path` of native kind.

- .new(name : String, *parts) : Path

  Creates a new `Path` of native kind.

- .posix(name : String = "") : Path

  Creates a new `Path` of POSIX kind.

- .posix(parts : Enumerable) : Path

  Creates a new `Path` of POSIX kind.

- .posix(name : String, *parts) : Path

  Creates a new `Path` of POSIX kind.

- .windows(name : String = "") : Path

  Creates a new `Path` of Windows kind.

- .windows(parts : Enumerable) : Path

  Creates a new `Path` of Windows kind.

- .windows(name : String, *parts) : Path

  Creates a new `Path` of Windows kind.

##
Instance Method Summary

- #/(part : Path | String) : Path

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

- #<=>(other : Path)

  Compares this path to other.

- #absolute? : Bool

  Returns `true` if this path is absolute.

- #anchor : Path?

  Returns the concatenation of `#drive` and `#root`.

- #basename(suffix : String? = nil) : String

  Returns the last component of this path.

- #dirname : String

  Returns all components of this path except the last one.

- #drive : Path?

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

- #drive_and_root : Tuple(String?, String?)

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

- #each_parent(&block : Path -> )

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

- #each_part(&)

  Yields each component of this path as a `String`.

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

  Converts this path to an absolute path.

- #extension : String

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

- #inspect(io : IO)

  Inspects this path to io.

- #join(part) : Path

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

- #join(*parts) : Path

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

- #join(parts : Enumerable) : Path

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

- #native? : Bool

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

- #normalize(*, remove_final_separator : Bool = true) : Path

  Removes redundant elements from this path and returns the shortest equivalent path by purely lexical processing.

- #parent : Path

  Returns the parent path of this path.

- #parents : Array(Path)

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

- #parts : Array(String)

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

- #posix? : Bool

  Returns `true` if this is a POSIX path.

- #root : Path?

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

- #sibling(name : Path | String) : Path?

  Resolves path name in this path's parent directory.

- #to_kind(kind)

  Converts this path to the given kind.

- #to_posix : Path

  Converts this path to a POSIX path.

- #to_s : String

  Returns the string representation of this path.

- #to_s(io : IO)

  Appends the string representation of this path to io.

- #to_uri : URI

  Returns a new `URI` with `file` scheme from this path.

- #to_windows : Path

  Converts this path to a Windows path.

- #windows? : Bool

  Returns `true` if this is a Windows path.

### Instance methods inherited from module `Comparable(Path)`

<(other : T) <, <=(other : T) <=, <=>(other : T) <=>, ==(other : T) ==, >(other : T) >, >=(other : T) >=, clamp(min, max)
clamp(range : Range) clamp

### Instance methods inherited from struct `Struct`

==(other) : Bool ==, hash(hasher) hash, inspect(io : IO) : Nil inspect, pretty_print(pp) : Nil pretty_print, to_s(io : IO) : Nil to_s

### Instance methods inherited from struct `Value`

==(other : JSON::Any)
==(other : YAML::Any)
==(other) ==, dup dup

### Instance methods inherited from class `Object`

!=(other) !=, !~(other) !~, ==(other) ==, ===(other : JSON::Any)
===(other : YAML::Any)
===(other) ===, =~(other) =~, __crystal_pseudo_! : Bool __crystal_pseudo_!, __crystal_pseudo_as(type : Class) __crystal_pseudo_as, __crystal_pseudo_as?(type : Class) __crystal_pseudo_as?, __crystal_pseudo_is_a?(type : Class) : Bool __crystal_pseudo_is_a?, __crystal_pseudo_nil? : Bool __crystal_pseudo_nil?, __crystal_pseudo_responds_to?(name : Symbol) : Bool __crystal_pseudo_responds_to?, class class, dup dup, hash(hasher)
hash hash, inspect : String
inspect(io : IO) : Nil inspect, itself itself, not_nil! not_nil!, pretty_inspect(width = 79, newline = "\n", indent = 0) : String pretty_inspect, pretty_print(pp : PrettyPrint) : Nil pretty_print, tap(&) tap, to_json(io : IO)
to_json to_json, to_pretty_json(indent : String = " ")
to_pretty_json(io : IO, indent : String = " ") to_pretty_json, to_s : String
to_s(io : IO) : Nil to_s, to_yaml(io : IO)
to_yaml to_yaml, try(&) try, unsafe_as(type : T.class) forall T unsafe_as

### Class methods inherited from class `Object`

from_json(string_or_io, root : String)
from_json(string_or_io) from_json, from_yaml(string_or_io : String | IO) from_yaml

##
Constructor Detail

def self.[](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.

def self.[](name : String, *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.

def self.home : Path #

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

def self.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.

def self.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.

def self.new(name : String, *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.

def self.posix(name : String = "") : Path #

Creates a new `Path` of POSIX kind.

def self.posix(parts : Enumerable) : Path #

Creates a new `Path` of POSIX kind.

def self.posix(name : String, *parts) : Path #

Creates a new `Path` of POSIX kind.

def self.windows(name : String = "") : Path #

Creates a new `Path` of Windows kind.

def self.windows(parts : Enumerable) : Path #

Creates a new `Path` of Windows kind.

def self.windows(name : String, *parts) : Path #

Creates a new `Path` of Windows kind.

##
Instance Method Detail

def /(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"]

def <=>(other : Path) #

Compares this path to other.

The comparison is performed strictly lexically: `foo` and `./foo` are not treated as equal. 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.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

def absolute? : Bool #

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 and root (`C:\`) or with a UNC share (`\\server\share\`).

def anchor : Path? #

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\\")

def basename(suffix : String? = nil) : String #

Returns the last component of this path.

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

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

def dirname : String #

Returns all components of this path except the last one.

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

def drive : Path? #

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")

NOTE Drives are only available for Windows paths. It can either be a drive letter (`C:`) or a UNC share (`\\host\share`).

def drive_and_root : Tuple(String?, String?) #

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

def each_parent(&block : Path -> ) #

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"]

def 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.

def ends_with_separator? #
def 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).

def extension : String #

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

    Path["foo.cr"].extension # => ".cr"
    Path["foo"].extension    # => ""

def inspect(io : IO) #

Inspects this path to io.

def 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/"]

def 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/"]

def 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")

def native? : Bool #

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

def 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.

1. 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 only if it is the root (`"/"`, `\`, or `C:\`).

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

def parent : Path #

Returns the parent path of this path.

If the path is empty or `"."`, 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"]

def parents : Array(Path) #

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

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

def parts : Array(String) #

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"]

def posix? : Bool #

Returns `true` if this is a POSIX path.

def root : Path? #

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("\\")

def sibling(name : Path | String) : Path? #

Resolves path name in this path's parent directory.

Raises `Path::Error` if `#parent` is `nil`.

def to_kind(kind) #

Converts this path to the given kind.

See `#to_windows` and `#to_posix` for details.

def to_posix : Path #

Converts this path to a POSIX path.

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

It returns a copy of this instance if it already has POSIX kind. Otherwise a new instance is created with `Kind::POSIX` and all occurences of backslash file separators (`\\`) replaced by forward slash (`#/`).

def to_s : String #

Returns the string representation of this path.

def to_s(io : IO) #

Appends the string representation of this path to io.

def to_uri : 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.

def to_windows : Path #

Converts this path to a Windows path.

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

This creates a new instance with the same string representation but with `Kind::WINDOWS`.

def windows? : Bool #

Returns `true` if this is a Windows path.