File Coverage

blib/lib/Directory/Scanner/Stream.pm
Criterion Covered Total %
statement 69 74 93.2
branch 14 22 63.6
condition 7 11 63.6
subroutine 16 17 94.1
pod 1 8 12.5
total 107 132 81.0


line stmt bran cond sub pod time code
1             package Directory::Scanner::Stream;
2             # ABSTRACT: Streaming directory iterator
3              
4 9     9   46 use strict;
  9         14  
  9         203  
5 9     9   36 use warnings;
  9         11  
  9         160  
6              
7 9     9   32 use Carp ();
  9         13  
  9         82  
8 9     9   42 use Scalar::Util ();
  9         30  
  9         164  
9 9     9   5730 use Path::Tiny ();
  9         97522  
  9         418  
10              
11             our $VERSION = '0.04';
12             our $AUTHORITY = 'cpan:STEVAN';
13              
14 9   50 9   67 use constant DEBUG => $ENV{DIR_SCANNER_STREAM_DEBUG} // 0;
  9         12  
  9         470  
15              
16             ## ...
17              
18 9     9   3420 use parent 'UNIVERSAL::Object';
  9         2095  
  9         66  
19 9     9   13521 use roles 'Directory::Scanner::API::Stream';
  9         293902  
  9         52  
20             use slots (
21 0         0 origin => sub { die 'You must supply a `origin` directory path' },
22             # internal state ...
23             _head => sub {},
24             _handle => sub {},
25 61         149 _is_done => sub { 0 },
26 61         150 _is_closed => sub { 0 },
27 9     9   5977 );
  9         1487  
  9         91  
28              
29             ## ...
30              
31             sub BUILD {
32 61     61 1 1553 my ($self, $params) = @_;
33              
34 61         111 my $dir = $self->{origin};
35              
36             # upgrade this to a Path:Tiny
37             # object if needed
38 61 100 66     365 $self->{origin} = $dir = Path::Tiny::path( $dir )
39             unless Scalar::Util::blessed( $dir )
40             && $dir->isa('Path::Tiny');
41              
42             # make sure the directory is
43             # fit to be streamed
44 61 50       852 (-d $dir)
45             || Carp::confess 'Supplied path value must be a directory ('.$dir.')';
46 61 50       1167 (-r $dir)
47             || Carp::confess 'Supplied path value must be a readable directory ('.$dir.')';
48              
49 61         909 my $handle;
50 61 50       259 opendir( $handle, $dir )
51             || Carp::confess 'Unable to open handle for directory('.$dir.') because: ' . $!;
52              
53 61         1873 $self->{_handle} = $handle;
54             }
55              
56             sub clone {
57 45     45 0 89 my ($self, $dir) = @_;
58 45   33     123 $dir ||= $self->{origin};
59 45         110 return $self->new( origin => $dir );
60             }
61              
62             ## accessor
63              
64 0     0 0 0 sub origin { $_[0]->{_origin} }
65              
66             ## API::Stream ...
67              
68 8     8 0 492 sub head { $_[0]->{_head} }
69 8     8 0 2435 sub is_done { $_[0]->{_is_done} }
70 64     64 0 950 sub is_closed { $_[0]->{_is_closed} }
71              
72             sub close {
73             closedir( $_[0]->{_handle} )
74 61 50   61 0 706 || Carp::confess 'Unable to close handle for directory because: ' . $!;
75 61         125 $_[0]->{_is_closed} = 1;
76 61         131 return;
77             }
78              
79             sub next {
80 171     171 0 1239 my $self = $_[0];
81              
82 171 50       274 return if $self->{_is_done};
83              
84             Carp::confess 'Cannot call `next` on a closed stream'
85 171 50       256 if $self->{_is_closed};
86              
87 171         178 my $next;
88 171         177 while (1) {
89 293         328 undef $next; # clear any previous values, just cause ...
90 293         289 $self->_log('Entering loop ... ') if DEBUG;
91              
92 293         280 $self->_log('About to read directory ...') if DEBUG;
93 293 100       1833 if ( my $name = readdir( $self->{_handle} ) ) {
94              
95 232         291 $self->_log('Read directory ...') if DEBUG;
96 232 50       344 next unless defined $name;
97              
98 232         225 $self->_log('Got ('.$name.') from directory read ...') if DEBUG;
99 232 100 100     636 next if $name eq '.' || $name eq '..'; # skip these ...
100              
101 110         316 $next = $self->{origin}->child( $name );
102              
103             # directory is not readable or has been removed, so skip it
104 110 50       3814 if ( ! -r $next ) {
105 0         0 $self->_log('Directory/File not readable ...') if DEBUG;
106 0         0 next;
107             }
108             else {
109 110         2242 $self->_log('Value is good, ready to return it') if DEBUG;
110 110         188 last;
111             }
112             }
113             else {
114 61         99 $self->_log('Exiting loop ... DONE') if DEBUG;
115              
116             # cleanup ...
117 61         111 $self->{_head} = undef;
118 61         77 $self->{_is_done} = 1;
119 61         79 last;
120             }
121 0         0 $self->_log('... looping') if DEBUG;
122             }
123              
124 171         308 $self->_log('Got next value('.$next.')') if DEBUG;
125 171         570 return $self->{_head} = $next;
126             }
127              
128             1;
129              
130             __END__