GTM

 view release on metacpan or  search on metacpan

lib/GTM/Run.pm  view on Meta::CPAN


=head1 NAME

GTM::Run - run interactive processes 

=head1 SYNOPSIS

   use GTM::Run;

   my $hdl = new GTM::Run ("mumps -direct");
   $hdl->expect(
        qr/GTM\>/,
        qr/^%.*/m,
        sub {
            die $_[1] if $_[2];
            shift->write ("D ^\%GO\n");
        }
   );

=head1 DESCRIPTION

This module is a helper-module for running interactive
processes in a "expect"-like way.

=head1 METHODS

=over 4

=cut

package GTM::Run;
use common::sense;
use AnyEvent;
use AnyEvent::Util;
use AnyEvent::Handle;
use POSIX qw(setsid dup2 _exit waitpid);
use re 'eval';
use GTM qw(set_busy output %override);

our $VERSION = $GTM::VERSION;
our $midx;

=item $handle = B<new> GTM::Run ($command)

Creates a GTM::Run object.
The $command is either a single string, which is then passed to a shell, or an arrayref,
which is passed to the "execvp" function.
If command is not a fully qualified command (ie: starts not with /) $ENV{gtm_dist} will be prepended.

=cut 

sub new {
    my ($class, $cmd) = @_;
    my $self = bless {@_}, $class;
    if (ref $cmd eq "ARRAY") {
        $cmd->[0] = "$ENV{gtm_dist}/$cmd->[0]" unless $cmd->[0] =~ m@^/@;
    } else {
        $cmd = "$ENV{gtm_dist}/$cmd" unless $cmd =~ m@^/@;
    }

    my ($fh1, $fh2) = portable_socketpair;
    my $pid = fork;
    if (!defined $pid) {
        die "can't fork: $!";
    }
    if (!$pid) {
        setsid;
        close $fh2;
        dup2 (fileno $fh1, 0);
        dup2 (fileno $fh1, 1);
        dup2 (fileno $fh1, 2);
        close $fh1;
        local %ENV = (%ENV, %override);
        ref $cmd
          ? exec {$cmd->[0]} @$cmd
          : exec $cmd;

        _exit (99);
    }
    my $hdl = new AnyEvent::Handle (
        fh       => $fh2,
        no_delay => 1,
        on_error => sub {
            my ($hdl, $fatal, $msg) = @_;
            die "on_error fatal=$fatal msg=\"$msg\"\n";
            $hdl->destroy;
        },

    );
    $self->{pid} = $pid;
    $self->{hdl} = $hdl;
    set_busy (1);
    $self;
}

sub merge_regexp (@) {
    my @re = @_;
    @re = map { qr{(?:$_(?{$GTM::Run::midx= mArK;}))}x } @re;
    my $r = join "|", @re;
    $r =~ s/mArK/$_/ for (0 .. @re - 1);
    $r;
}

=item $handle->B<close> ()

Closes the command. This runs waitpid so be sure that your command will terminate.
For mumps this means that "Halt\n" must be written before.

=cut

sub close ($) {
    my $self = shift;
    my $hdl  = $self->{hdl};
    die "already closed" if $self->{closed};
    $hdl->on_eof   (undef);
    $hdl->on_error (sub { });
    $hdl->on_read  (sub { });
    $self->flush;
    $hdl->destroy;
    waitpid ($self->{pid}, 0) if kill (0, $self->{pid});
    set_busy (0);
    $self->{closed} = 1;

}

=item $handle->B<write> ($data, ...)

writes $data to the process

=cut

sub write ($@) {
    my $self = shift;
    my $hdl  = $self->{hdl};
    $hdl->push_write (join "", @_);
}

our $expect_debug = 0;

=item $handle->B<debug> ($bool)

writes regular expression debug-info to STDERR if enabled.
Here an example:

    $self->expect(
        qr/^No globals selected/m,
        qr/^Header Label:/m,
        sub {
           ...
        },
   );

This writes:

  RE: (?m-xis:^No globals selected) == 0
  RE: (?m-xis:^Header Label:) == 1
  RE: match index == 1

if debugging is enabled.

=cut

sub debug ($$) {
    $expect_debug = !!$_[1];
}

=item $handle->B<expect> ($re0, [$re1,...,] &cb [, $re .... &cb])

Waits for input that matches one of the given regular expressions.
&cb will be invoked with three arguments: $class, $data, $reidx.
$reidx is the index of the regular expression that matched.

A callback may die - B<close> will be invoked and the die
gets propagated.
Subsequent callbacks within the same expect-call will be skipped. 

=cut

sub expect($@) {
    my $self = shift;
    my $hdl  = $self->{hdl};



( run in 1.591 second using v1.01-cache-2.11-cpan-99c4e6809bf )