=begin pod

=TITLE class IO::Path

=SUBTITLE File or directory path

    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 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::Path::Unix>, L<IO::Path::Win32>,
L<IO::Path::Cygwin>, L<IO::Path::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 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

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("/etc/passwd").basename;   # passwd

=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;   # 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("/etc/passwd").dirname;    # /etc

=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;    # C:

=head2 method parts

    method parts(IO::Path:D:) returns 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
    # ("dirname" => "/etc", "volume" => "", "basename" => "passwd").hash

=head2 method path

    method path(IO::Path:D:)

Returns the invocant.

=head2 method Str

    method Str(IO::Path:D:)

Returns the full path as a string. If the path object was constructed from a
single string, the method Str will return that string.

=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:D)

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:D)

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

=head2 method absolute

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

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

=head2 method relative

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

Returns a new C<IO::Path> object relative to the C<$base> path.

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

    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;
    }
    
    say $perl-files[1..3];

=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 processes 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 --> @success-dirnames);
    multi sub mkdir($path, $mode = 0o777 --> Bool);
    method    mkdir(IO::Path:D: $mode 0o777 --> Bool)

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

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($dir as IO --> 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).

=head2 routine chmod

    sub    chmod($mode, *@filenames --> @success-filenames)
    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|
http://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation>, and is
best written as an octal number:

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

    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 --> @success-filenames);

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.

=head1 File Test operators

X<-e> X<-f>

The C<-e> and C<-f> file test operators do not exist in Perl 6. Use instead
C<:e> and C<:f>.

X<-M> X<-A> X<-C>

The C<-M>, C<-A> and C<-C> file test operators do not exist in Perl 6, use
instead the L<modified|/type/IO::Path#method_modified>,
L<accessed|/type/IO::Path#method_accessed> and
L<changed|/type/IO::Path#method_changed> methods instead.

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

    :e Exists
    :d Directory
    :f File
    :l Symbolic link
    :r Readable
    :w Writable
    :x Executable
    :s Size
    :z Zero size

Usage:

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

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

    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 $file, either by creating one yourself,
or by getting it from another subroutine, such as C<dir>,
you can write this:

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

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

    say "path/to/file".IO.modified;   #  e.g. Instant:1424089165

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

    say DateTime.new("path/to/file".IO.modified);  # e.g. 2015-02-16T12:18:50Z

or more readably:

    my $modification_instant = "path/to/file".IO.modified;
    my $modification_time = DateTime.new($modification_instant);
    say $modification_time;         # e.g. 2015-02-16T12:18:50Z

=head2 method accessed

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

    say "path/to/file".IO.accessed;   #  e.g. Instant:1424353577

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

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

or more readably:

    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.

    "path/to/file".IO.changed;        #  e.g. Instant:1424089165

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

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

or more readably:

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

=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
