File Coverage

lib/IPC/Open3/Simple.pm
Criterion Covered Total %
statement 62 63 98.4
branch 18 20 90.0
condition 5 9 55.5
subroutine 9 9 100.0
pod 2 2 100.0
total 96 103 93.2


line stmt bran cond sub pod time code
1             #################################################################
2             #
3             # IPC::Open3::Simple - A simple alternative to IPC::Open3
4             #
5             # $Id: Simple.pm,v 1.7 2006/07/20 13:30:02 erwan Exp $
6             #
7             # 060714 erwan Created
8             #
9             #################################################################
10              
11 1     1   100502 use strict;
  1         3  
  1         44  
12 1     1   6 use warnings;
  1         2  
  1         50  
13              
14             package IPC::Open3::Simple;
15              
16 1     1   5 use Carp qw(croak confess);
  1         2  
  1         67  
17 1     1   6 use IPC::Open3;
  1         2  
  1         50  
18 1     1   6 use IO::Select;
  1         2  
  1         30  
19 1     1   6 use IO::Handle;
  1         3  
  1         38  
20 1     1   6 use Data::Dumper;
  1         2  
  1         692  
21              
22             our $VERSION = '0.04';
23              
24             #-----------------------------------------------------------------
25             #
26             # new - constructor. takes a hash where keys are in, out and err
27             # and values are closures/coderefs
28             #
29              
30             sub new {
31 6     6 1 12886 my($pkg,%args) = @_;
32 6   33     61 $pkg = ref $pkg || $pkg;
33 6         175 my $self = bless({},$pkg);
34              
35 6         21 foreach my $type ('in','out','err') {
36 17 100       60 if (exists $args{$type}) {
37 7 100       790 croak "".__PACKAGE__."::new expects coderefs" if (ref $args{$type} ne 'CODE');
38 6         102 $self->{$type} = $args{$type};
39             }
40             }
41            
42 5         27 return $self;
43             }
44              
45             #-----------------------------------------------------------------
46             #
47             # run - execute a list of shell commands in a separate process
48             # and redirect input/output to the closures provided to new()
49             #
50              
51             sub run {
52 7     7 1 1204 my($self,@args) = @_;
53            
54             # note: in theory, it should work to write:
55             # my $pid = open3($child_in, $child_out, $child_err, @arguments)
56             # but that does not work (bug?). $child_err is then undefined
57             # (in perldoc for open2, the explanation is that stderr=stdout if $child_out == $child_err, which they do when they are both undefined)
58              
59             # TODO: support interactive ipc with child process?
60            
61 7 50       146 my $pid = open3(\*CHILD_IN, \*CHILD_OUT, \*CHILD_ERR, @args)
62             or confess "ERROR: failed to execute command [".join(" ",@args)."]";
63              
64 7         65161 my $reader = IO::Select->new();
65              
66 7         161 my $child_in = \*CHILD_IN;
67 7         18 my $child_out = \*CHILD_OUT;
68 7         34 my $child_err = \*CHILD_ERR;
69             # $child_in->autoflush; IPC::Open3 does it already
70 7         119 $child_out->autoflush;
71 7         903 $child_err->autoflush;
72              
73             # listen to stdout and stderr, or close them
74 7 100       240 if (exists $self->{out}) {
75 3         25 $reader->add($child_out);
76             } else {
77 4         50 $child_out->close();
78             }
79              
80 7 100       325 if (exists $self->{err}) {
81 3         25 $reader->add($child_err);
82             } else {
83 4         25 $child_err->close();
84             }
85              
86             # forward stdin to provided function, or close it
87 7 100       192 if (exists $self->{in}) {
88 3         14 &{$self->{in}}($child_in);
  3         2551  
89             } else {
90 4         19 $child_in->close();
91             }
92              
93             # parse output of cvs command
94 7 100       5509 if ($reader->handles) {
95 4         151 while (my @ready = $reader->can_read()) {
96 10         7934 foreach my $fh (@ready) {
97 14         12438 my $line = <$fh>;
98 14 100       62 if (!defined $line) {
99             # reached EOF on this filehandle
100 6         33 $reader->remove($fh);
101 6         311 $fh->close();
102             } else {
103 8         23 chomp $line;
104 8 100 100     80 if ($child_out->opened && fileno($fh) == fileno(\*CHILD_OUT)) {
    50 33        
105 4         121 &{$self->{out}}($line);
  4         126  
106             } elsif ($child_err->opened && fileno($fh) == fileno(\*CHILD_ERR)) {
107 4         188 &{$self->{err}}($line);
  4         27  
108             } else {
109 0         0 confess "BUG: got an unexpected filehandle:".Dumper($fh);
110             }
111             }
112             }
113             }
114             }
115              
116             # wait for child process to die
117 7         89269 waitpid($pid, 0);
118              
119 7         414 return $self;
120             }
121              
122             1;
123              
124             __END__