File Coverage

blib/lib/File/Find/Object.pm
Criterion Covered Total %
statement 247 249 99.2
branch 38 46 82.6
condition 8 9 88.8
subroutine 57 57 100.0
pod 8 8 100.0
total 358 369 97.0


line stmt bran cond sub pod time code
1             package File::Find::Object;
2             $File::Find::Object::VERSION = '0.3.9';
3 6     6   1505302 use strict;
  6         13  
  6         250  
4 6     6   33 use warnings;
  6         11  
  6         645  
5              
6             package File::Find::Object::DeepPath;
7             $File::Find::Object::DeepPath::VERSION = '0.3.9';
8 6     6   104 use strict;
  6         17  
  6         137  
9 6     6   24 use warnings;
  6         39  
  6         333  
10 6     6   6636 use integer;
  6         108  
  6         35  
11 6     6   313 use 5.008;
  6         36  
12              
13 6     6   547 use parent 'File::Find::Object::PathComp';
  6         362  
  6         49  
14              
15 6     6   360 use File::Spec ();
  6         14  
  6         2160  
16              
17             sub new
18             {
19 54     54   119 my ( $class, $top, $from ) = @_;
20              
21 54         86 my $self = {};
22 54         93 bless $self, $class;
23              
24 54         152 $self->_stat_ret( $top->_top_stat_copy() );
25              
26 54         81 my $find = { %{ $from->_inodes() } };
  54         201  
27 54 50       170 if ( my $inode = $self->_inode )
28             {
29             $find->{ join( ",", $self->_dev(), $inode ) } =
30 54         73 $#{ $top->_dir_stack() };
  54         194  
31             }
32 54         192 $self->_set_inodes($find);
33              
34 54         111 $self->_last_dir_scanned(undef);
35              
36 54         126 $top->_fill_actions($self);
37              
38 54         69 push @{ $top->_curr_comps() }, "";
  54         119  
39              
40 54 50       125 return $top->_open_dir() ? $self : undef;
41             }
42              
43             sub _move_next
44             {
45 115     115   211 my ( $self, $top ) = @_;
46              
47 115 100       225 if (
48             defined(
49             $self->_curr_file( $top->_current_father()->_next_traverse_to() )
50             )
51             )
52             {
53 64         188 $top->_curr_comps()->[-1] = $self->_curr_file();
54 64         170 $top->_calc_curr_path();
55              
56 64         185 $top->_fill_actions($self);
57 64         173 $top->_mystat();
58              
59 64         299 return 1;
60             }
61             else
62             {
63 51         233 return 0;
64             }
65             }
66              
67             package File::Find::Object::TopPath;
68             $File::Find::Object::TopPath::VERSION = '0.3.9';
69 6     6   47 use parent 'File::Find::Object::PathComp';
  6         12  
  6         37  
70              
71             sub new
72             {
73 14     14   26 my $class = shift;
74 14         23 my $top = shift;
75              
76 14         26 my $self = {};
77 14         25 bless $self, $class;
78              
79 14         54 $top->_fill_actions($self);
80              
81 14         65 return $self;
82             }
83              
84             sub _move_to_next_target
85             {
86 19     19   33 my $self = shift;
87 19         27 my $top = shift;
88              
89 19         45 my $target = $self->_curr_file( $top->_calc_next_target() );
90 19         35 @{ $top->_curr_comps() } = ($target);
  19         54  
91 19         60 $top->_calc_curr_path();
92              
93 19         443 return $target;
94             }
95              
96             sub _move_next
97             {
98 28     28   40 my $self = shift;
99 28         41 my $top = shift;
100              
101 28         64 while ( $top->_increment_target_index() )
102             {
103 19 100       62 if ( -e $self->_move_to_next_target($top) )
104             {
105 17         58 $top->_fill_actions($self);
106 17         45 $top->_mystat();
107 17         61 $self->_stat_ret( $top->_top_stat_copy() );
108 17         65 $top->_dev( $self->_dev );
109              
110 17         59 my $inode = $self->_inode();
111 17 50       58 $self->_set_inodes(
112             ( $inode == 0 )
113             ? {}
114             : {
115             join( ",", $self->_dev(), $inode ) => 0,
116             },
117             );
118              
119 17         86 return 1;
120             }
121             }
122              
123 11         35 return 0;
124             }
125              
126             package File::Find::Object;
127              
128 6     6   2510 use strict;
  6         13  
  6         215  
129 6     6   30 use warnings;
  6         12  
  6         367  
130              
131 6     6   38 use parent 'File::Find::Object::Base';
  6         25  
  6         39  
