File Coverage

blib/lib/Iterator/IO.pm
Criterion Covered Total %
statement 140 143 97.9
branch 62 76 81.5
condition 33 42 78.5
subroutine 16 16 100.0
pod 4 4 100.0
total 255 281 90.7


line stmt bran cond sub pod time code
1             =for gpg
2             -----BEGIN PGP SIGNED MESSAGE-----
3             Hash: SHA1
4              
5             =head1 NAME
6              
7             Iterator::IO - Filesystem and stream iterators.
8              
9             =head1 VERSION
10              
11             This documentation describes version 0.02 of Iterator::IO.pm, August 23, 2005.
12              
13             =cut
14              
15 2     2   40865 use strict;
  2         4  
  2         69  
16 2     2   8 use warnings;
  2         3  
  2         99  
17             package Iterator::IO;
18             our $VERSION = '0.02';
19              
20 2     2   11 use base 'Exporter';
  2         7  
  2         212  
21 2     2   8 use vars qw/@EXPORT @EXPORT_OK %EXPORT_TAGS/;
  2         3  
  2         179  
22              
23             @EXPORT = qw(idir_listing idir_walk ifile ifile_reverse);
24             @EXPORT_OK = @EXPORT;
25              
26 2     2   1696 use Iterator;
  2         53110  
  2         3228  
