File Coverage

blib/lib/File/Tee.pm
Criterion Covered Total %
statement 99 171 57.8
branch 40 114 35.0
condition 1 12 8.3
subroutine 9 9 100.0
pod 1 1 100.0
total 150 307 48.8


line stmt bran cond sub pod time code
1             package File::Tee;
2              
3             our $VERSION = '0.07';
4              
5 1     1   23432 use strict;
  1         2  
  1         36  
6 1     1   5 use warnings;
  1         2  
  1         27  
7 1     1   4 no warnings 'uninitialized';
  1         6  
  1         55  
8              
9             require Exporter;
10             our @ISA = qw(Exporter);
11             our @EXPORT_OK = qw(tee);
12              
13 1     1   4 use Carp;
  1         2  
  1         100  
14              
15 1     1   793 use Symbol qw(qualify_to_ref);
  1         1062  
  1         74  
16 1     1   948 use POSIX qw(_exit);
  1         7190  
  1         6  
17 1     1   1175 use Fcntl qw(:flock);
  1         2  
  1         2106  
18              
19             sub tee (*;@) {
20 1 50   1 1 507 @_ >= 2 or croak 'Usage: tee($fh, $target, ...)';
21              
22 1         9 my $fh = qualify_to_ref(shift, caller);
23              
24 1         14 my $last_mode;
25              
26             my @target;
27 1         5 while (@_) {
28 5         9 my $arg = shift @_;
29 5         6 my %target;
30 5 100       36 my %opts = ( ref $arg eq 'HASH' ? %$arg :
    100          
31             ref $arg eq 'CODE' ? ( process => $arg ) :
32             ( open => $arg ) );
33              
34 5         14 $target{ignore_errors} = delete $opts{ignore_errors};
35 5         9 $target{prefix} = delete $opts{prefix};
36 5         11 $target{end} = delete $opts{end};
37 5         9 $target{begin} = delete $opts{begin};
38 5         8 $target{preprocess} = delete $opts{preprocess};
39 5         8 $target{process} = delete $opts{process};
40 5 100       13 unless (defined $target{process}) {
41 3         13 $target{mode} = delete $opts{mode};
42 3         9 $target{open} = delete $opts{open};
43 3         6 $target{reopen} = delete $opts{reopen};
44 3         6 $target{autoflush} = delete $opts{autoflush};
45 3         5 $target{lock} = delete $opts{lock};
46             }
47              
48 5 50       12 %opts and croak "bad options '".join("', '", keys %opts)."'";
49              
50 5 100       13 unless (defined $target{process}) {
51 3 100       10 if (defined $target{reopen}) {
    50          
52 1 50       4 croak "both 'open' and 'reopen' options used for the same target"
53             if defined $target{open};
54 1         2 $target{open} = $target{reopen};
55 1         2 $target{reopen} = 1;
56             }
57             elsif (!defined $target{open}) {
58 0         0 croak "missing mandatory argument 'open'";
59             }
60              
61 3 50       10 $target{autoflush} = 1 unless defined $target{autoflush};
62              
63 3 50       14 $target{open} = [$target{open}]
64             unless ref $target{open} eq 'ARRAY';
65 3 50       8 unless (defined $target{mode}) {
66 3 50       8 if (ref $target{open}[0]) {
67 0 0       0 if (ref $target{open}[0] eq 'CODE') {
68 0         0 $target{mode} = 'CODE';
69             }
70             else {
71 0 0       0 $target{mode} = (defined $last_mode ? $last_mode : '>>&');
72             }
73             }
74             else {
75 3         4 my ($mode, $fn) = shift(@{$target{open}}) =~ /^(\+?[<>]{1,2}(?:&=?)?|\|-?|)\s*(.*)$/;
  3         23  
76              
77 3 50       11 $mode = (defined $last_mode ? $last_mode : '>>') unless length $mode;
    100          
78 3 50       9 $mode = '|-' if $mode eq '|';
79              
80 3 100       9 unshift @{$target{open}}, $fn
  2         4  
81             if length $fn;
82              
83 3         6 $target{mode} = $mode;
84             }
85             }
86              
87 3 50       13 $target{mode} =~ /^(?:>{1,2}&?|\|-|CODE)$/ or croak "invalid mode '$target{mode}'";
88              
89             # file name is next argument or slurp everything when mode is '|-'
90 3 100       13 unless (@{$target{open}} > 0) {
  3         10  
91 1 50 33     8 if (ref $arg ne 'HASH' and @_) {
92 1 50       9 if ($target{mode} eq '|-') {
93 0         0 @{$target{open}} = splice @_;
  0         0  
94             }
95             else {
96 1         2 my $last_mode = $target{mode};
97 1         3 @{$target{open}} = shift;
  1         3  
98             }
99             }
100             else {
101 0         0 croak "missing target file name";
102             }
103             }
104              
105 3 50       10 $target{open}[0] = qualify_to_ref($target{open}[0], caller)
106             if $target{mode} =~ tr/&//;
107              
108 3 50       29 unless ($target{mode} eq '|-') {
109 3 50       12 open my $teefh, $target{mode}, @{$target{open}}
  3         211  
110             or return undef;
111              
112 3 100       11 if ($target{reopen}) {
113 1         7 $target{mode} =~ s/>+/>>/;
114 1 50       16 close $teefh
115             or return undef;
116             }
117             else {
118 2         4 $target{teefh} = $teefh;
119 2 50       7 if ($target{autoflush}) {
120 2         6 my $oldsel = select $teefh;
121 2         5 $| = 1;
122 2         6 select $oldsel;
123             }
124             }
125             }
126             }
127              
128 5         20 push @target, \%target;
129             }
130              
131 1         2 my $fileno = eval { fileno($fh) };
  1         3  
132              
133 1 50       3 defined $fileno
134             or croak "only real file handles can be tee'ed";
135              
136 1 50       3 unless (defined $fileno) {
137 0         0 return undef;
138             }
139              
140             # flush any data buffered in $fh
141 1         4 my $oldsel = select($fh);
142 1         11 my @oldstate = ($|, $%, $=, $-, $~, $^, $.);
143 1         2 $| = 1;
144 1         3 select $oldsel;
145              
146 1 50       26 open my $out, ">&$fileno" or return undef;
147              
148 1         3 $oldsel = select $out;
149 1         3 $| = $oldstate[0];
150 1         3 select $oldsel;
151              
152 1         1039 my $pid = open $fh, '|-';
153 1 50       49 unless ($pid) {
154 0 0       0 defined $pid
155             or return undef;
156              
157 0         0 $SIG{INT} = 'IGNORE';
158 0         0 undef @ARGV;
159 0         0 eval { $0 = "perl [File::Tee]" };
  0         0  
160              
161 0         0 my $error = 0;
162              
163 0         0 my $oldsel = select STDERR;
164 0         0 $| = 1;
165              
166 0         0 for my $target (@target) {
167 0         0 my $begin = $target->{begin};
168 0 0       0 &$begin if $begin;
169             }
170 0         0 my $buffer = '';
171 0         0 my $eof;
172 0   0     0 while(!$error and !$eof) {
173 0         0 my $read = sysread STDIN, $buffer, 16*1024, length $buffer;
174 0 0       0 if ($read) {
175 0         0 print $out substr $buffer, -$read;
176             }
177             else {
178 0         0 $eof = 1;
179             }
180 0   0     0 while (!$error and length $buffer) {
181 0         0 my $line;
182 0         0 my $eol = index $buffer, $/;
183 0 0       0 if ($eol >= 0) {
    0          
184 0         0 $line = substr $buffer, 0, $eol + length $/, '';
185             }
186             elsif ($eof) {
187 0         0 $line = $buffer;
188 0         0 $buffer = '';
189             }
190             else {
191 0         0 last;
192             }
193              
194 0         0 for my $target (@target) {
195 0         0 my $cp = $line;
196 0 0       0 $cp = join('', $target->{preprocess}($cp)) if $target->{preprocess};
197 0 0       0 $cp = $target->{prefix} . $cp if length $target->{prefix};
198 0         0 my $process = $target->{process};
199 0 0       0 if ($process) {
200 0         0 my $ok;
201 0         0 $ok = &$process for ($cp);
202 0 0 0     0 $error = 1 unless ($ok or $target->{ignore_errors});
203             }
204             else {
205 0         0 my $teefh = $target->{teefh};
206 0 0       0 unless ($teefh) {
207 0         0 undef $teefh;
208 0 0       0 if (open $teefh, $target->{mode}, @{$target->{open}}) {
  0         0  
209 0 0       0 unless ($target->{reopen}) {
210 0         0 $target->{teefh} = $teefh;
211 0 0       0 if ($target->{autoflush}) {
212 0         0 my $oldsel = select $teefh;
213 0         0 $| = 1;
214 0         0 select $oldsel;
215             }
216             }
217             }
218             else {
219 0 0       0 $error = 1 unless $target->{ignore_errors};
220 0         0 next;
221             }
222             }
223 0 0       0 flock($teefh, LOCK_EX) if $target->{lock};
224 0         0 print $teefh $cp;
225 0 0       0 flock($teefh, LOCK_UN) if $target->{lock};
226              
227 0 0       0 if ($target->{reopen}) {
228 0 0       0 unless (close $teefh) {
229 0 0       0 $error = 1 unless $target->{ignore_errors};
230             }
231 0         0 delete $target->{teefh};
232             }
233             }
234             }
235             }
236             }
237              
238 0         0 for my $target (@target) {
239              
240 0         0 my $end = $target->{end};
241 0 0       0 &$end if $end;
242              
243 0         0 my $teefh = $target->{teefh};
244 0 0       0 if ($teefh) {
245 0 0       0 unless (close $teefh) {
246 0 0       0 $error = 1 unless $target->{ignore_errors};
247             }
248             }
249             }
250              
251 0 0       0 close $out or $error = 1;
252              
253 0         0 _exit($error);
254             }
255             # close $teefh;
256              
257 1         48 $oldsel = select($fh);
258 1     1   8 no warnings 'once';
  1         2  
  1         324  
259 1         133 ($|, $%, $=, $-, $~, $^, $.) = @oldstate;
260 1         15 select($oldsel);
261              
262 1         305 return $pid;
263             }
264              
265             1;
266             __END__