Commit | Line | Data |
3fea05b9 |
1 | package Path::Class::Dir; |
2 | |
3 | $VERSION = '0.17'; |
4 | |
5 | use strict; |
6 | use Path::Class::File; |
7 | use Path::Class::Entity; |
8 | use Carp(); |
9 | use base qw(Path::Class::Entity); |
10 | |
11 | use IO::Dir (); |
12 | use File::Path (); |
13 | |
14 | sub new { |
15 | my $self = shift->SUPER::new(); |
16 | |
17 | # If the only arg is undef, it's probably a mistake. Without this |
18 | # special case here, we'd return the root directory, which is a |
19 | # lousy thing to do to someone when they made a mistake. Return |
20 | # undef instead. |
21 | return if @_==1 && !defined($_[0]); |
22 | |
23 | my $s = $self->_spec; |
24 | |
25 | my $first = (@_ == 0 ? $s->curdir : |
26 | $_[0] eq '' ? (shift, $s->rootdir) : |
27 | shift() |
28 | ); |
29 | |
30 | ($self->{volume}, my $dirs) = $s->splitpath( $s->canonpath($first) , 1); |
31 | $self->{dirs} = [$s->splitdir($s->catdir($dirs, @_))]; |
32 | |
33 | return $self; |
34 | } |
35 | |
36 | sub is_dir { 1 } |
37 | |
38 | sub as_foreign { |
39 | my ($self, $type) = @_; |
40 | |
41 | my $foreign = do { |
42 | local $self->{file_spec_class} = $self->_spec_class($type); |
43 | $self->SUPER::new; |
44 | }; |
45 | |
46 | # Clone internal structure |
47 | $foreign->{volume} = $self->{volume}; |
48 | my ($u, $fu) = ($self->_spec->updir, $foreign->_spec->updir); |
49 | $foreign->{dirs} = [ map {$_ eq $u ? $fu : $_} @{$self->{dirs}}]; |
50 | return $foreign; |
51 | } |
52 | |
53 | sub stringify { |
54 | my $self = shift; |
55 | my $s = $self->_spec; |
56 | return $s->catpath($self->{volume}, |
57 | $s->catdir(@{$self->{dirs}}), |
58 | ''); |
59 | } |
60 | |
61 | sub volume { shift()->{volume} } |
62 | |
63 | sub file { |
64 | local $Path::Class::Foreign = $_[0]->{file_spec_class} if $_[0]->{file_spec_class}; |
65 | return Path::Class::File->new(@_); |
66 | } |
67 | |
68 | sub dir_list { |
69 | my $self = shift; |
70 | my $d = $self->{dirs}; |
71 | return @$d unless @_; |
72 | |
73 | my $offset = shift; |
74 | if ($offset < 0) { $offset = $#$d + $offset + 1 } |
75 | |
76 | return wantarray ? @$d[$offset .. $#$d] : $d->[$offset] unless @_; |
77 | |
78 | my $length = shift; |
79 | if ($length < 0) { $length = $#$d + $length + 1 - $offset } |
80 | return @$d[$offset .. $length + $offset - 1]; |
81 | } |
82 | |
83 | sub subdir { |
84 | my $self = shift; |
85 | return $self->new($self, @_); |
86 | } |
87 | |
88 | sub parent { |
89 | my $self = shift; |
90 | my $dirs = $self->{dirs}; |
91 | my ($curdir, $updir) = ($self->_spec->curdir, $self->_spec->updir); |
92 | |
93 | if ($self->is_absolute) { |
94 | my $parent = $self->new($self); |
95 | pop @{$parent->{dirs}}; |
96 | return $parent; |
97 | |
98 | } elsif ($self eq $curdir) { |
99 | return $self->new($updir); |
100 | |
101 | } elsif (!grep {$_ ne $updir} @$dirs) { # All updirs |
102 | return $self->new($self, $updir); # Add one more |
103 | |
104 | } elsif (@$dirs == 1) { |
105 | return $self->new($curdir); |
106 | |
107 | } else { |
108 | my $parent = $self->new($self); |
109 | pop @{$parent->{dirs}}; |
110 | return $parent; |
111 | } |
112 | } |
113 | |
114 | sub relative { |
115 | # File::Spec->abs2rel before version 3.13 returned the empty string |
116 | # when the two paths were equal - work around it here. |
117 | my $self = shift; |
118 | my $rel = $self->_spec->abs2rel($self->stringify, @_); |
119 | return $self->new( length $rel ? $rel : $self->_spec->curdir ); |
120 | } |
121 | |
122 | sub open { IO::Dir->new(@_) } |
123 | sub mkpath { File::Path::mkpath(shift()->stringify, @_) } |
124 | sub rmtree { File::Path::rmtree(shift()->stringify, @_) } |
125 | |
126 | sub remove { |
127 | rmdir( shift() ); |
128 | } |
129 | |
130 | sub recurse { |
131 | my $self = shift; |
132 | my %opts = (preorder => 1, depthfirst => 0, @_); |
133 | |
134 | my $callback = $opts{callback} |
135 | or Carp::croak( "Must provide a 'callback' parameter to recurse()" ); |
136 | |
137 | my @queue = ($self); |
138 | |
139 | my $visit_entry; |
140 | my $visit_dir = |
141 | $opts{depthfirst} && $opts{preorder} |
142 | ? sub { |
143 | my $dir = shift; |
144 | $callback->($dir); |
145 | unshift @queue, $dir->children; |
146 | } |
147 | : $opts{preorder} |
148 | ? sub { |
149 | my $dir = shift; |
150 | $callback->($dir); |
151 | push @queue, $dir->children; |
152 | } |
153 | : sub { |
154 | my $dir = shift; |
155 | $visit_entry->($_) foreach $dir->children; |
156 | $callback->($dir); |
157 | }; |
158 | |
159 | $visit_entry = sub { |
160 | my $entry = shift; |
161 | if ($entry->is_dir) { $visit_dir->($entry) } # Will call $callback |
162 | else { $callback->($entry) } |
163 | }; |
164 | |
165 | while (@queue) { |
166 | $visit_entry->( shift @queue ); |
167 | } |
168 | } |
169 | |
170 | sub children { |
171 | my ($self, %opts) = @_; |
172 | |
173 | my $dh = $self->open or Carp::croak( "Can't open directory $self: $!" ); |
174 | |
175 | my @out; |
176 | while (my $entry = $dh->read) { |
177 | # XXX What's the right cross-platform way to do this? |
178 | next if (!$opts{all} && ($entry eq '.' || $entry eq '..')); |
179 | push @out, $self->file($entry); |
180 | $out[-1] = $self->subdir($entry) if -d $out[-1]; |
181 | } |
182 | return @out; |
183 | } |
184 | |
185 | sub next { |
186 | my $self = shift; |
187 | unless ($self->{dh}) { |
188 | $self->{dh} = $self->open or Carp::croak( "Can't open directory $self: $!" ); |
189 | } |
190 | |
191 | my $next = $self->{dh}->read; |
192 | unless (defined $next) { |
193 | delete $self->{dh}; |
194 | return undef; |
195 | } |
196 | |
197 | # Figure out whether it's a file or directory |
198 | my $file = $self->file($next); |
199 | $file = $self->subdir($next) if -d $file; |
200 | return $file; |
201 | } |
202 | |
203 | sub subsumes { |
204 | my ($self, $other) = @_; |
205 | die "No second entity given to subsumes()" unless $other; |
206 | |
207 | $other = $self->new($other) unless UNIVERSAL::isa($other, __PACKAGE__); |
208 | $other = $other->dir unless $other->is_dir; |
209 | |
210 | if ($self->is_absolute) { |
211 | $other = $other->absolute; |
212 | } elsif ($other->is_absolute) { |
213 | $self = $self->absolute; |
214 | } |
215 | |
216 | $self = $self->cleanup; |
217 | $other = $other->cleanup; |
218 | |
219 | if ($self->volume) { |
220 | return 0 unless $other->volume eq $self->volume; |
221 | } |
222 | |
223 | # The root dir subsumes everything (but ignore the volume because |
224 | # we've already checked that) |
225 | return 1 if "@{$self->{dirs}}" eq "@{$self->new('')->{dirs}}"; |
226 | |
227 | my $i = 0; |
228 | while ($i <= $#{ $self->{dirs} }) { |
229 | return 0 if $i > $#{ $other->{dirs} }; |
230 | return 0 if $self->{dirs}[$i] ne $other->{dirs}[$i]; |
231 | $i++; |
232 | } |
233 | return 1; |
234 | } |
235 | |
236 | sub contains { |
237 | my ($self, $other) = @_; |
238 | return !!(-d $self and (-e $other or -l $other) and $self->subsumes($other)); |
239 | } |
240 | |
241 | 1; |
242 | __END__ |
243 | |
244 | =head1 NAME |
245 | |
246 | Path::Class::Dir - Objects representing directories |
247 | |
248 | =head1 SYNOPSIS |
249 | |
250 | use Path::Class qw(dir); # Export a short constructor |
251 | |
252 | my $dir = dir('foo', 'bar'); # Path::Class::Dir object |
253 | my $dir = Path::Class::Dir->new('foo', 'bar'); # Same thing |
254 | |
255 | # Stringifies to 'foo/bar' on Unix, 'foo\bar' on Windows, etc. |
256 | print "dir: $dir\n"; |
257 | |
258 | if ($dir->is_absolute) { ... } |
259 | if ($dir->is_relative) { ... } |
260 | |
261 | my $v = $dir->volume; # Could be 'C:' on Windows, empty string |
262 | # on Unix, 'Macintosh HD:' on Mac OS |
263 | |
264 | $dir->cleanup; # Perform logical cleanup of pathname |
265 | $dir->resolve; # Perform physical cleanup of pathname |
266 | |
267 | my $file = $dir->file('file.txt'); # A file in this directory |
268 | my $subdir = $dir->subdir('george'); # A subdirectory |
269 | my $parent = $dir->parent; # The parent directory, 'foo' |
270 | |
271 | my $abs = $dir->absolute; # Transform to absolute path |
272 | my $rel = $abs->relative; # Transform to relative path |
273 | my $rel = $abs->relative('/foo'); # Relative to /foo |
274 | |
275 | print $dir->as_foreign('Mac'); # :foo:bar: |
276 | print $dir->as_foreign('Win32'); # foo\bar |
277 | |
278 | # Iterate with IO::Dir methods: |
279 | my $handle = $dir->open; |
280 | while (my $file = $handle->read) { |
281 | $file = $dir->file($file); # Turn into Path::Class::File object |
282 | ... |
283 | } |
284 | |
285 | # Iterate with Path::Class methods: |
286 | while (my $file = $dir->next) { |
287 | # $file is a Path::Class::File or Path::Class::Dir object |
288 | ... |
289 | } |
290 | |
291 | |
292 | =head1 DESCRIPTION |
293 | |
294 | The C<Path::Class::Dir> class contains functionality for manipulating |
295 | directory names in a cross-platform way. |
296 | |
297 | =head1 METHODS |
298 | |
299 | =over 4 |
300 | |
301 | =item $dir = Path::Class::Dir->new( <dir1>, <dir2>, ... ) |
302 | |
303 | =item $dir = dir( <dir1>, <dir2>, ... ) |
304 | |
305 | Creates a new C<Path::Class::Dir> object and returns it. The |
306 | arguments specify names of directories which will be joined to create |
307 | a single directory object. A volume may also be specified as the |
308 | first argument, or as part of the first argument. You can use |
309 | platform-neutral syntax: |
310 | |
311 | my $dir = dir( 'foo', 'bar', 'baz' ); |
312 | |
313 | or platform-native syntax: |
314 | |
315 | my $dir = dir( 'foo/bar/baz' ); |
316 | |
317 | or a mixture of the two: |
318 | |
319 | my $dir = dir( 'foo/bar', 'baz' ); |
320 | |
321 | All three of the above examples create relative paths. To create an |
322 | absolute path, either use the platform native syntax for doing so: |
323 | |
324 | my $dir = dir( '/var/tmp' ); |
325 | |
326 | or use an empty string as the first argument: |
327 | |
328 | my $dir = dir( '', 'var', 'tmp' ); |
329 | |
330 | If the second form seems awkward, that's somewhat intentional - paths |
331 | like C</var/tmp> or C<\Windows> aren't cross-platform concepts in the |
332 | first place (many non-Unix platforms don't have a notion of a "root |
333 | directory"), so they probably shouldn't appear in your code if you're |
334 | trying to be cross-platform. The first form is perfectly natural, |
335 | because paths like this may come from config files, user input, or |
336 | whatever. |
337 | |
338 | As a special case, since it doesn't otherwise mean anything useful and |
339 | it's convenient to define this way, C<< Path::Class::Dir->new() >> (or |
340 | C<dir()>) refers to the current directory (C<< File::Spec->curdir >>). |
341 | To get the current directory as an absolute path, do C<< |
342 | dir()->absolute >>. |
343 | |
344 | Finally, as another special case C<dir(undef)> will return undef, |
345 | since that's usually an accident on the part of the caller, and |
346 | returning the root directory would be a nasty surprise just asking for |
347 | trouble a few lines later. |
348 | |
349 | =item $dir->stringify |
350 | |
351 | This method is called internally when a C<Path::Class::Dir> object is |
352 | used in a string context, so the following are equivalent: |
353 | |
354 | $string = $dir->stringify; |
355 | $string = "$dir"; |
356 | |
357 | =item $dir->volume |
358 | |
359 | Returns the volume (e.g. C<C:> on Windows, C<Macintosh HD:> on Mac OS, |
360 | etc.) of the directory object, if any. Otherwise, returns the empty |
361 | string. |
362 | |
363 | =item $dir->is_dir |
364 | |
365 | Returns a boolean value indicating whether this object represents a |
366 | directory. Not surprisingly, C<Path::Class::File> objects always |
367 | return false, and C<Path::Class::Dir> objects always return true. |
368 | |
369 | =item $dir->is_absolute |
370 | |
371 | Returns true or false depending on whether the directory refers to an |
372 | absolute path specifier (like C</usr/local> or C<\Windows>). |
373 | |
374 | =item $dir->is_relative |
375 | |
376 | Returns true or false depending on whether the directory refers to a |
377 | relative path specifier (like C<lib/foo> or C<./dir>). |
378 | |
379 | =item $dir->cleanup |
380 | |
381 | Performs a logical cleanup of the file path. For instance: |
382 | |
383 | my $dir = dir('/foo//baz/./foo')->cleanup; |
384 | # $dir now represents '/foo/baz/foo'; |
385 | |
386 | =item $dir->resolve |
387 | |
388 | Performs a physical cleanup of the file path. For instance: |
389 | |
390 | my $dir = dir('/foo//baz/../foo')->resolve; |
391 | # $dir now represents '/foo/foo', assuming no symlinks |
392 | |
393 | This actually consults the filesystem to verify the validity of the |
394 | path. |
395 | |
396 | =item $file = $dir->file( <dir1>, <dir2>, ..., <file> ) |
397 | |
398 | Returns a C<Path::Class::File> object representing an entry in C<$dir> |
399 | or one of its subdirectories. Internally, this just calls C<< |
400 | Path::Class::File->new( @_ ) >>. |
401 | |
402 | =item $subdir = $dir->subdir( <dir1>, <dir2>, ... ) |
403 | |
404 | Returns a new C<Path::Class::Dir> object representing a subdirectory |
405 | of C<$dir>. |
406 | |
407 | =item $parent = $dir->parent |
408 | |
409 | Returns the parent directory of C<$dir>. Note that this is the |
410 | I<logical> parent, not necessarily the physical parent. It really |
411 | means we just chop off entries from the end of the directory list |
412 | until we cain't chop no more. If the directory is relative, we start |
413 | using the relative forms of parent directories. |
414 | |
415 | The following code demonstrates the behavior on absolute and relative |
416 | directories: |
417 | |
418 | $dir = dir('/foo/bar'); |
419 | for (1..6) { |
420 | print "Absolute: $dir\n"; |
421 | $dir = $dir->parent; |
422 | } |
423 | |
424 | $dir = dir('foo/bar'); |
425 | for (1..6) { |
426 | print "Relative: $dir\n"; |
427 | $dir = $dir->parent; |
428 | } |
429 | |
430 | ########### Output on Unix ################ |
431 | Absolute: /foo/bar |
432 | Absolute: /foo |
433 | Absolute: / |
434 | Absolute: / |
435 | Absolute: / |
436 | Absolute: / |
437 | Relative: foo/bar |
438 | Relative: foo |
439 | Relative: . |
440 | Relative: .. |
441 | Relative: ../.. |
442 | Relative: ../../.. |
443 | |
444 | =item @list = $dir->children |
445 | |
446 | Returns a list of C<Path::Class::File> and/or C<Path::Class::Dir> |
447 | objects listed in this directory, or in scalar context the number of |
448 | such objects. Obviously, it is necessary for C<$dir> to |
449 | exist and be readable in order to find its children. |
450 | |
451 | Note that the children are returned as subdirectories of C<$dir>, |
452 | i.e. the children of F<foo> will be F<foo/bar> and F<foo/baz>, not |
453 | F<bar> and F<baz>. |
454 | |
455 | Ordinarily C<children()> will not include the I<self> and I<parent> |
456 | entries C<.> and C<..> (or their equivalents on non-Unix systems), |
457 | because that's like I'm-my-own-grandpa business. If you do want all |
458 | directory entries including these special ones, pass a true value for |
459 | the C<all> parameter: |
460 | |
461 | @c = $dir->children(); # Just the children |
462 | @c = $dir->children(all => 1); # All entries |
463 | |
464 | =item $abs = $dir->absolute |
465 | |
466 | Returns a C<Path::Class::Dir> object representing C<$dir> as an |
467 | absolute path. An optional argument, given as either a string or a |
468 | C<Path::Class::Dir> object, specifies the directory to use as the base |
469 | of relativity - otherwise the current working directory will be used. |
470 | |
471 | =item $rel = $dir->relative |
472 | |
473 | Returns a C<Path::Class::Dir> object representing C<$dir> as a |
474 | relative path. An optional argument, given as either a string or a |
475 | C<Path::Class::Dir> object, specifies the directory to use as the base |
476 | of relativity - otherwise the current working directory will be used. |
477 | |
478 | =item $boolean = $dir->subsumes($other) |
479 | |
480 | Returns true if this directory spec subsumes the other spec, and false |
481 | otherwise. Think of "subsumes" as "contains", but we only look at the |
482 | I<specs>, not whether C<$dir> actually contains C<$other> on the |
483 | filesystem. |
484 | |
485 | The C<$other> argument may be a C<Path::Class::Dir> object, a |
486 | C<Path::Class::File> object, or a string. In the latter case, we |
487 | assume it's a directory. |
488 | |
489 | # Examples: |
490 | dir('foo/bar' )->subsumes(dir('foo/bar/baz')) # True |
491 | dir('/foo/bar')->subsumes(dir('/foo/bar/baz')) # True |
492 | dir('foo/bar' )->subsumes(dir('bar/baz')) # False |
493 | dir('/foo/bar')->subsumes(dir('foo/bar')) # False |
494 | |
495 | |
496 | =item $boolean = $dir->contains($other) |
497 | |
498 | Returns true if this directory actually contains C<$other> on the |
499 | filesystem. C<$other> doesn't have to be a direct child of C<$dir>, |
500 | it just has to be subsumed. |
501 | |
502 | =item $foreign = $dir->as_foreign($type) |
503 | |
504 | Returns a C<Path::Class::Dir> object representing C<$dir> as it would |
505 | be specified on a system of type C<$type>. Known types include |
506 | C<Unix>, C<Win32>, C<Mac>, C<VMS>, and C<OS2>, i.e. anything for which |
507 | there is a subclass of C<File::Spec>. |
508 | |
509 | Any generated objects (subdirectories, files, parents, etc.) will also |
510 | retain this type. |
511 | |
512 | =item $foreign = Path::Class::Dir->new_foreign($type, @args) |
513 | |
514 | Returns a C<Path::Class::Dir> object representing C<$dir> as it would |
515 | be specified on a system of type C<$type>. Known types include |
516 | C<Unix>, C<Win32>, C<Mac>, C<VMS>, and C<OS2>, i.e. anything for which |
517 | there is a subclass of C<File::Spec>. |
518 | |
519 | The arguments in C<@args> are the same as they would be specified in |
520 | C<new()>. |
521 | |
522 | =item @list = $dir->dir_list([OFFSET, [LENGTH]]) |
523 | |
524 | Returns the list of strings internally representing this directory |
525 | structure. Each successive member of the list is understood to be an |
526 | entry in its predecessor's directory list. By contract, C<< |
527 | Path::Class->new( $dir->dir_list ) >> should be equivalent to C<$dir>. |
528 | |
529 | The semantics of this method are similar to Perl's C<splice> or |
530 | C<substr> functions; they return C<LENGTH> elements starting at |
531 | C<OFFSET>. If C<LENGTH> is omitted, returns all the elements starting |
532 | at C<OFFSET> up to the end of the list. If C<LENGTH> is negative, |
533 | returns the elements from C<OFFSET> onward except for C<-LENGTH> |
534 | elements at the end. If C<OFFSET> is negative, it counts backward |
535 | C<OFFSET> elements from the end of the list. If C<OFFSET> and |
536 | C<LENGTH> are both omitted, the entire list is returned. |
537 | |
538 | In a scalar context, C<dir_list()> with no arguments returns the |
539 | number of entries in the directory list; C<dir_list(OFFSET)> returns |
540 | the single element at that offset; C<dir_list(OFFSET, LENGTH)> returns |
541 | the final element that would have been returned in a list context. |
542 | |
543 | =item $fh = $dir->open() |
544 | |
545 | Passes C<$dir> to C<< IO::Dir->open >> and returns the result as an |
546 | C<IO::Dir> object. If the opening fails, C<undef> is returned and |
547 | C<$!> is set. |
548 | |
549 | =item $dir->mkpath($verbose, $mode) |
550 | |
551 | Passes all arguments, including C<$dir>, to C<< File::Path::mkpath() |
552 | >> and returns the result (a list of all directories created). |
553 | |
554 | =item $dir->rmtree($verbose, $cautious) |
555 | |
556 | Passes all arguments, including C<$dir>, to C<< File::Path::rmtree() |
557 | >> and returns the result (the number of files successfully deleted). |
558 | |
559 | =item $dir->remove() |
560 | |
561 | Removes the directory, which must be empty. Returns a boolean value |
562 | indicating whether or not the directory was successfully removed. |
563 | This method is mainly provided for consistency with |
564 | C<Path::Class::File>'s C<remove()> method. |
565 | |
566 | =item $dir_or_file = $dir->next() |
567 | |
568 | A convenient way to iterate through directory contents. The first |
569 | time C<next()> is called, it will C<open()> the directory and read the |
570 | first item from it, returning the result as a C<Path::Class::Dir> or |
571 | C<Path::Class::File> object (depending, of course, on its actual |
572 | type). Each subsequent call to C<next()> will simply iterate over the |
573 | directory's contents, until there are no more items in the directory, |
574 | and then the undefined value is returned. For example, to iterate |
575 | over all the regular files in a directory: |
576 | |
577 | while (my $file = $dir->next) { |
578 | next unless -f $file; |
579 | my $fh = $file->open('r') or die "Can't read $file: $!"; |
580 | ... |
581 | } |
582 | |
583 | If an error occurs when opening the directory (for instance, it |
584 | doesn't exist or isn't readable), C<next()> will throw an exception |
585 | with the value of C<$!>. |
586 | |
587 | =item $dir->recurse( callback => sub {...} ) |
588 | |
589 | Iterates through this directory and all of its children, and all of |
590 | its children's children, etc., calling the C<callback> subroutine for |
591 | each entry. This is a lot like what the C<File::Find> module does, |
592 | and of course C<File::Find> will work fine on C<Path::Class> objects, |
593 | but the advantage of the C<recurse()> method is that it will also feed |
594 | your callback routine C<Path::Class> objects rather than just pathname |
595 | strings. |
596 | |
597 | The C<recurse()> method requires a C<callback> parameter specifying |
598 | the subroutine to invoke for each entry. It will be passed the |
599 | C<Path::Class> object as its first argument. |
600 | |
601 | C<recurse()> also accepts two boolean parameters, C<depthfirst> and |
602 | C<preorder> that control the order of recursion. The default is a |
603 | preorder, breadth-first search, i.e. C<< depthfirst => 0, preorder => 1 >>. |
604 | At the time of this writing, all combinations of these two parameters |
605 | are supported I<except> C<< depthfirst => 0, preorder => 0 >>. |
606 | |
607 | =item $st = $file->stat() |
608 | |
609 | Invokes C<< File::stat::stat() >> on this directory and returns a |
610 | C<File::stat> object representing the result. |
611 | |
612 | =item $st = $file->lstat() |
613 | |
614 | Same as C<stat()>, but if C<$file> is a symbolic link, C<lstat()> |
615 | stats the link instead of the directory the link points to. |
616 | |
617 | =back |
618 | |
619 | =head1 AUTHOR |
620 | |
621 | Ken Williams, ken@mathforum.org |
622 | |
623 | =head1 SEE ALSO |
624 | |
625 | Path::Class, Path::Class::File, File::Spec |
626 | |
627 | =cut |