File Coverage

blib/lib/IO/ReadHandle/Chain.pm
Criterion Covered Total %
statement 159 175 90.8
branch 53 70 75.7
condition 29 35 82.8
subroutine 29 30 96.6
pod 13 13 100.0
total 283 323 87.6


line stmt bran cond sub pod time code
1             package IO::ReadHandle::Chain;
2              
3 2     2   119786 use v5.14;
  2         15  
4 2     2   8 use strict;
  2         4  
  2         32  
5 2     2   7 use warnings;
  2         4  
  2         59  
6              
7 2     2   8 use Carp;
  2         4  
  2         91  
8 2     2   10 use Scalar::Util qw(reftype);
  2         3  
  2         84  
9 2     2   375 use Symbol qw(gensym);
  2         600  
  2         94  
10              
11             =head1 NAME
12              
13             B - Chain several sources through a single file
14             read handle
15              
16             =head1 VERSION
17              
18             Version 1.2.2
19              
20             =cut
21              
22 2     2   652 use version; our $VERSION = version->declare('v1.2.2');
  2         2970  
  2         11  
23              
24             =head1 SYNOPSIS
25              
26             use IO::ReadHandle::Chain;
27              
28             open $ifh, '<', 'somefile.txt';
29             $text = 'This is some text.';
30             $cfh = IO::ReadHandle::Chain->new('file.txt', \$text, $ifh);
31             print while <$cfh>;
32             # prints lines from file 'file.txt', then lines from scalar $text,
33             # then lines from file handle $ifh
34              
35             $line_number = $.; # cumulative line number from all sources
36              
37             @lines = <$cfh>; # or get all lines at once
38              
39             # or read bytes instead
40             $buffer = '';
41             $bytecount = read($cfh, $buffer, 100);
42             $bytecount = sysread($cfh, $buffer, 100);
43              
44             # or single characters
45             $c = getc($cfh);
46              
47             close($cfh);
48              
49             # OO, too
50             $line = $cfh->getline;
51             @lines = $cfh->getlines;
52             $bytecount = $cfh->read($buffer, $size, $offset);
53             $bytecount = $cfh->sysread($buffer, $size, $offset);
54             $c = $cfh->getc;
55             $line_number = $cfh->input_line_number;
56             $cfh->close;
57             print "end!\n" if $cfh->eof;
58              
59             # specific to IO::ReadHandle::Chain:
60             $current_source = $cfh->current_source;
61              
62             $cfh->set_field('mykey', $myvalue);
63             $value = $cfh->get_field('mykey');
64             $cfh->remove_field('mykey');
65              
66             =head1 DESCRIPTION
67              
68             This module chains any number of data sources (scalar, file, IO
69             handle) together for reading through a single file read handle. This
70             is convenient if you have multiple data sources of which some are very
71             large and you need to pretend that they are all inside a single data
72             source.
73              
74             Use the B object for reading as you would any
75             other filehandle.
76              
77             The module raises an exception if you try to C or C or
78             C through an B.
79              
80             When reading by lines, then the input record separator
81             L<$E|perlvar/"$/"> is used to separate the data into lines.
82              
83             The chain filehandle object does not close any of the file handles
84             that are passed to it as data sources.
85              
86             An B provides some methods that are not
87             available from a standard L:
88              
89             The L, L, and L methods
90             manipulate fields in a private area of the object -- private in the
91             sense that the other methods of the module do not access that area;
92             It's all yours.
93              
94             The L method identifies the current source being read
95             from.
96              
97             =head1 METHODS
98              
99             =head2 new
100              
101             $cfh = IO::ReadHandle::Chain->new(@sources);
102              
103             Creates a filehandle object based on the specified C<@sources>. The
104             sources are read in the order in which they are specified. To read
105             from a particular file, specify that file's path as a source. To read
106             the contents of a scalar, specify a reference to that scalar as a
107             source. To read from an already open file handle, specify that file
108             handle as a source.
109              
110             Croaks if any of the sources are not a scalar, a scalar reference, or
111             a file handle.
112              
113             =cut
114              
115             sub new {
116 15     15 1 12127 my ( $class, @sources ) = @_;
117 15   33     41 my $self = bless gensym(), ref($class) || $class;
118 15         261 tie *$self, $self;
119 15         35 return $self->open(@sources);
120             }
121              
122             sub TIEHANDLE {
123 15 50   15   52 return $_[0] if ref( $_[0] );
124 0         0 my ( $class, @sources ) = @_;
125 0         0 my $self = bless gensym(), $class;
126 0         0 return $self->open(@sources);
127             }
128              
129             # gets the specified field from the module's hash in the GLOB's hash
130             # part
131             sub _get {
132 679     679   912 my ( $self, $field ) = @_;
133 679         763 my $pkg = __PACKAGE__;
134 679         1374 return *$self->{$pkg}->{$field};
135             }
136              
137             # sets the specified field in the module's hash in the GLOB's hash
138             # part to the specified value
139             sub _set {
140 103     103   159 my ( $self, $field, $value ) = @_;
141 103         123 my $pkg = __PACKAGE__;
142 103         160 *$self->{$pkg}->{$field} = $value;
143 103         165 return $self;
144             }
145              
146             # if the $field is defined, then deletes the specified field from the
147             # module's hash in the object's hash part. Otherwise, deletes the
148             # module's hash from the GLOB's hash part.
149             sub _delete {
150 45     45   61 my ( $self, $field ) = @_;
151 45         63 my $pkg = __PACKAGE__;
152 45 100       79 if ( defined $field ) {
153 20         38 delete *$self->{$pkg}->{$field};
154             }
155             else {
156 25         51 delete *$self->{$pkg};
157             }
158 45         63 return $self;
159             }
160              
161             =head2 close
162              
163             $cfh->close;
164             close $cfh;
165              
166             Closes the stream. Closes any filehandles that the instance created,
167             but does not close any filehandles that were passed into the instance
168             as sources.
169              
170             Returns the object.
171              
172             =cut
173              
174             sub close {
175 11     11 1 34 my ($self) = @_;
176 11         24 $self->_delete;
177 11         15 return $self;
178             }
179              
180             =head2 current_source
181              
182             $current_source = $cfh->current_source;
183              
184             Returns text describing the source that the next input from the stream
185             will come from, or (at the end of the data) that the last input came
186             from.
187              
188             For a source specified as a path name, returns that path name.
189              
190             For a source specified as a filehandle, returns the result of calling
191             the C method on that filehandle, unless it returns the
192             undefined value or the filehandle doesn't support the
193             C method, in which case the current method returns the
194             stringified version of the filehandle.
195              
196             For a source specified as a reference to a scalar, returns
197             C with the C<...> replaced with up to the first 10
198             characters of the scalar, with newlines replaced by spaces.
199              
200             =cut
201              
202             sub current_source {
203 25     25 1 38 my ($self) = @_;
204 25         39 my $source = $self->_get('source');
205 25 100       44 return unless defined $source;
206 24 100       46 if ( ref $source ) {
207 13 100       33 if ( reftype($source) eq 'GLOB' ) {
208 2         4 my $s = eval { $source->current_source };
  2         23  
209 2 50       10 return defined($s) ? $s : "$source";
210             }
211 11 50       22 if ( reftype($source) eq 'SCALAR' ) {
212 11         73 return sprintf( 'SCALAR(%.10s)', $$source =~ s/\n/ /gr );
213             }
214             }
215 11         44 return $source;
216             }
217              
218             =head2 eof
219              
220             $end_of_data = eof $cfh;
221             $end_of_data = $cfh->eof;
222              
223             Returns 1 when there is no (more) data to read from the stream, and
224             C<''> otherwise.
225              
226             =cut
227              
228             sub eof {
229 27     27 1 1023 return EOF(@_);
230             }
231              
232             =head2 get_field
233              
234             $value = $cfh->get_field($field);
235             $value = $cfh->get_field($field, $default);
236              
237             Returns the value of the private field C<$field> from the filehandle.
238              
239             If that field does not yet exist, and if C<$default> is not specified,
240             then does not modify the object and returns the undefined value.
241              
242             If the field does not yet exist but C<$default> is specified, then
243             creates the field, assigns it the value C<$default>, and then returns
244             that value.
245              
246             =cut
247              
248             sub get_field {
249 9     9 1 24 my ( $self, $field, $default ) = @_;
250 9         18 my $href = $self->_get('_');
251 9 100       122 if ( @_ >= 3 ) { # $default specified
252 3 100       6 if ( not $href ) {
253 1         2 $href = {};
254 1         5 $self->_set( '_', $href );
255             }
256 3   66     9 $href->{$field} //= $default;
257             }
258             else { # no $default specified
259 6 100       19 return unless $href;
260             }
261 7         28 return $href->{$field};
262             }
263              
264             =head2 getc
265              
266             $char = $cfh->getc;
267             $char = getc $ifh;
268              
269             Returns the next character from the stream, or C if there are
270             no more characters.
271              
272             =cut
273              
274             sub getc {
275 1     1 1 7 return GETC(@_);
276             }
277              
278             =head2 getline
279              
280             $line = $cfh->getline;
281             $line = <$cfh>;
282             $line = readline $cfh;
283              
284             Reads the next line from the stream. The input record separator
285             (L<$E|perlvar/"$/">) or end-of-data mark the end of the line.
286              
287             =cut
288              
289             sub getline {
290 13     13 1 107 my ($self) = @_;
291 13         23 my $line = <$self>;
292 13         40 return $line;
293             }
294              
295             =head2 getlines
296              
297             @lines = $cfh->getlines;
298             @lines = <$cfh>;
299              
300             Reads all remaining lines from the stream. The input record separator
301             (L<$E|perlvar/"$/">) or end-of-data mark the end of each line.
302              
303             =cut
304              
305             sub getlines {
306 1     1 1 6 my ($self) = @_;
307 1         3 my @lines = <$self>;
308 1         6 return @lines;
309             }
310              
311             =head2 input_line_number
312              
313             $line_number = $cfh->input_line_number; # get
314             $previous_value = $cfh->input_line_number($new_value); # set
315             $line_number = $.; # until next read from any filehandle
316              
317             Returns the number of lines read through the filehandle, and makes
318             that number also available in the special variable L<$.|perlvar/$.>.
319             If no lines have been read yet, then returns the undefined value.
320              
321             The line number is cumulative across all sources specified for the
322             B.
323              
324             If an argument is specified, then the method sets the current line
325             number to that value -- without changing the position in the stream.
326              
327             =cut
328              
329             sub input_line_number {
330 26     26 1 112 my $self = shift;
331 26 50       47 if (@_) {
332 0         0 $self->_set( 'line_number', $_[0] );
333             }
334 26         35 return $self->_get('line_number');
335             }
336              
337             =head2 open
338              
339             $cfh->open(@sources);
340              
341             (Re)opens the B object, for reading the
342             specified C<@sources>. See L for details about the C<@sources>.
343             Croaks if any of the sources are unacceptable.
344              
345             Returns the B on success.
346              
347             =cut
348              
349             sub open {
350 15     15 1 32 my ( $self, @sources ) = @_;
351 15         28 foreach my $source (@sources) {
352 23 100 100     247 croak "Sources must be scalar, scalar reference, or file handle"
      100        
353             if ref($source) ne ''
354             and reftype($source) ne 'GLOB'
355             and reftype($source) ne 'SCALAR';
356             }
357              
358             # we must preserve the line number, but clear everything else
359 14         27 my $line_number = $self->_get('line_number');
360 14         32 $self->_delete; # clear all
361 14         32 $self->_set( line_number => $line_number );
362              
363             # store the new sources
364 14         27 $self->_set( sources => \@sources );
365              
366 14         72 return $self;
367             }
368              
369             =head2 read
370              
371             $cfh->read($buffer, $length, $offset);
372             read $cfh, $buffer, $length, $offset;
373              
374             Reads up to C<$length> characters from the stream into the C<$buffer>
375             at offset C<$offset>. Returns the number of characters read, or 0
376             when there are no more characters.
377              
378             =cut
379              
380             sub read {
381 2     2 1 1770 return READ(@_);
382             }
383              
384             =head2 remove_field
385              
386             $cfh->remove_field($field);
387              
388             Removes the filehandle's private field with the specified name, if it
389             exists. Returns the filehandle.
390              
391             =cut
392              
393             sub remove_field {
394 1     1 1 3 my ( $self, $field ) = @_;
395 1         3 my $href = $self->_get('_');
396 1 50       4 if ($href) {
397 1         2 delete $href->{$field};
398             }
399 1         4 return $self;
400             }
401              
402             =head2 set_field
403              
404             $cfh->set_field($field, $value);
405              
406             Sets the filehandle's private field with key C<$field> to the
407             specified C<$value>. Returns the filehandle.
408              
409             =cut
410              
411             sub set_field {
412 1     1 1 4 my ( $self, $field, $value ) = @_;
413 1         3 my $href = $self->_get('_');
414 1 50       3 if ( not $href ) {
415 0         0 $self->_set( '_', $href = {} );
416             }
417 1         3 $href->{$field} = $value;
418 1         3 return $self;
419             }
420              
421             # Tie::Handle method implementations
422              
423             sub EOF {
424 126     126   212 my ($self) = @_;
425 126         179 my $ifh = $self->_get('ifh');
426 126 100 100     278 return '' if $ifh && not( $ifh->eof );
427              
428 58   100     168 while ( not( $self->_get('ifh') ) || $self->_get('ifh')->eof ) {
429 59 100       167 if ( $self->_get('ifh') ) {
430 20         36 $self->_delete('ifh');
431             }
432 59         89 my $sources_lref = $self->_get('sources');
433 59 100 100     102 last unless $sources_lref && @{$sources_lref};
  57         149  
434 21         23 my $source = shift @{$sources_lref};
  21         34  
435 21         41 $self->_set( source => $source );
436 21 100 100     103 if ( ( reftype($source) // '' ) eq 'GLOB' ) {
    50 66        
437 4         14 $self->_set( ifh => $source );
438             }
439             elsif (
440             ref($source) eq '' # read from file
441             or reftype($source) eq 'SCALAR'
442             )
443             { # read from scalar
444 17 50   1   263 CORE::open my $ifh, '<', $source or croak $!;
  1         6  
  1         2  
  1         5  
445 17         623 $self->_set( ifh => $ifh );
446             }
447             else {
448 0         0 croak 'Unsupported source type ' . ref($source);
449             }
450             }
451              
452 58         243 my $result;
453 58 100       89 if ( $self->_get('ifh') ) {
454 20         37 $result = '';
455             }
456             else {
457 38         51 $result = 1;
458             }
459 58         82 $. = $self->_get('line_number');
460 58         155 return $result;
461             }
462              
463             sub READLINE {
464 47     47   155 my ($self) = @_;
465 47 100       77 if (wantarray) {
466 5         10 my @lines = ();
467 5         6 my $line;
468 5         10 push @lines, $line while $line = $self->READLINE;
469 5         28 return @lines;
470             }
471             else {
472 42 100       58 if ( $self->EOF ) {
473 10         14 $. = $self->_get('line_number');
474 10         54 return;
475             }
476              
477             # $self->EOF has lined up the next source in $self->{ifh}
478              
479 32         176 my $ifh = $self->_get('ifh');
480 32         109 my $line = <$ifh>;
481 32 100       72 if ( $ifh->eof ) {
482              
483             # Does line end in the input record separator? If yes, then
484             # return the line. If no, then attempt to append the first line
485             # from the next source.
486 16         144 while ( $line !~ m#$/$# ) {
487 16 50       33 if ( $ifh->eof ) {
488 16 100       76 last if $self->EOF;
489              
490             # $self->EOF has lined up the next source in $self->{ifh}
491 8         16 $ifh = $self->_get('ifh');
492             }
493 8         85 $line .= <$ifh>;
494             }
495             }
496 32 50       126 if ( defined $line ) {
497 32   100     60 $self->_set( line_number => ( $self->_get('line_number') // 0 ) + 1 );
498 32         51 $. = $self->_get('line_number');
499             }
500 32         162 return $line;
501             }
502             }
503              
504             sub READ {
505 11     11   65 my ( $self, undef, $length, $offset ) = @_;
506 11         17 my $bufref = \$_[1];
507 11   100     37 $offset //= 0;
508              
509             # Adjust buffer for appending at $offset: Any previous contents
510             # beyond that offset are lost. If the buffer is not that long, then
511             # pad with \0 until it is long enough. (This is what CORE::read
512             # does, too.)
513              
514 11   50     23 $$bufref //= '';
515 11         24 my $l = length($$bufref);
516 11 50       20 if ( $offset < 0 ) {
517 0         0 $offset = $l - $offset;
518 0 0       0 if ( $offset < 0 ) {
519              
520             # TODO: what does CORE::read do in this case?
521 0         0 $offset = 0;
522             }
523             }
524 11 100       29 if ( $offset < $l ) {
    50          
525              
526             # chop off everything beyond $offset
527 6         16 substr $$bufref, $offset, $l - $offset, '';
528             }
529             elsif ( $offset > $l ) {
530              
531             # pad \0 until the offset
532 0         0 $$bufref .= '\x0' x ( $offset - $l );
533             }
534              
535 11 100       17 if ( $self->EOF ) {
536 2         7 return 0;
537             }
538              
539             # $self->EOF has lined up the next source in $self->{ifh}
540              
541 9         47 my $ifh = $self->_get('ifh');
542 9         29 my $n = $ifh->read( $$bufref, $length, $offset );
543 9   66     71 while ( $ifh->eof && $n < $length ) {
544 3 100       25 last if $self->EOF;
545              
546             # $self->EOF has lined up the next source in $self->{ifh}
547 1         3 $ifh = $self->_get('ifh');
548 1         8 my $thisn = $ifh->read( $$bufref, $length - $n, $offset + $n );
549 1         10 $n += $thisn;
550             }
551 9         64 return $n;
552             }
553              
554             sub GETC {
555 1     1   13 my ($self) = @_;
556 1         3 my $buf = '';
557 1         3 my $n = $self->READ( $buf, 1, 0 );
558 1 50       10 return $n ? $buf : undef;
559             }
560              
561             sub CLOSE {
562 0     0   0 my ($self) = @_;
563 0 0       0 if ( $self->{ifh} ) {
564 0         0 delete $self->{ifh};
565 0         0 @{ $self->{sources} } = ();
  0         0  
566             }
567 0         0 return;
568             }
569              
570             =head1 AUTHOR
571              
572             Louis Strous, C<< >>
573              
574             =head1 BUGS
575              
576             Please report any bugs or feature requests to
577             C, or through the web
578             interface at
579             L.
580             I will be notified, and then you'll automatically be notified of
581             progress on your bug as I make changes.
582              
583             =head1 SUPPORT
584              
585             You can find documentation for this module with the perldoc command.
586              
587             perldoc IO::ReadHandle::Chain
588              
589             You can also look for information at:
590              
591             =over 4
592              
593             =item * RT: CPAN's request tracker (report bugs here)
594              
595             L
596              
597             =item * AnnoCPAN: Annotated CPAN documentation
598              
599             L
600              
601             =item * CPAN Ratings
602              
603             L
604              
605             =item * Search CPAN
606              
607             L
608              
609             =back
610              
611             =head1 LICENSE AND COPYRIGHT
612              
613             Copyright 2017, 2018 Louis Strous.
614              
615             This program is free software; you can redistribute it and/or modify it
616             under the terms of the the Artistic License (2.0). You may obtain a
617             copy of the full license at:
618              
619             L
620              
621             Any use, modification, and distribution of the Standard or Modified
622             Versions is governed by this Artistic License. By using, modifying or
623             distributing the Package, you accept this license. Do not use, modify,
624             or distribute the Package, if you do not accept this license.
625              
626             If your Modified Version has been derived from a Modified Version made
627             by someone other than you, you are nevertheless required to ensure that
628             your Modified Version complies with the requirements of this license.
629              
630             This license does not grant you the right to use any trademark, service
631             mark, tradename, or logo of the Copyright Holder.
632              
633             This license includes the non-exclusive, worldwide, free-of-charge
634             patent license to make, have made, use, offer to sell, sell, import and
635             otherwise transfer the Package with respect to any patent claims
636             licensable by the Copyright Holder that are necessarily infringed by the
637             Package. If you institute patent litigation (including a cross-claim or
638             counterclaim) against any party alleging that the Package constitutes
639             direct or contributory patent infringement, then this Artistic License
640             to you shall terminate on the date that such litigation is filed.
641              
642             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
643             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
644             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
645             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
646             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
647             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
648             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
649             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
650              
651             =head1 SEE ALSO
652              
653             L.
654              
655             =cut
656              
657             1; # End of IO::ReadHandle::Chain