=begin pod

=TITLE class IO::Path

=SUBTITLE File or directory path

=for code :skip-test
class IO::Path is Cool does IO { }

Built-in class for file and directory paths.

Conceptually, an C<IO::Path> object consists of a volume (think C<C:> on
windows), a directory, and a basename.

C<IO::Path> supports both purely textual operations, and operations that
access the file system.

The current working directory is available as an C<IO::Path> object in C<$*CWD>.

The behavior of C<IO::Path> is dependent on the operating system it runs on;
to get reproducible behavior across operating systems, you can use one of its
subclasses instead: L<IO::Spec::Unix>, L<IO::Spec::Win32>,
L<IO::Spec::Cygwin>, L<IO::Spec::QNX>.

The rest of this document silently assumes Unix semantics in its examples,
unless when stated otherwise.

=comment TODO: Investigate if the $SPEC = $*SPEC, :$CWD = $*CWD params that
         a bunch of these routines have in their signature in Rakudo, are
         "official" Perl 6 API, and if so, document them here in a central
         place so all the routine documentations can link to it without
         having to repeat it.

=head1 Methods

=head2 method new

    multi method new(Str:D $path)
    multi method new(:$basename!, :$dirname = '.', :$volume = '')

Creates a new C<IO::Path> object from a path string (which is being parsed for
volume, directory name and basename), or from volume, directory name and
basename passed as named arguments.

=head2 method abspath

    method abspath(IO::Path:D: --> Str)

Returns the absolute path as a string.

=head2 method basename

    method basename(IO::Path:D:)

Returns the basename part of the path object. That is, it returns the name of
the file relative to its directory.

    say IO::Path.new("docs/README.pod").basename;      # OUTPUT: «README.pod␤»


=head2 method extension

    method extension(IO::Path:D:)

Returns the extension (if any) of the path object.

    say IO::Path.new("docs/README.pod").extension; # OUTPUT: «pod␤»

=head2 method dirname

    method dirname(IO::Path:D:)

Returns the directory name portion of the path object. That is, it returns
the path excluding the volume and the base name.

    say IO::Path.new("/home/camelia/myfile.p6").dirname;    # OUTPUT: «/home/camelia␤»
    say IO::Path.new("/home/camelia").dirname;              # OUTPUT: «/home␤»
    say IO::Path.new("/home").dirname;                      # OUTPUT: «/␤»


=head2 method volume

    method volume(IO::Path:D:)

Returns the volume portion of the path object. On Unix system, this is always
the empty string.

    say IO::Path::Win32.new("C:\\Windows\\registry.ini").volume;    # OUTPUT: «C:␤»

=head2 method parts

    method parts(IO::Path:D: --> Hash)

Returns a hash with the keys C<dirname>, C<path> and C<volume>, and as
values the return values of the methods with the same names.

    say IO::Path.new("/etc/passwd").parts.perl;
    # OUTPUT: «{:basename("passwd"), :directory("/etc"), :dirname("/etc"), :volume("")}␤»

=head2 method path

    method path(IO::Path:D: --> Str)

Returns the full path as a string.

=head2 method Str

    method Str(IO::Path:D:)

Returns the complete text that was used to create the path as a string, whether
it was given as a single string or as named arguments.

    say IO::Path.new(basename => "foo", dirname => "/bar").Str;  # OUTPUT: «/bar/foo␤»
    say IO::Path.new("/bar/foo").Str;                            # OUTPUT: «/bar/foo␤»

=head2 method open

    method open(IO::Path:D: *%opts)

Opens the path as a file; the named options control the mode, and are the
same as the L<open> function accepts.

=head2 method watch

    method watch(IO::Path:D: --> Supply)

Watches the path for modifications. Only implemented in Rakudo with the
MoarVM backend at the moment.

=head2 method is-absolute

    method is-absolute(IO::Path:D: --> Bool)

Returns C<True> if the path is an absolute path, and C<False> otherwise.

=head2 method is-relative

    method is-relative(IO::Path:D: --> Bool)

Returns C<True> if the path is a relative path, and C<False> otherwise.

=head2 method absolute

    method absolute(IO::Path:D: $base = ~$*CWD --> Str)

Returns a new C<Str> object that is an absolute path, based on C<$base>.

=head2 method relative

    method relative(IO::Path:D: $base = ~$*CWD --> Str)

Returns a new C<Str> object relative to the C<$base> path.

=head2 method parent

    method parent(IO::Path:D: --> IO::Path)

Removes the last portion of the path and returns the result as a new C<IO::Path>.

    my $io = IO::Path.new( "/etc/passwd" );
    say $io.parent;                          # OUTPUT: «"/etc".IO␤»

=head2 method child

    method child(IO::Path:D: $childname --> IO::Path)