27              
28             # Function name: idir_listing
29             # Synopsis: $iter = idir_listing ($path)
30             # Description: Returns the full file names in the specified directory.
31             # Created: 07/28/2005 by EJR
32             # Parameters: $path - Directory. If omitted, uses current dir.
33             # Returns: Iterator
34             # Exceptions: Iterator::X::Am_Now_Exhausted
35             sub idir_listing
36             {
37 18     18 1 7891 require IO::Dir;
38 18         26695 require Cwd;
39              
40 18   33     181 my $path = shift || Cwd::getcwd();
41 18         42 $path =~ s|/ \z||x; # remove any trailing slash
42 18         110 my $d = new IO::Dir $path;
43 18 50       1350 Iterator::X::IO_Error (message => qq{Cannot read "$path": $!},
44             error => $!)
45             unless $d;
46              
47             return Iterator->new (sub
48             {
49             # Get next file, skipping . and ..
50 73     73   1641 my $next;
51 73         83 while (1)
52             {
53 109         265 $next = $d->read;
54              
55 109 100       894 if (! defined $next)
56             {
57 18         26 undef $d; # allow garbage collection
58 18         47 Iterator::is_done();
59             }
60              
61 91 100 100     379 last if $next ne '.' && $next ne '..';
62             }
63              
64             # Return the filename
65 55         247 return "$path/$next";
66 18         156 });
67             }
68              
69              
70             # Function name: idir_walk
71             # Synopsis: $iter = idir_walk ($path)
72             # Description: Returns the directory tree below a given dir.
73             # Created: 07/28/2005 by EJR
74             # Parameters: $path - Directory. If omitted, uses current dir.
75             # Returns: Iterator
76             # Exceptions: Iterator::X::Am_Now_Exhausted
77             sub idir_walk
78             {
79 1     1 1 8501 my @dir_queue;
80 1         6 my $path = shift;
81 1         17 my $files = idir_listing($path);
82              
83             return Iterator->new (sub
84             {
85             # If no more files in current directory,
86             # get next directory off the queue
87 41     41   2297 while ($files->is_exhausted)
88             {
89             # Nothing else on the queue? Then we're done.
90 17 100       600 if (@dir_queue == 0)
91             {
92 1         2 undef $files; # allow garbage collection
93 1         4 Iterator::is_done();
94             }
95              
96             # Create an iterator to return the files in that directory
97 16         37 $files = idir_listing(shift @dir_queue);
98             }
99              
100             # Get next file in current directory
101 40         618 my $next = $files->value;
102              
103             # If this is a directory (and not a symlink), remember it for later recursion
104 40 100 66     8844 if (-d $next && !-l $next)
105             {
106 16         41 unshift @dir_queue, $next;
107             }
108              
109 40         165 return $next;
110 1         42 });
111             }
112              
113             # Function name: ifile
114             # Synopsis: $iter = ifile ($filename, {options});
115             # Description: Returns the lines of a file, one at a time.
116             # Created: 07/28/2005 by EJR
117             # Parameters: $filename - File name to open.
118             # \%options - hashref of options
119             # Returns: Iterator
120             # Exceptions: Iterator::X::Parameter_Error
121             # Iterator::X::IO_Error
122             # Iterator::X::Am_Now_Exhausted
123             sub ifile
124             {
125 10     10 1 22541 require IO::File;
126              
127 10         10012 my $filename = shift;
128              
129             # Options
130 10         11 my ($autochomp, $sep_defined, $separator);
131 10         15 $autochomp = 1; # default
132              
133             ################################################################
134             # Backwards-compatibility block.
135             # THIS WILL GO AWAY!
136             #
137             # This parses the old 'chomp' and 'nochomp' and 'sep' arguments.
138 10 100       27 if (@_)
139             {
140 9 100 100     65 if ($_[0] eq 'chomp' || $_[0] eq 'nochomp' || !defined($_[0]))
      66        
141             {
142 4         7 my $chomp_opt = shift;
143 4 50       12 if (defined $chomp_opt)
144             {
145 4 50 66     17 Iterator::X::Parameter_Error->throw(q{Invalid "chomp" argument to ifile})
146             if ($chomp_opt ne 'chomp' && $chomp_opt ne 'nochomp');
147              
148 4         9 $autochomp = $chomp_opt eq 'chomp';
149             }
150              
151             # Separator argument
152 4 100       11 if (@_)
153             {
154 2         3 $sep_defined = 1;
155 2         5 $separator = shift;
156             }
157             }
158             }
159             ################################################################
160              
161 10 100       24 if (@_)
162             {
163 5 100       29 Iterator::X::Parameter_Error->throw
164             (q{Second argument to ifile must be a hashref})
165             if ref $_[0] ne 'HASH';
166              
167 4         4 Option: while (my ($opt,$val) = each %{$_[0]})
  11         35  
168             {
169 7         11 my $lcopt = lc $opt; # because we're friendly.
170              
171 7 100       15 if ($lcopt eq 'chomp')
172             {
173 4         4 $autochomp = $val;
174 4         8 next Option;
175             }
176 3 50 100     20 if ($lcopt eq 'rs' || $lcopt eq '$/' || $lcopt eq 'input_record_separator')
      66        
177             {
178 3         4 $separator = $val;
179 3         4 $sep_defined = 1;
180 3         6 next Option;
181             }
182             Iterator::X::OptionError->throw
183 0         0 (message => qq{Unknown option: "$opt" in call to ifile},
184             name => $opt);
185             }
186             }
187              
188             # Open the file handle.
189 9         47 my $fh = new IO::File ($filename);
190 9 50       668 Iterator::X::IO_Error (message => qq{Cannot read "$filename": $!},
191             error => $!)
192             unless $fh;
193              
194             return Iterator->new (sub
195             {
196 45     45   971 my $line;
197              
198             # Get next line (delimited by $separator if such is defined);
199             {
200 45 100       46 local $/ = $sep_defined? $separator : $/;
  45         164  
201 45         1054 $line = $fh->getline();
202 45 100 100     1637 chomp $line if defined $line && $autochomp;
203             }
204              
205             # Done?
206 45 100       89 if (!defined $line)
207             {
208 9         33 $fh->close;
209 9         142 undef $fh;
210 9         40 Iterator::is_done();
211             }
212              
213             # Return the line
214 36         141 return $line;
215 9         69 });
216             }
217              
218             # Function name: ifile_reverse
219             # Synopsis: $iter = ifile_reverse ($filename, {options})
220             # Description: Returns the lines of a file, in reverse order
221             # Created: 07/28/2005 by EJR
222             # Parameters: $filename - File name to open.
223             # \%options - options: chomp => bool, $/ => string
224             # Returns: Iterator
225             # Exceptions: Iterator::X::Parameter_Error
226             # Iterator::X::IO_Error
227             # Iterator::X::Am_Now_Exhausted
228             sub ifile_reverse
229             {
230 10     10 1 14153 require IO::File;
231              
232 10         30 my $filename = shift;
233              
234             # Options
235 10         11 my ($autochomp, $sep_defined, $separator);
236 10         10 $autochomp = 1; # default
237              
238             ################################################################
239             # Backwards-compatibility block.
240             # THIS WILL GO AWAY!
241             #
242             # This parses the old 'chomp' and 'nochomp' and 'sep' arguments.
243 10 100       22 if (@_)
244             {
245 9 100 100     56 if ($_[0] eq 'chomp' || $_[0] eq 'nochomp' || !defined($_[0]))
      66        
246             {
247 4         5 my $chomp_opt = shift;
248 4 50       8 if (defined $chomp_opt)
249             {
250 4 50 66     16 Iterator::X::Parameter_Error->throw(q{Invalid "chomp" argument to ifile})
251             if ($chomp_opt ne 'chomp' && $chomp_opt ne 'nochomp');
252              
253 4         6 $autochomp = $chomp_opt eq 'chomp';
254             }
255              
256             # Separator argument
257 4 100       9 if (@_)
258             {
259 2         3 $sep_defined = 1;
260 2         3 $separator = shift;
261             }
262             }
263             }
264             ################################################################
265              
266 10 100       21 if (@_)
267             {
268 5 100       17 Iterator::X::Parameter_Error->throw
269             (q{Second argument to ifile_reverse must be a hashref})
270             if ref $_[0] ne 'HASH';
271              
272 4         5 Option: while (my ($opt,$val) = each %{$_[0]})
  11         34  
273             {
274 7         7 my $lcopt = lc $opt; # because we're friendly.
275              
276 7 100       15 if ($lcopt eq 'chomp')
277             {
278 4         5 $autochomp = $val;
279 4         6 next Option;
280             }
281 3 50 100     15 if ($lcopt eq 'rs' || $lcopt eq '$/' || $lcopt eq 'input_record_separator')
      66        
282             {
283 3         4 $separator = $val;
284 3         3 $sep_defined = 1;
285 3         5 next Option;
286             }
287             Iterator::X::OptionError->throw
288 0         0 (message => qq{Unknown option: "$opt" in call to ifile},
289             name => $opt);
290             }
291             }
292              
293             # Must read chunks of the end of the file into memory
294 9         11 my $block_size = 8192; # somewhat arbitrary
295              
296 9 50       38 my $fh = IO::File->new ($filename)
297             or Iterator::X::IO_Error (message => qq{Cannot read "$filename": $!},
298             error => $!);
299              
300             # Buffer variables
301 9         556 my $leftover;
302             my @lines;
303              
304             # Are we at the start of the file?
305 9     18   28 my $at_start = sub {$fh->tell == 0};
  18         38  
306              
307             my $break = sub
308             {
309 9     9   10 my $block = shift;
310 9 50       18 $block .= $leftover if defined $leftover;
311              
312             # Split up the block
313 9 100       17 my $sep = $sep_defined? $separator : $/;
314 9         119 @lines = reverse split /(?<=\Q$sep\E)/, $block;
315 9         19 $leftover = pop @lines;
316 9         28 };
317              
318             my $prev_block = sub
319             {
320 9     9   33 my $pos = $fh->tell;
321 9         41 my $bytes = 1 + ($pos-1) % $block_size;
322 9         9 my $buf;
323              
324 9         39 my $seek_ok = seek $fh, -$bytes, 1;
325 9 50       19 Iterator::X::IO_Error->throw
326             (message => qq{Seek error on $filename: $!},
327             os_error => $!)
328             unless $seek_ok;
329              
330 9         111 my $num_read = read $fh, $buf, $bytes;
331 9 50       18 Iterator::X::IO_Error->throw
332             (message => qq{Read error on $filename: $!},
333             os_error => $!)
334             if ! defined $num_read;
335              
336 9         36 seek $fh, -$bytes, 1;
337 9 50       16 Iterator::X::IO_Error->throw
338             (message => qq{Seek error on $filename: $!},
339             os_error => $!)
340             unless $seek_ok;
341              
342 9         23 return $buf;
343 9         28 };
344              
345 9         43 seek $fh, 0, 2; # end of file
346 9         14 $break->( $prev_block->() );
347              
348             return Iterator->new (sub
349             {
350 45 100   45   1046 if (@lines == 0)
351             {
352 18 50       25 if ($at_start->())
353             {
354 18         87 @lines = $leftover;
355 18         22 undef $leftover;
356             }
357             else
358             {
359 0         0 $break->( $prev_block->() );
360             }
361             }
362              
363             # Return the line (chomped if so requested)
364 45         58 my $line = shift @lines;
365              
366             # Exhausted?
367 45 100       87 Iterator::is_done()
368             if ! defined $line;
369              
370 36 100       56 my $sep = $sep_defined? $separator : $/;
371 36 100       135 $line =~ s/\Q$sep\E$// if $autochomp;
372 36         110 return $line;
373 9         56 });
374             }
375              
376              
377             1;
378             __END__