File Coverage

blib/lib/IO/AsyncX/System.pm
Criterion Covered Total %
statement 47 50 94.0
branch 17 24 70.8
condition n/a
subroutine 13 15 86.6
pod 1 3 33.3
total 78 92 84.7


line stmt bran cond sub pod time code
1             package IO::AsyncX::System;
2             # ABSTRACT: system() in background for IO::Async
3 1     1   58520 use strict;
  1         1  
  1         27  
4 1     1   3 use warnings;
  1         2  
  1         20  
5              
6 1     1   334 use parent qw(IO::Async::Notifier);
  1         206  
  1         5  
7              
8             our $VERSION = '0.003';
9              
10             =head1 NAME
11              
12             IO::AsyncX::System - fork+exec, capturing STDOUT/STDERR
13              
14             =head1 VERSION
15              
16             version 0.003
17              
18             =head1 SYNOPSIS
19              
20             use feature qw(say);
21             use IO::Async::Loop;
22             use IO::AsyncX::System;
23             my $loop = IO::Async::Loop->new;
24             $loop->add(
25             my $system = IO::AsyncX::System->new
26             );
27             my ($code, $stdout, $stderr) = $system->run([qw(ls)])->get;
28             say for @$stdout;
29              
30             =head1 DESCRIPTION
31              
32             Intended as a replacement for L in L-using code.
33             Provides a single L method which will fork+exec (via L),
34             capturing STDOUT/STDERR, and returning a L holding the exit code and output.
35              
36             =cut
37              
38 1     1   8615 use curry;
  1         134  
  1         19  
39 1     1   4 use Future;
  1         1  
  1         15  
40 1     1   405 use Encode qw(decode_utf8);
  1         6364  
  1         61  
41 1     1   388 use IO::Async::Process;
  1         2047  
  1         327  
42              
43             =head1 METHODS
44              
45             =cut
46              
47             =head2 run
48              
49             Takes a single parameter defining the command to run:
50              
51             $system->run(['ls']);
52              
53             plus optional named parameters:
54              
55             =over 4
56              
57             =item * utf8 - interprets all input/output as UTF-8, so STDOUT/STDERR will be returned as arrayrefs containing Perl strings rather than raw bytes
58              
59             =item * binary - the reverse of utf8 (and the default)
60              
61             =item * stdin - an arrayref of data to pass as STDIN
62              
63             =item * timeout - kill the process if it doesn't complete within this many seconds
64              
65             =back
66              
67             Returns a L which resolves to the exit code, STDOUT and STDERR arrayrefs:
68              
69             $system->run([...]) ==> ($exitcode, $stdout_arrayref, $stderr_arrayref)
70              
71             STDIN/STDOUT/STDERR are arrayrefs split by line. In binary mode, they will hold a single element each.
72              
73             =cut
74              
75             sub run {
76 5     5 1 18598 my ($self, $cmd, %args) = @_;
77 5         9 my $stdout = [];
78 5         8 my $stderr = [];
79 5         10 my $stdin = [];
80 0         0 my $stdin_def = {
81             (
82             # Allow both ['x','y'] and "x\ny" as input, although we only document the former
83             defined($args{stdin})
84 5 50       33 ? (from => ref($args{stdin}) ? join "\n", @{delete $args{stdin}} : $args{stdin})
    100          
85             : (from => '')
86             ),
87             };
88 5 100       54 my $stdout_def = {
89             on_read => (
90             $args{utf8}
91             ? $self->curry::read_utf8($stdout)
92             : $self->curry::read_binary($stdout)
93             ),
94             };
95 5 100       88 my $stderr_def = {
96             on_read => (
97             $args{utf8}
98             ? $self->curry::read_utf8($stderr)
99             : $self->curry::read_binary($stderr)
100             ),
101             };
102 5         51 my $f = $self->loop->new_future;
103             my $proc = IO::Async::Process->new(
104             command => $cmd,
105             stdin => $stdin_def,
106             stdout => $stdout_def,
107             stderr => $stderr_def,
108 4 50   4   1160 on_finish => sub { $f->done($_[1], $stdout, $stderr) unless $f->is_ready },
109 0 0   0   0 on_exception => sub { $f->fail($_[1]) unless $f->is_ready },
110 5         865 );
111 5         777 $self->add_child($proc);
112 5     5   28502 $f->on_ready(sub { $self->remove_child($proc) });
  5         253  
113 5 100       192 return $f unless $args{timeout};
114             Future->wait_any(
115             $f,
116             $self->loop->timeout_future(after => $args{timeout})->on_fail(
117 1 50   1   502097 sub { $proc->kill(9) if $proc->is_running }
118             )
119 1 0   0   15 )->on_cancel(sub { $f->cancel unless $f->is_ready });
  0         0  
120             }
121              
122             sub read_binary {
123 6     6 0 3964 my ($self, $target, $stream, $buf, $eof) = @_;
124 6 100       16 push @$target, '' unless @$target;
125 6         8 $target->[0] .= $$buf;
126 6         9 $$buf = '';
127 6         15 0
128             }
129              
130             sub read_utf8 {
131 6     6 0 17729 my ($self, $target, $stream, $buf, $eof) = @_;
132 6         29 push @$target, decode_utf8($1) while $$buf =~ s/^(.*)\n//;
133 6 100       34 return 0 unless length $$buf;
134 4 100       29 push @$target, decode_utf8($$buf) if $eof;
135 4         110 0
136             }
137              
138             1;
139              
140             __END__