Fix the doc for splitpath() in the File::Spec modules :
[p5sagit/p5-mst-13.2.git] / lib / File / Spec / Mac.pm
1 package File::Spec::Mac;
2
3 use strict;
4 use vars qw(@ISA $VERSION);
5 require File::Spec::Unix;
6
7 $VERSION = '1.3';
8
9 @ISA = qw(File::Spec::Unix);
10
11 use Cwd;
12 my $macfiles;
13 if ($^O eq 'MacOS') {
14         $macfiles = eval { require Mac::Files };
15 }
16
17 =head1 NAME
18
19 File::Spec::Mac - File::Spec for Mac OS (Classic)
20
21 =head1 SYNOPSIS
22
23  require File::Spec::Mac; # Done internally by File::Spec if needed
24
25 =head1 DESCRIPTION
26
27 Methods for manipulating file specifications.
28
29 =head1 METHODS
30
31 =over 2
32
33 =item canonpath
34
35 On Mac OS, there's nothing to be done. Returns what it's given.
36
37 =cut
38
39 sub canonpath {
40     my ($self,$path) = @_;
41     return $path;
42 }
43
44 =item catdir()
45
46 Concatenate two or more directory names to form a path separated by colons
47 (":") ending with a directory. Resulting paths are B<relative> by default,
48 but can be forced to be absolute (but avoid this, see below). Automatically
49 puts a trailing ":" on the end of the complete path, because that's what's
50 done in MacPerl's environment and helps to distinguish a file path from a
51 directory path.
52
53 B<IMPORTANT NOTE:> Beginning with version 1.3 of this module, the resulting
54 path is relative by default and I<not> absolute. This descision was made due
55 to portability reasons. Since C<File::Spec-E<gt>catdir()> returns relative paths
56 on all other operating systems, it will now also follow this convention on Mac
57 OS. Note that this may break some existing scripts.
58
59 The intended purpose of this routine is to concatenate I<directory names>.
60 But because of the nature of Macintosh paths, some additional possibilities
61 are allowed to make using this routine give reasonable results for some
62 common situations. In other words, you are also allowed to concatenate
63 I<paths> instead of directory names (strictly speaking, a string like ":a"
64 is a path, but not a name, since it contains a punctuation character ":").
65
66 So, beside calls like
67
68     catdir("a") = ":a:"
69     catdir("a","b") = ":a:b:"
70     catdir() = ""                    (special case)
71
72 calls like the following
73
74     catdir(":a:") = ":a:"
75     catdir(":a","b") = ":a:b:"
76     catdir(":a:","b") = ":a:b:"
77     catdir(":a:",":b:") = ":a:b:"
78     catdir(":") = ":"
79
80 are allowed.
81
82 Here are the rules that are used in C<catdir()>; note that we try to be as
83 compatible as possible to Unix:
84
85 =over 2
86
87 =item 1.
88
89 The resulting path is relative by default, i.e. the resulting path will have a
90 leading colon.
91
92 =item 2.
93
94 A trailing colon is added automatically to the resulting path, to denote a
95 directory.
96
97 =item 3.
98
99 Generally, each argument has one leading ":" and one trailing ":"
100 removed (if any). They are then joined together by a ":". Special
101 treatment applies for arguments denoting updir paths like "::lib:",
102 see (4), or arguments consisting solely of colons ("colon paths"),
103 see (5).
104
105 =item 4.
106
107 When an updir path like ":::lib::" is passed as argument, the number
108 of directories to climb up is handled correctly, not removing leading
109 or trailing colons when necessary. E.g.
110
111     catdir(":::a","::b","c")    = ":::a::b:c:"
112     catdir(":::a::","::b","c")  = ":::a:::b:c:"
113
114 =item 5.
115
116 Adding a colon ":" or empty string "" to a path at I<any> position
117 doesn't alter the path, i.e. these arguments are ignored. (When a ""
118 is passed as the first argument, it has a special meaning, see
119 (6)). This way, a colon ":" is handled like a "." (curdir) on Unix,
120 while an empty string "" is generally ignored (see
121 C<Unix-E<gt>canonpath()> ). Likewise, a "::" is handled like a ".."
122 (updir), and a ":::" is handled like a "../.." etc.  E.g.
123
124     catdir("a",":",":","b")   = ":a:b:"
125     catdir("a",":","::",":b") = ":a::b:"
126
127 =item 6.
128
129 If the first argument is an empty string "" or is a volume name, i.e. matches
130 the pattern /^[^:]+:/, the resulting path is B<absolute>.
131
132 =item 7.
133
134 Passing an empty string "" as the first argument to C<catdir()> is
135 like passingC<File::Spec-E<gt>rootdir()> as the first argument, i.e.
136
137     catdir("","a","b")          is the same as
138
139     catdir(rootdir(),"a","b").
140
141 This is true on Unix, where C<catdir("","a","b")> yields "/a/b" and
142 C<rootdir()> is "/". Note that C<rootdir()> on Mac OS is the startup
143 volume, which is the closest in concept to Unix' "/". This should help
144 to run existing scripts originally written for Unix.
145
146 =item 8.
147
148 For absolute paths, some cleanup is done, to ensure that the volume
149 name isn't immediately followed by updirs. This is invalid, because
150 this would go beyond "root". Generally, these cases are handled like
151 their Unix counterparts:
152
153  Unix:
154     Unix->catdir("","")                 =  "/"
155     Unix->catdir("",".")                =  "/"
156     Unix->catdir("","..")               =  "/"              # can't go beyond root
157     Unix->catdir("",".","..","..","a")  =  "/a"
158  Mac:
159     Mac->catdir("","")                  =  rootdir()         # (e.g. "HD:")
160     Mac->catdir("",":")                 =  rootdir()
161     Mac->catdir("","::")                =  rootdir()         # can't go beyond root
162     Mac->catdir("",":","::","::","a")   =  rootdir() . "a:"  # (e.g. "HD:a:")
163
164 However, this approach is limited to the first arguments following
165 "root" (again, see C<Unix-E<gt>canonpath()> ). If there are more
166 arguments that move up the directory tree, an invalid path going
167 beyond root can be created.
168
169 =back
170
171 As you've seen, you can force C<catdir()> to create an absolute path
172 by passing either an empty string or a path that begins with a volume
173 name as the first argument. However, you are strongly encouraged not
174 to do so, since this is done only for backward compatibility. Newer
175 versions of File::Spec come with a method called C<catpath()> (see
176 below), that is designed to offer a portable solution for the creation
177 of absolute paths.  It takes volume, directory and file portions and
178 returns an entire path. While C<catdir()> is still suitable for the
179 concatenation of I<directory names>, you are encouraged to use
180 C<catpath()> to concatenate I<volume names> and I<directory
181 paths>. E.g.
182
183     $dir      = File::Spec->catdir("tmp","sources");
184     $abs_path = File::Spec->catpath("MacintoshHD:", $dir,"");
185
186 yields
187
188     "MacintoshHD:tmp:sources:" .
189
190 =cut
191
192 sub catdir {
193         my $self = shift;
194         return '' unless @_;
195         my @args = @_;
196         my $first_arg;
197         my $relative;
198
199         # take care of the first argument
200
201         if ($args[0] eq '')  { # absolute path, rootdir
202                 shift @args;
203                 $relative = 0;
204                 $first_arg = $self->rootdir;
205
206         } elsif ($args[0] =~ /^[^:]+:/) { # absolute path, volume name
207                 $relative = 0;
208                 $first_arg = shift @args;
209                 # add a trailing ':' if need be (may be it's a path like HD:dir)
210                 $first_arg = "$first_arg:" unless ($first_arg =~ /:\Z(?!\n)/);
211
212         } else { # relative path
213                 $relative = 1;
214                 if ( $args[0] =~ /^::+\Z(?!\n)/ ) {
215                         # updir colon path ('::', ':::' etc.), don't shift
216                         $first_arg = ':';
217                 } elsif ($args[0] eq ':') {
218                         $first_arg = shift @args;
219                 } else {
220                         # add a trailing ':' if need be
221                         $first_arg = shift @args;
222                         $first_arg = "$first_arg:" unless ($first_arg =~ /:\Z(?!\n)/);
223                 }
224         }
225
226         # For all other arguments,
227         # (a) ignore arguments that equal ':' or '',
228         # (b) handle updir paths specially:
229         #     '::'                      -> concatenate '::'
230         #     '::' . '::'       -> concatenate ':::' etc.
231         # (c) add a trailing ':' if need be
232
233         my $result = $first_arg;
234         while (@args) {
235                 my $arg = shift @args;
236                 unless (($arg eq '') || ($arg eq ':')) {
237                         if ($arg =~ /^::+\Z(?!\n)/ ) { # updir colon path like ':::'
238                                 my $updir_count = length($arg) - 1;
239                                 while ((@args) && ($args[0] =~ /^::+\Z(?!\n)/) ) { # while updir colon path
240                                         $arg = shift @args;
241                                         $updir_count += (length($arg) - 1);
242                                 }
243                                 $arg = (':' x $updir_count);
244                         } else {
245                                 $arg =~ s/^://s; # remove a leading ':' if any
246                                 $arg = "$arg:" unless ($arg =~ /:\Z(?!\n)/); # ensure trailing ':'
247                         }
248                         $result .= $arg;
249                 }#unless
250         }
251
252         if ( ($relative) && ($result !~ /^:/) ) {
253                 # add a leading colon if need be
254                 $result = ":$result";
255         }
256
257         unless ($relative) {
258                 # remove updirs immediately following the volume name
259                 $result =~ s/([^:]+:)(:*)(.*)\Z(?!\n)/$1$3/;
260         }
261
262         return $result;
263 }
264
265 =item catfile
266
267 Concatenate one or more directory names and a filename to form a
268 complete path ending with a filename. Resulting paths are B<relative>
269 by default, but can be forced to be absolute (but avoid this).
270
271 B<IMPORTANT NOTE:> Beginning with version 1.3 of this module, the
272 resulting path is relative by default and I<not> absolute. This
273 descision was made due to portability reasons. Since
274 C<File::Spec-E<gt>catfile()> returns relative paths on all other
275 operating systems, it will now also follow this convention on Mac OS.
276 Note that this may break some existing scripts.
277
278 The last argument is always considered to be the file portion. Since
279 C<catfile()> uses C<catdir()> (see above) for the concatenation of the
280 directory portions (if any), the following with regard to relative and
281 absolute paths is true:
282
283     catfile("")     = ""
284     catfile("file") = "file"
285
286 but
287
288     catfile("","")        = rootdir()         # (e.g. "HD:")
289     catfile("","file")    = rootdir() . file  # (e.g. "HD:file")
290     catfile("HD:","file") = "HD:file"
291
292 This means that C<catdir()> is called only when there are two or more
293 arguments, as one might expect.
294
295 Note that the leading ":" is removed from the filename, so that
296
297     catfile("a","b","file")  = ":a:b:file"    and
298
299     catfile("a","b",":file") = ":a:b:file"
300
301 give the same answer.
302
303 To concatenate I<volume names>, I<directory paths> and I<filenames>,
304 you are encouraged to use C<catpath()> (see below).
305
306 =cut
307
308 sub catfile {
309     my $self = shift;
310     return '' unless @_;
311     my $file = pop @_;
312     return $file unless @_;
313     my $dir = $self->catdir(@_);
314     $file =~ s/^://s;
315     return $dir.$file;
316 }
317
318 =item curdir
319
320 Returns a string representing the current directory. On Mac OS, this is ":".
321
322 =cut
323
324 sub curdir {
325     return ":";
326 }
327
328 =item devnull
329
330 Returns a string representing the null device. On Mac OS, this is "Dev:Null".
331
332 =cut
333
334 sub devnull {
335     return "Dev:Null";
336 }
337
338 =item rootdir
339
340 Returns a string representing the root directory.  Under MacPerl,
341 returns the name of the startup volume, since that's the closest in
342 concept, although other volumes aren't rooted there. The name has a
343 trailing ":", because that's the correct specification for a volume
344 name on Mac OS.
345
346 If Mac::Files could not be loaded, the empty string is returned.
347
348 =cut
349
350 sub rootdir {
351 #
352 #  There's no real root directory on Mac OS. The name of the startup
353 #  volume is returned, since that's the closest in concept.
354 #
355     return '' unless $macfiles;
356     my $system = Mac::Files::FindFolder(&Mac::Files::kOnSystemDisk,
357         &Mac::Files::kSystemFolderType);
358     $system =~ s/:.*\Z(?!\n)/:/s;
359     return $system;
360 }
361
362 =item tmpdir
363
364 Returns the contents of $ENV{TMPDIR}, if that directory exits or the current working
365 directory otherwise. Under MacPerl, $ENV{TMPDIR} will contain a path like
366 "MacintoshHD:Temporary Items:", which is a hidden directory on your startup volume.
367
368 =cut
369
370 my $tmpdir;
371 sub tmpdir {
372     return $tmpdir if defined $tmpdir;
373     $tmpdir = $ENV{TMPDIR} if -d $ENV{TMPDIR};
374     unless (defined($tmpdir)) {
375         $tmpdir = cwd();
376     }
377     return $tmpdir;
378 }
379
380 =item updir
381
382 Returns a string representing the parent directory. On Mac OS, this is "::".
383
384 =cut
385
386 sub updir {
387     return "::";
388 }
389
390 =item file_name_is_absolute
391
392 Takes as argument a path and returns true, if it is an absolute path.
393 If the path has a leading ":", it's a relative path. Otherwise, it's an
394 absolute path, unless the path doesn't contain any colons, i.e. it's a name
395 like "a". In this particular case, the path is considered to be relative
396 (i.e. it is considered to be a filename). Use ":" in the appropriate place
397 in the path if you want to distinguish unambiguously. As a special case,
398 the filename '' is always considered to be absolute. Note that with version
399 1.2 of File::Spec::Mac, this does no longer consult the local filesystem.
400
401 E.g.
402
403     File::Spec->file_name_is_absolute("a");             # false (relative)
404     File::Spec->file_name_is_absolute(":a:b:");         # false (relative)
405     File::Spec->file_name_is_absolute("MacintoshHD:");  # true (absolute)
406     File::Spec->file_name_is_absolute("");              # true (absolute)
407
408
409 =cut
410
411 sub file_name_is_absolute {
412     my ($self,$file) = @_;
413     if ($file =~ /:/) {
414         return (! ($file =~ m/^:/s) );
415     } elsif ( $file eq '' ) {
416         return 1 ;
417     } else {
418         return 0; # i.e. a file like "a"
419     }
420 }
421
422 =item path
423
424 Returns the null list for the MacPerl application, since the concept is
425 usually meaningless under Mac OS. But if you're using the MacPerl tool under
426 MPW, it gives back $ENV{Commands} suitably split, as is done in
427 :lib:ExtUtils:MM_Mac.pm.
428
429 =cut
430
431 sub path {
432 #
433 #  The concept is meaningless under the MacPerl application.
434 #  Under MPW, it has a meaning.
435 #
436     return unless exists $ENV{Commands};
437     return split(/,/, $ENV{Commands});
438 }
439
440 =item splitpath
441
442     ($volume,$directories,$file) = File::Spec->splitpath( $path );
443     ($volume,$directories,$file) = File::Spec->splitpath( $path, $no_file );
444
445 Splits a path into volume, directory, and filename portions.
446
447 On Mac OS, assumes that the last part of the path is a filename unless
448 $no_file is true or a trailing separator ":" is present.
449
450 The volume portion is always returned with a trailing ":". The directory portion
451 is always returned with a leading (to denote a relative path) and a trailing ":"
452 (to denote a directory). The file portion is always returned I<without> a leading ":".
453 Empty portions are returned as empty string ''.
454
455 The results can be passed to C<catpath()> to get back a path equivalent to
456 (usually identical to) the original path.
457
458
459 =cut
460
461 sub splitpath {
462     my ($self,$path, $nofile) = @_;
463     my ($volume,$directory,$file);
464
465     if ( $nofile ) {
466         ( $volume, $directory ) = $path =~ m|^((?:[^:]+:)?)(.*)|s;
467     }
468     else {
469         $path =~
470             m|^( (?: [^:]+: )? )
471                ( (?: .*: )? )
472                ( .* )
473              |xs;
474         $volume    = $1;
475         $directory = $2;
476         $file      = $3;
477     }
478
479     $volume = '' unless defined($volume);
480         $directory = ":$directory" if ( $volume && $directory ); # take care of "HD::dir"
481     if ($directory) {
482         # Make sure non-empty directories begin and end in ':'
483         $directory .= ':' unless (substr($directory,-1) eq ':');
484         $directory = ":$directory" unless (substr($directory,0,1) eq ':');
485     } else {
486         $directory = '';
487     }
488     $file = '' unless defined($file);
489
490     return ($volume,$directory,$file);
491 }
492
493
494 =item splitdir
495
496 The opposite of C<catdir()>.
497
498     @dirs = File::Spec->splitdir( $directories );
499
500 $directories should be only the directory portion of the path on systems
501 that have the concept of a volume or that have path syntax that differentiates
502 files from directories. Consider using C<splitpath()> otherwise.
503
504 Unlike just splitting the directories on the separator, empty directory names
505 (C<"">) can be returned. Since C<catdir()> on Mac OS always appends a trailing
506 colon to distinguish a directory path from a file path, a single trailing colon
507 will be ignored, i.e. there's no empty directory name after it.
508
509 Hence, on Mac OS, both
510
511     File::Spec->splitdir( ":a:b::c:" );    and
512     File::Spec->splitdir( ":a:b::c" );
513
514 yield:
515
516     ( "a", "b", "::", "c")
517
518 while
519
520     File::Spec->splitdir( ":a:b::c::" );
521
522 yields:
523
524     ( "a", "b", "::", "c", "::")
525
526
527 =cut
528
529 sub splitdir {
530         my ($self, $path) = @_;
531         my @result = ();
532         my ($head, $sep, $tail, $volume, $directories);
533
534         return ('') if ( (!defined($path)) || ($path eq '') );
535         return (':') if ($path eq ':');
536
537         ( $volume, $sep, $directories ) = $path =~ m|^((?:[^:]+:)?)(:*)(.*)|s;
538
539         # deprecated, but handle it correctly
540         if ($volume) {
541                 push (@result, $volume);
542                 $sep .= ':';
543         }
544
545         while ($sep || $directories) {
546                 if (length($sep) > 1) {
547                         my $updir_count = length($sep) - 1;
548                         for (my $i=0; $i<$updir_count; $i++) {
549                                 # push '::' updir_count times;
550                                 # simulate Unix '..' updirs
551                                 push (@result, '::');
552                         }
553                 }
554                 $sep = '';
555                 if ($directories) {
556                         ( $head, $sep, $tail ) = $directories =~ m|^((?:[^:]+)?)(:*)(.*)|s;
557                         push (@result, $head);
558                         $directories = $tail;
559                 }
560         }
561         return @result;
562 }
563
564
565 =item catpath
566
567     $path = File::Spec->catpath($volume,$directory,$file);
568
569 Takes volume, directory and file portions and returns an entire path. On Mac OS,
570 $volume, $directory and $file are concatenated.  A ':' is inserted if need be. You
571 may pass an empty string for each portion. If all portions are empty, the empty
572 string is returned. If $volume is empty, the result will be a relative path,
573 beginning with a ':'. If $volume and $directory are empty, a leading ":" (if any)
574 is removed form $file and the remainder is returned. If $file is empty, the
575 resulting path will have a trailing ':'.
576
577
578 =cut
579
580 sub catpath {
581     my ($self,$volume,$directory,$file) = @_;
582
583     if ( (! $volume) && (! $directory) ) {
584         $file =~ s/^:// if $file;
585         return $file ;
586     }
587
588     my $path = $volume; # may be ''
589     $path .= ':' unless (substr($path, -1) eq ':'); # ensure trailing ':'
590
591     if ($directory) {
592         $directory =~ s/^://; # remove leading ':' if any
593         $path .= $directory;
594         $path .= ':' unless (substr($path, -1) eq ':'); # ensure trailing ':'
595     }
596
597     if ($file) {
598         $file =~ s/^://; # remove leading ':' if any
599         $path .= $file;
600     }
601
602     return $path;
603 }
604
605 =item abs2rel
606
607 Takes a destination path and an optional base path and returns a relative path
608 from the base path to the destination path:
609
610     $rel_path = File::Spec->abs2rel( $path ) ;
611     $rel_path = File::Spec->abs2rel( $path, $base ) ;
612
613 Note that both paths are assumed to have a notation that distinguishes a
614 directory path (with trailing ':') from a file path (without trailing ':').
615
616 If $base is not present or '', then the current working directory is used.
617 If $base is relative, then it is converted to absolute form using C<rel2abs()>.
618 This means that it is taken to be relative to the current working directory.
619
620 Since Mac OS has the concept of volumes, this assumes that both paths
621 are on the $destination volume, and ignores the $base volume (!).
622
623 If $base doesn't have a trailing colon, the last element of $base is
624 assumed to be a filename. This filename is ignored (!). Otherwise all path
625 components are assumed to be directories.
626
627 If $path is relative, it is converted to absolute form using C<rel2abs()>.
628 This means that it is taken to be relative to the current working directory.
629
630 Based on code written by Shigio Yamaguchi.
631
632
633 =cut
634
635 # maybe this should be done in canonpath() ?
636 sub _resolve_updirs {
637         my $path = shift @_;
638         my $proceed;
639
640         # resolve any updirs, e.g. "HD:tmp::file" -> "HD:file"
641         do {
642                 $proceed = ($path =~ s/^(.*):[^:]+::(.*?)\z/$1:$2/);
643         } while ($proceed);
644
645         return $path;
646 }
647
648
649 sub abs2rel {
650     my($self,$path,$base) = @_;
651
652     # Clean up $path
653     if ( ! $self->file_name_is_absolute( $path ) ) {
654         $path = $self->rel2abs( $path ) ;
655     }
656
657     # Figure out the effective $base and clean it up.
658     if ( !defined( $base ) || $base eq '' ) {
659         $base = cwd();
660     }
661     elsif ( ! $self->file_name_is_absolute( $base ) ) {
662         $base = $self->rel2abs( $base ) ;
663         $base = _resolve_updirs( $base ); # resolve updirs in $base
664     }
665     else {
666         $base = _resolve_updirs( $base );
667     }
668
669     # Split up paths
670     my ( $path_dirs, $path_file ) =  ($self->splitpath( $path ))[1,2] ;
671
672     # ignore $base's volume and file
673     my $base_dirs = ($self->splitpath( $base ))[1] ;
674
675     # Now, remove all leading components that are the same
676     my @pathchunks = $self->splitdir( $path_dirs );
677     my @basechunks = $self->splitdir( $base_dirs );
678         
679     while ( @pathchunks &&
680             @basechunks &&
681             lc( $pathchunks[0] ) eq lc( $basechunks[0] ) ) {
682         shift @pathchunks ;
683         shift @basechunks ;
684     }
685
686     # @pathchunks now has the directories to descend in to.
687     # ensure relative path, even if @pathchunks is empty
688     $path_dirs = $self->catdir( ':', @pathchunks );
689
690     # @basechunks now contains the number of directories to climb out of.
691     $base_dirs = (':' x @basechunks) . ':' ;
692
693     return $self->catpath( '', $self->catdir( $base_dirs, $path_dirs ), $path_file ) ;
694 }
695
696 =item rel2abs
697
698 Converts a relative path to an absolute path:
699
700     $abs_path = File::Spec->rel2abs( $path ) ;
701     $abs_path = File::Spec->rel2abs( $path, $base ) ;
702
703 Note that both paths are assumed to have a notation that distinguishes a
704 directory path (with trailing ':') from a file path (without trailing ':').
705
706 If $base is not present or '', then $base is set to the current working
707 directory. If $base is relative, then it is converted to absolute form
708 using C<rel2abs()>. This means that it is taken to be relative to the
709 current working directory.
710
711 If $base doesn't have a trailing colon, the last element of $base is
712 assumed to be a filename. This filename is ignored (!). Otherwise all path
713 components are assumed to be directories.
714
715 If $path is already absolute, it is returned and $base is ignored.
716
717 Based on code written by Shigio Yamaguchi.
718
719 =cut
720
721 sub rel2abs {
722     my ($self,$path,$base) = @_;
723
724     if ( ! $self->file_name_is_absolute($path) ) {
725         # Figure out the effective $base and clean it up.
726         if ( !defined( $base ) || $base eq '' ) {
727             $base = cwd();
728         }
729         elsif ( ! $self->file_name_is_absolute($base) ) {
730             $base = $self->rel2abs($base) ;
731         }
732
733         # Split up paths
734
735         # igonore $path's volume
736         my ( $path_dirs, $path_file ) = ($self->splitpath($path))[1,2] ;
737
738         # ignore $base's file part
739         my ( $base_vol, $base_dirs, undef ) = $self->splitpath($base) ;
740
741         # Glom them together
742         $path_dirs = ':' if ($path_dirs eq '');
743         $base_dirs =~ s/:$//; # remove trailing ':', if any
744         $base_dirs = $base_dirs . $path_dirs;
745
746         $path = $self->catpath( $base_vol, $base_dirs, $path_file );
747     }
748     return $path;
749 }
750
751
752 =back
753
754 =head1 AUTHORS
755
756 See the authors list in I<File::Spec>. Mac OS support by Paul Schinder
757 <schinder@pobox.com> and Thomas Wegner <wegner_thomas@yahoo.com>.
758
759
760 =head1 SEE ALSO
761
762 L<File::Spec>
763
764 =cut
765
766 1;