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