struct Path

Overview

APath 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. APath can represent a root, a root and a sequence of names, or simply one or more name elements. APath 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:

# 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:

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

Defined in:

json/to_json.cr
path.cr
yaml/to_yaml.cr

Constant Summary

SEPARATORS = separators(Kind.native)

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

Constructors

Instance Method Summary

Instance methods inherited from module Comparable(Path)

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

Instance methods inherited from struct Struct

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

Class methods inherited from struct Struct

pre_initialize(address : Pointer) : Nil pre_initialize

Instance methods inherited from struct Value

==(other : Log::Metadata::Value)
==(other : JSON::Any)
==(other : YAML::Any)
==(other)
==
, dup dup

Instance methods inherited from class Object

! : Bool !, !=(other) !=, !~(other) !~, ==(other) ==, ===(other : JSON::Any)
===(other : YAML::Any)
===(other)
===
, =~(other) =~, as(type : Class) as, as?(type : Class) as?, class class, dup dup, hash(hasher)
hash
hash
, in?(collection : Object) : Bool
in?(*values : Object) : Bool
in?
, inspect(io : IO) : Nil
inspect : String
inspect
, is_a?(type : Class) : Bool is_a?, itself itself, nil? : Bool nil?, not_nil!(message)
not_nil!
not_nil!
, pretty_inspect(width = 79, newline = "\n", indent = 0) : String pretty_inspect, pretty_print(pp : PrettyPrint) : Nil pretty_print, responds_to?(name : Symbol) : Bool responds_to?, tap(&) tap, to_json(io : IO) : Nil
to_json : String
to_json
, to_pretty_json(indent : String = " ") : String
to_pretty_json(io : IO, indent : String = " ") : Nil
to_pretty_json
, to_s(io : IO) : Nil
to_s : String
to_s
, to_yaml(io : IO) : Nil
to_yaml : String
to_yaml
, try(&) try, unsafe_as(type : T.class) forall T unsafe_as

Class methods inherited from class Object

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

Macros inherited from class Object

class_getter(*names, &block) class_getter, class_getter!(*names) class_getter!, class_getter?(*names, &block) class_getter?, class_property(*names, &block) class_property, class_property!(*names) class_property!, class_property?(*names, &block) class_property?, class_setter(*names) class_setter, def_clone def_clone, def_equals(*fields) def_equals, def_equals_and_hash(*fields) def_equals_and_hash, def_hash(*fields) def_hash, delegate(*methods, to object) delegate, forward_missing_to(delegate) forward_missing_to, getter(*names, &block) getter, getter!(*names) getter!, getter?(*names, &block) getter?, property(*names, &block) property, property!(*names) property!, property?(*names, &block) property?, setter(*names) setter

Constructor Detail

def self.[](parts : Enumerable) : Path #

Creates a newPath of native kind.

When compiling for a windows target, this is equal toPath.windows(), otherwisePath.posix is used.


def self.[](name : String | Path, *parts) : Path #

Creates a newPath of native kind.

When compiling for a windows target, this is equal toPath.windows(), otherwisePath.posix is used.


def self.from_json_object_key?(key : String) : Path #

def self.home : Path #

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


def self.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) #

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

Creates a newPath of native kind.

When compiling for a windows target, this is equal toPath.windows(), otherwisePath.posix is used.


def self.new(path : Path) : Path #

Creates a newPath of native kind.

When compiling for a windows target, this is equal toPath.windows(), otherwisePath.posix is used.


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

Creates a newPath of native kind.

When compiling for a windows target, this is equal toPath.windows(), otherwisePath.posix is used.


def self.new(pull : JSON::PullParser) : self #

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

Creates a newPath of native kind.

When compiling for a windows target, this is equal toPath.windows(), otherwisePath.posix is used.


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

Creates a newPath of POSIX kind.


def self.posix(path : Path) : Path #

Creates a newPath of POSIX kind.


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

Creates a newPath of POSIX kind.


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

Creates a newPath of POSIX kind.


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

Creates a newPath of Windows kind.


def self.windows(path : Path) : Path #

Creates a newPath of Windows kind.


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

Creates a newPath of Windows kind.


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

Creates a newPath of Windows kind.


Instance Method Detail

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

Appends the givenpart 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.


def <=>(other : Path) #

Compares this path toother.

The comparison is performed strictly lexically:foo and./foo arenot treated as equal. Nor are paths of differentkind. 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

def ==(other : self) #

Returnstrue if this path is considered equivalent toother.

