File Coverage

blib/lib/GTM/Run.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             GTM::Run - run interactive processes
5              
6             =head1 SYNOPSIS
7              
8             use GTM::Run;
9              
10             my $hdl = new GTM::Run ("mumps -direct");
11             $hdl->expect(
12             qr/GTM\>/,
13             qr/^%.*/m,
14             sub {
15             die $_[1] if $_[2];
16             shift->write ("D ^\%GO\n");
17             }
18             );
19              
20             =head1 DESCRIPTION
21              
22             This module is a helper-module for running interactive
23             processes in a "expect"-like way.
24              
25             =head1 METHODS
26              
27             =over 4
28              
29             =cut
30              
31             package GTM::Run;
32 1     1   5843 use common::sense;
  1         2  
  1         8  
33 1     1   1891 use AnyEvent;
  1         6673  
  1         49  
34 1     1   1306 use AnyEvent::Util;
  1         15595  
  1         116  
35 1     1   1494 use AnyEvent::Handle;
  1         7255  
  1         40  
36 1     1   1061 use POSIX qw(setsid dup2 _exit waitpid);
  1         8368  
  1         7  
37 1     1   1024 use re 'eval';
  1         2  
  1         39  
38 1     1   61 use GTM qw(set_busy output %override);
  0            
  0            
39              
40             our $VERSION = $GTM::VERSION;
41             our $midx;
42              
43             =item $handle = B GTM::Run ($command)
44              
45             Creates a GTM::Run object.
46             The $command is either a single string, which is then passed to a shell, or an arrayref,
47             which is passed to the "execvp" function.
48             If command is not a fully qualified command (ie: starts not with /) $ENV{gtm_dist} will be prepended.
49              
50             =cut
51              
52             sub new {
53             my ($class, $cmd) = @_;
54             my $self = bless {@_}, $class;
55             if (ref $cmd eq "ARRAY") {
56             $cmd->[0] = "$ENV{gtm_dist}/$cmd->[0]" unless $cmd->[0] =~ m@^/@;
57             } else {
58             $cmd = "$ENV{gtm_dist}/$cmd" unless $cmd =~ m@^/@;
59             }
60              
61             my ($fh1, $fh2) = portable_socketpair;
62             my $pid = fork;
63             if (!defined $pid) {
64             die "can't fork: $!";
65             }
66             if (!$pid) {
67             setsid;
68             close $fh2;
69             dup2 (fileno $fh1, 0);
70             dup2 (fileno $fh1, 1);
71             dup2 (fileno $fh1, 2);
72             close $fh1;
73             local %ENV = (%ENV, %override);
74             ref $cmd
75             ? exec {$cmd->[0]} @$cmd
76             : exec $cmd;
77              
78             _exit (99);
79             }
80             my $hdl = new AnyEvent::Handle (
81             fh => $fh2,
82             no_delay => 1,
83             on_error => sub {
84             my ($hdl, $fatal, $msg) = @_;
85             die "on_error fatal=$fatal msg=\"$msg\"\n";
86             $hdl->destroy;
87             },
88              
89             );
90             $self->{pid} = $pid;
91             $self->{hdl} = $hdl;
92             set_busy (1);
93             $self;
94             }
95              
96             sub merge_regexp (@) {
97             my @re = @_;
98             @re = map { qr{(?:$_(?{$GTM::Run::midx= mArK;}))}x } @re;
99             my $r = join "|", @re;
100             $r =~ s/mArK/$_/ for (0 .. @re - 1);
101             $r;
102             }
103              
104             =item $handle->B ()
105              
106             Closes the command. This runs waitpid so be sure that your command will terminate.
107             For mumps this means that "Halt\n" must be written before.
108              
109             =cut
110              
111             sub close ($) {
112             my $self = shift;
113             my $hdl = $self->{hdl};
114             die "already closed" if $self->{closed};
115             $hdl->on_eof (undef);
116             $hdl->on_error (sub { });
117             $hdl->on_read (sub { });
118             $self->flush;
119             $hdl->destroy;
120             waitpid ($self->{pid}, 0) if kill (0, $self->{pid});
121             set_busy (0);
122             $self->{closed} = 1;
123              
124             }
125              
126             =item $handle->B ($data, ...)
127              
128             writes $data to the process
129              
130             =cut
131              
132             sub write ($@) {
133             my $self = shift;
134             my $hdl = $self->{hdl};
135             $hdl->push_write (join "", @_);
136             }
137              
138             our $expect_debug = 0;
139              
140             =item $handle->B ($bool)
141              
142             writes regular expression debug-info to STDERR if enabled.
143             Here an example:
144              
145             $self->expect(
146             qr/^No globals selected/m,
147             qr/^Header Label:/m,
148             sub {
149             ...
150             },
151             );
152              
153             This writes:
154              
155             RE: (?m-xis:^No globals selected) == 0
156             RE: (?m-xis:^Header Label:) == 1
157             RE: match index == 1
158              
159             if debugging is enabled.
160              
161             =cut
162              
163             sub debug ($$) {
164             $expect_debug = !!$_[1];
165             }
166              
167             =item $handle->B ($re0, [$re1,...,] &cb [, $re .... &cb])
168              
169             Waits for input that matches one of the given regular expressions.
170             &cb will be invoked with three arguments: $class, $data, $reidx.
171             $reidx is the index of the regular expression that matched.
172              
173             A callback may die - B will be invoked and the die
174             gets propagated.
175             Subsequent callbacks within the same expect-call will be skipped.
176              
177             =cut
178              
179             sub expect($@) {
180             my $self = shift;
181             my $hdl = $self->{hdl};
182             my @re;
183             my $mre;
184             my $done;
185             my $die;
186             for my $i (@_) {
187             if (ref $i eq "Regexp") {
188             push @re, $i;
189             next;
190             }
191             die "expected code-ref or regexp" if (ref $i ne "CODE");
192             my $mre = merge_regexp (@re);
193             my @xre = @re;
194             my $cv = AnyEvent->condvar;
195             local $midx;
196             $hdl->push_read (
197             regex => $mre,
198             sub {
199             return if $done;
200             if ($expect_debug) {
201             for (my $i = 0 ; $i < @xre ; $i++) {
202             print STDERR "RE: $xre[$i] == $i\n";
203             }
204             print STDERR "RE: match index == $midx\n\n";
205             }
206             eval { $i->($self, $_[1], $midx); };
207             if ($@) {
208             $done = 1;
209             $die = $@;
210             $self->close;
211             }
212             $cv->send;
213             },
214             );
215             @re = ();
216              
217             $cv->recv;
218             die $die if $die;
219             }
220             }
221              
222             sub DESTROY {
223             my $hdl = shift;
224             $hdl->close unless $hdl->{closed};
225             }
226              
227             =item $hdl->B ()
228              
229             Waits until the output buffer is empty.
230              
231             =cut
232              
233             sub flush ($) {
234             my $self = shift;
235             my $hdl = $self->{hdl};
236             my $cv = AnyEvent->condvar;
237             $hdl->on_drain (sub { $cv->send });
238             $cv->recv;
239              
240             }
241              
242             =back
243              
244             =head1 SEE ALSO
245              
246             L
247              
248             =head1 AUTHOR
249              
250             Stefan Traby
251             http://oesiman.de/gt.m/
252              
253             =cut
254              
255             1;
256