File Coverage

blib/lib/IO/ReadHandle/Chain.pm
Criterion Covered Total %
statement 99 108 91.6
branch 31 38 81.5
condition 18 19 94.7
subroutine 19 19 100.0
pod 1 1 100.0
total 168 185 90.8


line stmt bran cond sub pod time code
1             package IO::ReadHandle::Chain;
2              
3 2     2   147319 use 5.006;
  2         18  
4 2     2   11 use strict;
  2         4  
  2         48  
5 2     2   10 use warnings;
  2         4  
  2         63  
6              
7 2     2   10 use Carp;
  2         4  
  2         122  
8 2     2   337 use IO::Handle qw(input_record_separator);
  2         4192  
  2         186  
9 2     2   17 use Scalar::Util qw(blessed reftype);
  2         14  
  2         154  
10 2     2   15 use Symbol qw(gensym);
  2         5  
  2         2639  
11              
12             =head1 NAME
13              
14             IO::ReadHandle::Chain - Chain several sources through a single file
15             read handle
16              
17             =head1 VERSION
18              
19             Version 1.0
20              
21             =cut
22              
23             our $VERSION = '1.0';
24              
25             =head1 SYNOPSIS
26              
27             This module chains any number of data sources (scalar, file, IO
28             handle) together for reading through a single file read handle.
29              
30             This is convenient if you have multiple data sources of which some are
31             very large and you need to pretend that they are all inside a single
32             data source.
33              
34             Use the IO::ReadHandle::Chain object for reading as you would any
35             other file handle.
36              
37             use IO::ReadHandle::Chain;
38              
39             open $ifh, '<', 'somefile.txt';
40             $text = 'This is some text.';
41             $cfh = IO::ReadHandle::Chain->new('file.txt', \$text, $ifh);
42             print while <$cfh>;
43             # prints lines from file 'file.txt', then lines from scalar $text,
44             # then lines from file handle $ifh
45              
46             @lines = <$cfh>; # or get all lines at once
47              
48             # or read bytes instead
49             $buffer = '';
50             $bytecount = read($cfh, $buffer, 100);
51             $bytecount = sysread($cfh, $buffer, 100);
52              
53             close($cfh);
54              
55             # OO, too
56             $line = $cfh->getline;
57             @lines = $cfh->getlines;
58             $bytecount = $cfh->read($buffer, $size, $offset);
59             $bytecount = $cfh->sysread($buffer, $size, $offset);
60             $cfh->close;
61             print "end!\n" if $cfh->eof;
62              
63             You cannot write or seek through an IO::ReadHandle::Chain.
64              
65             When reading by lines, then for each data source the associated input
66             record separator is used to separate the data into lines.
67              
68             For any of the data sources that are file handles, when the end of the
69             associated data stream is reached, or if the chain filehandle object
70             is closed, then it tries to reset the file handle's position to what
71             it was when the module started reading from the file handle.
72              
73             The chain filehandle object does not close any of the file handles
74             that are passed to it as data sources.
75              
76             =head1 SUBROUTINES/METHODS
77              
78             =head2 new(@sources)
79              
80             Creates a filehandle object based on the specified C<@sources>. The
81             sources are read in the order in which they are specified. To read
82             from a particular file, specify that file's path as a source. To read
83             the contents of a scalar, specify a reference to that scalar as a
84             source. To read from an already open file handle, specify that file
85             handle as a source.
86              
87             Croaks if any of the sources are not a scalar, a scalar reference, or
88             a file handle.
89              
90             =cut
91              
92             sub new {
93 15     15 1 14157 my ($class, @sources) = @_;
94 15         91 my $ifh = gensym(); # get generic symbol
95 15         345 tie(*$ifh, __PACKAGE__, @sources); # calls TIEHANDLE
96 14         108 return $ifh;
97             }
98              
99             sub TIEHANDLE {
100 15     15   52 my ($class, @sources) = @_;
101 15         49 foreach my $source (@sources) {
102 23 100 100     279 croak "Sources must be scalar, scalar reference, or file handle"
      100        
103             if ref($source) ne ''
104             and reftype($source) ne 'GLOB'
105             and reftype($source) ne 'SCALAR';
106             }
107 14         86 return bless { sources => \@sources }, $class;
108             }
109              
110             sub EOF {
111 71     71   166 my ($self) = @_;
112 71 100 100     308 return 0 if $self->{ifh} && not($self->{ifh}->eof);
113              
114 41   100     312 while (not($self->{ifh}) || $self->{ifh}->eof) {
115 42 100       259 if ($self->{ifh}) {
116 20 100       70 if (exists $self->{initial_position}) {
117             # Try to reset the file handle's position. It may fail, for
118             # example if the file handle is not seekable.
119 3         8 eval { seek $self->{ifh}, $self->{initial_position}, 0 };
  3         16  
120 3         8 delete $self->{initial_position};
121             }
122 20         52 delete $self->{ifh};
123             }
124 42 100       89 last unless @{$self->{sources}};
  42         135  
125 21         45 my $source = shift @{$self->{sources}};
  21         67  
126 21         44 my $ifh;
127 21 100 100     189 if ((reftype($source) // '') eq 'GLOB') {
    50 66        
128 4         12 $self->{ifh} = $source;
129 4         30 $self->{initial_position} = tell($source);
130             } elsif (ref($source) eq '' # read from file
131             or reftype($source) eq 'SCALAR') { # read from scalar
132 17 50   1   326 open my $ifh, '<', $source or croak $!;
  1         11  
  1         3  
  1         9  
133 17         870 $self->{ifh} = $ifh;
134             } else {
135 0         0 croak 'Unsupported source type ' . ref($source);
136             }
137             }
138              
139 41 100       436 if ($self->{ifh}) {
140             # figure out this file's input record separator
141 20         109 my $old = select($self->{ifh});
142 20         78 $self->{input_record_separator} = $/;
143 20         66 select($old);
144 20         90 return '';
145             } else {
146 21         101 return 1;
147             }
148             }
149              
150             sub READLINE {
151 48     48   1088 my ($self) = @_;
152 48 100       134 if (wantarray) {
153 6         15 my @lines = ();
154 6         11 my $line;
155 6         21 push @lines, $line while $line = $self->READLINE;
156 6         37 return @lines;
157             } else {
158 42 100       113 return undef if $self->EOF;
159              
160             # $self->EOF has lined up the next source in $self->{ifh}
161              
162 32         329 my $ifh = $self->{ifh};
163 32         142 my $line = <$ifh>;
164 32 100       107 if ($ifh->eof) {
165             # Does line end in $ifh's input record separator? If yes, then
166             # return the line. If no, then attempt to append the first line
167             # from the next source.
168 16         229 while ($line !~ m/$self->{input_record_separator}$/) {
169 16 50       56 if ($ifh->eof) {
170 16 100       143 last if $self->EOF;
171             # $self->EOF has lined up the next source in $self->{ifh}
172 8         51 $ifh = $self->{ifh};
173             }
174 8         93 $line .= <$ifh>;
175             }
176             }
177 32         308 return $line;
178             }
179             }
180              
181             sub READ {
182 10     10   1479 my ($self, undef, $length, $offset) = @_;
183 10         23 my $bufref = \$_[1];
184 10   100     58 $offset //= 0;
185              
186 10 100       27 return 0 if $self->EOF;
187              
188             # $self->EOF has lined up the next source in $self->{ifh}
189              
190 9         94 my $ifh = $self->{ifh};
191 9         48 my $n = $ifh->read($$bufref, $length, $offset);
192 9         99 while ($n < $length) {
193 3 100       12 last if $self->EOF;
194             # $self->EOF has lined up the next source in $self->{ifh}
195 1         7 $ifh = $self->{ifh};
196 1         7 my $thisn = $ifh->read($$bufref, $length - $n, $offset + $n);
197 1         11 $n += $thisn;
198             }
199 9         46 return $n;
200             }
201              
202             sub GETC {
203 1     1   29 my ($self) = @_;
204 1         3 my $buf = '';
205 1         6 my $n = $self->READ($buf, 1, 0);
206 1 50       11 return $n? $buf: undef;
207             }
208              
209             sub CLOSE {
210 11     11   1608 my ($self) = @_;
211 11 50       34 if ($self->{ifh}) {
212 0 0       0 if (exists $self->{initial_position}) {
213             # Try to reset the file handle's position. It may fail, for
214             # example if the file handle is not seekable.
215 0         0 eval { seek $self->{ifh}, $self->{initial_position}, 0 };
  0         0  
216 0         0 delete $self->{initial_position};
217             }
218 0         0 delete $self->{ifh};
219 0         0 delete $self->{input_record_separator};
220 0         0 @{$self->{sources}} = ();
  0         0  
221             }
222 11         38 return;
223             }
224              
225             sub PRINT {
226 1     1   14 my ($self) = @_;
227 1         189 croak "Cannot print via a " . blessed($self);
228             }
229              
230             sub PRINTF {
231 1     1   894 my ($self) = @_;
232 1         67 croak "Cannot printf via a " . blessed($self);
233             }
234              
235             sub WRITE {
236 1     1   492 my ($self) = @_;
237 1         79 croak "Cannot syswrite via a " . blessed($self);
238             }
239              
240             sub SEEK {
241 1     1   551 my ($self) = @_;
242 1         72 croak "Cannot seek via a " . blessed($self);
243             }
244              
245             =head1 AUTHOR
246              
247             Louis Strous, C<< >>
248              
249             =head1 BUGS
250              
251             Please report any bugs or feature requests to
252             C, or through the web
253             interface at
254             L.
255             I will be notified, and then you'll automatically be notified of
256             progress on your bug as I make changes.
257              
258             =head1 SUPPORT
259              
260             You can find documentation for this module with the perldoc command.
261              
262             perldoc IO::ReadHandle::Chain
263              
264             You can also look for information at:
265              
266             =over 4
267              
268             =item * RT: CPAN's request tracker (report bugs here)
269              
270             L
271              
272             =item * AnnoCPAN: Annotated CPAN documentation
273              
274             L
275              
276             =item * CPAN Ratings
277              
278             L
279              
280             =item * Search CPAN
281              
282             L
283              
284             =back
285              
286             =head1 LICENSE AND COPYRIGHT
287              
288             Copyright 2017 Louis Strous.
289              
290             This program is free software; you can redistribute it and/or modify it
291             under the terms of the the Artistic License (2.0). You may obtain a
292             copy of the full license at:
293              
294             L
295              
296             Any use, modification, and distribution of the Standard or Modified
297             Versions is governed by this Artistic License. By using, modifying or
298             distributing the Package, you accept this license. Do not use, modify,
299             or distribute the Package, if you do not accept this license.
300              
301             If your Modified Version has been derived from a Modified Version made
302             by someone other than you, you are nevertheless required to ensure that
303             your Modified Version complies with the requirements of this license.
304              
305             This license does not grant you the right to use any trademark, service
306             mark, tradename, or logo of the Copyright Holder.
307              
308             This license includes the non-exclusive, worldwide, free-of-charge
309             patent license to make, have made, use, offer to sell, sell, import and
310             otherwise transfer the Package with respect to any patent claims
311             licensable by the Copyright Holder that are necessarily infringed by the
312             Package. If you institute patent litigation (including a cross-claim or
313             counterclaim) against any party alleging that the Package constitutes
314             direct or contributory patent infringement, then this Artistic License
315             to you shall terminate on the date that such litigation is filed.
316              
317             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
318             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
319             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
320             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
321             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
322             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
323             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
324             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
325              
326             =cut
327              
328             1; # End of IO::ReadHandle::Chain