File Coverage

blib/lib/Tie/FileHandle/Split.pm
Criterion Covered Total %
statement 72 78 92.3
branch 12 24 50.0
condition n/a
subroutine 18 18 100.0
pod 5 5 100.0
total 107 125 85.6


line stmt bran cond sub pod time code
1             #!/usr/local/bin/perl
2            
3             package Tie::FileHandle::Split;
4            
5             =head1 NAME
6            
7             Tie::FileHandle::Split - Filehandle tie that captures, splits and stores output into files in a given path.
8            
9             =head1 VERSION
10            
11             Version 0.95
12            
13             =cut
14            
15             $VERSION = 0.95;
16            
17             =head1 DESCRIPTION
18            
19             This module, when tied to a filehandle, will capture and store all that
20             is output to that handle. You should then select a path to store files and a
21             size to split files.
22            
23             =head1 SYNOPSIS
24            
25             # $path should exist or the current process have enough priv. for creation.
26             # $size should be > 0.
27             tie *HANDLE, 'Tie::FileHandle::Split', $path, $size;
28            
29             # Register code to listen to file creation
30             (tied *HANDLE)->add_file_creation_listeners( sub {
31             my ( $tied_object, $filename) = @_;
32             print "Created $filename with size: " . -s $filename . "\n";
33             } );
34            
35             # Will create int( $many_times_size / $split_size) files of size $split_size.
36             # Will call each listener int( $many_times_size / $split_size) times.
37             # Buffers will hold $many_times_size % $split_size outstanding bytes.
38             (tied *HANDLE)->print( ' ' x $many_times_size );
39            
40             # Write all outstanding output from buffers to files.
41             # The last file created can be smaller than split_size
42             (tied *HANDLE)->write_buffers;
43            
44             # Get generated filenames to the moment
45             (tied *HANDLE)->get_filenames();
46            
47             =cut
48            
49 1     1   133656 use 5.10.0;
  1         4  
  1         42  
50 1     1   6 use strict;
  1         2  
  1         27  
51 1     1   6 use warnings;
  1         7  
  1         34  
52            
53 1     1   5 use vars qw(@ISA $VERSION);
  1         2  
  1         58  
54 1     1   5 use base qw(Tie::FileHandle::Base);
  1         3  
  1         120  
55            
56 1     1   6 use File::Path;
  1         3  
  1         75  
57 1     1   6 use File::Temp;
  1         2  
  1         90  
58 1     1   6 use Carp;
  1         2  
  1         1142  