132              
133 6     6   4471 use File::Find::Object::Result ();
  6         21  
  6         186  
134              
135 6     6   43 use Fcntl ':mode';
  6         10  
  6         1381  
136 6     6   50 use List::Util ();
  6         12  
  6         751  
137              
138             sub _get_options_ids
139             {
140 20     20   41 my $class = shift;
141             return [
142 20         87 qw(
143             callback
144             depth
145             filter
146             followlink
147             nocrossfs
148             )
149             ];
150             }
151              
152             # _curr_comps are the components (comps) of the master object's current path.
153             # _curr_path is the concatenated path itself.
154              
155             use Class::XSAccessor accessors => {
156             (
157 108         358 map { $_ => $_ } (
158             qw(
159             _check_subdir_h
160             _curr_comps
161             _current
162             _curr_path
163             _def_actions
164             _dev
165             _dir_stack
166             item_obj
167             _target_index
168             _targets
169             _top_is_dir
170             _top_is_link
171             _top_stat
172             ),
173 6         40 @{ __PACKAGE__->_get_options_ids() }
  6         38  
174             )
175             )
176 6     6   38 };
  6         11  
177              
178             __PACKAGE__->_make_copy_methods(
179             [
180             qw(
181             _top_stat
182             )
183             ]
184             );
185              
186 6     6   5992 use Carp;
  6         12  
  6         15462  
