File Coverage

blib/lib/Path/Class/Dir.pm
Criterion Covered Total %
statement 191 196 97.4
branch 83 96 86.4
condition 25 37 67.5
subroutine 38 40 95.0
pod 25 25 100.0
total 362 394 91.8


line stmt bran cond sub pod time code
1 7     7   21 use strict;
  7         6  
  7         264  
2              
3             package Path::Class::Dir;
4             {
5             $Path::Class::Dir::VERSION = '0.37';
6             }
7              
8 7     7   24 use Path::Class::File;
  7         5  
  7         112  
9 7     7   17 use Carp();
  7         7  
  7         100  
10 7     7   836 use parent qw(Path::Class::Entity);
  7         442  
  7         34  
11              
12 7     7   3115 use IO::Dir ();
  7         40993  
  7         126  
13 7     7   35 use File::Path ();
  7         6  
  7         79  
14 7     7   2030 use File::Temp ();
  7         23943  
  7         105  
15 7     7   28 use Scalar::Util ();
  7         691  
  7         10521  
16              
17             # updir & curdir on the local machine, for screening them out in
18             # children(). Note that they don't respect 'foreign' semantics.
19             my $Updir = __PACKAGE__->_spec->updir;
20             my $Curdir = __PACKAGE__->_spec->curdir;
21              
22             sub new {
23 498     498 1 1476 my $self = shift->SUPER::new();
24              
25             # If the only arg is undef, it's probably a mistake. Without this
26             # special case here, we'd return the root directory, which is a
27             # lousy thing to do to someone when they made a mistake. Return
28             # undef instead.
29 498 100 100     1532 return if @_==1 && !defined($_[0]);
30              
31 497         731 my $s = $self->_spec;
32            
33 497 100 100     1473 my $first = (@_ == 0 ? $s->curdir :
    100          
34             !ref($_[0]) && $_[0] eq '' ? (shift, $s->rootdir) :
35             shift()
36             );
37            
38 497         592 $self->{dirs} = [];
39 497 100 66     1685 if ( Scalar::Util::blessed($first) && $first->isa("Path::Class::Dir") ) {
40 237         282 $self->{volume} = $first->{volume};
41 237         158 push @{$self->{dirs}}, @{$first->{dirs}};
  237         237  
  237         445  
42             }
43             else {
44 260         1309 ($self->{volume}, my $dirs) = $s->splitpath( $s->canonpath("$first") , 1);
45 260 100       567 push @{$self->{dirs}}, $dirs eq $s->rootdir ? "" : $s->splitdir($dirs);
  260         1101  
46             }
47              
48 497         537 push @{$self->{dirs}}, map {
49 497         545 Scalar::Util::blessed($_) && $_->isa("Path::Class::Dir")
50 127 50 33     687 ? @{$_->{dirs}}
  0         0  
51             : $s->splitdir( $s->canonpath($_) )
52             } @_;
53              
54              
55 497         1094 return $self;
56             }
57              
58 112     112 1 346 sub file_class { "Path::Class::File" }
59              
60 61     61 1 103 sub is_dir { 1 }
61              
62             sub as_foreign {
63 41     41 1 670 my ($self, $type) = @_;
64              
65 41         36 my $foreign = do {
66 41         78 local $self->{file_spec_class} = $self->_spec_class($type);
67 41         88 $self->SUPER::new;
68             };
69            
70             # Clone internal structure
71 41         56 $foreign->{volume} = $self->{volume};
72 41         85 my ($u, $fu) = ($self->_spec->updir, $foreign->_spec->updir);
73 41 100       43 $foreign->{dirs} = [ map {$_ eq $u ? $fu : $_} @{$self->{dirs}}];
  99         214  
  41         68  
74 41         93 return $foreign;
75             }
76              
77             sub stringify {
78 853     853 1 5334 my $self = shift;
79 853         1193 my $s = $self->_spec;
80             return $s->catpath($self->{volume},
81 853         817 $s->catdir(@{$self->{dirs}}),
  853         239888  
82             '');
83             }
84              
85 53     53 1 130 sub volume { shift()->{volume} }
86              
87             sub file {
88 113 100   113 1 231 local $Path::Class::Foreign = $_[0]->{file_spec_class} if $_[0]->{file_spec_class};
89 113         149 return $_[0]->file_class->new(@_);
90             }
91              
92 11     11 1 75 sub basename { shift()->{dirs}[-1] }
93              
94             sub dir_list {
95 35     35 1 281 my $self = shift;
96 35         24 my $d = $self->{dirs};
97 35 100       108 return @$d unless @_;
98            
99 9         9 my $offset = shift;
100 9 100       15 if ($offset < 0) { $offset = $#$d + $offset + 1 }
  5         5  
101            
102 9 100       26 return wantarray ? @$d[$offset .. $#$d] : $d->[$offset] unless @_;
    100          
103            
104 5         3 my $length = shift;
105 5 100       10 if ($length < 0) { $length = $#$d + $length + 1 - $offset }
  2         2  
106 5         14 return @$d[$offset .. $length + $offset - 1];
107             }
108              
109             sub components {
110 24     24 1 55 my $self = shift;
111 24         50 return $self->dir_list(@_);
112             }
113              
114             sub subdir {
115 74     74 1 725 my $self = shift;
116 74         110 return $self->new($self, @_);
117             }
118              
119             sub parent {
120 31     31 1 59 my $self = shift;
121 31         28 my $dirs = $self->{dirs};
122 31         60 my ($curdir, $updir) = ($self->_spec->curdir, $self->_spec->updir);
123              
124 31 100       73 if ($self->is_absolute) {
    100          
    100          
    100          
125 11         15 my $parent = $self->new($self);
126 11 100       17 pop @{$parent->{dirs}} if @$dirs > 1;
  10         9  
127 11         40 return $parent;
128              
129             } elsif ($self eq $curdir) {
130 2         3 return $self->new($updir);
131              
132 38         104 } elsif (!grep {$_ ne $updir} @$dirs) { # All updirs
133 1         3 return $self->new($self, $updir); # Add one more
134              
135             } elsif (@$dirs == 1) {
136 4         6 return $self->new($curdir);
137              
138             } else {
139 13         22 my $parent = $self->new($self);
140 13         14 pop @{$parent->{dirs}};
  13         14  
141 13         33 return $parent;
142             }
143             }
144              
145             sub relative {
146             # File::Spec->abs2rel before version 3.13 returned the empty string
147             # when the two paths were equal - work around it here.
148 36     36 1 54 my $self = shift;
149 36         52 my $rel = $self->_spec->abs2rel($self->stringify, @_);
150 36 50       91 return $self->new( length $rel ? $rel : $self->_spec->curdir );
151             }
152              
153 54     54 1 135 sub open { IO::Dir->new(@_) }
154 15     15 1 44 sub mkpath { File::Path::mkpath(shift()->stringify, @_) }
155 8     8 1 2091 sub rmtree { File::Path::rmtree(shift()->stringify, @_) }
156              
157             sub remove {
158 0     0 1 0 rmdir( shift() );
159             }
160              
161             sub traverse {
162 15     15 1 446 my $self = shift;
163 15         14 my ($callback, @args) = @_;
164 15         17 my @children = $self->children;
165             return $self->$callback(
166             sub {
167 15     15   39 my @inner_args = @_;
168 15         15 return map { $_->traverse($callback, @inner_args) } @children;
  24         103  
169             },
170             @args
171 15         294 );
172             }
173              
174             sub traverse_if {
175 9     9 1 436 my $self = shift;
176 9         8 my ($callback, $condition, @args) = @_;
177 9         13 my @children = grep { $condition->($_) } $self->children;
  12         170  
178             return $self->$callback(
179             sub {
180 9     9   23 my @inner_args = @_;
181 9         13 return map { $_->traverse_if($callback, $condition, @inner_args) } @children;
  6         15  
182             },
183             @args
184 9         46 );
185             }
186              
187             sub recurse {
188 6     6 1 4580 my $self = shift;
189 6         18 my %opts = (preorder => 1, depthfirst => 0, @_);
190            
191             my $callback = $opts{callback}
192 6 50       16 or Carp::croak( "Must provide a 'callback' parameter to recurse()" );
193            
194 6         10 my @queue = ($self);
195            
196 6         7 my $visit_entry;
197             my $visit_dir =
198             $opts{depthfirst} && $opts{preorder}
199             ? sub {
200 5     5   5 my $dir = shift;
201 5         10 my $ret = $callback->($dir);
202 5 50 50     23 unless( ($ret||'') eq $self->PRUNE ) {
203 5         8 unshift @queue, $dir->children;
204             }
205             }
206             : $opts{preorder}
207             ? sub {
208 18     18   15 my $dir = shift;
209 18         26 my $ret = $callback->($dir);
210 18 100 100     114 unless( ($ret||'') eq $self->PRUNE ) {
211 16         23 push @queue, $dir->children;
212             }
213             }
214             : sub {
215 5     5   2 my $dir = shift;
216 5         10 $visit_entry->($_) foreach $dir->children;
217 5         14 $callback->($dir);
218 6 100 66     32 };
    100          
219            
220             $visit_entry = sub {
221 48     48   116 my $entry = shift;
222 48 100       82 if ($entry->is_dir) { $visit_dir->($entry) } # Will call $callback
  28         35  
223 20         34 else { $callback->($entry) }
224 6         14 };
225            
226 6         14 while (@queue) {
227 40         617 $visit_entry->( shift @queue );
228             }
229             }
230              
231             sub children {
232 51     51 1 62 my ($self, %opts) = @_;
233            
234 51 50       68 my $dh = $self->open or Carp::croak( "Can't open directory $self: $!" );
235            
236 51         553 my @out;
237 51         95 while (defined(my $entry = $dh->read)) {
238 182 100 66     1310 next if !$opts{all} && $self->_is_local_dot_dir($entry);
239 80 50 33     146 next if ($opts{no_hidden} && $entry =~ /^\./);
240 80         102 push @out, $self->file($entry);
241 80 100       261 $out[-1] = $self->subdir($entry) if -d $out[-1];
242             }
243 51         370 return @out;
244             }
245              
246             sub _is_local_dot_dir {
247 182     182   132 my $self = shift;
248 182         130 my $dir = shift;
249              
250 182   100     1072 return ($dir eq $Updir or $dir eq $Curdir);
251             }
252              
253             sub next {
254 10     10 1 20 my $self = shift;
255 10 100       18 unless ($self->{dh}) {
256 2 50       3 $self->{dh} = $self->open or Carp::croak( "Can't open directory $self: $!" );
257             }
258            
259 10         43 my $next = $self->{dh}->read;
260 10 100       73 unless (defined $next) {
261 2         5 delete $self->{dh};
262             ## no critic
263 2         36 return undef;
264             }
265            
266             # Figure out whether it's a file or directory
267 8         11 my $file = $self->file($next);
268 8 100       18 $file = $self->subdir($next) if -d $file;
269 8         23 return $file;
270             }
271              
272             sub subsumes {
273 26 50   26 1 58 Carp::croak "Too many arguments given to subsumes()" if $#_ > 2;
274 26         27 my ($self, $other) = @_;
275 26 50       39 Carp::croak( "No second entity given to subsumes()" ) unless defined $other;
276              
277 26 100       31 $other = $self->new($other) unless eval{$other->isa( "Path::Class::Entity")};
  26         108  
278 26 100       40 $other = $other->dir unless $other->is_dir;
279              
280 26 100       44 if ($self->is_absolute) {
    50          
281 8         107 $other = $other->absolute;
282             } elsif ($other->is_absolute) {
283 0         0 $self = $self->absolute;
284             }
285              
286 26         107 $self = $self->cleanup;
287 26         45 $other = $other->cleanup;
288              
289 26 100 66     36 if ($self->volume || $other->volume) {
290 1 50       3 return 0 unless $other->volume eq $self->volume;
291             }
292              
293             # The root dir subsumes everything (but ignore the volume because
294             # we've already checked that)
295 26 100       26 return 1 if "@{$self->{dirs}}" eq "@{$self->new('')->{dirs}}";
  26         40  
  26         33  
296              
297             # The current dir subsumes every relative path (unless starting with updir)
298 22 100       45 if ($self eq $self->_spec->curdir) {
299 12         25 return $other->{dirs}[0] ne $self->_spec->updir;
300             }
301              
302 10         13 my $i = 0;
303 10         12 while ($i <= $#{ $self->{dirs} }) {
  23         37  
304 17 100       14 return 0 if $i > $#{ $other->{dirs} };
  17         25  
305 16 100       37 return 0 if $self->{dirs}[$i] ne $other->{dirs}[$i];
306 13         11 $i++;
307             }
308 6         21 return 1;
309             }
310              
311             sub contains {
312 12 50   12 1 33 Carp::croak "Too many arguments given to contains()" if $#_ > 2;
313 12         12 my ($self, $other) = @_;
314 12 50       18 Carp::croak "No second entity given to contains()" unless defined $other;
315 12 100 66     28 return unless -d $self and (-e $other or -l $other);
      33        
316              
317             # We're going to resolve the path, and don't want side effects on the objects
318             # so clone them. This also handles strings passed as $other.
319 10         23 $self= $self->new($self)->resolve;
320 10         14 $other= $self->new($other)->resolve;
321            
322 10         20 return $self->subsumes($other);
323             }
324              
325             sub tempfile {
326 0     0 1   my $self = shift;
327 0           return File::Temp::tempfile(@_, DIR => $self->stringify);
328             }
329              
330             1;
331             __END__