59            
60             # Tie::FileHandle implementation
61             # Usage: tie *HANDLE, 'Tie::FileHandle::Split', $path, $split_size
62             sub TIEHANDLE {
63 2     2   2256 my ( $class, $path, $split_size ) = @_;
64            
65 2         15 my $self = {
66             class => $class,
67             path => $path,
68             split_size => $split_size,
69             buffer => '',
70             buffer_size => 0,
71             filenames => [],
72             listeners => {},
73             };
74            
75 2 50       38 File::Path::make_path( $self->{path} ) unless -d $self->{path};
76            
77 2         10 bless $self, $class;
78             }
79            
80             # Tie::FileHandle implementation
81             # Print to the selected handle
82             sub PRINT {
83 10     10   8798 my ( $self, $data ) = @_;
84 10         7230 $self->{buffer} .= $data;
85 10         29 $self->{buffer_size} += length( $data );
86            
87 10         30 $self->_write_files( $self->{split_size} );
88             }
89            
90             sub _write_files{
91 13     13   20 my ( $self, $min_size ) = @_;
92            
93 13         17 my $written_chunks = 0;
94            
95 13         49 while ( $self->{buffer_size} - $min_size * $written_chunks >= $min_size ) {
96 10         52 my ($fh, $filename) = File::Temp::tempfile( DIR => $self->{path} );
97            
98             # Added complexity to work buffer with a cursor and doing a single buffer chomp
99 10         4793 $fh->print( substr $self->{buffer},$min_size * $written_chunks++, $min_size );
100 10         28658 $fh->autoflush;
101 10         1200 $fh->close;
102            
103             # Call listeners
104 10         217 foreach my $listener ( keys %{$self->{listeners}} ) {
  10         63  
105 8         5040 &{$self->{listeners}->{$listener}}( $self, $filename );
  8         25  
106             }
107            
108 10         2312 push @{$self->{filenames}}, $filename;
  10         95  
109             }
110 13 100       46 if ( $written_chunks ) {
111 6         12 $self->{buffer_size} -= $min_size * $written_chunks;
112 6 50       45 if ( $self->{buffer_size} > 0 ) {
113 0         0 $self->{buffer} = substr $self->{buffer}, -$self->{buffer_size} ;
114             } else {
115 6         43 $self->{buffer} = '';
116             }
117             }
118             }
119            
120             =head1 METHODS
121            
122             =head3 C
123            
124             C writes all outstanding buffers to files.
125             It is automatically called before destroying the object to ensure all data
126             written to the tied filehandle is written to files. If additional data is
127             written to the filehandle after a call to C a new file will be
128             created. On a standard file split operation it is called after writting all data
129             to the tied file handle ensure the last bit of data is written (in the most
130             common case where data size is not exactly divisible by the split size).
131            
132             =cut
133            
134             sub write_buffers {
135             # Must implement
136 2     2 1 1018 my ( $self ) = @_;
137            
138             # this should not happen...
139 2         8 $self->_write_files( $self->{split_size} );
140 2 100       9 if ( $self->{buffer_size} > 0 ) {
141 1         4 $self->_write_files( $self->{buffer_size} );
142             }
143             }
144            
145             =head3 C
146            
147             C returns a list of the files generates until the moment of the
148             call. It should be used to get the names of files and rename them to the
149             desired filenames. In a standard splitting operation C is
150             called after outputting all data to the filehandle and calling C.
151            
152             =cut
153            
154             # Returns filenames generated up to the moment the method is called
155             sub get_filenames {
156 12     12 1 2149 my ( $self ) = @_;
157            
158 12 50       42 return @{$self->{filenames}} if defined $self->{filenames};
  12         62  
159             }
160            
161             =head3 C
162            
163             C adds methods to the list of listeners of the
164             file creation event. Methods should be code, array, arrayref or any
165             non-recursive structure resulting from them. Since methods are added to a HASH,
166             several elements pointing to the same piece of code will be added only once.
167             Code observing this event is called once per file created of the $split_size
168             size defined in the tie clause. When called the Tie::FileHandle::Split object
169             and the complete path to the newly created file is passed as parameter. The file
170             is of the specified C<$split_size> defined in the tie clause unless generated
171             from a C call, has been closed and an effort has been made for it
172             to sync (untested).
173            
174             =cut
175            
176             sub add_file_creation_listeners {
177 4     4 1 2288 my ( $self, @listeners ) = @_;
178            
179 4         9 foreach my $listener ( @listeners ) {
180 7 100       21 if( ref( $listener ) eq 'CODE' ) {
    50          
    0          
181 6         24 $self->{listeners}->{$listener} = $listener;
182             } elsif ( ref( $listener ) eq 'ARRAY' ) {
183 1         22 $self->add_file_creation_listeners( @$listener );
184             } elsif ( ref( $listener ) eq 'ARRAYREF' ) {
185 0         0 $self->add_file_creation_listeners( $listener );
186             } else {
187 0         0 croak("Unsupported structure in add_file_creation_listeners. " .
188             "Can use any structure containing CODE, ARRAY and ARRAYREF. " .
189             "Looks like a " . ref( $listener ) );
190             }
191             }
192             }
193            
194             =head3 C
195            
196             C removes a list of methods from the list of
197             listeners of the file creation event. Methods should be code, array, arrayref or
198             any non-recursive structure resulting from them.
199            
200             =cut
201            
202             sub remove_file_creation_listeners {
203 2     2 1 885 my ( $self, @listeners ) = @_;
204            
205 2         4 foreach my $listener ( @listeners ) {
206 2 50       6 if( ref( $listener ) eq 'CODE' ) {
    0          
    0          
207 2         10 delete $self->{listeners}->{$listener};
208             } elsif ( ref( $listener ) eq 'ARRAY' ) {
209 0         0 $self->remove_file_creation_listeners( @$listener );
210             } elsif ( ref( $listener ) eq 'ARRAYREF' ) {
211 0         0 $self->remove_file_creation_listeners( $listener );
212             } else {
213 0         0 croak("Unsupported structure in add_file_creation_listeners. " .
214             "Can use any structure containing CODE, ARRAY and ARRAYREF. " .
215             "Looks like a " . ref( $listener ) );
216             }
217             }
218             }
219            
220             =head3 C
221            
222             C removes all methods from the list of listeners
223             of the file creation event.
224            
225             =cut
226            
227             sub clear_file_creation_listeners {
228 2     2 1 2004 my ( $self ) = @_;
229            
230 2         7 $self->{listeners} = {};
231             }
232            
233             sub _get_listeners {
234 1     1   5 my ( $self ) = @_;
235             # Behold! Dereferencing fixes incompatibility with pre 5.14 perl.
236             # Both keys and each are affected if a hashref is passed.
237 1         2 return map $_,keys %{$self->{listeners}};
  1         7  
238             }
239            
240             sub DESTROY {
241 1     1   2 my ( $self ) = @_;
242            
243 1 50       8 $self->write_buffers() if ( $self->{buffer_size} > 0 );
244             }
245            
246             1;
247            
248             =head1 TODO
249            
250             =over 4
251            
252             =item * Very untested for anything other than writing to the filehandle.
253            
254             =item * write_buffers should sync to disk, untested and seeking advice.
255            
256             =back
257            
258             =head1 BUGS
259            
260             No known bugs. Please report and suggest tests to gbarco@cpan.org.
261            
262             =cut
263            
264             =head1 AUTHORS AND COPYRIGHT
265            
266             Written by Gonzalo Barco based on Tie::FileHandle::Buffer written by Robby Walker ( robwalker@cpan.org ).
267            
268             Project repository can be found at https://github.com/gbarco/Tie-FileHandle-Split.
269            
270             You may redistribute/modify/etc. this module under the same terms as Perl itself.
271