Blame lib/Capture/Tiny.pm

Packit d18d0a
use 5.006;
Packit d18d0a
use strict;
Packit d18d0a
use warnings;
Packit d18d0a
package Capture::Tiny;
Packit d18d0a
# ABSTRACT: Capture STDOUT and STDERR from Perl, XS or external programs
Packit d18d0a
our $VERSION = '0.46';
Packit d18d0a
use Carp ();
Packit d18d0a
use Exporter ();
Packit d18d0a
use IO::Handle ();
Packit d18d0a
use File::Spec ();
Packit d18d0a
use File::Temp qw/tempfile tmpnam/;
Packit d18d0a
use Scalar::Util qw/reftype blessed/;
Packit d18d0a
# Get PerlIO or fake it
Packit d18d0a
BEGIN {
Packit d18d0a
  local $@;
Packit d18d0a
  eval { require PerlIO; PerlIO->can('get_layers') }
Packit d18d0a
    or *PerlIO::get_layers = sub { return () };
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
#--------------------------------------------------------------------------#
Packit d18d0a
# create API subroutines and export them
Packit d18d0a
# [do STDOUT flag, do STDERR flag, do merge flag, do tee flag]
Packit d18d0a
#--------------------------------------------------------------------------#
Packit d18d0a
Packit d18d0a
my %api = (
Packit d18d0a
  capture         => [1,1,0,0],
Packit d18d0a
  capture_stdout  => [1,0,0,0],
Packit d18d0a
  capture_stderr  => [0,1,0,0],
Packit d18d0a
  capture_merged  => [1,1,1,0],
Packit d18d0a
  tee             => [1,1,0,1],
Packit d18d0a
  tee_stdout      => [1,0,0,1],
Packit d18d0a
  tee_stderr      => [0,1,0,1],
Packit d18d0a
  tee_merged      => [1,1,1,1],
Packit d18d0a
);
Packit d18d0a
Packit d18d0a
for my $sub ( keys %api ) {
Packit d18d0a
  my $args = join q{, }, @{$api{$sub}};
Packit d18d0a
  eval "sub $sub(&;@) {unshift \@_, $args; goto \\&_capture_tee;}"; ## no critic
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
our @ISA = qw/Exporter/;
Packit d18d0a
our @EXPORT_OK = keys %api;
Packit d18d0a
our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK );
Packit d18d0a
Packit d18d0a
#--------------------------------------------------------------------------#
Packit d18d0a
# constants and fixtures
Packit d18d0a
#--------------------------------------------------------------------------#
Packit d18d0a
Packit d18d0a
my $IS_WIN32 = $^O eq 'MSWin32';
Packit d18d0a
Packit d18d0a
##our $DEBUG = $ENV{PERL_CAPTURE_TINY_DEBUG};
Packit d18d0a
##
Packit d18d0a
##my $DEBUGFH;
Packit d18d0a
##open $DEBUGFH, "> DEBUG" if $DEBUG;
Packit d18d0a
##
Packit d18d0a
##*_debug = $DEBUG ? sub(@) { print {$DEBUGFH} @_ } : sub(){0};
Packit d18d0a
Packit d18d0a
our $TIMEOUT = 30;
Packit d18d0a
Packit d18d0a
#--------------------------------------------------------------------------#
Packit d18d0a
# command to tee output -- the argument is a filename that must
Packit d18d0a
# be opened to signal that the process is ready to receive input.
Packit d18d0a
# This is annoying, but seems to be the best that can be done
Packit d18d0a
# as a simple, portable IPC technique
Packit d18d0a
#--------------------------------------------------------------------------#
Packit d18d0a
my @cmd = ($^X, '-C0', '-e', <<'HERE');
Packit d18d0a
use Fcntl;
Packit d18d0a
$SIG{HUP}=sub{exit};
Packit d18d0a
if ( my $fn=shift ) {
Packit d18d0a
    sysopen(my $fh, qq{$fn}, O_WRONLY|O_CREAT|O_EXCL) or die $!;
Packit d18d0a
    print {$fh} $$;
Packit d18d0a
    close $fh;
Packit d18d0a
}
Packit d18d0a
my $buf; while (sysread(STDIN, $buf, 2048)) {
Packit d18d0a
    syswrite(STDOUT, $buf); syswrite(STDERR, $buf);
Packit d18d0a
}
Packit d18d0a
HERE
Packit d18d0a
Packit d18d0a
#--------------------------------------------------------------------------#
Packit d18d0a
# filehandle manipulation
Packit d18d0a
#--------------------------------------------------------------------------#
Packit d18d0a
Packit d18d0a
sub _relayer {
Packit d18d0a
  my ($fh, $apply_layers) = @_;
Packit d18d0a
  # _debug("# requested layers (@{$layers}) for @{[fileno $fh]}\n");
Packit d18d0a
Packit d18d0a
  # eliminate pseudo-layers
Packit d18d0a
  binmode( $fh, ":raw" );
Packit d18d0a
  # strip off real layers until only :unix is left
Packit d18d0a
  while ( 1 < ( my $layers =()= PerlIO::get_layers( $fh, output => 1 ) ) ) {
Packit d18d0a
      binmode( $fh, ":pop" );
Packit d18d0a
  }
Packit d18d0a
  # apply other layers
Packit d18d0a
  my @to_apply = @$apply_layers;
Packit d18d0a
  shift @to_apply; # eliminate initial :unix
Packit d18d0a
  # _debug("# applying layers  (unix @to_apply) to @{[fileno $fh]}\n");
Packit d18d0a
  binmode($fh, ":" . join(":",@to_apply));
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
sub _name {
Packit d18d0a
  my $glob = shift;
Packit d18d0a
  no strict 'refs'; ## no critic
Packit d18d0a
  return *{$glob}{NAME};
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
sub _open {
Packit d18d0a
  open $_[0], $_[1] or Carp::confess "Error from open(" . join(q{, }, @_) . "): $!";
Packit d18d0a
  # _debug( "# open " . join( ", " , map { defined $_ ? _name($_) : 'undef' } @_ ) . " as " . fileno( $_[0] ) . "\n" );
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
sub _close {
Packit d18d0a
  # _debug( "# closing " . ( defined $_[0] ? _name($_[0]) : 'undef' )  . " on " . fileno( $_[0] ) . "\n" );
Packit d18d0a
  close $_[0] or Carp::confess "Error from close(" . join(q{, }, @_) . "): $!";
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
my %dup; # cache this so STDIN stays fd0
Packit d18d0a
my %proxy_count;
Packit d18d0a
sub _proxy_std {
Packit d18d0a
  my %proxies;
Packit d18d0a
  if ( ! defined fileno STDIN ) {
Packit d18d0a
    $proxy_count{stdin}++;
Packit d18d0a
    if (defined $dup{stdin}) {
Packit d18d0a
      _open \*STDIN, "<&=" . fileno($dup{stdin});
Packit d18d0a
      # _debug( "# restored proxy STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" );
Packit d18d0a
    }
Packit d18d0a
    else {
Packit d18d0a
      _open \*STDIN, "<" . File::Spec->devnull;
Packit d18d0a
      # _debug( "# proxied STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" );
Packit d18d0a
      _open $dup{stdin} = IO::Handle->new, "<&=STDIN";
Packit d18d0a
    }
Packit d18d0a
    $proxies{stdin} = \*STDIN;
Packit d18d0a
    binmode(STDIN, ':utf8') if $] >= 5.008; ## no critic
Packit d18d0a
  }
Packit d18d0a
  if ( ! defined fileno STDOUT ) {
Packit d18d0a
    $proxy_count{stdout}++;
Packit d18d0a
    if (defined $dup{stdout}) {
Packit d18d0a
      _open \*STDOUT, ">&=" . fileno($dup{stdout});
Packit d18d0a
      # _debug( "# restored proxy STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" );
Packit d18d0a
    }
Packit d18d0a
    else {
Packit d18d0a
      _open \*STDOUT, ">" . File::Spec->devnull;
Packit d18d0a
       # _debug( "# proxied STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" );
Packit d18d0a
      _open $dup{stdout} = IO::Handle->new, ">&=STDOUT";
Packit d18d0a
    }
Packit d18d0a
    $proxies{stdout} = \*STDOUT;
Packit d18d0a
    binmode(STDOUT, ':utf8') if $] >= 5.008; ## no critic
Packit d18d0a
  }
Packit d18d0a
  if ( ! defined fileno STDERR ) {
Packit d18d0a
    $proxy_count{stderr}++;
Packit d18d0a
    if (defined $dup{stderr}) {
Packit d18d0a
      _open \*STDERR, ">&=" . fileno($dup{stderr});
Packit d18d0a
       # _debug( "# restored proxy STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" );
Packit d18d0a
    }
Packit d18d0a
    else {
Packit d18d0a
      _open \*STDERR, ">" . File::Spec->devnull;
Packit d18d0a
       # _debug( "# proxied STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" );
Packit d18d0a
      _open $dup{stderr} = IO::Handle->new, ">&=STDERR";
Packit d18d0a
    }
Packit d18d0a
    $proxies{stderr} = \*STDERR;
Packit d18d0a
    binmode(STDERR, ':utf8') if $] >= 5.008; ## no critic
Packit d18d0a
  }
Packit d18d0a
  return %proxies;
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
sub _unproxy {
Packit d18d0a
  my (%proxies) = @_;
Packit d18d0a
  # _debug( "# unproxying: " . join(" ", keys %proxies) . "\n" );
Packit d18d0a
  for my $p ( keys %proxies ) {
Packit d18d0a
    $proxy_count{$p}--;
Packit d18d0a
    # _debug( "# unproxied " . uc($p) . " ($proxy_count{$p} left)\n" );
Packit d18d0a
    if ( ! $proxy_count{$p} ) {
Packit d18d0a
      _close $proxies{$p};
Packit d18d0a
      _close $dup{$p} unless $] < 5.008; # 5.6 will have already closed this as dup
Packit d18d0a
      delete $dup{$p};
Packit d18d0a
    }
Packit d18d0a
  }
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
sub _copy_std {
Packit d18d0a
  my %handles;
Packit d18d0a
  for my $h ( qw/stdout stderr stdin/ ) {
Packit d18d0a
    next if $h eq 'stdin' && ! $IS_WIN32; # WIN32 hangs on tee without STDIN copied
Packit d18d0a
    my $redir = $h eq 'stdin' ? "<&" : ">&";
Packit d18d0a
    _open $handles{$h} = IO::Handle->new(), $redir . uc($h); # ">&STDOUT" or "<&STDIN"
Packit d18d0a
  }
Packit d18d0a
  return \%handles;
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
# In some cases we open all (prior to forking) and in others we only open
Packit d18d0a
# the output handles (setting up redirection)
Packit d18d0a
sub _open_std {
Packit d18d0a
  my ($handles) = @_;
Packit d18d0a
  _open \*STDIN, "<&" . fileno $handles->{stdin} if defined $handles->{stdin};
Packit d18d0a
  _open \*STDOUT, ">&" . fileno $handles->{stdout} if defined $handles->{stdout};
Packit d18d0a
  _open \*STDERR, ">&" . fileno $handles->{stderr} if defined $handles->{stderr};
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
#--------------------------------------------------------------------------#
Packit d18d0a
# private subs
Packit d18d0a
#--------------------------------------------------------------------------#
Packit d18d0a
Packit d18d0a
sub _start_tee {
Packit d18d0a
  my ($which, $stash) = @_; # $which is "stdout" or "stderr"
Packit d18d0a
  # setup pipes
Packit d18d0a
  $stash->{$_}{$which} = IO::Handle->new for qw/tee reader/;
Packit d18d0a
  pipe $stash->{reader}{$which}, $stash->{tee}{$which};
Packit d18d0a
  # _debug( "# pipe for $which\: " .  _name($stash->{tee}{$which}) . " " . fileno( $stash->{tee}{$which} ) . " => " . _name($stash->{reader}{$which}) . " " . fileno( $stash->{reader}{$which}) . "\n" );
Packit d18d0a
  select((select($stash->{tee}{$which}), $|=1)[0]); # autoflush
Packit d18d0a
  # setup desired redirection for parent and child
Packit d18d0a
  $stash->{new}{$which} = $stash->{tee}{$which};
Packit d18d0a
  $stash->{child}{$which} = {
Packit d18d0a
    stdin   => $stash->{reader}{$which},
Packit d18d0a
    stdout  => $stash->{old}{$which},
Packit d18d0a
    stderr  => $stash->{capture}{$which},
Packit d18d0a
  };
Packit d18d0a
  # flag file is used to signal the child is ready
Packit d18d0a
  $stash->{flag_files}{$which} = scalar tmpnam();
Packit d18d0a
  # execute @cmd as a separate process
Packit d18d0a
  if ( $IS_WIN32 ) {
Packit d18d0a
    my $old_eval_err=$@;
Packit d18d0a
    undef $@;
Packit d18d0a
Packit d18d0a
    eval "use Win32API::File qw/GetOsFHandle SetHandleInformation fileLastError HANDLE_FLAG_INHERIT INVALID_HANDLE_VALUE/ ";
Packit d18d0a
    # _debug( "# Win32API::File loaded\n") unless $@;
Packit d18d0a
    my $os_fhandle = GetOsFHandle( $stash->{tee}{$which} );
Packit d18d0a
    # _debug( "# Couldn't get OS handle: " . fileLastError() . "\n") if ! defined $os_fhandle || $os_fhandle == INVALID_HANDLE_VALUE();
Packit d18d0a
    my $result = SetHandleInformation( $os_fhandle, HANDLE_FLAG_INHERIT(), 0);
Packit d18d0a
    # _debug( $result ? "# set no-inherit flag on $which tee\n" : ("# can't disable tee handle flag inherit: " . fileLastError() . "\n"));
Packit d18d0a
    _open_std( $stash->{child}{$which} );
Packit d18d0a
    $stash->{pid}{$which} = system(1, @cmd, $stash->{flag_files}{$which});
Packit d18d0a
    # not restoring std here as it all gets redirected again shortly anyway
Packit d18d0a
    $@=$old_eval_err;
Packit d18d0a
  }
Packit d18d0a
  else { # use fork
Packit d18d0a
    _fork_exec( $which, $stash );
Packit d18d0a
  }
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
sub _fork_exec {
Packit d18d0a
  my ($which, $stash) = @_; # $which is "stdout" or "stderr"
Packit d18d0a
  my $pid = fork;
Packit d18d0a
  if ( not defined $pid ) {
Packit d18d0a
    Carp::confess "Couldn't fork(): $!";
Packit d18d0a
  }
Packit d18d0a
  elsif ($pid == 0) { # child
Packit d18d0a
    # _debug( "# in child process ...\n" );
Packit d18d0a
    untie *STDIN; untie *STDOUT; untie *STDERR;
Packit d18d0a
    _close $stash->{tee}{$which};
Packit d18d0a
    # _debug( "# redirecting handles in child ...\n" );
Packit d18d0a
    _open_std( $stash->{child}{$which} );
Packit d18d0a
    # _debug( "# calling exec on command ...\n" );
Packit d18d0a
    exec @cmd, $stash->{flag_files}{$which};
Packit d18d0a
  }
Packit d18d0a
  $stash->{pid}{$which} = $pid
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
my $have_usleep = eval "use Time::HiRes 'usleep'; 1";
Packit d18d0a
sub _files_exist {
Packit d18d0a
  return 1 if @_ == grep { -f } @_;
Packit d18d0a
  Time::HiRes::usleep(1000) if $have_usleep;
Packit d18d0a
  return 0;
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
sub _wait_for_tees {
Packit d18d0a
  my ($stash) = @_;
Packit d18d0a
  my $start = time;
Packit d18d0a
  my @files = values %{$stash->{flag_files}};
Packit d18d0a
  my $timeout = defined $ENV{PERL_CAPTURE_TINY_TIMEOUT}
Packit d18d0a
              ? $ENV{PERL_CAPTURE_TINY_TIMEOUT} : $TIMEOUT;
Packit d18d0a
  1 until _files_exist(@files) || ($timeout && (time - $start > $timeout));
Packit d18d0a
  Carp::confess "Timed out waiting for subprocesses to start" if ! _files_exist(@files);
Packit d18d0a
  unlink $_ for @files;
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
sub _kill_tees {
Packit d18d0a
  my ($stash) = @_;
Packit d18d0a
  if ( $IS_WIN32 ) {
Packit d18d0a
    # _debug( "# closing handles\n");
Packit d18d0a
    close($_) for values %{ $stash->{tee} };
Packit d18d0a
    # _debug( "# waiting for subprocesses to finish\n");
Packit d18d0a
    my $start = time;
Packit d18d0a
    1 until wait == -1 || (time - $start > 30);
Packit d18d0a
  }
Packit d18d0a
  else {
Packit d18d0a
    _close $_ for values %{ $stash->{tee} };
Packit d18d0a
    waitpid $_, 0 for values %{ $stash->{pid} };
Packit d18d0a
  }
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
sub _slurp {
Packit d18d0a
  my ($name, $stash) = @_;
Packit d18d0a
  my ($fh, $pos) = map { $stash->{$_}{$name} } qw/capture pos/;
Packit d18d0a
  # _debug( "# slurping captured $name from " . fileno($fh) . " at pos $pos with layers: @{[PerlIO::get_layers($fh)]}\n");
Packit d18d0a
  seek( $fh, $pos, 0 ) or die "Couldn't seek on capture handle for $name\n";
Packit d18d0a
  my $text = do { local $/; scalar readline $fh };
Packit d18d0a
  return defined($text) ? $text : "";
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
#--------------------------------------------------------------------------#
Packit d18d0a
# _capture_tee() -- generic main sub for capturing or teeing
Packit d18d0a
#--------------------------------------------------------------------------#
Packit d18d0a
Packit d18d0a
sub _capture_tee {
Packit d18d0a
  # _debug( "# starting _capture_tee with (@_)...\n" );
Packit d18d0a
  my ($do_stdout, $do_stderr, $do_merge, $do_tee, $code, @opts) = @_;
Packit d18d0a
  my %do = ($do_stdout ? (stdout => 1) : (),  $do_stderr ? (stderr => 1) : ());
Packit d18d0a
  Carp::confess("Custom capture options must be given as key/value pairs\n")
Packit d18d0a
    unless @opts % 2 == 0;
Packit d18d0a
  my $stash = { capture => { @opts } };
Packit d18d0a
  for ( keys %{$stash->{capture}} ) {
Packit d18d0a
    my $fh = $stash->{capture}{$_};
Packit d18d0a
    Carp::confess "Custom handle for $_ must be seekable\n"
Packit d18d0a
      unless ref($fh) eq 'GLOB' || (blessed($fh) && $fh->isa("IO::Seekable"));
Packit d18d0a
  }
Packit d18d0a
  # save existing filehandles and setup captures
Packit d18d0a
  local *CT_ORIG_STDIN  = *STDIN ;
Packit d18d0a
  local *CT_ORIG_STDOUT = *STDOUT;
Packit d18d0a
  local *CT_ORIG_STDERR = *STDERR;
Packit d18d0a
  # find initial layers
Packit d18d0a
  my %layers = (
Packit d18d0a
    stdin   => [PerlIO::get_layers(\*STDIN) ],
Packit d18d0a
    stdout  => [PerlIO::get_layers(\*STDOUT, output => 1)],
Packit d18d0a
    stderr  => [PerlIO::get_layers(\*STDERR, output => 1)],
Packit d18d0a
  );
Packit d18d0a
  # _debug( "# existing layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/;
Packit d18d0a
  # get layers from underlying glob of tied filehandles if we can
Packit d18d0a
  # (this only works for things that work like Tie::StdHandle)
Packit d18d0a
  $layers{stdout} = [PerlIO::get_layers(tied *STDOUT)]
Packit d18d0a
    if tied(*STDOUT) && (reftype tied *STDOUT eq 'GLOB');
Packit d18d0a
  $layers{stderr} = [PerlIO::get_layers(tied *STDERR)]
Packit d18d0a
    if tied(*STDERR) && (reftype tied *STDERR eq 'GLOB');
Packit d18d0a
  # _debug( "# tied object corrected layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/;
Packit d18d0a
  # bypass scalar filehandles and tied handles
Packit d18d0a
  # localize scalar STDIN to get a proxy to pick up FD0, then restore later to CT_ORIG_STDIN
Packit d18d0a
  my %localize;
Packit d18d0a
  $localize{stdin}++,  local(*STDIN)
Packit d18d0a
    if grep { $_ eq 'scalar' } @{$layers{stdin}};
Packit d18d0a
  $localize{stdout}++, local(*STDOUT)
Packit d18d0a
    if $do_stdout && grep { $_ eq 'scalar' } @{$layers{stdout}};
Packit d18d0a
  $localize{stderr}++, local(*STDERR)
Packit d18d0a
    if ($do_stderr || $do_merge) && grep { $_ eq 'scalar' } @{$layers{stderr}};
Packit d18d0a
  $localize{stdin}++, local(*STDIN), _open( \*STDIN, "<&=0")
Packit d18d0a
    if tied *STDIN && $] >= 5.008;
Packit d18d0a
  $localize{stdout}++, local(*STDOUT), _open( \*STDOUT, ">&=1")
Packit d18d0a
    if $do_stdout && tied *STDOUT && $] >= 5.008;
Packit d18d0a
  $localize{stderr}++, local(*STDERR), _open( \*STDERR, ">&=2")
Packit d18d0a
    if ($do_stderr || $do_merge) && tied *STDERR && $] >= 5.008;
Packit d18d0a
  # _debug( "# localized $_\n" ) for keys %localize;
Packit d18d0a
  # proxy any closed/localized handles so we don't use fds 0, 1 or 2
Packit d18d0a
  my %proxy_std = _proxy_std();
Packit d18d0a
  # _debug( "# proxy std: @{ [%proxy_std] }\n" );
Packit d18d0a
  # update layers after any proxying
Packit d18d0a
  $layers{stdout} = [PerlIO::get_layers(\*STDOUT, output => 1)] if $proxy_std{stdout};
Packit d18d0a
  $layers{stderr} = [PerlIO::get_layers(\*STDERR, output => 1)] if $proxy_std{stderr};
Packit d18d0a
  # _debug( "# post-proxy layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/;
Packit d18d0a
  # store old handles and setup handles for capture
Packit d18d0a
  $stash->{old} = _copy_std();
Packit d18d0a
  $stash->{new} = { %{$stash->{old}} }; # default to originals
Packit d18d0a
  for ( keys %do ) {
Packit d18d0a
    $stash->{new}{$_} = ($stash->{capture}{$_} ||= File::Temp->new);
Packit d18d0a
    seek( $stash->{capture}{$_}, 0, 2 ) or die "Could not seek on capture handle for $_\n";
Packit d18d0a
    $stash->{pos}{$_} = tell $stash->{capture}{$_};
Packit d18d0a
    # _debug("# will capture $_ on " . fileno($stash->{capture}{$_})."\n" );
Packit d18d0a
    _start_tee( $_ => $stash ) if $do_tee; # tees may change $stash->{new}
Packit d18d0a
  }
Packit d18d0a
  _wait_for_tees( $stash ) if $do_tee;
Packit d18d0a
  # finalize redirection
Packit d18d0a
  $stash->{new}{stderr} = $stash->{new}{stdout} if $do_merge;
Packit d18d0a
  # _debug( "# redirecting in parent ...\n" );
Packit d18d0a
  _open_std( $stash->{new} );
Packit d18d0a
  # execute user provided code
Packit d18d0a
  my ($exit_code, $inner_error, $outer_error, $orig_pid, @result);
Packit d18d0a
  {
Packit d18d0a
    $orig_pid = $$;
Packit d18d0a
    local *STDIN = *CT_ORIG_STDIN if $localize{stdin}; # get original, not proxy STDIN
Packit d18d0a
    # _debug( "# finalizing layers ...\n" );
Packit d18d0a
    _relayer(\*STDOUT, $layers{stdout}) if $do_stdout;
Packit d18d0a
    _relayer(\*STDERR, $layers{stderr}) if $do_stderr;
Packit d18d0a
    # _debug( "# running code $code ...\n" );
Packit d18d0a
    my $old_eval_err=$@;
Packit d18d0a
    undef $@;
Packit d18d0a
    eval { @result = $code->(); $inner_error = $@ };
Packit d18d0a
    $exit_code = $?; # save this for later
Packit d18d0a
    $outer_error = $@; # save this for later
Packit d18d0a
    STDOUT->flush if $do_stdout;
Packit d18d0a
    STDERR->flush if $do_stderr;
Packit d18d0a
    $@ = $old_eval_err;
Packit d18d0a
  }
Packit d18d0a
  # restore prior filehandles and shut down tees
Packit d18d0a
  # _debug( "# restoring filehandles ...\n" );
Packit d18d0a
  _open_std( $stash->{old} );
Packit d18d0a
  _close( $_ ) for values %{$stash->{old}}; # don't leak fds
Packit d18d0a
  # shouldn't need relayering originals, but see rt.perl.org #114404
Packit d18d0a
  _relayer(\*STDOUT, $layers{stdout}) if $do_stdout;
Packit d18d0a
  _relayer(\*STDERR, $layers{stderr}) if $do_stderr;
Packit d18d0a
  _unproxy( %proxy_std );
Packit d18d0a
  # _debug( "# killing tee subprocesses ...\n" ) if $do_tee;
Packit d18d0a
  _kill_tees( $stash ) if $do_tee;
Packit d18d0a
  # return captured output, but shortcut in void context
Packit d18d0a
  # unless we have to echo output to tied/scalar handles;
Packit d18d0a
  my %got;
Packit d18d0a
  if ( $orig_pid == $$ and ( defined wantarray or ($do_tee && keys %localize) ) ) {
Packit d18d0a
    for ( keys %do ) {
Packit d18d0a
      _relayer($stash->{capture}{$_}, $layers{$_});
Packit d18d0a
      $got{$_} = _slurp($_, $stash);
Packit d18d0a
      # _debug("# slurped " . length($got{$_}) . " bytes from $_\n");
Packit d18d0a
    }
Packit d18d0a
    print CT_ORIG_STDOUT $got{stdout}
Packit d18d0a
      if $do_stdout && $do_tee && $localize{stdout};
Packit d18d0a
    print CT_ORIG_STDERR $got{stderr}
Packit d18d0a
      if $do_stderr && $do_tee && $localize{stderr};
Packit d18d0a
  }
Packit d18d0a
  $? = $exit_code;
Packit d18d0a
  $@ = $inner_error if $inner_error;
Packit d18d0a
  die $outer_error if $outer_error;
Packit d18d0a
  # _debug( "# ending _capture_tee with (@_)...\n" );
Packit d18d0a
  return unless defined wantarray;
Packit d18d0a
  my @return;
Packit d18d0a
  push @return, $got{stdout} if $do_stdout;
Packit d18d0a
  push @return, $got{stderr} if $do_stderr && ! $do_merge;
Packit d18d0a
  push @return, @result;
Packit d18d0a
  return wantarray ? @return : $return[0];
Packit d18d0a
}
Packit d18d0a
Packit d18d0a
1;
Packit d18d0a
Packit d18d0a
__END__
Packit d18d0a
Packit d18d0a
=pod
Packit d18d0a
Packit d18d0a
=encoding UTF-8
Packit d18d0a
Packit d18d0a
=head1 NAME
Packit d18d0a
Packit d18d0a
Capture::Tiny - Capture STDOUT and STDERR from Perl, XS or external programs
Packit d18d0a
Packit d18d0a
=head1 VERSION
Packit d18d0a
Packit d18d0a
version 0.46
Packit d18d0a
Packit d18d0a
=head1 SYNOPSIS
Packit d18d0a
Packit d18d0a
  use Capture::Tiny ':all';
Packit d18d0a
Packit d18d0a
  # capture from external command
Packit d18d0a
Packit d18d0a
  ($stdout, $stderr, $exit) = capture {
Packit d18d0a
    system( $cmd, @args );
Packit d18d0a
  };
Packit d18d0a
Packit d18d0a
  # capture from arbitrary code (Perl or external)
Packit d18d0a
Packit d18d0a
  ($stdout, $stderr, @result) = capture {
Packit d18d0a
    # your code here
Packit d18d0a
  };
Packit d18d0a
Packit d18d0a
  # capture partial or merged output
Packit d18d0a
Packit d18d0a
  $stdout = capture_stdout { ... };
Packit d18d0a
  $stderr = capture_stderr { ... };
Packit d18d0a
  $merged = capture_merged { ... };
Packit d18d0a
Packit d18d0a
  # tee output
Packit d18d0a
Packit d18d0a
  ($stdout, $stderr) = tee {
Packit d18d0a
    # your code here
Packit d18d0a
  };
Packit d18d0a
Packit d18d0a
  $stdout = tee_stdout { ... };
Packit d18d0a
  $stderr = tee_stderr { ... };
Packit d18d0a
  $merged = tee_merged { ... };
Packit d18d0a
Packit d18d0a
=head1 DESCRIPTION
Packit d18d0a
Packit d18d0a
Capture::Tiny provides a simple, portable way to capture almost anything sent
Packit d18d0a
to STDOUT or STDERR, regardless of whether it comes from Perl, from XS code or
Packit d18d0a
from an external program.  Optionally, output can be teed so that it is
Packit d18d0a
captured while being passed through to the original filehandles.  Yes, it even
Packit d18d0a
works on Windows (usually).  Stop guessing which of a dozen capturing modules
Packit d18d0a
to use in any particular situation and just use this one.
Packit d18d0a
Packit d18d0a
=head1 USAGE
Packit d18d0a
Packit d18d0a
The following functions are available.  None are exported by default.
Packit d18d0a
Packit d18d0a
=head2 capture
Packit d18d0a
Packit d18d0a
  ($stdout, $stderr, @result) = capture \&cod;;
Packit d18d0a
  $stdout = capture \&cod;;
Packit d18d0a
Packit d18d0a
The C<capture> function takes a code reference and returns what is sent to
Packit d18d0a
STDOUT and STDERR as well as any return values from the code reference.  In
Packit d18d0a
scalar context, it returns only STDOUT.  If no output was received for a
Packit d18d0a
filehandle, it returns an empty string for that filehandle.  Regardless of calling
Packit d18d0a
context, all output is captured -- nothing is passed to the existing filehandles.
Packit d18d0a
Packit d18d0a
It is prototyped to take a subroutine reference as an argument. Thus, it
Packit d18d0a
can be called in block form:
Packit d18d0a
Packit d18d0a
  ($stdout, $stderr) = capture {
Packit d18d0a
    # your code here ...
Packit d18d0a
  };
Packit d18d0a
Packit d18d0a
Note that the coderef is evaluated in list context.  If you wish to force
Packit d18d0a
scalar context on the return value, you must use the C<scalar> keyword.
Packit d18d0a
Packit d18d0a
  ($stdout, $stderr, $count) = capture {
Packit d18d0a
    my @list = qw/one two three/;
Packit d18d0a
    return scalar @list; # $count will be 3
Packit d18d0a
  };
Packit d18d0a
Packit d18d0a
Also note that within the coderef, the C<@_> variable will be empty.  So don't
Packit d18d0a
use arguments from a surrounding subroutine without copying them to an array
Packit d18d0a
first:
Packit d18d0a
Packit d18d0a
  sub wont_work {
Packit d18d0a
    my ($stdout, $stderr) = capture { do_stuff( @_ ) };    # WRONG
Packit d18d0a
    ...
Packit d18d0a
  }
Packit d18d0a
Packit d18d0a
  sub will_work {
Packit d18d0a
    my @args = @_;
Packit d18d0a
    my ($stdout, $stderr) = capture { do_stuff( @args ) }; # RIGHT
Packit d18d0a
    ...
Packit d18d0a
  }
Packit d18d0a
Packit d18d0a
Captures are normally done to an anonymous temporary filehandle.  To
Packit d18d0a
capture via a named file (e.g. to externally monitor a long-running capture),
Packit d18d0a
provide custom filehandles as a trailing list of option pairs:
Packit d18d0a
Packit d18d0a
  my $out_fh = IO::File->new("out.txt", "w+");
Packit d18d0a
  my $err_fh = IO::File->new("out.txt", "w+");
Packit d18d0a
  capture { ... } stdout => $out_fh, stderr => $err_fh;
Packit d18d0a
Packit d18d0a
The filehandles must be read/write and seekable.  Modifying the files or
Packit d18d0a
filehandles during a capture operation will give unpredictable results.
Packit d18d0a
Existing IO layers on them may be changed by the capture.
Packit d18d0a
Packit d18d0a
When called in void context, C<capture> saves memory and time by
Packit d18d0a
not reading back from the capture handles.
Packit d18d0a
Packit d18d0a
=head2 capture_stdout
Packit d18d0a
Packit d18d0a
  ($stdout, @result) = capture_stdout \&cod;;
Packit d18d0a
  $stdout = capture_stdout \&cod;;
Packit d18d0a
Packit d18d0a
The C<capture_stdout> function works just like C<capture> except only
Packit d18d0a
STDOUT is captured.  STDERR is not captured.
Packit d18d0a
Packit d18d0a
=head2 capture_stderr
Packit d18d0a
Packit d18d0a
  ($stderr, @result) = capture_stderr \&cod;;
Packit d18d0a
  $stderr = capture_stderr \&cod;;
Packit d18d0a
Packit d18d0a
The C<capture_stderr> function works just like C<capture> except only
Packit d18d0a
STDERR is captured.  STDOUT is not captured.
Packit d18d0a
Packit d18d0a
=head2 capture_merged
Packit d18d0a
Packit d18d0a
  ($merged, @result) = capture_merged \&cod;;
Packit d18d0a
  $merged = capture_merged \&cod;;
Packit d18d0a
Packit d18d0a
The C<capture_merged> function works just like C<capture> except STDOUT and
Packit d18d0a
STDERR are merged. (Technically, STDERR is redirected to the same capturing
Packit d18d0a
handle as STDOUT before executing the function.)
Packit d18d0a
Packit d18d0a
Caution: STDOUT and STDERR output in the merged result are not guaranteed to be
Packit d18d0a
properly ordered due to buffering.
Packit d18d0a
Packit d18d0a
=head2 tee
Packit d18d0a
Packit d18d0a
  ($stdout, $stderr, @result) = tee \&cod;;
Packit d18d0a
  $stdout = tee \&cod;;
Packit d18d0a
Packit d18d0a
The C<tee> function works just like C<capture>, except that output is captured
Packit d18d0a
as well as passed on to the original STDOUT and STDERR.
Packit d18d0a
Packit d18d0a
When called in void context, C<tee> saves memory and time by
Packit d18d0a
not reading back from the capture handles, except when the
Packit d18d0a
original STDOUT OR STDERR were tied or opened to a scalar
Packit d18d0a
handle.
Packit d18d0a
Packit d18d0a
=head2 tee_stdout
Packit d18d0a
Packit d18d0a
  ($stdout, @result) = tee_stdout \&cod;;
Packit d18d0a
  $stdout = tee_stdout \&cod;;
Packit d18d0a
Packit d18d0a
The C<tee_stdout> function works just like C<tee> except only
Packit d18d0a
STDOUT is teed.  STDERR is not teed (output goes to STDERR as usual).
Packit d18d0a
Packit d18d0a
=head2 tee_stderr
Packit d18d0a
Packit d18d0a
  ($stderr, @result) = tee_stderr \&cod;;
Packit d18d0a
  $stderr = tee_stderr \&cod;;
Packit d18d0a
Packit d18d0a
The C<tee_stderr> function works just like C<tee> except only
Packit d18d0a
STDERR is teed.  STDOUT is not teed (output goes to STDOUT as usual).
Packit d18d0a
Packit d18d0a
=head2 tee_merged
Packit d18d0a
Packit d18d0a
  ($merged, @result) = tee_merged \&cod;;
Packit d18d0a
  $merged = tee_merged \&cod;;
Packit d18d0a
Packit d18d0a
The C<tee_merged> function works just like C<capture_merged> except that output
Packit d18d0a
is captured as well as passed on to STDOUT.
Packit d18d0a
Packit d18d0a
Caution: STDOUT and STDERR output in the merged result are not guaranteed to be
Packit d18d0a
properly ordered due to buffering.
Packit d18d0a
Packit d18d0a
=head1 LIMITATIONS
Packit d18d0a
Packit d18d0a
=head2 Portability
Packit d18d0a
Packit d18d0a
Portability is a goal, not a guarantee.  C<tee> requires fork, except on
Packit d18d0a
Windows where C<system(1, @cmd)> is used instead.  Not tested on any
Packit d18d0a
particularly esoteric platforms yet.  See the
Packit d18d0a
L<CPAN Testers Matrix|http://matrix.cpantesters.org/?dist=Capture-Tiny>
Packit d18d0a
for test result by platform.
Packit d18d0a
Packit d18d0a
=head2 PerlIO layers
Packit d18d0a
Packit d18d0a
Capture::Tiny does its best to preserve PerlIO layers such as ':utf8' or
Packit d18d0a
':crlf' when capturing (only for Perl 5.8.1+) .  Layers should be applied to
Packit d18d0a
STDOUT or STDERR I<before> the call to C<capture> or C<tee>.  This may not work
Packit d18d0a
for tied filehandles (see below).
Packit d18d0a
Packit d18d0a
=head2 Modifying filehandles before capturing
Packit d18d0a
Packit d18d0a
Generally speaking, you should do little or no manipulation of the standard IO
Packit d18d0a
filehandles prior to using Capture::Tiny.  In particular, closing, reopening,
Packit d18d0a
localizing or tying standard filehandles prior to capture may cause a variety of
Packit d18d0a
unexpected, undesirable and/or unreliable behaviors, as described below.
Packit d18d0a
Capture::Tiny does its best to compensate for these situations, but the
Packit d18d0a
results may not be what you desire.
Packit d18d0a
Packit d18d0a
=head3 Closed filehandles
Packit d18d0a
Packit d18d0a
Capture::Tiny will work even if STDIN, STDOUT or STDERR have been previously
Packit d18d0a
closed.  However, since they will be reopened to capture or tee output, any
Packit d18d0a
code within the captured block that depends on finding them closed will, of
Packit d18d0a
course, not find them to be closed.  If they started closed, Capture::Tiny will
Packit d18d0a
close them again when the capture block finishes.
Packit d18d0a
Packit d18d0a
Note that this reopening will happen even for STDIN or a filehandle not being
Packit d18d0a
captured to ensure that the filehandle used for capture is not opened to file
Packit d18d0a
descriptor 0, as this causes problems on various platforms.
Packit d18d0a
Packit d18d0a
Prior to Perl 5.12, closed STDIN combined with PERL_UNICODE=D leaks filehandles
Packit d18d0a
and also breaks tee() for undiagnosed reasons.  So don't do that.
Packit d18d0a
Packit d18d0a
=head3 Localized filehandles
Packit d18d0a
Packit d18d0a
If code localizes any of Perl's standard filehandles before capturing, the capture
Packit d18d0a
will affect the localized filehandles and not the original ones.  External system
Packit d18d0a
calls are not affected by localizing a filehandle in Perl and will continue
Packit d18d0a
to send output to the original filehandles (which will thus not be captured).
Packit d18d0a
Packit d18d0a
=head3 Scalar filehandles
Packit d18d0a
Packit d18d0a
If STDOUT or STDERR are reopened to scalar filehandles prior to the call to
Packit d18d0a
C<capture> or C<tee>, then Capture::Tiny will override the output filehandle for
Packit d18d0a
the duration of the C<capture> or C<tee> call and then, for C<tee>, send captured
Packit d18d0a
output to the output filehandle after the capture is complete.  (Requires Perl
Packit d18d0a
5.8)
Packit d18d0a
Packit d18d0a
Capture::Tiny attempts to preserve the semantics of STDIN opened to a scalar
Packit d18d0a
reference, but note that external processes will not be able to read from such
Packit d18d0a
a handle.  Capture::Tiny tries to ensure that external processes will read from
Packit d18d0a
the null device instead, but this is not guaranteed.
Packit d18d0a
Packit d18d0a
=head3 Tied output filehandles
Packit d18d0a
Packit d18d0a
If STDOUT or STDERR are tied prior to the call to C<capture> or C<tee>, then
Packit d18d0a
Capture::Tiny will attempt to override the tie for the duration of the
Packit d18d0a
C<capture> or C<tee> call and then send captured output to the tied filehandle after
Packit d18d0a
the capture is complete.  (Requires Perl 5.8)
Packit d18d0a
Packit d18d0a
Capture::Tiny may not succeed resending UTF-8 encoded data to a tied
Packit d18d0a
STDOUT or STDERR filehandle.  Characters may appear as bytes.  If the tied filehandle
Packit d18d0a
is based on L<Tie::StdHandle>, then Capture::Tiny will attempt to determine
Packit d18d0a
appropriate layers like C<:utf8> from the underlying filehandle and do the right
Packit d18d0a
thing.
Packit d18d0a
Packit d18d0a
=head3 Tied input filehandle
Packit d18d0a
Packit d18d0a
Capture::Tiny attempts to preserve the semantics of tied STDIN, but this
Packit d18d0a
requires Perl 5.8 and is not entirely predictable.  External processes
Packit d18d0a
will not be able to read from such a handle.
Packit d18d0a
Packit d18d0a
Unless having STDIN tied is crucial, it may be safest to localize STDIN when
Packit d18d0a
capturing:
Packit d18d0a
Packit d18d0a
  my ($out, $err) = do { local *STDIN; capture { ... } };
Packit d18d0a
Packit d18d0a
=head2 Modifying filehandles during a capture
Packit d18d0a
Packit d18d0a
Attempting to modify STDIN, STDOUT or STDERR I<during> C<capture> or C<tee> is
Packit d18d0a
almost certainly going to cause problems.  Don't do that.
Packit d18d0a
Packit d18d0a
=head3 Forking inside a capture
Packit d18d0a
Packit d18d0a
Forks aren't portable.  The behavior of filehandles during a fork is even
Packit d18d0a
less so.  If Capture::Tiny detects that a fork has occurred within a
Packit d18d0a
capture, it will shortcut in the child process and return empty strings for
Packit d18d0a
captures.  Other problems may occur in the child or parent, as well.
Packit d18d0a
Forking in a capture block is not recommended.
Packit d18d0a
Packit d18d0a
=head3 Using threads
Packit d18d0a
Packit d18d0a
Filehandles are global.  Mixing up I/O and captures in different threads
Packit d18d0a
without coordination is going to cause problems.  Besides, threads are
Packit d18d0a
officially discouraged.
Packit d18d0a
Packit d18d0a
=head3 Dropping privileges during a capture
Packit d18d0a
Packit d18d0a
If you drop privileges during a capture, temporary files created to
Packit d18d0a
facilitate the capture may not be cleaned up afterwards.
Packit d18d0a
Packit d18d0a
=head2 No support for Perl 5.8.0
Packit d18d0a
Packit d18d0a
It's just too buggy when it comes to layers and UTF-8.  Perl 5.8.1 or later
Packit d18d0a
is recommended.
Packit d18d0a
Packit d18d0a
=head2 Limited support for Perl 5.6
Packit d18d0a
Packit d18d0a
Perl 5.6 predates PerlIO.  UTF-8 data may not be captured correctly.
Packit d18d0a
Packit d18d0a
=head1 ENVIRONMENT
Packit d18d0a
Packit d18d0a
=head2 PERL_CAPTURE_TINY_TIMEOUT
Packit d18d0a
Packit d18d0a
Capture::Tiny uses subprocesses internally for C<tee>.  By default,
Packit d18d0a
Capture::Tiny will timeout with an error if such subprocesses are not ready to
Packit d18d0a
receive data within 30 seconds (or whatever is the value of
Packit d18d0a
C<$Capture::Tiny::TIMEOUT>).  An alternate timeout may be specified by setting
Packit d18d0a
the C<PERL_CAPTURE_TINY_TIMEOUT> environment variable.  Setting it to zero will
Packit d18d0a
disable timeouts.  B<NOTE>, this does not timeout the code reference being
Packit d18d0a
captured -- this only prevents Capture::Tiny itself from hanging your process
Packit d18d0a
waiting for its child processes to be ready to proceed.
Packit d18d0a
Packit d18d0a
=head1 SEE ALSO
Packit d18d0a
Packit d18d0a
This module was inspired by L<IO::CaptureOutput>, which provides
Packit d18d0a
similar functionality without the ability to tee output and with more
Packit d18d0a
complicated code and API.  L<IO::CaptureOutput> does not handle layers
Packit d18d0a
or most of the unusual cases described in the L</Limitations> section and
Packit d18d0a
I no longer recommend it.
Packit d18d0a
Packit d18d0a
There are many other CPAN modules that provide some sort of output capture,
Packit d18d0a
albeit with various limitations that make them appropriate only in particular
Packit d18d0a
circumstances.  I'm probably missing some.  The long list is provided to show
Packit d18d0a
why I felt Capture::Tiny was necessary.
Packit d18d0a
Packit d18d0a
=over 4
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IO::Capture>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IO::Capture::Extended>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IO::CaptureOutput>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IPC::Capture>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IPC::Cmd>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IPC::Open2>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IPC::Open3>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IPC::Open3::Simple>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IPC::Open3::Utils>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IPC::Run>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IPC::Run::SafeHandles>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IPC::Run::Simple>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IPC::Run3>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IPC::System::Simple>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<Tee>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<IO::Tee>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<File::Tee>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<Filter::Handle>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<Tie::STDERR>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<Tie::STDOUT>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
L<Test::Output>
Packit d18d0a
Packit d18d0a
=back
Packit d18d0a
Packit d18d0a
=for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
Packit d18d0a
Packit d18d0a
=head1 SUPPORT
Packit d18d0a
Packit d18d0a
=head2 Bugs / Feature Requests
Packit d18d0a
Packit d18d0a
Please report any bugs or feature requests through the issue tracker
Packit d18d0a
at L<https://github.com/dagolden/Capture-Tiny/issues>.
Packit d18d0a
You will be notified automatically of any progress on your issue.
Packit d18d0a
Packit d18d0a
=head2 Source Code
Packit d18d0a
Packit d18d0a
This is open source software.  The code repository is available for
Packit d18d0a
public review and contribution under the terms of the license.
Packit d18d0a
Packit d18d0a
L<https://github.com/dagolden/Capture-Tiny>
Packit d18d0a
Packit d18d0a
  git clone https://github.com/dagolden/Capture-Tiny.git
Packit d18d0a
Packit d18d0a
=head1 AUTHOR
Packit d18d0a
Packit d18d0a
David Golden <dagolden@cpan.org>
Packit d18d0a
Packit d18d0a
=head1 CONTRIBUTORS
Packit d18d0a
Packit d18d0a
=for stopwords Dagfinn Ilmari Mannsåker David E. Wheeler fecundf Graham Knop Peter Rabbitson
Packit d18d0a
Packit d18d0a
=over 4
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
David E. Wheeler <david@justatheory.com>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
fecundf <not.com+github@gmail.com>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
Graham Knop <haarg@haarg.org>
Packit d18d0a
Packit d18d0a
=item *
Packit d18d0a
Packit d18d0a
Peter Rabbitson <ribasushi@cpan.org>
Packit d18d0a
Packit d18d0a
=back
Packit d18d0a
Packit d18d0a
=head1 COPYRIGHT AND LICENSE
Packit d18d0a
Packit d18d0a
This software is Copyright (c) 2009 by David Golden.
Packit d18d0a
Packit d18d0a
This is free software, licensed under:
Packit d18d0a
Packit d18d0a
  The Apache License, Version 2.0, January 2004
Packit d18d0a
Packit d18d0a
=cut