Appends C<$childname> to the end of the path, adding path separators where
needed and returns the result as a new C<IO::Path>.

    my $io = IO::Path.new( "/bin" );
    say $io.child('netstat');                # OUTPUT: «"/bin/netstat".IO␤»

=head2 method resolve

    method resolve(IO::Path:D: --> IO::Path)

Returns a new C<IO::Path> object with all symbolic links and references to the
parent directory (C<..>) resolved.  This means that the filesystem is examined
for each directory in the path, and any symlinks found are followed.

    # bar is a symlink pointing to "/baz"
    my $io = "foo/./bar/..".IO.resolve;      # now "/" (the parent of "/baz")

NOTE: Currently (May 2016) this method doesn't work correctly on all platforms,
e.g. Windows, since it assumes POSIX semantics.

=head2 routine dir

    sub    dir(Cool $path = '.', Mu :$test = none('.', '..'))
    method dir(IO::Path:D: Mu :$test = none('.', '..'))

Returns the contents of a directory as a lazy list of C<IO::Path> objects
representing relative paths, filtered by L<smart-matching|
/language/operators#infix_~~> their names against the C<:test> parameter.

Examples:

    # To iterate over the contents of the current directory:
    for dir() -> $file {
        say $file;
    }

    # As before, but include even '.' and '..' which are filtered out by
    # the default :test matcher:
    for dir(test => *) -> $file {
        say $file;
    }

    # To get the names of all .jpg and .jpeg files in ~/Downloads:
=for code :skip-test
my @jpegs = "%*ENV<HOME>/Downloads".IO.dir(test => /:i '.' jpe?g $/)».Str;

=comment TODO: Turn the following into a FAQ entry, and link to it from here.

An example program that lists all files and directories recursively:

    sub MAIN($dir = '.') {
        my @todo = $dir.IO;
        while @todo {
            for @todo.pop.dir -> $path {
                say $path.Str;
                @todo.push: $path if $path.d;
            }
        }
    }

A lazy way to find the first three files ending in ".p6" recursively starting from the current directory:

=for code :skip-test
my @stack = '.';
my $perl-files := gather while ( @stack ) {
    my $current = @stack.pop;
    take $current if ($current.IO.f && $current.IO.extension ~~ rx:i/p6$/);
    @stack.append(dir($current)>>.path) if $current.IO.d;
}
=for code :skip-test
say $perl-files[1..3];

=head2 method e

    method e(--> Bool)

Returns C<Bool::True> if the invocant is a valid path that exists.

=head2 method d

    method d(--> Bool)

Returns C<Bool::True> if the invocant is a path and the directory exists.

=head2 method f

    method f(--> Bool)

Returns C<Bool::True> if the invocant is a path and the file exists.

=head2 method s

    method s(--> Int)

If the path is valid and points to a regular file the size of that file
in bytes is returned. If the path is valid but pointing to something which
is not a file, e.g. a directory, a L<X::IO::NotAFile> exception is thrown.
An exception is also thrown if the path is invalid, in that case the exception
type is L<X::IO::DoesNotExist>.

=head2 method l

    method l(--> Bool)

Returns C<Bool::True> if the invocant is a path and a symlink.

=head2 method r

    method r(--> Bool)

Returns C<Bool::True> if the invocant is a path and accessible.

=head2 method w

    method w(--> Bool)

Returns C<Bool::True> if the invocant is a path and writable.

=head2 method rw

    method rw(--> Bool)

Returns C<Bool::True> if the invocant is a path, readable and writable.

=head2 method x

    method x(--> Bool)

Returns C<Bool::True> if the invocant is a path and executable.

=head2 method rwx

    method rwx(--> Bool)

Returns C<Bool::True> if the invocant is a path, executable, readable and writable.

=head2 method z

    method z(--> Bool)

Returns C<Bool::True> if the invocant is a path and the size is 0.

=head2 routine slurp

    multi method slurp(IO::Path:D: :$bin, :$enc)

Return the file content for this path, like L<slurp|/routine/slurp>.

=head2 routine spurt

    multi method spurt(IO::Path:D: Blob $contents, :$bin, |c)
    multi method spurt(IO::Path:D: Cool $contents, :$bin, |c)

Write C<$contents> to this path, like L<spurt|/routine/spurt>.

=head2 routine chdir

    sub chdir(Str() $path, :$test = 'r' --> IO::Path)
    multi method chdir(IO::Path:U: $path, :$test = 'r' --> IO::Path)
    multi method chdir(IO::Path:D: Str() $path is copy, :$test = 'r' --> IO::Path)

Alter the process' notion of the current working directory (as found in
C<$*CWD>).

The subroutine and class method forms both change the directory relative to
C<$*CWD> if the supplied C<$path> is not absolute.  The object method form
changes the directory relative to the path of the invocant if C<$path> is
not absolute.  The optional named parameter C<test> can specify a file test
that should be applied to the target path if the operation is successful.
The default C<test> is that the directory is readable.