The comparison is performed strictly lexically:foo and./foo arenot treated as equal. Nor are paths of differentkind. 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

def absolute? : Bool #

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


def anchor : Path | Nil #

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("//?")

def basename(suffix : String | Nil = nil) : String #

Returns the last component of this path.

Ifsuffix 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                 # => ""

def dirname : String #

Returns all components of this path except the last one.

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

def drive : Path | Nil #

Returns a path representing the drive component ornil 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 (\\., \\?).


def drive_and_root : Tuple(String | Nil, String | Nil) #

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(& : String -> ) #

Yields each component of this path as aString.

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

See#parts for more examples.


def each_part : Iterator(String) #

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.


def ends_with_separator? : Bool #

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) unlessbase 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. &quot;~&quot; is expanded to the value passed tohome. If it isfalse (default), home is not expanded. Iftrue, it is expanded to the user's home directory (Path.home).

Ifexpand_base istrue,base itself will be expanded inDir.current if it is not an absolute path. This guarantees the method returns an absolute path (assuming thatDir.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        # => ""
Path["foo.tar.gz"].extension # => ".gz"

def hash(hasher) #
Description copied from struct Struct

SeeObject#hash(hasher)


def inspect(io : IO) #

Inspects this path toio.


def join(parts : Enumerable) : Path #

Appends the givenparts 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.


def join(part) : Path #

Appends the givenpart 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"]

def join(*parts) : Path #

Appends the givenparts 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.


def native? : Bool #

Returnstrue 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.
  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 ("/",\, orC:\), or if this path is a Windows local device path that also ends in a slash. This trailing slash is significant;\\.\C: refers to thevolumeC:, 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


def parent : Path #

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

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 anArray(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 #

Returnstrue if this is a POSIX path.


def relative? : Bool #

Returnstrue if this path is relative.

See#absolute?


def relative_to(base : Path | String) : Path #

Same as#relative_to but returnsself ifself can't be expressed as relative path tobase.


def relative_to?(base : Path) : Path | Nil #

Returns a relative path that is lexically equivalent toself when joined tobase with an intervening separator.

The returned path is in normalized form.

That means with normalized pathsbase.join(target.relative_to(base)) is equivalent totarget.

Returnsnil ifself cannot be expressed as relative tobase 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 andC:\foo are not equivalent, nor are\\?\UNC\server\share\foo and\\server\share\foo).


def relative_to?(base : String) : Path | Nil #

Returns a relative path that is lexically equivalent toself when joined tobase with an intervening separator.

The returned path is in normalized form.

That means with normalized pathsbase.join(target.relative_to(base)) is equivalent totarget.

Returnsnil ifself cannot be expressed as relative tobase 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 andC:\foo are not equivalent, nor are\\?\UNC\server\share\foo and\\server\share\foo).


def root : Path | Nil #

Returns the root path component of this path ornil 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

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

Resolves pathname in this path's parent directory.

RaisesPath::Error if#parent isnil.


def stem : String #

Returns the last component of this path without the extension.

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

Path["file.cr"].stem     # => "file"
Path["file.tar.gz"].stem # => "file.tar"
Path["foo/file.cr"].stem # => "file"

def to_json(json : JSON::Builder) : Nil #

def to_json_object_key : String #

def to_kind(kind, *, mappings : Bool = true) : Path #

Converts this path to the givenkind.

See#to_windows and#to_posix for details.

  • #to_native converts to the native path semantics.

def to_native : Path #

Converts this path to a native path.

  • #to_kind performs a configurable conversion.

def to_posix(*, mappings : Bool = true) : Path #

Converts this path to a POSIX path.

It returns a new instance withKind::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")

Whenmappings istrue (default), replacements for forbidden characters in Windows paths are substituted by the original characters when converting to a POSIX path. Originals are calculated by subtracting0xF000 from the replacement codepoint. For example, theU+F05C becomesU+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")

def to_s(io : IO) #

Appends the string representation of this path toio.


def to_s : String #

Returns the string representation of this path.


def to_uri : URI #

Returns a newURI withfile scheme from this path.

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


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

Whenmappings istrue (default), forbidden characters in Windows paths are substituted by replacement characters when converting from a POSIX path. Replacements are calculated by adding0xF000 to their codepoint. For example, the backslash characterU+005C becomesU+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.

def to_yaml(yaml : YAML::Nodes::Builder) : Nil #

def windows? : Bool #

Returnstrue if this is a Windows path.