187              
188             sub new
189             {
190 14     14 1 1754759 my ( $class, $options, @targets ) = @_;
191              
192             # The *existence* of an _st key inside the struct
193             # indicates that the stack is full.
194             # So now it's empty.
195 14         67 my $tree = {
196             _dir_stack => [],
197             _curr_comps => [],
198             };
199              
200 14         38 bless( $tree, $class );
201              
202 14         27 foreach my $opt ( @{ $tree->_get_options_ids() } )
  14         82  
203             {
204 70         383 $tree->$opt( $options->{$opt} );
205             }
206              
207 14         69 $tree->_gen_check_subdir_helper();
208              
209 14         61 $tree->_targets( \@targets );
210 14         42 $tree->_target_index(-1);
211              
212 14         47 $tree->_calc_default_actions();
213              
214 14         20 push @{ $tree->_dir_stack() },
  14         126  
215             $tree->_current( File::Find::Object::TopPath->new($tree) );
216              
217 14         62 $tree->_last_dir_scanned(undef);
218              
219 14         44 return $tree;
220             }
221              
222             sub _curr_not_a_dir
223             {
224 159     159   488 return !shift->_top_is_dir();
225             }
226              
227             # Calculates _curr_path from $self->_curr_comps().
228             # Must be called whenever _curr_comps is modified.
229             sub _calc_curr_path
230             {
231 83     83   123 my $self = shift;
232              
233 83         116 $self->_curr_path( File::Spec->catfile( @{ $self->_curr_comps() } ) );
  83         919  
234              
235 83         187 return;
236             }
237              
238             sub _calc_current_item_obj
239             {
240 81     81   136 my $self = shift;
241              
242 81         109 my @comps = @{ $self->_curr_comps() };
  81         262  
243              
244 81         329 my $ret = {
245             path => scalar( $self->_curr_path() ),
246             dir_components => \@comps,
247             base => shift(@comps),
248             stat_ret => scalar( $self->_top_stat_copy() ),
249             is_file => scalar( -f _ ),
250             is_dir => scalar( -d _ ),
251             is_link => $self->_top_is_link(),
252             };
253              
254 81 100       205 if ( $self->_curr_not_a_dir() )
255             {
256 24         65 $ret->{basename} = pop(@comps);
257             }
258              
259 81         706 return bless $ret, "File::Find::Object::Result";
260             }
261              
262             sub next_obj
263             {
264 92     92 1 5020 my $self = shift;
265              
266 92   100     198 until (
      100        
267             $self->_process_current || ( ( !$self->_master_move_to_next() )
268             && $self->_me_die() )
269             )
270             {
271             # Do nothing
272             }
273              
274 92         210 return $self->item_obj();
275             }
276              
277             sub next
278             {
279 78     78 1 2195 my $self = shift;
280              
281 78         220 $self->next_obj();
282              
283 78         164 return $self->item();
284             }
285              
286             sub item
287             {
288 78     78 1 94 my $self = shift;
289              
290 78 100       441 return $self->item_obj() ? $self->item_obj()->path() : undef;
291             }
292              
293             sub _current_father
294             {
295 115     115   407 return shift->_dir_stack->[-2];
296             }
297              
298             sub _increment_target_index
299             {
300 30     30   41 my $self = shift;
301 30         83 $self->_target_index( $self->_target_index() + 1 );
302              
303 30         49 return ( $self->_target_index() < scalar( @{ $self->_targets() } ) );
  30         107  
304             }
305              
306             sub _calc_next_target
307             {
308 19     19   29 my $self = shift;
309              
310 19         57 my $target = $self->_targets()->[ $self->_target_index() ];
311              
312 19 50       149 return defined($target) ? File::Spec->canonpath($target) : undef;
313             }
314              
315             sub _master_move_to_next
316             {
317 143     143   211 my $self = shift;
318              
319 143         327 return $self->_current()->_move_next($self);
320             }
321              
322             sub _me_die
323             {
324 62     62   87 my $self = shift;
325              
326 62 100       142 if ( exists( $self->{_st} ) )
327             {
328 51         100 return $self->_become_default();
329             }
330              
331 11         78 $self->item_obj( undef() );
332 11         73 return 1;
333             }
334              
335             sub _become_default
336             {
337 51     51   69 my $self = shift;
338              
339 51         81 my $st = $self->_dir_stack();
340              
341 51         80 pop(@$st);
342 51         297 $self->_current( $st->[-1] );
343 51         70 pop( @{ $self->_curr_comps() } );
  51         91  
344              
345 51 100       132 if ( @$st == 1 )
346             {
347 13         23 delete( $self->{_st} );
348             }
349             else
350             {
351             # If depth is false, then we no longer need the _curr_path
352             # of the directories above the previously-set value, because we
353             # already traversed them.
354 38 50       113 if ( $self->depth() )
355             {
356 0         0 $self->_calc_curr_path();
357             }
358             }
359              
360 51         263 return 0;
361             }
362              
363             sub _calc_default_actions
364             {
365 14     14   27 my $self = shift;
366              
367 14 100       66 my @calc_obj =
368             $self->callback()
369             ? (qw(_run_cb))
370             : (qw(_set_obj));
371              
372 14         31 my @rec = qw(_recurse);
373              
374 14 50       95 $self->_def_actions(
375             [
376             $self->depth()
377             ? ( @rec, @calc_obj )
378             : ( @calc_obj, @rec )
379             ]
380             );
381              
382 14         29 return;
383             }
384              
385             sub _fill_actions
386             {
387 149     149   252 my $self = shift;
388 149         211 my $other = shift;
389              
390 149         215 $other->_actions( [ @{ $self->_def_actions() } ] );
  149         598  
391              
392 149         256 return;
393             }
394              
395             sub _mystat
396             {
397 81     81   109 my $self = shift;
398              
399 81         1897 $self->_top_stat( [ lstat( $self->_curr_path() ) ] );
400              
401 81         322 $self->_top_is_dir( scalar( -d _ ) );
402              
403 81 100       240 if ( $self->_top_is_link( scalar( -l _ ) ) )
404             {
405 2         79 stat( $self->_curr_path() );
406 2         11 $self->_top_is_dir( scalar( -d _ ) );
407             }
408              
409 81         140 return "SKIP";
410             }
411              
412             sub _next_action
413             {
414 234     234   304 my $self = shift;
415              
416 234         296 return shift( @{ $self->_current->_actions() } );
  234         883  
417             }
418              
419             sub _check_process_current
420             {
421 224     224   370 my $self = shift;
422              
423 224   66     914 return ( defined( $self->_current->_curr_file() )
424             && $self->_filter_wrapper() );
425             }
426              
427             # Return true if there is something next
428             sub _process_current
429             {
430 224     224   325 my $self = shift;
431              
432 224 100       400 if ( !$self->_check_process_current() )
433             {
434 14         93 return 0;
435             }
436             else
437             {
438 210         375 return $self->_process_current_actions();
439             }
440             }
441              
442             sub _set_obj
443             {
444 81     81   118 my $self = shift;
445              
446 81         161 $self->item_obj( $self->_calc_current_item_obj() );
447              
448 81         169 return 1;
449             }
450              
451             sub _run_cb
452             {
453 10     10   14 my $self = shift;
454              
455 10         23 $self->_set_obj();
456              
457 10         57 $self->callback()->( $self->_curr_path() );
458              
459 10         508 return 1;
460             }
461              
462             sub _process_current_actions
463             {
464 210     210   299 my $self = shift;
465              
466 210         377 while ( my $action = $self->_next_action() )
467             {
468 159         437 my $status = $self->$action();
469              
470 159 100       429 if ( $status ne "SKIP" )
471             {
472 135         612 return $status;
473             }
474             }
475              
476 75         313 return 0;
477             }
478              
479             sub _recurse
480             {
481 78     78   105 my $self = shift;
482              
483 78 100       181 $self->_check_subdir()
484             or return "SKIP";
485              
486 54         83 push @{ $self->_dir_stack() },
  54         409  
487             $self->_current(
488             File::Find::Object::DeepPath->new( $self, $self->_current() ) );
489              
490 54         118 $self->{_st} = 1;
491              
492 54         115 return 0;
493             }
494              
495             sub _filter_wrapper
496             {
497 210     210   341 my $self = shift;
498              
499 210 50       747 return defined( $self->filter() )
500             ? $self->filter()->( $self->_curr_path() )
501             : 1;
502             }
503              
504             sub _check_subdir
505             {
506 78     78   106 my $self = shift;
507              
508             # If current is not a directory always return 0, because we may
509             # be asked to traverse single-files.
510              
511 78 100       170 if ( $self->_curr_not_a_dir() )
512             {
513 23         75 return 0;
514             }
515             else
516             {
517 55         2064 return $self->_check_subdir_h()->($self);
518             }
519             }
520              
521             sub _warn_about_loop
522             {
523 1     1   2 my $self = shift;
524 1         2 my $component_idx = shift;
525              
526             # Don't pass strings directly to the format.
527             # Instead - use %s
528             # This was a security problem.
529             warn(
530             sprintf(
531             "Avoid loop %s => %s\n",
532             File::Spec->catdir(
533 1         4 @{ $self->_curr_comps() }[ 0 .. $component_idx ]
  1         22  
534             ),
535             $self->_curr_path(),
536             )
537             );
538              
539 1         8 return;
540             }
541              
542             sub _is_loop
543             {
544 41     41   61 my $self = shift;
545              
546 41         78 my $key = join( ",", @{ $self->_top_stat() }[ 0, 1 ] );
  41         219  
547 41         162 my $lookup = $self->_current->_inodes;
548              
549 41 100       119 if ( exists( $lookup->{$key} ) )
550             {
551 1         44 $self->_warn_about_loop( $lookup->{$key} );
552 1         6 return 1;
553             }
554             else
555             {
556 40         200 return;
557             }
558             }
559              
560             # We eval "" the helper of check_subdir because the conditions that
561             # affect the checks are instance-wide and constant and so we can
562             # determine how the code should look like.
563              
564             sub _gen_check_subdir_helper
565             {
566 14     14   28 my $self = shift;
567              
568 14         59 my @clauses;
569              
570 14 100       57 if ( !$self->followlink() )
571             {
572 13         52 push @clauses, '$s->_top_is_link()';
573             }
574              
575 14 100       60 if ( $self->nocrossfs() )
576             {
577 1         3 push @clauses, '($s->_top_stat->[0] != $s->_dev())';
578             }
579              
580 14         27 push @clauses, '$s->_is_loop()';
581              
582 14         94 $self->_check_subdir_h(
583             _context_less_eval(
584             'sub { my $s = shift; '
585             . 'return ((!exists($s->{_st})) || !('
586             . join( "||", @clauses ) . '));' . '}'
587             )
588             );
589             }
590              
591             sub _context_less_eval
592             {
593             ## no critic
594 14     14   47 my $code = shift;
595 14         1933 return eval $code;
596             ## use critic
597             }
598              
599             sub _open_dir
600             {
601 64     64   98 my $self = shift;
602              
603 64         230 return $self->_current()->_component_open_dir( $self->_curr_path() );
604             }
605              
606             sub set_traverse_to
607             {
608 2     2 1 17 my ( $self, $children ) = @_;
609              
610             # Make sure we scan the current directory for sub-items first.
611 2         8 $self->get_current_node_files_list();
612              
613 2         13 $self->_current->_traverse_to( [@$children] );
614             }
615              
616             sub get_traverse_to
617             {
618 1     1 1 6 my $self = shift;
619              
620 1         40 return $self->_current->_traverse_to_copy();
621             }
622              
623             sub get_current_node_files_list
624             {
625 10     10 1 70 my $self = shift;
626              
627             # _open_dir can return undef if $self->_current is not a directory.
628 10 50       21 if ( $self->_open_dir() )
629             {
630 10         57 return $self->_current->_files_copy();
631             }
632             else
633             {
634 0         0 return [];
635             }
636             }
637              
638             sub prune
639             {
640 1     1 1 33 my $self = shift;
641              
642 1         4 return $self->set_traverse_to( [] );
643             }
644              
645             1;
646              
647             __END__