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