File Coverage

blib/lib/Path/Class/Dir.pm
Criterion Covered Total %
statement 192 197 97.4
branch 84 98 85.7
condition 25 37 67.5
subroutine 38 40 95.0
pod 25 25 100.0
total 364 397 91.6


line stmt bran cond sub pod time code
1 7     7   25 use strict;
  7         13  
  7         285  
2              
3             package Path::Class::Dir;
4             {
5             $Path::Class::Dir::VERSION = '0.36';
6             }
7              
8 7     7   26 use Path::Class::File;
  7         23  
  7         143  
9 7     7   24 use Carp();
  7         8  
  7         107  
10 7     7   775 use parent qw(Path::Class::Entity);
  7         509  
  7         44  
11              
12 7     7   3807 use IO::Dir ();
  7         44408  
  7         160  
13 7     7   41 use File::Path ();
  7         8  
  7         78  
14 7     7   1959 use File::Temp ();
  7         26841  
  7         125  
15 7     7   31 use Scalar::Util ();
  7         10  
  7         12878  
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 427     427 1 1443 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 427 100 100     1385 return if @_==1 && !defined($_[0]);
30              
31 426         680 my $s = $self->_spec;
32            
33 426 100 100     1392 my $first = (@_ == 0 ? $s->curdir :
    100          
34             !ref($_[0]) && $_[0] eq '' ? (shift, $s->rootdir) :
35             shift()
36             );
37            
38 426         570 $self->{dirs} = [];
39 426 100 66     1563 if ( Scalar::Util::blessed($first) && $first->isa("Path::Class::Dir") ) {
40 217         261 $self->{volume} = $first->{volume};
41 217         155 push @{$self->{dirs}}, @{$first->{dirs}};
  217         259  
  217         392  
42             }
43             else {
44 209         1232 ($self->{volume}, my $dirs) = $s->splitpath( $s->canonpath("$first") , 1);
45 209 100       660 push @{$self->{dirs}}, $dirs eq $s->rootdir ? "" : $s->splitdir($dirs);
  209         1015  
46             }
47              
48 426         502 push @{$self->{dirs}}, map {
49 426         576 Scalar::Util::blessed($_) && $_->isa("Path::Class::Dir")
50 116 50 33     722 ? @{$_->{dirs}}
  0         0  
51             : $s->splitdir( $s->canonpath($_) )
52             } @_;
53              
54              
55 426         1104 return $self;
56             }
57              
58 112     112 1 414 sub file_class { "Path::Class::File" }
59              
60 56     56 1 94 sub is_dir { 1 }
61              
62             sub as_foreign {
63 41     41 1 841 my ($self, $type) = @_;
64              
65 41         39 my $foreign = do {
66 41         83 local $self->{file_spec_class} = $self->_spec_class($type);
67 41         104 $self->SUPER::new;
68             };
69            
70             # Clone internal structure
71 41         61 $foreign->{volume} = $self->{volume};
72 41         92 my ($u, $fu) = ($self->_spec->updir, $foreign->_spec->updir);
73 41 100       49 $foreign->{dirs} = [ map {$_ eq $u ? $fu : $_} @{$self->{dirs}}];
  99         212  
  41         79  
74 41         103 return $foreign;
75             }
76              
77             sub stringify {
78 762     762 1 5111 my $self = shift;
79 762         1218 my $s = $self->_spec;
80             return $s->catpath($self->{volume},
81 762         798 $s->catdir(@{$self->{dirs}}),
  762         24300  
82             '');
83             }
84              
85 43     43 1 124 sub volume { shift()->{volume} }
86              
87             sub file {
88 113 100   113 1 224 local $Path::Class::Foreign = $_[0]->{file_spec_class} if $_[0]->{file_spec_class};
89 113         173 return $_[0]->file_class->new(@_);
90             }
91              
92 11     11 1 105 sub basename { shift()->{dirs}[-1] }
93              
94             sub dir_list {
95 32     32 1 392 my $self = shift;
96 32         29 my $d = $self->{dirs};
97 32 100       88 return @$d unless @_;
98            
99 9         7 my $offset = shift;
100 9 100       16 if ($offset < 0) { $offset = $#$d + $offset + 1 }
  5         7  
101            
102 9 100       42 return wantarray ? @$d[$offset .. $#$d] : $d->[$offset] unless @_;
    100          
103            
104 5         3 my $length = shift;
105 5 100       9 if ($length < 0) { $length = $#$d + $length + 1 - $offset }
  2         2  
106 5         26 return @$d[$offset .. $length + $offset - 1];
107             }
108              
109             sub components {
110 21     21 1 35 my $self = shift;
111 21         26 return $self->dir_list(@_);
112             }
113              
114             sub subdir {
115 74     74 1 930 my $self = shift;
116 74         124 return $self->new($self, @_);
117             }
118              
119             sub parent {
120 31     31 1 54 my $self = shift;
121 31         27 my $dirs = $self->{dirs};
122 31         51 my ($curdir, $updir) = ($self->_spec->curdir, $self->_spec->updir);
123              
124 31 100       63 if ($self->is_absolute) {
    100          
    100          
    100          
125 11         14 my $parent = $self->new($self);
126 11 100       16 pop @{$parent->{dirs}} if @$dirs > 1;
  10         11  
127 11         36 return $parent;
128              
129             } elsif ($self eq $curdir) {
130 2         3 return $self->new($updir);
131              
132 38         133 } elsif (!grep {$_ ne $updir} @$dirs) { # All updirs
133 1         2 return $self->new($self, $updir); # Add one more
134              
135             } elsif (@$dirs == 1) {
136 4         5 return $self->new($curdir);
137              
138             } else {
139 13         20 my $parent = $self->new($self);
140 13         15 pop @{$parent->{dirs}};
  13         13  
141 13         45 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 22     22 1 44 my $self = shift;
149 22         36 my $rel = $self->_spec->abs2rel($self->stringify, @_);
150 22 50       62 return $self->new( length $rel ? $rel : $self->_spec->curdir );
151             }
152              
153 54     54 1 148 sub open { IO::Dir->new(@_) }
154 15     15 1 49 sub mkpath { File::Path::mkpath(shift()->stringify, @_) }
155 8     8 1 2006 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 1089 my $self = shift;
163 15         17 my ($callback, @args) = @_;
164 15         27 my @children = $self->children;
165             return $self->$callback(
166             sub {
167 15     15   46 my @inner_args = @_;
168 15         16 return map { $_->traverse($callback, @inner_args) } @children;
  24         118  
169             },
170             @args
171 15         364 );
172             }
173              
174             sub traverse_if {
175 9     9 1 539 my $self = shift;
176 9         10 my ($callback, $condition, @args) = @_;
177 9         12 my @children = grep { $condition->($_) } $self->children;
  12         184  
178             return $self->$callback(
179             sub {
180 9     9   52 my @inner_args = @_;
181 9         14 return map { $_->traverse_if($callback, $condition, @inner_args) } @children;
  6         10  
182             },
183             @args
184 9         46 );
185             }
186              
187             sub recurse {
188 6     6 1 4363 my $self = shift;
189 6         24 my %opts = (preorder => 1, depthfirst => 0, @_);
190            
191             my $callback = $opts{callback}
192 6 50       17 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         9 my $ret = $callback->($dir);
202 5 50 50     23 unless( ($ret||'') eq $self->PRUNE ) {
203 5         10 unshift @queue, $dir->children;
204             }
205             }
206             : $opts{preorder}
207             ? sub {
208 18     18   12 my $dir = shift;
209 18         29 my $ret = $callback->($dir);
210 18 100 100     106 unless( ($ret||'') eq $self->PRUNE ) {
211 16         22 push @queue, $dir->children;
212             }
213             }
214             : sub {
215 5     5   4 my $dir = shift;
216 5         8 $visit_entry->($_) foreach $dir->children;
217 5         12 $callback->($dir);
218 6 100 66     50 };
    100          
219            
220             $visit_entry = sub {
221 48     48   118 my $entry = shift;
222 48 100       87 if ($entry->is_dir) { $visit_dir->($entry) } # Will call $callback
  28         36  
223 20         33 else { $callback->($entry) }
224 6         17 };
225            
226 6         14 while (@queue) {
227 40         505 $visit_entry->( shift @queue );
228             }
229             }
230              
231             sub children {
232 51     51 1 60 my ($self, %opts) = @_;
233            
234 51 50       69 my $dh = $self->open or Carp::croak( "Can't open directory $self: $!" );
235            
236 51         635 my @out;
237 51         106 while (defined(my $entry = $dh->read)) {
238 182 100 66     1368 next if !$opts{all} && $self->_is_local_dot_dir($entry);
239 80 50 33     160 next if ($opts{no_hidden} && $entry =~ /^\./);
240 80         108 push @out, $self->file($entry);
241 80 100       255 $out[-1] = $self->subdir($entry) if -d $out[-1];
242             }
243 51         384 return @out;
244             }
245              
246             sub _is_local_dot_dir {
247 182     182   165 my $self = shift;
248 182         159 my $dir = shift;
249              
250 182   100     857 return ($dir eq $Updir or $dir eq $Curdir);
251             }
252              
253             sub next {
254 10     10 1 26 my $self = shift;
255 10 100       19 unless ($self->{dh}) {
256 2 50       6 $self->{dh} = $self->open or Carp::croak( "Can't open directory $self: $!" );
257             }
258            
259 10         47 my $next = $self->{dh}->read;
260 10 100       101 unless (defined $next) {
261 2         7 delete $self->{dh};
262             ## no critic
263 2         42 return undef;
264             }
265            
266             # Figure out whether it's a file or directory
267 8         13 my $file = $self->file($next);
268 8 100       19 $file = $self->subdir($next) if -d $file;
269 8         28 return $file;
270             }
271              
272             sub subsumes {
273 21 50   21 1 57 Carp::croak "Too many arguments given to subsumes()" if $#_ > 2;
274 21         25 my ($self, $other) = @_;
275 21 50       44 Carp::croak( "No second entity given to subsumes()" ) unless $other;
276              
277 21 100       25 $other = $self->new($other) unless eval{$other->isa( "Path::Class::Entity")} ;
  21         118  
278 21 100       41 $other = $other->dir unless $other->is_dir;
279              
280 21 100       75 if ($self->is_absolute) {
    50          
281 8         152 $other = $other->absolute;
282             } elsif ($other->is_absolute) {
283 0         0 $self = $self->absolute;
284             }
285              
286 21         136 $self = $self->cleanup;
287 21         43 $other = $other->cleanup;
288              
289 21 100 66     42 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 21 100       23 return 1 if "@{$self->{dirs}}" eq "@{$self->new('')->{dirs}}";
  21         35  
  21         32  
296              
297             # The current dir subsumes every relative path (unless starting with updir)
298 17 100       49 if ($self eq $self->_spec->curdir) {
299 8         11 return $other->{dirs}[0] ne $self->_spec->updir;
300             }
301              
302 9         16 my $i = 0;
303 9         14 while ($i <= $#{ $self->{dirs} }) {
  21         44  
304 16 100       14 return 0 if $i > $#{ $other->{dirs} };
  16         46  
305 15 100       48 return 0 if $self->{dirs}[$i] ne $other->{dirs}[$i];
306 12         21 $i++;
307             }
308 5         22 return 1;
309             }
310              
311             sub contains {
312 8 50   8 1 21 Carp::croak "Too many arguments given to contains()" if $#_ > 2;
313 8         8 my ($self, $other) = @_;
314 8 50       17 Carp::croak "No second entity given to contains()" unless $other;
315 8 100 66     16 return unless -d $self and (-e $other or -l $other);
      33        
316              
317 6 50       11 $other = $self->new($other) unless eval{$other->isa("Path::Class::Entity")};
  6         23  
318 6         12 $other->resolve;
319 6         12 return $self->subsumes($other);
320             }
321              
322             sub tempfile {
323 0     0 1   my $self = shift;
324 0           return File::Temp::tempfile(@_, DIR => $self->stringify);
325             }
326              
327             1;
328             __END__