Commit | Line | Data |
3fea05b9 |
1 | # Copyright (c) 2009 by David Golden. All rights reserved. |
2 | # Licensed under Apache License, Version 2.0 (the "License"). |
3 | # You may not use this file except in compliance with the License. |
4 | # A copy of the License was distributed with this file or you may obtain a |
5 | # copy of the License from http://www.apache.org/licenses/LICENSE-2.0 |
6 | |
7 | package Capture::Tiny; |
8 | use 5.006; |
9 | use strict; |
10 | use warnings; |
11 | use Carp (); |
12 | use Exporter (); |
13 | use IO::Handle (); |
14 | use File::Spec (); |
15 | use File::Temp qw/tempfile tmpnam/; |
16 | # Get PerlIO or fake it |
17 | BEGIN { eval { require PerlIO; 1 } or *PerlIO::get_layers = sub { return () }; } |
18 | |
19 | our $VERSION = '0.06'; |
20 | $VERSION = eval $VERSION; ## no critic |
21 | our @ISA = qw/Exporter/; |
22 | our @EXPORT_OK = qw/capture capture_merged tee tee_merged/; |
23 | our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK ); |
24 | |
25 | my $IS_WIN32 = $^O eq 'MSWin32'; |
26 | |
27 | our $DEBUG = $ENV{PERL_CAPTURE_TINY_DEBUG}; |
28 | my $DEBUGFH; |
29 | open $DEBUGFH, ">&STDERR" if $DEBUG; |
30 | |
31 | *_debug = $DEBUG ? sub(@) { print {$DEBUGFH} @_ } : sub(){0}; |
32 | |
33 | #--------------------------------------------------------------------------# |
34 | # command to tee output -- the argument is a filename that must |
35 | # be opened to signal that the process is ready to receive input. |
36 | # This is annoying, but seems to be the best that can be done |
37 | # as a simple, portable IPC technique |
38 | #--------------------------------------------------------------------------# |
39 | my @cmd = ($^X, '-e', '$SIG{HUP}=sub{exit}; ' |
40 | . 'if( my $fn=shift ){ open my $fh, qq{>$fn}; print {$fh} $$; close $fh;} ' |
41 | . 'my $buf; while (sysread(STDIN, $buf, 2048)) { ' |
42 | . 'syswrite(STDOUT, $buf); syswrite(STDERR, $buf)}' |
43 | ); |
44 | |
45 | #--------------------------------------------------------------------------# |
46 | # filehandle manipulation |
47 | #--------------------------------------------------------------------------# |
48 | |
49 | sub _relayer { |
50 | my ($fh, $layers) = @_; |
51 | _debug("# requested layers (@{$layers}) to $fh\n"); |
52 | my %seen; |
53 | my @unique = grep { $_ ne 'unix' and $_ ne 'perlio' and !$seen{$_}++ } @$layers; |
54 | _debug("# applying unique layers (@unique) to $fh\n"); |
55 | binmode($fh, join(":", "", "raw", @unique)); |
56 | } |
57 | |
58 | sub _name { |
59 | my $glob = shift; |
60 | no strict 'refs'; ## no critic |
61 | return *{$glob}{NAME}; |
62 | } |
63 | |
64 | sub _open { |
65 | open $_[0], $_[1] or Carp::confess "Error from open(" . join(q{, }, @_) . "): $!"; |
66 | _debug( "# open " . join( ", " , map { defined $_ ? _name($_) : 'undef' } @_ ) . " as " . fileno( $_[0] ) . "\n" ); |
67 | } |
68 | |
69 | sub _close { |
70 | close $_[0] or Carp::confess "Error from close(" . join(q{, }, @_) . "): $!"; |
71 | _debug( "# closed " . ( defined $_[0] ? _name($_[0]) : 'undef' ) . "\n" ); |
72 | } |
73 | |
74 | my %dup; # cache this so STDIN stays fd0 |
75 | my %proxy_count; |
76 | sub _proxy_std { |
77 | my %proxies; |
78 | if ( ! defined fileno STDIN ) { |
79 | $proxy_count{stdin}++; |
80 | if (defined $dup{stdin}) { |
81 | _open \*STDIN, "<&=" . fileno($dup{stdin}); |
82 | _debug( "# restored proxy STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" ); |
83 | } |
84 | else { |
85 | _open \*STDIN, "<" . File::Spec->devnull; |
86 | _debug( "# proxied STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" ); |
87 | _open $dup{stdin} = IO::Handle->new, "<&=STDIN"; |
88 | } |
89 | $proxies{stdin} = \*STDIN; |
90 | binmode(STDIN, ':utf8') if $] >= 5.008; |
91 | } |
92 | if ( ! defined fileno STDOUT ) { |
93 | $proxy_count{stdout}++; |
94 | if (defined $dup{stdout}) { |
95 | _open \*STDOUT, ">&=" . fileno($dup{stdout}); |
96 | _debug( "# restored proxy STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" ); |
97 | } |
98 | else { |
99 | _open \*STDOUT, ">" . File::Spec->devnull; |
100 | _debug( "# proxied STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" ); |
101 | _open $dup{stdout} = IO::Handle->new, ">&=STDOUT"; |
102 | } |
103 | $proxies{stdout} = \*STDOUT; |
104 | binmode(STDOUT, ':utf8') if $] >= 5.008; |
105 | } |
106 | if ( ! defined fileno STDERR ) { |
107 | $proxy_count{stderr}++; |
108 | if (defined $dup{stderr}) { |
109 | _open \*STDERR, ">&=" . fileno($dup{stderr}); |
110 | _debug( "# restored proxy STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" ); |
111 | } |
112 | else { |
113 | _open \*STDERR, ">" . File::Spec->devnull; |
114 | _debug( "# proxied STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" ); |
115 | _open $dup{stderr} = IO::Handle->new, ">&=STDERR"; |
116 | } |
117 | $proxies{stderr} = \*STDERR; |
118 | binmode(STDERR, ':utf8') if $] >= 5.008; |
119 | } |
120 | return %proxies; |
121 | } |
122 | |
123 | sub _unproxy { |
124 | my (%proxies) = @_; |
125 | _debug( "# unproxing " . join(" ", keys %proxies) . "\n" ); |
126 | for my $p ( keys %proxies ) { |
127 | $proxy_count{$p}--; |
128 | _debug( "# unproxied " . uc($p) . " ($proxy_count{$p} left)\n" ); |
129 | if ( ! $proxy_count{$p} ) { |
130 | _close $proxies{$p}; |
131 | _close $dup{$p} unless $] < 5.008; # 5.6 will have already closed this as dup |
132 | delete $dup{$p}; |
133 | } |
134 | } |
135 | } |
136 | |
137 | sub _copy_std { |
138 | my %handles = map { $_, IO::Handle->new } qw/stdin stdout stderr/; |
139 | _debug( "# copying std handles ...\n" ); |
140 | _open $handles{stdin}, "<&STDIN"; |
141 | _open $handles{stdout}, ">&STDOUT"; |
142 | _open $handles{stderr}, ">&STDERR"; |
143 | return \%handles; |
144 | } |
145 | |
146 | sub _open_std { |
147 | my ($handles) = @_; |
148 | _open \*STDIN, "<&" . fileno $handles->{stdin}; |
149 | _open \*STDOUT, ">&" . fileno $handles->{stdout}; |
150 | _open \*STDERR, ">&" . fileno $handles->{stderr}; |
151 | } |
152 | |
153 | #--------------------------------------------------------------------------# |
154 | # private subs |
155 | #--------------------------------------------------------------------------# |
156 | |
157 | sub _start_tee { |
158 | my ($which, $stash) = @_; |
159 | # setup pipes |
160 | $stash->{$_}{$which} = IO::Handle->new for qw/tee reader/; |
161 | pipe $stash->{reader}{$which}, $stash->{tee}{$which}; |
162 | _debug( "# pipe for $which\: " . _name($stash->{tee}{$which}) . " " |
163 | . fileno( $stash->{tee}{$which} ) . " => " . _name($stash->{reader}{$which}) |
164 | . " " . fileno( $stash->{reader}{$which}) . "\n" ); |
165 | select((select($stash->{tee}{$which}), $|=1)[0]); # autoflush |
166 | # setup desired redirection for parent and child |
167 | $stash->{new}{$which} = $stash->{tee}{$which}; |
168 | $stash->{child}{$which} = { |
169 | stdin => $stash->{reader}{$which}, |
170 | stdout => $stash->{old}{$which}, |
171 | stderr => $stash->{capture}{$which}, |
172 | }; |
173 | # flag file is used to signal the child is ready |
174 | $stash->{flag_files}{$which} = scalar tmpnam(); |
175 | # execute @cmd as a separate process |
176 | if ( $IS_WIN32 ) { |
177 | eval "use Win32API::File qw/CloseHandle GetOsFHandle SetHandleInformation fileLastError HANDLE_FLAG_INHERIT INVALID_HANDLE_VALUE/ "; |
178 | _debug( "# Win32API::File loaded\n") unless $@; |
179 | my $os_fhandle = GetOsFHandle( $stash->{tee}{$which} ); |
180 | _debug( "# Couldn't get OS handle: " . fileLastError() . "\n") if ! defined $os_fhandle || $os_fhandle == INVALID_HANDLE_VALUE(); |
181 | if ( SetHandleInformation( $os_fhandle, HANDLE_FLAG_INHERIT(), 0) ) { |
182 | _debug( "# set no-inherit flag on $which tee\n" ); |
183 | } |
184 | else { |
185 | _debug( "# can't disable tee handle flag inherit: " . fileLastError() . "\n"); |
186 | } |
187 | _open_std( $stash->{child}{$which} ); |
188 | $stash->{pid}{$which} = system(1, @cmd, $stash->{flag_files}{$which}); |
189 | # not restoring std here as it all gets redirected again shortly anyway |
190 | } |
191 | else { # use fork |
192 | _fork_exec( $which, $stash ); |
193 | } |
194 | } |
195 | |
196 | sub _fork_exec { |
197 | my ($which, $stash) = @_; |
198 | my $pid = fork; |
199 | if ( not defined $pid ) { |
200 | Carp::confess "Couldn't fork(): $!"; |
201 | } |
202 | elsif ($pid == 0) { # child |
203 | _debug( "# in child process ...\n" ); |
204 | untie *STDIN; untie *STDOUT; untie *STDERR; |
205 | _close $stash->{tee}{$which}; |
206 | _debug( "# redirecting handles in child ...\n" ); |
207 | _open_std( $stash->{child}{$which} ); |
208 | _debug( "# calling exec on command ...\n" ); |
209 | exec @cmd, $stash->{flag_files}{$which}; |
210 | } |
211 | $stash->{pid}{$which} = $pid |
212 | } |
213 | |
214 | sub _files_exist { -f $_ || return 0 for @_; return 1 } |
215 | |
216 | sub _wait_for_tees { |
217 | my ($stash) = @_; |
218 | my $start = time; |
219 | my @files = values %{$stash->{flag_files}}; |
220 | 1 until _files_exist(@files) || (time - $start > 30); |
221 | Carp::confess "Timed out waiting for subprocesses to start" if ! _files_exist(@files); |
222 | unlink $_ for @files; |
223 | } |
224 | |
225 | sub _kill_tees { |
226 | my ($stash) = @_; |
227 | if ( $IS_WIN32 ) { |
228 | _debug( "# closing handles with CloseHandle\n"); |
229 | CloseHandle( GetOsFHandle($_) ) for values %{ $stash->{tee} }; |
230 | _debug( "# waiting for subprocesses to finish\n"); |
231 | my $start = time; |
232 | 1 until wait == -1 || (time - $start > 30); |
233 | } |
234 | else { |
235 | _close $_ for values %{ $stash->{tee} }; |
236 | waitpid $_, 0 for values %{ $stash->{pid} }; |
237 | } |
238 | } |
239 | |
240 | sub _slurp { |
241 | seek $_[0],0,0; local $/; return scalar readline $_[0]; |
242 | } |
243 | |
244 | #--------------------------------------------------------------------------# |
245 | # _capture_tee() -- generic main sub for capturing or teeing |
246 | #--------------------------------------------------------------------------# |
247 | |
248 | sub _capture_tee { |
249 | _debug( "# starting _capture_tee with (@_)...\n" ); |
250 | my ($tee_stdout, $tee_stderr, $merge, $code) = @_; |
251 | # save existing filehandles and setup captures |
252 | local *CT_ORIG_STDIN = *STDIN ; |
253 | local *CT_ORIG_STDOUT = *STDOUT; |
254 | local *CT_ORIG_STDERR = *STDERR; |
255 | # find initial layers |
256 | my %layers = ( |
257 | stdin => [PerlIO::get_layers(\*STDIN) ], |
258 | stdout => [PerlIO::get_layers(\*STDOUT)], |
259 | stderr => [PerlIO::get_layers(\*STDERR)], |
260 | ); |
261 | _debug( "# existing layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/; |
262 | # bypass scalar filehandles and tied handles |
263 | my %localize; |
264 | $localize{stdin}++, local(*STDIN) if grep { $_ eq 'scalar' } @{$layers{stdin}}; |
265 | $localize{stdout}++, local(*STDOUT) if grep { $_ eq 'scalar' } @{$layers{stdout}}; |
266 | $localize{stderr}++, local(*STDERR) if grep { $_ eq 'scalar' } @{$layers{stderr}}; |
267 | $localize{stdout}++, local(*STDOUT), _open( \*STDOUT, ">&=1") if tied *STDOUT && $] >= 5.008; |
268 | $localize{stderr}++, local(*STDERR), _open( \*STDERR, ">&=2") if tied *STDERR && $] >= 5.008; |
269 | _debug( "# localized $_\n" ) for keys %localize; |
270 | my %proxy_std = _proxy_std(); |
271 | _debug( "# proxy std is @{ [%proxy_std] }\n" ); |
272 | my $stash = { old => _copy_std() }; |
273 | # update layers after any proxying |
274 | %layers = ( |
275 | stdin => [PerlIO::get_layers(\*STDIN) ], |
276 | stdout => [PerlIO::get_layers(\*STDOUT)], |
277 | stderr => [PerlIO::get_layers(\*STDERR)], |
278 | ); |
279 | _debug( "# post-proxy layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/; |
280 | # get handles for capture and apply existing IO layers |
281 | $stash->{new}{$_} = $stash->{capture}{$_} = File::Temp->new for qw/stdout stderr/; |
282 | _debug("# will capture $_ on " .fileno($stash->{capture}{$_})."\n" ) for qw/stdout stderr/; |
283 | # tees may change $stash->{new} |
284 | _start_tee( stdout => $stash ) if $tee_stdout; |
285 | _start_tee( stderr => $stash ) if $tee_stderr; |
286 | _wait_for_tees( $stash ) if $tee_stdout || $tee_stderr; |
287 | # finalize redirection |
288 | $stash->{new}{stderr} = $stash->{new}{stdout} if $merge; |
289 | $stash->{new}{stdin} = $stash->{old}{stdin}; |
290 | _debug( "# redirecting in parent ...\n" ); |
291 | _open_std( $stash->{new} ); |
292 | # execute user provided code |
293 | my $exit_code; |
294 | { |
295 | local *STDIN = *CT_ORIG_STDIN if $localize{stdin}; # get original, not proxy STDIN |
296 | local *STDERR = *STDOUT if $merge; # minimize buffer mixups during $code |
297 | _debug( "# finalizing layers ...\n" ); |
298 | _relayer(\*STDOUT, $layers{stdout}); |
299 | _relayer(\*STDERR, $layers{stderr}) unless $merge; |
300 | _debug( "# running code $code ...\n" ); |
301 | $code->(); |
302 | $exit_code = $?; # save this for later |
303 | } |
304 | # restore prior filehandles and shut down tees |
305 | _debug( "# restoring ...\n" ); |
306 | _open_std( $stash->{old} ); |
307 | _close( $_ ) for values %{$stash->{old}}; # don't leak fds |
308 | _unproxy( %proxy_std ); |
309 | _kill_tees( $stash ) if $tee_stdout || $tee_stderr; |
310 | # return captured output |
311 | _relayer($stash->{capture}{stdout}, $layers{stdout}); |
312 | _relayer($stash->{capture}{stderr}, $layers{stderr}) unless $merge; |
313 | _debug( "# slurping captured $_ with layers: @{[PerlIO::get_layers($stash->{capture}{$_})]}\n") for qw/stdout stderr/; |
314 | my $got_out = _slurp($stash->{capture}{stdout}); |
315 | my $got_err = $merge ? q() : _slurp($stash->{capture}{stderr}); |
316 | print CT_ORIG_STDOUT $got_out if $localize{stdout} && $tee_stdout; |
317 | print CT_ORIG_STDERR $got_err if !$merge && $localize{stderr} && $tee_stdout; |
318 | $? = $exit_code; |
319 | _debug( "# ending _capture_tee with (@_)...\n" ); |
320 | return $got_out if $merge; |
321 | return wantarray ? ($got_out, $got_err) : $got_out; |
322 | } |
323 | |
324 | #--------------------------------------------------------------------------# |
325 | # create API subroutines from [tee STDOUT flag, tee STDERR, merge flag] |
326 | #--------------------------------------------------------------------------# |
327 | |
328 | my %api = ( |
329 | capture => [0,0,0], |
330 | capture_merged => [0,0,1], |
331 | tee => [1,1,0], |
332 | tee_merged => [1,0,1], # don't tee STDOUT since merging |
333 | ); |
334 | |
335 | for my $sub ( keys %api ) { |
336 | my $args = join q{, }, @{$api{$sub}}; |
337 | eval "sub $sub(&) {unshift \@_, $args; goto \\&_capture_tee;}"; ## no critic |
338 | } |
339 | |
340 | 1; |
341 | |
342 | __END__ |
343 | |
344 | =begin wikidoc |
345 | |
346 | = NAME |
347 | |
348 | Capture::Tiny - Capture STDOUT and STDERR from Perl, XS or external programs |
349 | |
350 | = VERSION |
351 | |
352 | This documentation describes version %%VERSION%%. |
353 | |
354 | = SYNOPSIS |
355 | |
356 | use Capture::Tiny qw/capture tee capture_merged tee_merged/; |
357 | |
358 | ($stdout, $stderr) = capture { |
359 | # your code here |
360 | }; |
361 | |
362 | ($stdout, $stderr) = tee { |
363 | # your code here |
364 | }; |
365 | |
366 | $merged = capture_merged { |
367 | # your code here |
368 | }; |
369 | |
370 | $merged = tee_merged { |
371 | # your code here |
372 | }; |
373 | |
374 | = DESCRIPTION |
375 | |
376 | Capture::Tiny provides a simple, portable way to capture anything sent to |
377 | STDOUT or STDERR, regardless of whether it comes from Perl, from XS code or |
378 | from an external program. Optionally, output can be teed so that it is |
379 | captured while being passed through to the original handles. Yes, it even |
380 | works on Windows. Stop guessing which of a dozen capturing modules to use in |
381 | any particular situation and just use this one. |
382 | |
383 | This module was heavily inspired by [IO::CaptureOutput], which provides |
384 | similar functionality without the ability to tee output and with more |
385 | complicated code and API. |
386 | |
387 | = USAGE |
388 | |
389 | The following functions are available. None are exported by default. |
390 | |
391 | == capture |
392 | |
393 | ($stdout, $stderr) = capture \&code; |
394 | $stdout = capture \&code; |
395 | |
396 | The {capture} function takes a code reference and returns what is sent to |
397 | STDOUT and STDERR. In scalar context, it returns only STDOUT. If no output |
398 | was received, returns an empty string. Regardless of context, all output is |
399 | captured -- nothing is passed to the existing handles. |
400 | |
401 | It is prototyped to take a subroutine reference as an argument. Thus, it |
402 | can be called in block form: |
403 | |
404 | ($stdout, $stderr) = capture { |
405 | # your code here ... |
406 | }; |
407 | |
408 | == capture_merged |
409 | |
410 | $merged = capture_merged \&code; |
411 | |
412 | The {capture_merged} function works just like {capture} except STDOUT and |
413 | STDERR are merged. (Technically, STDERR is redirected to STDOUT before |
414 | executing the function.) If no output was received, returns an empty string. |
415 | As with {capture} it may be called in block form. |
416 | |
417 | Caution: STDOUT and STDERR output in the merged result are not guaranteed to be |
418 | properly ordered due to buffering. |
419 | |
420 | == tee |
421 | |
422 | ($stdout, $stderr) = tee \&code; |
423 | $stdout = tee \&code; |
424 | |
425 | The {tee} function works just like {capture}, except that output is captured |
426 | as well as passed on to the original STDOUT and STDERR. As with {capture} it |
427 | may be called in block form. |
428 | |
429 | == tee_merged |
430 | |
431 | $merged = tee_merged \&code; |
432 | |
433 | The {tee_merged} function works just like {capture_merged} except that output |
434 | is captured as well as passed on to STDOUT. As with {capture} it may be called |
435 | in block form. |
436 | |
437 | Caution: STDOUT and STDERR output in the merged result are not guaranteed to be |
438 | properly ordered due to buffering. |
439 | |
440 | = LIMITATIONS |
441 | |
442 | == Portability |
443 | |
444 | Portability is a goal, not a guarantee. {tee} requires fork, except on |
445 | Windows where {system(1, @cmd)} is used instead. Not tested on any |
446 | particularly esoteric platforms yet. |
447 | |
448 | == PerlIO layers |
449 | |
450 | Capture::Tiny does it's best to preserve PerlIO layers such as ':utf8' or |
451 | ':crlf' when capturing. Layers should be applied to STDOUT or STDERR ~before~ |
452 | the call to {capture} or {tee}. |
453 | |
454 | == Closed STDIN, STDOUT or STDERR |
455 | |
456 | Capture::Tiny will work even if STDIN, STDOUT or STDERR have been previously |
457 | closed. However, since they may be reopened to capture or tee output, any code |
458 | within the captured block that depends on finding them closed will, of course, |
459 | not find them to be closed. If they started closed, Capture::Tiny will reclose |
460 | them again when the capture block finishes. |
461 | |
462 | == Scalar filehandles and STDIN, STDOUT or STDERR |
463 | |
464 | If STDOUT or STDERR are reopened to scalar filehandles prior to the call to |
465 | {capture} or {tee}, then Capture::Tiny will override the output handle for the |
466 | duration of the {capture} or {tee} call and then send captured output to the |
467 | output handle after the capture is complete. (Requires Perl 5.8) |
468 | |
469 | Capture::Tiny attempts to preserve the semantics of STDIN opened to a scalar |
470 | reference. |
471 | |
472 | == Tied STDIN, STDOUT or STDERR |
473 | |
474 | If STDOUT or STDERR are tied prior to the call to {capture} or {tee}, then |
475 | Capture::Tiny will attempt to override the tie for the duration of the |
476 | {capture} or {tee} call and then send captured output to the tied handle after |
477 | the capture is complete. (Requires Perl 5.8) |
478 | |
479 | Capture::Tiny does not (yet) support resending utf8 encoded data to a tied |
480 | STDOUT or STDERR handle. Characters will appear as bytes. |
481 | |
482 | Capture::Tiny attempts to preserve the semantics of tied STDIN, but capturing |
483 | or teeing when STDIN is tied is currently broken on Windows. |
484 | |
485 | == Modifiying STDIN, STDOUT or STDERR during a capture |
486 | |
487 | Attempting to modify STDIN, STDOUT or STDERR ~during~ {capture} or {tee} is |
488 | almost certainly going to cause problems. Don't do that. |
489 | |
490 | = BUGS |
491 | |
492 | Please report any bugs or feature requests using the CPAN Request Tracker. |
493 | Bugs can be submitted through the web interface at |
494 | [http://rt.cpan.org/Dist/Display.html?Queue=Capture-Tiny] |
495 | |
496 | When submitting a bug or request, please include a test-file or a patch to an |
497 | existing test-file that illustrates the bug or desired feature. |
498 | |
499 | = SEE ALSO |
500 | |
501 | This is a selection of CPAN modules that provide some sort of output capture, |
502 | albeit with various limitations that make them appropriate only in particular |
503 | circumstances. I'm probably missing some. The long list is provided to show |
504 | why I felt Capture::Tiny was necessary. |
505 | |
506 | * [IO::Capture] |
507 | * [IO::Capture::Extended] |
508 | * [IO::CaptureOutput] |
509 | * [IPC::Capture] |
510 | * [IPC::Cmd] |
511 | * [IPC::Open2] |
512 | * [IPC::Open3] |
513 | * [IPC::Open3::Simple] |
514 | * [IPC::Open3::Utils] |
515 | * [IPC::Run] |
516 | * [IPC::Run::SafeHandles] |
517 | * [IPC::Run::Simple] |
518 | * [IPC::Run3] |
519 | * [IPC::System::Simple] |
520 | * [Tee] |
521 | * [IO::Tee] |
522 | * [File::Tee] |
523 | * [Filter::Handle] |
524 | * [Tie::STDERR] |
525 | * [Tie::STDOUT] |
526 | * [Test::Output] |
527 | |
528 | = AUTHOR |
529 | |
530 | David A. Golden (DAGOLDEN) |
531 | |
532 | = COPYRIGHT AND LICENSE |
533 | |
534 | Copyright (c) 2009 by David A. Golden. All rights reserved. |
535 | |
536 | Licensed under Apache License, Version 2.0 (the "License"). You may not use |
537 | this file except in compliance with the License. A copy of the License was |
538 | distributed with this file or you may obtain a copy of the License from |
539 | http://www.apache.org/licenses/LICENSE-2.0 |
540 | |
541 | Files produced as output though the use of this software, shall not be |
542 | considered Derivative Works, but shall be considered the original work of the |
543 | Licensor. |
544 | |
545 | Unless required by applicable law or agreed to in writing, software |
546 | distributed under the License is distributed on an "AS IS" BASIS, |
547 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
548 | See the License for the specific language governing permissions and |
549 | limitations under the License. |
550 | |
551 | =end wikidoc |
552 | |
553 | =cut |
554 | |