=begin pod :kind("Type") :subkind("class") :category("domain-specific")

=TITLE class Proc

=SUBTITLE Running process (filehandle-based interface)

    class Proc {}

C<Proc> is a representation of an invocation of an external
process. It provides access to the input, output and error stream as well as
the exit code. It is typically created through the C<run> subroutine:

=for code
my $proc = run 'echo', 'Hallo world', :out;
my $captured-output = $proc.out.slurp: :close;
say "Output was $captured-output.raku()";# OUTPUT: «Output was "Hallo world\n"␤»

Piping several commands is easy too. To achieve the equivalent of the
pipe C<echo "Hello, world" | cat -n> in Raku, and capture the output
from the second command, you can do

=for code
my $p1 = run 'echo', 'Hello, world', :out;
my $p2 = run 'cat', '-n', :in($p1.out), :out;
say $p2.out.get;

You can also feed the C<:in> (standard input) pipe directly from your program,
by setting it to C<True>, which will make the pipe available via C<.in> method
on the C<Proc>:

=for code
my $p = run "cat", "-n", :in, :out;
$p.in.say: "Hello,\nworld!";
$p.in.close;
say $p.out.slurp: :close;
# OUTPUT: «1  Hello,␤
#          2  world!␤»

In order to capture the standard error, C<:err> can be supplied:

=for code
my $p = run "ls", "-l", ".", "qqrq", :out, :err;
my $captured-output = $p.out.slurp: :close;
my $captured-error  = $p.err.slurp: :close;
my $exit-code       = $p.exitcode;

In sink context, a C<Proc> will call its C<sink> method, throwing an exception
if the process has exited with an exit code different from zero:

=for code :skip-test<Illustrates failure>
shell 'exit 1'
# OUTPUT: «(exit code 1) The spawned command 'exit 1' exited unsuccessfully (exit code: 1)␤»

B<Note:> Versions of L<Rakudo|/language/glossary#Rakudo> older than 2017.04 do
not have C<.slurp> available on L<IO::Pipe|/type/IO::Pipe> objects; use
L«C<.slurp-rest>|/routine/slurp-rest» instead.

Use L<Proc::Async|/type/Proc::Async> for non-blocking operations.


=head1 Potential Deadlocks

If you run an external program with C<:out> and C<:err> (so
capturing standard output and standard error separately), a deadlock
can occur, for example in the following scenario:

=item Your Raku script reads from the program's standard output until
the End of File (EOF) marker).
=item The external program writes to its standard error stream.
=item The external program runs into the standard error's buffer limit.
=item Your Raku script blocks, waiting for input on the output stream, while
the external program blocks until its standard error buffer is being drained.

You can avoid this by using C<:merge> to join the external program's
standard output and error streams, so that you only need to read from
one pipe. Of course, this presupposes that you do not need separate access
to the two streams. If you do, the only save approach is to use
L<Proc::Async>.

A similar deadlock can occur when you call an external program with
both the C<:in> option (to open a pipe to its standard input) and
one of the C<:out>, C<:err> or C<:merge> options. In this scenario,
it can happen that your Raku script blocks reading from the external
program's output while it waits for input, and vice versa.

In this scenario, switching to L<Proc::Async> is the most robust solution.

=head1 Methods

=head2 routine new

=begin code :skip-test<compile time error>
method new(Proc:U:
        :$in = '-',
        :$out = '-',
        :$err = '-',
        Bool :$bin = False,
        Bool :$chomp = True,
        Bool :$merge = False,
        Str:D :$enc = 'UTF-8',
        Str:D :$nl = "\n",
    --> Proc:D)

sub shell(
        $cmd,
        :$in = '-',
        :$out = '-',
        :$err = '-',
        Bool :$bin = False,
        Bool :$chomp = True,
        Bool :$merge = False,
        Str:D :$enc = 'UTF-8',
        Str:D :$nl = "\n",
        :$cwd = $*CWD,
        Hash() :$env = %*ENV
    --> Proc:D)
=end code

C<new> creates a new C<Proc> object, whereas C<run> or C<shell> create one and
spawn it with the command and arguments provided in C<@args> or C<$cmd>,
respectively.

C<$in>, C<$out> and C<$err> are the three standard streams of the to-be-launched
program, and default to C<"-"> meaning they inherit the stream from the parent
process. Setting one (or more) of them to C<True> makes the stream available as
an L<IO::Pipe|/type/IO::Pipe> object of the same name, like for example
C<$proc.out>. You can set them to C<False> to discard them. Or you can pass an
existing L<IO::Handle|/type/IO::Handle> object (for example C<IO::Pipe>) in, in
which case this handle is used for the stream.

