File Coverage

blib/lib/Directory/Scanner/Stream.pm
Criterion Covered Total %
statement 68 73 93.1
branch 14 22 63.6
condition 7 11 63.6
subroutine 17 18 94.4
pod 7 8 87.5
total 113 132 85.6


line stmt bran cond sub pod time code
1             package Directory::Scanner::Stream;
2             # ABSTRACT: Streaming directory iterator
3              
4 8     8   49 use strict;
  8         15  
  8         203  
5 8     8   35 use warnings;
  8         15  
  8         154  
6              
7 8     8   44 use Carp ();
  8         14  
  8         86  
8 8     8   33 use Scalar::Util ();
  8         15  
  8         124  
9 8     8   4358 use Path::Tiny ();
  8         92355  
  8         247  
10              
11 8     8   3083 use UNIVERSAL::Object;
  8         6788  
  8         214  
12 8     8   46 use Directory::Scanner::API::Stream;
  8         20  
  8         420  
13              
14             our $VERSION = '0.02';
15             our $AUTHORITY = 'cpan:STEVAN';
16              
17 8   50 8   48 use constant DEBUG => $ENV{DIR_SCANNER_STREAM_DEBUG} // 0;
  8         19  
  8         742  
18              
19             ## ...
20              
21 8     8   652 our @ISA; BEGIN { @ISA = ('UNIVERSAL::Object', 'Directory::Scanner::API::Stream') }
22             our %HAS; BEGIN {
23             %HAS = (
24 0         0 origin => sub { die 'You must supply a `origin` directory path' },
25             # internal state ...
26             _head => sub {},
27             _handle => sub {},
28 56         955 _is_done => sub { 0 },
29 56         481 _is_closed => sub { 0 },
30             )
31 8     8   3516 }
32              
33             ## ...
34              
35             sub BUILD {
36 56     56 1 1820 my ($self, $params) = @_;
37              
38 56         111 my $dir = $self->{origin};
39              
40             # upgrade this to a Path:Tiny
41             # object if needed
42 56 100 66     353 $self->{origin} = $dir = Path::Tiny::path( $dir )
43             unless Scalar::Util::blessed( $dir )
44             && $dir->isa('Path::Tiny');
45              
46             # make sure the directory is
47             # fit to be streamed
48 56 50       990 (-d $dir)
49             || Carp::confess 'Supplied path value must be a directory ('.$dir.')';
50 56 50       839 (-r $dir)
51             || Carp::confess 'Supplied path value must be a readable directory ('.$dir.')';
52              
53 56         498 my $handle;
54 56 50       239 opendir( $handle, $dir )
55             || Carp::confess 'Unable to open handle for directory('.$dir.') because: ' . $!;
56              
57 56         1384 $self->{_handle} = $handle;
58             }
59              
60             sub clone {
61 41     41 1 85 my ($self, $dir) = @_;
62 41   33     113 $dir ||= $self->{origin};
63 41         123 return $self->new( origin => $dir );
64             }
65              
66             ## accessor
67              
68 0     0 0 0 sub origin { $_[0]->{_origin} }
69              
70             ## API::Stream ...
71              
72 8     8 1 463 sub head { $_[0]->{_head} }
73 8     8 1 3072 sub is_done { $_[0]->{_is_done} }
74 59     59 1 1053 sub is_closed { $_[0]->{_is_closed} }
75              
76             sub close {
77             closedir( $_[0]->{_handle} )
78 56 50   56 1 621 || Carp::confess 'Unable to close handle for directory because: ' . $!;
79 56         134 $_[0]->{_is_closed} = 1;
80 56         95 return;
81             }
82              
83             sub next {
84 157     157 1 1380 my $self = $_[0];
85              
86 157 50       287 return if $self->{_is_done};
87              
88             Carp::confess 'Cannot call `next` on a closed stream'
89 157 50       267 if $self->{_is_closed};
90              
91 157         188 my $next;
92 157         198 while (1) {
93 269         315 undef $next; # clear any previous values, just cause ...
94 269         301 $self->_log('Entering loop ... ') if DEBUG;
95              
96 269         286 $self->_log('About to read directory ...') if DEBUG;
97 269 100       1416 if ( my $name = readdir( $self->{_handle} ) ) {
98              
99 213         266 $self->_log('Read directory ...') if DEBUG;
100 213 50       361 next unless defined $name;
101              
102 213         253 $self->_log('Got ('.$name.') from directory read ...') if DEBUG;
103 213 100 100     674 next if $name eq '.' || $name eq '..'; # skip these ...
104              
105 101         290 $next = $self->{origin}->child( $name );
106              
107             # directory is not readable or has been removed, so skip it
108 101 50       3735 if ( ! -r $next ) {
109 0         0 $self->_log('Directory/File not readable ...') if DEBUG;
110 0         0 next;
111             }
112             else {
113 101         1429 $self->_log('Value is good, ready to return it') if DEBUG;
114 101         203 last;
115             }
116             }
117             else {
118 56         77 $self->_log('Exiting loop ... DONE') if DEBUG;
119              
120             # cleanup ...
121 56         100 $self->{_head} = undef;
122 56         78 $self->{_is_done} = 1;
123 56         83 last;
124             }
125 0         0 $self->_log('... looping') if DEBUG;
126             }
127              
128 157         211 $self->_log('Got next value('.$next.')') if DEBUG;
129 157         494 return $self->{_head} = $next;
130             }
131              
132             1;
133              
134             __END__