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