Please bear in mind that the process streams reside in process
variables, not in the dynamic variables that make them available to our
programs. Thus, modifying
L<the dynamic filehandle variables (such as C<$*OUT>)|/language/variables#Special_filehandles:_STDIN,_STDOUT_and_STDERR>
inside the host process will have no effect in the spawned process,
unlike C<$*CWD> and C<$*ENV>, whose changes will be actually reflected
in it.

=begin code
my $p-name = "/tmp/program.raku";
my $program = Q:to/END/;
    #!/usr/bin/env raku

    $*OUT.say( qq/\t$*PROGRAM: This goes to standard output/ );
END

spurt $p-name, $program;

$*OUT.put: "1. standard output before doing anything weird";

{
    temp $*OUT = open '/tmp/out.txt', :w;
    $*OUT.put: "2. temp redefine standard output before this message";
    shell( "raku $p-name" ).so;
}

$*OUT.put: "3. everything should be back to normal";
# OUTPUT
# 1. standard output before doing anything weird
#     /tmp/program.p6: This goes to standard output
# 3. everything should be back to normal

# /tmp/out.txt will contain:
# 2. temp redefine standard output before this message
=end code

This program shows that the program spawned with C<shell> is not using
the temporary C<$*OUT> value defined in the host process (redirected to
C</tmp/out.txt>), but the initial C<STDOUT> defined in the process.

C<$bin> controls whether the streams are handled as binary (i.e.
L<Blob|/type/Blob> object) or text (i.e. L<Str|/type/Str> objects). If
C<$bin> is False, C<$enc> holds the character encoding to encode strings
sent to the input stream and decode binary data from the output and
error streams.

With C<$chomp> set to C<True>, newlines are stripped from the output and
err streams when reading with C<lines> or C<get>. C<$nl> controls what
your idea of a newline is.

If C<$merge> is set to True, the standard output and error stream end up
merged in C<$proc.out>.


=head2 method sink

Defined as:

    method sink(--> Nil)

When sunk, the C<Proc> object will throw L<X::Proc::Unsuccessful|/type/X::Proc::Unsuccessful>
if the process it ran exited unsuccessfully.

=for code :skip-test<illustrates exception>
shell 'ls /qqq';
# OUTPUT:
# (exit code 1) ls: cannot access '/qqq': No such file or directory
# The spawned command 'ls /qqq' exited unsuccessfully (exit code: 2)
#   in block <unit> at /tmp/3169qXElwq line 1
#

=head2 method spawn

    method spawn(*@args ($, *@), :$cwd = $*CWD, Hash() :$env = %*ENV, :$arg0,
                 :$win-verbatim-args = False --> Bool:D)

Runs the C<Proc> object with the given command, argument list, working directory,
and environment.

If C<:arg0> is set to a value, that value is passed as arg0 to the process
instead of the program name.

On Windows the flag C<$win-verbatim-args> disables all automatic quoting of
process arguments. See L<this blog|https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way>
for more information on windows command quoting. The flag is ignored on all
other platforms. The flag was introduced in Rakudo version 2020.06 and is not
present in older releases.

=head2 method shell

    method shell($cmd, :$cwd = $*CWD, :$env --> Bool:D)

Runs the C<Proc> object with the given command and environment which are
passed through to the shell for parsing and execution. See
L<C<shell>|/language/independent-routines#sub_shell> for an explanation of which shells
are used by default in the most common operating systems.

=head2 method command

    method command(Proc:D: --> List:D)

The command method is an accessor to a list containing the arguments
that were passed when the Proc object was executed via C<spawn> or
C<shell> or C<run>.

=head2 method Bool

    multi method Bool(Proc:D:)

Awaits for the process to finish and returns C<True> if both exit code and signal
of the process were 0, indicating a successful process termination. Returns C<False> otherwise.

=head2 method pid

    method pid()

Returns the C<PID> value of the process if available, or C<Nil>.

=head2 method exitcode

    method exitcode(Proc:D: --> Int:D)

Returns the exit code of the external process, or -1 if it has not exited yet.

=head2 method signal

    method signal(Proc:D:)

Returns the signal number with which the external process was killed, or
C<0> or an undefined value otherwise.

=end pod

# vim: expandtab softtabstop=4 shiftwidth=4 ft=perl6
