File Coverage

blib/lib/File/Finder.pm
Criterion Covered Total %
statement 93 94 98.9
branch 55 66 83.3
condition n/a
subroutine 20 20 100.0
pod 5 5 100.0
total 173 185 93.5


line stmt bran cond sub pod time code
1             package File::Finder;
2              
3 4     4   911701 use 5.006;
  4         17  
4 4     4   42 use strict;
  4         7  
  4         110  
5 4     4   20 use warnings;
  4         7  
  4         345  
6              
7 4     4   28 use base qw(Exporter);
  4         7  
  4         876  
8              
9             ## no exports
10              
11             our $VERSION = '1.01';
12              
13 4     4   31 use Carp qw(croak);
  4         7  
  4         675  
14              
15             ## public methods:
16              
17             sub new {
18 29     29 1 445035 my $class = shift;
19 29         216 bless {
20             options => {},
21             steps => [],
22             }, $class;
23             }
24              
25             sub as_wanted {
26 44     44 1 65 my $self = shift;
27 44     43   220 return sub { $self->_run };
  43         97  
28             }
29              
30             use overload
31 4         44 '&{}' => 'as_wanted',
32             # '""' => sub { overload::StrVal(shift) },
33 4     4   1570 ;
  4         4417  
34              
35             sub as_options {
36 30     30 1 55 my $self = shift;
37 30     2125   63 return { %{$self->{options}}, wanted => sub { $self->_run } };
  30         3101  
  2125         5378  
38             }
39              
40             sub in {
41 25     25 1 63 my $self = _force_object(shift);
42              
43             ## this must return count in a scalar context
44 25     1120   139 $self->collect(sub { $File::Find::name }, @_);
  1120         2375  
45             }
46              
47             sub collect {
48 25     25 1 64 my $self = _force_object(shift);
49 25         45 my $code = shift;
50              
51 25         44 my @result;
52 25     1120   195 my $self_store = $self->eval( sub { push @result, $code->() } );
  1120         1519  
53              
54 25         156 require File::Find;
55 25         96 File::Find::find($self_store->as_options, @_);
56              
57             ## this must return count in a scalar context
58 25         870 return @result;
59             }
60              
61             ## private methods
62              
63             sub _force_object {
64 1003     1003   3075 my $self_or_class = shift;
65 1003 100       1947 ref $self_or_class ? $self_or_class : $self_or_class->new;
66             }
67              
68             sub _clone {
69 308     308   485 my $self = _force_object(shift);
70             bless {
71 308         2494 options => {%{$self->{options}}},
72 308         450 steps => [@{$self->{steps}}],
  308         3075  
73             }, ref $self;
74             }
75              
76             ## we set this to ensure that _ is correct for all tests
77             $File::Find::dont_use_nlink = 1;
78             ## otherwise, we have to lstat/stat($_) inside _run
79             ## thanks, tye!
80              
81             sub _run {
82 2168     2168   2964 my $self = shift;
83              
84 2168         2269 my @stat;
85 2168 100       17521 @stat = stat if defined $_;
86              
87 2168         3250 my @state = (1);
88             ## $state[-1]:
89             ## if 2: we're in a true state, but we've just seen a NOT
90             ## if 1: we're in a true state
91             ## if 0: we're in a false state
92             ## if -1: we're in a "skipping" state (true OR ...[here]...)
93              
94 2168         2273 for my $step(@{$self->{steps}}) {
  2168         4668  
95              
96             ## verify underscore handle is good:
97 4842 100       6879 if (@stat) {
98 4590         6596 my @cache_stat = stat _;
99 4590 100       32785 stat unless "@stat" eq "@cache_stat";
100             }
101              
102 4842 100       7399 if (ref $step) { # coderef
    100          
    100          
    100          
    100          
    50          
103 4364 100       6989 if ($state[-1] >= 1) { # true state
104 3295 100       9812 if ($self->$step) { # coderef ran returning true
105 2249 100       4860 if ($state[-1] == 2) {
106 159         261 $state[-1] = 0;
107             }
108             } else {
109 1046         1655 $state[-1]--; # 2 => 1, 1 => 0
110             }
111             }
112             } elsif ($step eq "or") {
113             # -1 => -1, 0 => 1, 1 => -1, 2 is error
114 63 50       106 croak "not before or?" if $state[-1] > 1;
115 63 100       140 if ($state[-1] == 0) {
    100          
116 12         15 $state[-1] = 1;
117             } elsif ($state[-1] == 1) {
118 31         52 $state[-1] = -1;
119             }
120             } elsif ($step eq "left") {
121             ## start subrule
122             ## -1 => -1, 0 => -1, 1 => 1, 2 => 1
123 27 100       72 push @state, ($state[-1] >= 1) ? 1 : -1;
124             } elsif ($step eq "right") {
125             ## end subrule
126 27 50       52 croak "right without left" unless @state > 1;
127 27 50       52 croak "not before right" if $state[-1] > 1;
128 27         43 my $result = pop @state;
129 27 100       51 if ($state[-1] >= 1) {
130 11 100       24 if ($result) { # 1 or -1, so counts as true
131 9 100       42 if ($state[-1] == 2) {
132 2         3 $state[-1] = 0;
133             }
134             } else {
135 2         6 $state[-1]--; # 2 => 1, 1 => 0
136             }
137             }
138             } elsif ($step eq "comma") {
139 12 50       38 croak "not before comma" if $state[-1] > 1;
140 12 100       32 if (@state < 2) { # not in parens
141 3         9 $state[-1] = 1; # reset to true
142             } else { # in parens, reset as if start of parens
143 9 100       133 $state[-1] = (($state[-2] >= 1) ? 1 : -1);
144             }
145             } elsif ($step eq "not") {
146             # -1 => -1, 0 => 0, 1 => 2, 2 => 1
147 349 50       512 if ($state[-1] >= 1) {
148 349 100       516 $state[-1] = $state[-1] > 1 ? 1 : 2;
149             }
150             } else {
151 0         0 die "internal error at $step";
152             }
153             }
154 2168 50       3508 croak "left without right" unless @state == 1;
155 2168 50       3101 croak "trailing not" if $state[-1] > 1;
156 2168         87254 return $state[-1] != 0; # true and skipping are both true
157             }
158              
159             sub AUTOLOAD {
160 645     645   165112 my $self = _force_object(shift);
161              
162 645         2865 my ($method) = our $AUTOLOAD =~ /(?:.*::)?(.*)/;
163 645 100       4060 return if $method eq "DESTROY";
164              
165 308         2806 my $clone = $self->_clone;
166              
167             ## bring in the steps
168 308         614 my $steps_class = $clone->_steps_class;
169 308 50       939 $steps_class =~ /[^\w:]/
170             and die "bad value for \$steps_class: $steps_class";
171 308 50       21383 eval "require $steps_class"; die $@ if $@;
  308         1266  
172              
173 308 50       1800 my $sub_method = $steps_class->can($method)
174             or croak "Cannot add step $method";
175              
176 308         469 push @{$clone->{steps}}, $sub_method->($clone, @_);
  308         1238  
177 308         1836 $clone;
178             }
179              
180 308     308   569 sub _steps_class { "File::Finder::Steps" }
181              
182             1;
183             __END__