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 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 8     8   45 use strict;
  8         16  
  8         197  
5 8     8   39 use warnings;
  8         13  
  8         162  
6              
7 8     8   36 use Carp ();
  8         16  
  8         93  
8 8     8   35 use Scalar::Util ();
  8         14  
  8         112  
9 8     8   4257 use Path::Tiny ();
  8         81843  
  8         210  
10              
11 8     8   2379 use UNIVERSAL::Object;
  8         6540  
  8         196  
12 8     8   49 use Directory::Scanner::API::Stream;
  8         14  
  8         376  
13              
14             our $VERSION = '0.01';
15             our $AUTHORITY = 'cpan:STEVAN';
16              
17 8   50 8   44 use constant DEBUG => $ENV{DIR_SCANNER_STREAM_DEBUG} // 0;
  8         16  
  8         689  
18              
19             ## ...
20              
21 8     8   757 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         377 _is_done => sub { 0 },
29 56         1203 _is_closed => sub { 0 },
30             )
31 8     8   3398 }
32              
33             ## ...
34              
35             sub BUILD {
36 56     56 1 1898 my ($self, $params) = @_;
37              
38 56         114 my $dir = $self->{origin};
39              
40             # upgrade this to a Path:Tiny
41             # object if needed
42 56 100 66     373 $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       1007 (-d $dir)
49             || Carp::confess 'Supplied path value must be a directory ('.$dir.')';
50 56 50       894 (-r $dir)
51             || Carp::confess 'Supplied path value must be a readable directory ('.$dir.')';
52              
53 56         553 my $handle;
54 56 50       255 opendir( $handle, $dir )
55             || Carp::confess 'Unable to open handle for directory('.$dir.') because: ' . $!;
56              
57 56         1439 $self->{_handle} = $handle;
58             }
59              
60             sub clone {
61 41     41 0 82 my ($self, $dir) = @_;
62 41   33     135 $dir ||= $self->{origin};
63 41         116 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 0 462 sub head { $_[0]->{_head} }
73 8     8 0 3710 sub is_done { $_[0]->{_is_done} }
74 59     59 0 1468 sub is_closed { $_[0]->{_is_closed} }
75              
76             sub close {
77             closedir( $_[0]->{_handle} )
78 56 50   56 0 673 || Carp::confess 'Unable to close handle for directory because: ' . $!;
79 56         109 $_[0]->{_is_closed} = 1;
80 56         125 return;
81             }
82              
83             sub next {
84 157     157 0 1292 my $self = $_[0];
85              
86 157 50       333 return if $self->{_is_done};
87              
88             Carp::confess 'Cannot call `next` on a closed stream'
89 157 50       308 if $self->{_is_closed};
90              
91 157         217 my $next;
92 157         224 while (1) {
93 269         359 undef $next; # clear any previous values, just cause ...
94 269         337 $self->_log('Entering loop ... ') if DEBUG;
95              
96 269         331 $self->_log('About to read directory ...') if DEBUG;
97 269 100       1546 if ( my $name = readdir( $self->{_handle} ) ) {
98              
99 213         316 $self->_log('Read directory ...') if DEBUG;
100 213 50       408 next unless defined $name;
101              
102 213         291 $self->_log('Got ('.$name.') from directory read ...') if DEBUG;
103 213 100 100     755 next if $name eq '.' || $name eq '..'; # skip these ...
104              
105 101         337 $next = $self->{origin}->child( $name );
106              
107             # directory is not readable or has been removed, so skip it
108 101 50       4177 if ( ! -r $next ) {
109 0         0 $self->_log('Directory/File not readable ...') if DEBUG;
110 0         0 next;
111             }
112             else {
113 101         1605 $self->_log('Value is good, ready to return it') if DEBUG;
114 101         199 last;
115             }
116             }
117             else {
118 56         96 $self->_log('Exiting loop ... DONE') if DEBUG;
119              
120             # cleanup ...
121 56         122 $self->{_head} = undef;
122 56         93 $self->{_is_done} = 1;
123 56         91 last;
124             }
125 0         0 $self->_log('... looping') if DEBUG;
126             }
127              
128 157         247 $self->_log('Got next value('.$next.')') if DEBUG;
129 157         558 return $self->{_head} = $next;
130             }
131              
132             1;
133              
134             __END__