package App::Beam::Command; use strict; use Carp; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(CHAN_STDOUT CHAN_STDERR); our %EXPORT_TAGS = ( channels => [ qw(CHAN_STDOUT CHAN_STDERR) ] ); use IPC::Open3; use Symbol 'gensym'; use POSIX qw(:sys_wait_h); sub new { my $class = shift; return bless { argv => \@_ }, $class; } sub add { my $self = shift; push @{$self->{argv}}, @_; } use constant { CHAN_STDOUT => 0, CHAN_STDERR => 1 }; sub start_log { my ($self, $chan, $rfd) = @_; if (exists($self->{log}) && defined($self->{log}[$chan])) { local $^F = fileno($rfd); my $pid = fork(); if ($pid == 0) { local $_ = $rfd; &{$self->{log}[CHAN_STDERR]}; exit(0); } else { push @{$self->{pids}}, $pid; return undef; } } return $rfd; } sub run { my $self = shift; my $out = gensym; my $err = gensym; return $self->{status} if exists($self->{status}); eval { push @{$self->{pids}}, open3(\*STDIN, $out, $err, @{$self->{argv}}); $out = $self->start_log(CHAN_STDOUT, $out); $err = $self->start_log(CHAN_STDERR, $err); do { my $pid = waitpid(-1, 0); if ($pid > 0) { @{$self->{pids}} = grep { $pid != $_ } @{$self->{pids}}; } } while ($#{$self->{pids}} >= 0); }; if ($@) { $self->{status} = $@; } else { $self->{status} = $?; local $/ = undef; $self->{channel}[CHAN_STDOUT] = defined($out) ? <$out> : undef; $self->{channel}[CHAN_STDERR] = defined($err) ? <$err> : undef; } return $self->{status}; } sub set_logger { my ($self, $chan, $fun) = @_; $self->{log}[$chan] = $fun; } sub restart { my $self = shift; delete $self->{status}; delete $self->{channel}; delete $self->{pids}; return $self->run; } sub status { my $self = shift; return undef unless exists $self->{status}; return $self->{status}; } sub channel { my ($self, $chan) = @_; return undef unless (exists($self->{channel}) && defined($self->{channel}[$chan])); open(my $fd, '<', \$self->{channel}[$chan]) or croak "can't dup: $!"; return $fd; } sub argv { my ($self) = @_; return @{$self->{argv}}; } sub command_line { my ($self) = @_; return join(' ', $self->argv); } sub command_name { my ($self) = @_; return $self->{argv}[0]; } sub exit_code { my ($self) = @_; my $code = $self->status; return -1 if (!defined($code) || $code == -1 || ($code & 127)); return $code >> 8; } 1;