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.7';
3 5     5   364451 use strict;
  5         57  
  5         142  
4 5     5   30 use warnings;
  5         11  
  5         227  
5              
6             package File::Find::Object::DeepPath;
7             $File::Find::Object::DeepPath::VERSION = '0.3.7';
8 5     5   27 use strict;
  5         8  
  5         77  
9 5     5   23 use warnings;
  5         19  
  5         178  
10              
11 5     5   108 use 5.008;
  5         24  
12              
13 5     5   2624 use integer;
  5         73  
  5         29  
14              
15 5     5   1021 use parent 'File::Find::Object::PathComp';
  5         618  
  5         30  
16              
17 5     5   215 use File::Spec ();
  5         10  
  5         1562  
18              
19             sub new
20             {
21 54     54   136 my ( $class, $top, $from ) = @_;
22              
23 54         101 my $self = {};
24 54         122 bless $self, $class;
25              
26 54         143 $self->_stat_ret( $top->_top_stat_copy() );
27              
28 54         87 my $find = { %{ $from->_inodes() } };
  54         206  
29 54 50       164 if ( my $inode = $self->_inode )
30             {
31             $find->{ join( ",", $self->_dev(), $inode ) } =
32 54         81 $#{ $top->_dir_stack() };
  54         166  
33             }
34 54         137 $self->_set_inodes($find);
35              
36 54         100 $self->_last_dir_scanned(undef);
37              
38 54         121 $top->_fill_actions($self);
39              
40 54         75 push @{ $top->_curr_comps() }, "";
  54         118  
41              
42 54 50       167 return $top->_open_dir() ? $self : undef;
43             }
44              
45             sub _move_next
46             {
47 115     115   215 my ( $self, $top ) = @_;
48              
49 115 100       203 if (
50             defined(
51             $self->_curr_file( $top->_current_father()->_next_traverse_to() )
52             )
53             )
54             {
55 64         189 $top->_curr_comps()->[-1] = $self->_curr_file();
56 64         148 $top->_calc_curr_path();
57              
58 64         149 $top->_fill_actions($self);
59 64         153 $top->_mystat();
60              
61 64         298 return 1;
62             }
63             else
64             {
65 51         182 return 0;
66             }
67             }
68              
69             package File::Find::Object::TopPath;
70             $File::Find::Object::TopPath::VERSION = '0.3.7';
71 5     5   35 use parent 'File::Find::Object::PathComp';
  5         10  
  5         34  
72              
73             sub new
74             {
75 14     14   26 my $class = shift;
76 14         25 my $top = shift;
77              
78 14         22 my $self = {};
79 14         32 bless $self, $class;
80              
81 14         44 $top->_fill_actions($self);
82              
83 14         218 return $self;
84             }
85              
86             sub _move_to_next_target
87             {
88 19     19   37 my $self = shift;
89 19         27 my $top = shift;
90              
91 19         43 my $target = $self->_curr_file( $top->_calc_next_target() );
92 19         37 @{ $top->_curr_comps() } = ($target);
  19         55  
93 19         52 $top->_calc_curr_path();
94              
95 19         371 return $target;
96             }
97              
98             sub _move_next
99             {
100 28     28   42 my $self = shift;
101 28         37 my $top = shift;
102              
103 28         70 while ( $top->_increment_target_index() )
104             {
105 19 100       52 if ( -e $self->_move_to_next_target($top) )
106             {
107 17         92 $top->_fill_actions($self);
108 17         59 $top->_mystat();
109 17         77 $self->_stat_ret( $top->_top_stat_copy() );
110 17         72 $top->_dev( $self->_dev );
111              
112 17         52 my $inode = $self->_inode();
113 17 50       85 $self->_set_inodes(
114             ( $inode == 0 )
115             ? {}
116             : {
117             join( ",", $self->_dev(), $inode ) => 0,
118             },
119             );
120              
121 17         93 return 1;
122             }
123             }
124              
125 11         33 return 0;
126             }
127              
128             package File::Find::Object;
129              
130 5     5   1563 use strict;
  5         12  
  5         157  
131 5     5   29 use warnings;
  5         19  
  5         183  
132              
133 5     5   40 use parent 'File::Find::Object::Base';
  5         13  
  5         44  
134              
135 5     5   2735 use File::Find::Object::Result ();
  5         30  
  5         111  
136              
137 5     5   33 use Fcntl ':mode';
  5         9  
  5         910  
138 5     5   45 use List::Util ();
  5         11  
  5         533  
139              
140             sub _get_options_ids
141             {
142 19     19   46 my $class = shift;
143             return [
144 19         75 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         231 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         10 @{ __PACKAGE__->_get_options_ids() }
  5         38  
176             )
177             )
178 5     5   39 };
  5         11  
179              
180             __PACKAGE__->_make_copy_methods(
181             [
182             qw(
183             _top_stat
184             )
185             ]
186             );
187              
188 5     5   4462 use Carp;
  5         14  
  5         10013  