If the directory change is successful then the L<IO::Path>
representing the target directory is returned, otherwise it will
L<fail|/routine/fail> with L<X::IO::Chdir>.

=head2 routine mkdir

    multi sub mkdir(Int:D $mode, *@dirnames --> List)
    multi sub mkdir($path, $mode = 0o777 --> Bool)
    method    mkdir(IO::Path:D: $mode = 0o777 --> Bool)

Creates one or more directories with the L<permissions|
https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation>
specified as an integer (though still subject to L<umask|
https://en.wikipedia.org/wiki/Umask>).

Creates parent directories, as needed. That is, C<mkdir "foo/bar"> will
create C<foo> if it does not exist.

Throws an exception of type L<X::IO::Mkdir> if the
directory cannot be created, except for the C<*@dirnames> form, which
returns the names of the directories that were successfully created.

=head2 routine rmdir

    sub    rmdir(IO $dir --> Bool)
    method rmdir(IO::Path:D: --> Bool)

Remove the given directory if it is empty.

Returns C<True> on success.  Throws an exception of type
L<X::IO::Rmdir> if the directory cannot be removed (e.g.
the directory is not empty, or the path is not a directory).

Since this only works on an empty directory, to remove a directory and its
contents you will have to do something more complex.

    # When we have a directory first recurse, then remove it
    multi sub rm-all(IO::Path $path where :d) {
        .&rm-all for $path.dir;
        rmdir($path)
    }

    # Otherwise just remove the thing directly
    multi sub rm-all(IO::Path $path) { $path.unlink }

See also L<rmtree in File::Directory::Tree|https://github.com/labster/p6-file-directory-tree>.

=head2 routine chmod

    sub    chmod($mode, *@filenames --> List)
    method chmod(IO::Path:D: $mode --> Bool)

Changes the POSIX permissions of a file (or in the subroutine form, any number
of files) to C<$mode>.

The subroutine form returns the names of the files for which setting the new
mode was successful. The method form returns True on success, and otherwise
L<fails|/routine/fail> with L<X::IO::Chmod>.

The mode is expected as an integer following the L<standard numeric notation|
https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation>, and is
best written as an octal number:

=for code :skip-test
chmod 0o755, "myfile1", "myfile2"; # make two files executable by the owner
"myfile".IO.chmod(0o444);          # make a file read-only
$*CWD.chmod(0o544);                # make the working directory read-only

Make sure you I<don't> accidentally pass the intended octal digits as a decimal
number (or string containing a decimal number):

=for code :skip-test
chmod '0444', 'myfile';            # BAD!!! (interpreted as mode 0o674)

=head2 routine rename

    method rename(IO::Path:D: $to, :$createonly --> Bool)
    sub    rename($from, $to, :$createonly --> Bool)

Renames a file. Both C<$from> (the file to be renamed) and C<$to> (the
destination) can take arbitrary paths. If C<:createonly> is set to C<True>,
the rename fails if a file already exists in the destination.  Returns
C<True> upon success, or L<fails|/routine/fail> with L<X::IO::Rename> if the
operation could not be completed.

Please use L<move|/routine/move> if a file could not be moved by renaming
(usually because the destination is on a different physical storage device).

=head2 routine copy

    method copy(IO::Path:D: $to, :$createonly)
    sub    copy($from, $to, :$createonly)

Copies a file, as indicated by C<$from> or the invocant, to the
destination specified.  If C<:createonly> is set to C<True>, copy fails if a file
already exists in the destination.  Returns C<True> upon success, or an
appropriate C<Failure> if the operation could not be completed.

=head2 routine move

    method move(IO::Path:D: $to, :$createonly)
    sub    move($from, $to, :$createonly)

Moves a file. Both C<$from> (the file to be moved) and C<$to> (the
destination) can take arbitrary paths. If C<:createonly> is set to C<True>,
the move fails if a file already exists in the destination.  Returns C<True>
upon success, or L<fails|/routine/fail> with L<X::IO::Move> if the operation
could not be completed.

Please use L<rename|/routine/rename> if a file can be moved by renaming
(which is usually possible if the destination is on the same different
physical storage device).

=head2 routine symlink

    method symlink(IO::Path:D: Str $name --> Bool)
    sub    symlink(Str $target, Str $name --> Bool)

Create a new symbolic link named as $target (or the name of the invocant in
the method form) to the existing file named C<$name>.

Both forms will return C<True> on success or L<fail|/routine/fail> with
L<X::IO::Symlink> if the symbolic link could not be created.

=head2 routine link

    method link(IO::Path:D: Str $name --> Bool)
    sub    link(Str $target, Str $name --> Bool)

Create a new link named as $target (or the name of the invocant in the
method form) to the existing file named C<$name>.

Both forms will return C<True> on success or L<fail|/routine/fail> with
L<X::IO::Link> if the link operation could not be performed.

=head2 routine unlink

    method unlink(IO::Path:D: --> Bool)
    sub    unlink(*@filenames --> List)

Delete all specified ordinary files, links, or symbolic links.

The subroutine form returns the names of the files that were successfully
deleted. The method form returns C<True> on success, or
L<fails|/routine/fail> with L<X::IO::Unlink> if the operation could not be
completed.

=head2 method IO

    method IO(IO::Path:D: --> IO::Path)

Returns the invocant.

=head2 method SPEC

    method SPEC(IO::Path:D: --> IO::Spec)

Returns the L<IO::Spec|/type/IO::Spec> object that was (implicitly) specified at object
creation time.

    my $io = IO::Path.new("/bin/bash");
    say $io.SPEC;                            # OUTPUT: «(Unix)␤»
    say $io.SPEC.dir-sep;                    # OUTPUT: «/␤»

=head1 File test operators

X<|:e> X<|:d> X<|:f> X<|:l> X<|:r> X<|:w> X<|:x> X<|:s> X<|:z> X<|:rw> X<|:rwx>

=begin table
    op       meaning
    ==       ======
    :e       Exists
    :d       Directory
    :f       File
    :l       Symbolic link
    :r       Readable
    :w       Writable
    :x       Executable
    :s       Size
    :z       Zero size
    :rw      Readable and writable
    :rwx     Readable, writable, and executable

=end table

Usage:

If you have a string - a path to something in the filesystem:

    if "path/to/directory".IO ~~ :e & :d {
        say 'path exists and points to a directory';
    }

    my $file = "path/to/file";
    if $file.IO ~~ :e {
        say 'file exists';
    }

Instead of the colonpair syntax, you can use method calls too:

    if 'path/to/file'.IO.e {
        say 'file exists';
    }

If you already have an IO object in C<$file>, either by creating one yourself,
or by getting it from another subroutine, such as C<dir>,
you can write this:

    my $path = "path/to/file".IO;
    given $path {
        when :f { say 'path is a file' }
        when :d { say 'path is a directory' }
    }

=head1 File timestamp retrieval

There are also 3 methods for fetching the 3 timestamps of a file (inode),
on Operating Systems where these are available:

=head2 method modified

Return an L<Instant> object representing the timestamp when the file was
last modified.

=for code :skip-test
say "path/to/file".IO.modified;               #  e.g. Instant:1424089165

To obtain a human-readable form of the timestamp, call C<DateTime> method
on the returned C<Instant>, to convert it into a L<DateTime> object:

=for code :skip-test
say "path/to/file".IO.modified.DateTime;      # e.g. 2015-02-16T12:18:50Z

=head2 method accessed

Return an L<Instant> object representing the timestamp when the file was
last accessed.

=for code :skip-test
say "path/to/file".IO.accessed;               # e.g. Instant:1424353577

To obtain a human-readable form of the timestamp, use a L<DateTime> object:

=for code :skip-test
say DateTime.new("path/to/file".IO.accessed); # e.g. 2015-02-19T13:45:42Z

or more readably:

=for code :skip-test
my $access_instant = "path/to/file".IO.accessed;
my $access_time = DateTime.new($access_instant);
say $access_time;                             # e.g. 2015-02-19T13:45:42Z

=head2 method changed

Return an L<Instant> object representing the timestamp when the inode was
last changed.

=for code :skip-test
"path/to/file".IO.changed;                    # e.g. Instant:1424089165

To obtain a human-readable form of the timestamp, use a L<DateTime> object:

=for code :skip-test
say DateTime.new("path/to/file".IO.changed);  # e.g. 2015-02-16T12:18:50Z

or more readably:

=for code :skip-test
my $change_instant = "path/to/file".IO.changed;
my $change_time = DateTime.new($change_instant);
say $change_time;                             # e.g. 2015-02-16T12:18:50Z

=head1 File permissions retrieval

=head2 method mode

Return an L<IntStr> object representing the POSIX permissions of a file.  The
Str part of the result is the octal representation of the file permission, like
the form accepted by the chmod(1) utility.

=for code :skip-test
say ~"path/to/file".IO.mode;        # e.g. '0644'
say +"path/to/file".IO.mode;        # e.g. 420, where sprintf('%04o', 420) eq '0644'

The result of this can be used in the other methods that take a mode as an
argument.

=for code :skip-test
"path/to/file1".IO.chmod("path/to/file2".IO.mode);  # will change the
                                                    # permissions of file1
                                                    # to be the same as file2

=head1 Related roles and classes

See also the related role L<IO> and the related class L<IO::Handle>.

=end pod

# vim: expandtab shiftwidth=4 ft=perl6
