File Coverage

blib/lib/Path/Class/Dir.pm
Criterion Covered Total %
statement 182 187 97.3
branch 76 86 88.3
condition 20 28 71.4
subroutine 38 40 95.0
pod 25 25 100.0
total 341 366 93.1


line stmt bran cond sub pod time code
1 7     7   37 use strict;
  7         16  
  7         383  
2              
3             package Path::Class::Dir;
4             {
5             $Path::Class::Dir::VERSION = '0.35';
6             }
7              
8 7     7   35 use Path::Class::File;
  7         17  
  7         179  
9 7     7   35 use Carp();
  7         10  
  7         132  
10 7     7   2798 use parent qw(Path::Class::Entity);
  7         724  
  7         55  
11              
12 7     7   7760 use IO::Dir ();
  7         68540  
  7         167  
13 7     7   57 use File::Path ();
  7         15  
  7         97  
14 7     7   3881 use File::Temp ();
  7         40735  
  7         123  
15 7     7   48 use Scalar::Util ();
  7         13  
  7         16304  
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 376     376 1 2244 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 376 100 100     1760 return if @_==1 && !defined($_[0]);
30              
31 375         1100 my $s = $self->_spec;
32            
33 375 100       1300 my $first = (@_ == 0 ? $s->curdir :
    100          
34             $_[0] eq '' ? (shift, $s->rootdir) :
35             shift()
36             );
37            
38 375         1234 $self->{dirs} = [];
39 375 100 66     2284 if ( Scalar::Util::blessed($first) && $first->isa("Path::Class::Dir") ) {
40 215         461 $self->{volume} = $first->{volume};
41 215         249 push @{$self->{dirs}}, @{$first->{dirs}};
  215         337  
  215         668  
42             }
43             else {
44 160         1434 ($self->{volume}, my $dirs) = $s->splitpath( $s->canonpath("$first") , 1);
45 160 100       709 push @{$self->{dirs}}, $dirs eq $s->rootdir ? "" : $s->splitdir($dirs);
  160         1264  
46             }
47              
48 375         790 push @{$self->{dirs}}, map {
  0         0  
49 375         756 Scalar::Util::blessed($_) && $_->isa("Path::Class::Dir")
50 107 50 33     787 ? @{$_->{dirs}}
51             : $s->splitdir($_)
52             } @_;
53              
54              
55 375         1554 return $self;
56             }
57              
58 112     112 1 598 sub file_class { "Path::Class::File" }
59              
60 48     48 1 123 sub is_dir { 1 }
61              
62             sub as_foreign {
63 41     41 1 1021 my ($self, $type) = @_;
64              
65 41         51 my $foreign = do {
66 41         176 local $self->{file_spec_class} = $self->_spec_class($type);
67 41         148 $self->SUPER::new;
68             };
69            
70             # Clone internal structure
71 41         105 $foreign->{volume} = $self->{volume};
72 41         145 my ($u, $fu) = ($self->_spec->updir, $foreign->_spec->updir);
73 41 100       78 $foreign->{dirs} = [ map {$_ eq $u ? $fu : $_} @{$self->{dirs}}];
  99         349  
  41         102  
74 41         173 return $foreign;
75             }
76              
77             sub stringify {
78 881     881 1 8914 my $self = shift;
79 881         2172 my $s = $self->_spec;
80 881         47023 return $s->catpath($self->{volume},
81 881         1463 $s->catdir(@{$self->{dirs}}),
82             '');
83             }
84              
85 15     15 1 45 sub volume { shift()->{volume} }
86              
87             sub file {
88 113 100   113 1 428 local $Path::Class::Foreign = $_[0]->{file_spec_class} if $_[0]->{file_spec_class};
89 113         276 return $_[0]->file_class->new(@_);
90             }
91              
92 11     11 1 124 sub basename { shift()->{dirs}[-1] }
93              
94             sub dir_list {
95 32     32 1 404 my $self = shift;
96 32         48 my $d = $self->{dirs};
97 32 100       154 return @$d unless @_;
98            
99 9         10 my $offset = shift;
100 9 100       20 if ($offset < 0) { $offset = $#$d + $offset + 1 }
  5         11  
101            
102 9 100       37 return wantarray ? @$d[$offset .. $#$d] : $d->[$offset] unless @_;
    100          
103            
104 5         5 my $length = shift;
105 5 100       12 if ($length < 0) { $length = $#$d + $length + 1 - $offset }
  2         5  
106 5         22 return @$d[$offset .. $length + $offset - 1];
107             }
108              
109             sub components {
110 21     21 1 59 my $self = shift;
111 21         93 return $self->dir_list(@_);
112             }
113              
114             sub subdir {
115 74     74 1 1083 my $self = shift;
116 74         193 return $self->new($self, @_);
117             }
118              
119             sub parent {
120 29     29 1 71 my $self = shift;
121 29         46 my $dirs = $self->{dirs};
122 29         109 my ($curdir, $updir) = ($self->_spec->curdir, $self->_spec->updir);
123              
124 29 100       166 if ($self->is_absolute) {
    100          
    100          
    100          
125 10         19 my $parent = $self->new($self);
126 10 100       24 pop @{$parent->{dirs}} if @$dirs > 1;
  9         15  
127 10         45 return $parent;
128              
129 36         164 } elsif ($self eq $curdir) {
130 2         6 return $self->new($updir);
131              
132             } 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         11 return $self->new($curdir);
137              
138             } else {
139 12         32 my $parent = $self->new($self);
140 12         19 pop @{$parent->{dirs}};
  12         30  
141 12         83 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 16     16 1 62 my $self = shift;
149 16         48 my $rel = $self->_spec->abs2rel($self->stringify, @_);
150 16 50       77 return $self->new( length $rel ? $rel : $self->_spec->curdir );
151             }
152              
153 54     54 1 301 sub open { IO::Dir->new(@_) }
154 15     15 1 64 sub mkpath { File::Path::mkpath(shift()->stringify, @_) }
155 8     8 1 4311 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 914 my $self = shift;
163 15         21 my ($callback, @args) = @_;
164 15         32 my @children = $self->children;
165             return $self->$callback(
166             sub {
167 15     15   56 my @inner_args = @_;
168 15         25 return map { $_->traverse($callback, @inner_args) } @children;
  24         164  
169             },
170             @args
171 15         437 );
172             }
173              
174             sub traverse_if {
175 9     9 1 858 my $self = shift;
176 9         17 my ($callback, $condition, @args) = @_;
177 9         21 my @children = grep { $condition->($_) } $self->children;
  12         286  
178             return $self->$callback(
179             sub {
180 9     9   36 my @inner_args = @_;
181 9         21 return map { $_->traverse_if($callback, $condition, @inner_args) } @children;
  6         18  
182             },
183             @args
184 9         67 );
185             }
186              
187             sub recurse {
188 6     6 1 16811 my $self = shift;
189 6         43 my %opts = (preorder => 1, depthfirst => 0, @_);
190            
191 6 50       32 my $callback = $opts{callback}
192             or Carp::croak( "Must provide a 'callback' parameter to recurse()" );
193            
194 6         16 my @queue = ($self);
195            
196 6         12 my $visit_entry;
197             my $visit_dir =
198             $opts{depthfirst} && $opts{preorder}
199             ? sub {
200 5     5   10 my $dir = shift;
201 5         14 my $ret = $callback->($dir);
202 5 50 50     38 unless( ($ret||'') eq $self->PRUNE ) {
203 5         15 unshift @queue, $dir->children;
204             }
205             }
206             : $opts{preorder}
207             ? sub {
208 18     18   38 my $dir = shift;
209 18         55 my $ret = $callback->($dir);
210 18 100 100     163 unless( ($ret||'') eq $self->PRUNE ) {
211 16         39 push @queue, $dir->children;
212             }
213             }
214             : sub {
215 5     5   12 my $dir = shift;
216 5         20 $visit_entry->($_) foreach $dir->children;
217 5         30 $callback->($dir);
218 6 100 100     78 };
    100          
219            
220             $visit_entry = sub {
221 48     48   393 my $entry = shift;
222 48 100       145 if ($entry->is_dir) { $visit_dir->($entry) } # Will call $callback
  28         64  
223 20         53 else { $callback->($entry) }
224 6         29 };
225            
226 6         30 while (@queue) {
227 40         1017 $visit_entry->( shift @queue );
228             }
229             }
230              
231             sub children {
232 51     51 1 94 my ($self, %opts) = @_;
233            
234 51 50       122 my $dh = $self->open or Carp::croak( "Can't open directory $self: $!" );
235            
236 51         869 my @out;
237 51         167 while (defined(my $entry = $dh->read)) {
238 182 100 66     2250 next if !$opts{all} && $self->_is_local_dot_dir($entry);
239 80 50 33     234 next if ($opts{no_hidden} && $entry =~ /^\./);
240 80         179 push @out, $self->file($entry);
241 80 100       362 $out[-1] = $self->subdir($entry) if -d $out[-1];
242             }
243 51         675 return @out;
244             }
245              
246             sub _is_local_dot_dir {
247 182     182   232 my $self = shift;
248 182         217 my $dir = shift;
249              
250 182   100     1336 return ($dir eq $Updir or $dir eq $Curdir);
251             }
252              
253             sub next {
254 10     10 1 37 my $self = shift;
255 10 100       30 unless ($self->{dh}) {
256 2 50       18 $self->{dh} = $self->open or Carp::croak( "Can't open directory $self: $!" );
257             }
258            
259 10         67 my $next = $self->{dh}->read;
260 10 100       159 unless (defined $next) {
261 2         11 delete $self->{dh};
262             ## no critic
263 2         63 return undef;
264             }
265            
266             # Figure out whether it's a file or directory
267 8         19 my $file = $self->file($next);
268 8 100       26 $file = $self->subdir($next) if -d $file;
269 8         43 return $file;
270             }
271              
272             sub subsumes {
273 13     13 1 44 my ($self, $other) = @_;
274 13 50       49 die "No second entity given to subsumes()" unless $other;
275            
276 13 100       80 $other = $self->new($other) unless UNIVERSAL::isa($other, "Path::Class::Entity");
277 13 100       34 $other = $other->dir unless $other->is_dir;
278            
279 13 100       250 if ($self->is_absolute) {
    50          
280 8         152 $other = $other->absolute;
281             } elsif ($other->is_absolute) {
282 0         0 $self = $self->absolute;
283             }
284              
285 13         135 $self = $self->cleanup;
286 13         38 $other = $other->cleanup;
287              
288 13 100       33 if ($self->volume) {
289 1 50       3 return 0 unless $other->volume eq $self->volume;
290             }
291              
292             # The root dir subsumes everything (but ignore the volume because
293             # we've already checked that)
294 13 100       24 return 1 if "@{$self->{dirs}}" eq "@{$self->new('')->{dirs}}";
  13         40  
  13         23  
295            
296 9         21 my $i = 0;
297 9         10 while ($i <= $#{ $self->{dirs} }) {
  21         51  
298 16 100       19 return 0 if $i > $#{ $other->{dirs} };
  16         43  
299 15 100       47 return 0 if $self->{dirs}[$i] ne $other->{dirs}[$i];
300 12         16 $i++;
301             }
302 5         30 return 1;
303             }
304              
305             sub contains {
306 2     2 1 4 my ($self, $other) = @_;
307 2   66     7 return !!(-d $self and (-e $other or -l $other) and $self->subsumes($other));
308             }
309              
310             sub tempfile {
311 0     0 1   my $self = shift;
312 0           return File::Temp::tempfile(@_, DIR => $self->stringify);
313             }
314              
315             1;
316             __END__