189              
190             sub new
191             {
192 14     14 1 48541 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         70 my $tree = {
198             _dir_stack => [],
199             _curr_comps => [],
200             };
201              
202 14         36 bless( $tree, $class );
203              
204 14         36 foreach my $opt ( @{ $tree->_get_options_ids() } )
  14         59  
205             {
206 70         306 $tree->$opt( $options->{$opt} );
207             }
208              
209 14         58 $tree->_gen_check_subdir_helper();
210              
211 14         61 $tree->_targets( \@targets );
212 14         37 $tree->_target_index(-1);
213              
214 14         47 $tree->_calc_default_actions();
215              
216 14         23 push @{ $tree->_dir_stack() },
  14         108  
217             $tree->_current( File::Find::Object::TopPath->new($tree) );
218              
219 14         158 $tree->_last_dir_scanned(undef);
220              
221 14         77 return $tree;
222             }
223              
224             sub _curr_not_a_dir
225             {
226 159     159   461 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   119 my $self = shift;
234              
235 83         112 $self->_curr_path( File::Spec->catfile( @{ $self->_curr_comps() } ) );
  83         714  
236              
237 83         191 return;
238             }
239              
240             sub _calc_current_item_obj
241             {
242 81     81   115 my $self = shift;
243              
244 81         124 my @comps = @{ $self->_curr_comps() };
  81         256  
245              
246 81         337 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       197 if ( $self->_curr_not_a_dir() )
257             {
258 24         120 $ret->{basename} = pop(@comps);
259             }
260              
261 81         538 return bless $ret, "File::Find::Object::Result";
262             }
263              
264             sub next_obj
265             {
266 92     92 1 3381 my $self = shift;
267              
268 92   100     181 until (
      100        
269             $self->_process_current || ( ( !$self->_master_move_to_next() )
270             && $self->_me_die() )
271             )
272             {
273             # Do nothing
274             }
275              
276 92         225 return $self->item_obj();
277             }
278              
279             sub next
280             {
281 78     78 1 2017 my $self = shift;
282              
283 78         182 $self->next_obj();
284              
285 78         157 return $self->item();
286             }
287              
288             sub item
289             {
290 78     78 1 105 my $self = shift;
291              
292 78 100       363 return $self->item_obj() ? $self->item_obj()->path() : undef;
293             }
294              
295             sub _current_father
296             {
297 115     115   341 return shift->_dir_stack->[-2];
298             }
299              
300             sub _increment_target_index
301             {
302 30     30   43 my $self = shift;
303 30         97 $self->_target_index( $self->_target_index() + 1 );
304              
305 30         55 return ( $self->_target_index() < scalar( @{ $self->_targets() } ) );
  30         102  
306             }
307              
308             sub _calc_next_target
309             {
310 19     19   30 my $self = shift;
311              
312 19         49 my $target = $self->_targets()->[ $self->_target_index() ];
313              
314 19 50       117 return defined($target) ? File::Spec->canonpath($target) : undef;
315             }
316              
317             sub _master_move_to_next
318             {
319 143     143   223 my $self = shift;
320              
321 143         302 return $self->_current()->_move_next($self);
322             }
323              
324             sub _me_die
325             {
326 62     62   88 my $self = shift;
327              
328 62 100       138 if ( exists( $self->{_st} ) )
329             {
330 51         114 return $self->_become_default();
331             }
332              
333 11         45 $self->item_obj( undef() );
334 11         36 return 1;
335             }
336              
337             sub _become_default
338             {
339 51     51   98 my $self = shift;
340              
341 51         86 my $st = $self->_dir_stack();
342              
343 51         69 pop(@$st);
344 51         248 $self->_current( $st->[-1] );
345 51         68 pop( @{ $self->_curr_comps() } );
  51         110  
346              
347 51 100       120 if ( @$st == 1 )
348             {
349 13         29 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       102 if ( $self->depth() )
357             {
358 0         0 $self->_calc_curr_path();
359             }
360             }
361              
362 51         252 return 0;
363             }
364              
365             sub _calc_default_actions
366             {
367 14     14   28 my $self = shift;
368              
369 14 100       56 my @calc_obj =
370             $self->callback()
371             ? (qw(_run_cb))
372             : (qw(_set_obj));
373              
374 14         32 my @rec = qw(_recurse);
375              
376 14 50       68 $self->_def_actions(
377             [
378             $self->depth()
379             ? ( @rec, @calc_obj )
380             : ( @calc_obj, @rec )
381             ]
382             );
383              
384 14         26 return;
385             }
386              
387             sub _fill_actions
388             {
389 149     149   228 my $self = shift;
390 149         202 my $other = shift;
391              
392 149         212 $other->_actions( [ @{ $self->_def_actions() } ] );
  149         473  
393              
394 149         258 return;
395             }
396              
397             sub _mystat
398             {
399 81     81   115 my $self = shift;
400              
401 81         1481 $self->_top_stat( [ lstat( $self->_curr_path() ) ] );
402              
403 81         356 $self->_top_is_dir( scalar( -d _ ) );
404              
405 81 100       304 if ( $self->_top_is_link( scalar( -l _ ) ) )
406             {
407 2         29 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   312 my $self = shift;
417              
418 234         301 return shift( @{ $self->_current->_actions() } );
  234         694  
419             }
420              
421             sub _check_process_current
422             {
423 224     224   320 my $self = shift;
424              
425 224   66     791 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   320 my $self = shift;
433              
434 224 100       368 if ( !$self->_check_process_current() )
435             {
436 14         83 return 0;
437             }
438             else
439             {
440 210         429 return $self->_process_current_actions();
441             }
442             }
443              
444             sub _set_obj
445             {
446 81     81   134 my $self = shift;
447              
448 81         160 $self->item_obj( $self->_calc_current_item_obj() );
449              
450 81         154 return 1;
451             }
452              
453             sub _run_cb
454             {
455 10     10   21 my $self = shift;
456              
457 10         27 $self->_set_obj();
458              
459 10         50 $self->callback()->( $self->_curr_path() );
460              
461 10         332 return 1;
462             }
463              
464             sub _process_current_actions
465             {
466 210     210   285 my $self = shift;
467              
468 210         415 while ( my $action = $self->_next_action() )
469             {
470 159         376 my $status = $self->$action();
471              
472 159 100       394 if ( $status ne "SKIP" )
473             {
474 135         511 return $status;
475             }
476             }
477              
478 75         199 return 0;
479             }
480              
481             sub _recurse
482             {
483 78     78   116 my $self = shift;
484              
485 78 100       137 $self->_check_subdir()
486             or return "SKIP";
487              
488 54         90 push @{ $self->_dir_stack() },
  54         332  
489             $self->_current(
490             File::Find::Object::DeepPath->new( $self, $self->_current() ) );
491              
492 54         125 $self->{_st} = 1;
493              
494 54         92 return 0;
495             }
496              
497             sub _filter_wrapper
498             {
499 210     210   294 my $self = shift;
500              
501 210 50       748 return defined( $self->filter() )
502             ? $self->filter()->( $self->_curr_path() )
503             : 1;
504             }
505              
506             sub _check_subdir
507             {
508 78     78   103 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       130 if ( $self->_curr_not_a_dir() )
514             {
515 23         72 return 0;
516             }
517             else
518             {
519 55         1536 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         3 @{ $self->_curr_comps() }[ 0 .. $component_idx ]
  1         32  
536             ),
537             $self->_curr_path(),
538             )
539             );
540              
541 1         10 return;
542             }
543              
544             sub _is_loop
545             {
546 41     41   73 my $self = shift;
547              
548 41         73 my $key = join( ",", @{ $self->_top_stat() }[ 0, 1 ] );
  41         157  
549 41         126 my $lookup = $self->_current->_inodes;
550              
551 41 100       92 if ( exists( $lookup->{$key} ) )
552             {
553 1         4 $self->_warn_about_loop( $lookup->{$key} );
554 1         6 return 1;
555             }
556             else
557             {
558 40         199 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   32 my $self = shift;
569              
570 14         25 my @clauses;
571              
572 14 100       48 if ( !$self->followlink() )
573             {
574 13         34 push @clauses, '$s->_top_is_link()';
575             }
576              
577 14 100       63 if ( $self->nocrossfs() )
578             {
579 1         13 push @clauses, '($s->_top_stat->[0] != $s->_dev())';
580             }
581              
582 14         23 push @clauses, '$s->_is_loop()';
583              
584 14         84 $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   27 my $code = shift;
597 14         1401 return eval $code;
598             ## use critic
599             }
600              
601             sub _open_dir
602             {
603 64     64   119 my $self = shift;
604              
605 64         229 return $self->_current()->_component_open_dir( $self->_curr_path() );
606             }
607              
608             sub set_traverse_to
609             {
610 2     2 1 14 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         28 $self->_current->_traverse_to( [@$children] );
616             }
617              
618             sub get_traverse_to
619             {
620 1     1 1 8 my $self = shift;
621              
622 1         11 return $self->_current->_traverse_to_copy();
623             }
624              
625             sub get_current_node_files_list
626             {
627 10     10 1 72 my $self = shift;
628              
629             # _open_dir can return undef if $self->_current is not a directory.
630 10 50       23 if ( $self->_open_dir() )
631             {
632 10         28 return $self->_current->_files_copy();
633             }
634             else
635             {
636 0         0 return [];
637             }
638             }
639              
640             sub prune
641             {
642 1     1 1 48 my $self = shift;
643              
644 1         4 return $self->set_traverse_to( [] );
645             }
646              
647             1;
648              
649             __END__