1 package File::Spec::Mac;
4 use vars qw(@ISA $VERSION);
5 require File::Spec::Unix;
9 @ISA = qw(File::Spec::Unix);
15 File::Spec::Mac - File::Spec for Mac OS (Classic)
19 require File::Spec::Mac; # Done internally by File::Spec if needed
23 Methods for manipulating file specifications.
31 On Mac OS, there's nothing to be done. Returns what it's given.
36 my ($self,$path) = @_;
42 Concatenate two or more directory names to form a path separated by colons
43 (":") ending with a directory. Resulting paths are B<relative> by default,
44 but can be forced to be absolute (but avoid this, see below). Automatically
45 puts a trailing ":" on the end of the complete path, because that's what's
46 done in MacPerl's environment and helps to distinguish a file path from a
49 B<IMPORTANT NOTE:> Beginning with version 1.3 of this module, the resulting
50 path is relative by default and I<not> absolute. This descision was made due
51 to portability reasons. Since C<File::Spec-E<gt>catdir()> returns relative paths
52 on all other operating systems, it will now also follow this convention on Mac
53 OS. Note that this may break some existing scripts.
55 The intended purpose of this routine is to concatenate I<directory names>.
56 But because of the nature of Macintosh paths, some additional possibilities
57 are allowed to make using this routine give reasonable results for some
58 common situations. In other words, you are also allowed to concatenate
59 I<paths> instead of directory names (strictly speaking, a string like ":a"
60 is a path, but not a name, since it contains a punctuation character ":").
65 catdir("a","b") = ":a:b:"
66 catdir() = "" (special case)
68 calls like the following
71 catdir(":a","b") = ":a:b:"
72 catdir(":a:","b") = ":a:b:"
73 catdir(":a:",":b:") = ":a:b:"
78 Here are the rules that are used in C<catdir()>; note that we try to be as
79 compatible as possible to Unix:
85 The resulting path is relative by default, i.e. the resulting path will have a
90 A trailing colon is added automatically to the resulting path, to denote a
95 Generally, each argument has one leading ":" and one trailing ":"
96 removed (if any). They are then joined together by a ":". Special
97 treatment applies for arguments denoting updir paths like "::lib:",
98 see (4), or arguments consisting solely of colons ("colon paths"),
103 When an updir path like ":::lib::" is passed as argument, the number
104 of directories to climb up is handled correctly, not removing leading
105 or trailing colons when necessary. E.g.
107 catdir(":::a","::b","c") = ":::a::b:c:"
108 catdir(":::a::","::b","c") = ":::a:::b:c:"
112 Adding a colon ":" or empty string "" to a path at I<any> position
113 doesn't alter the path, i.e. these arguments are ignored. (When a ""
114 is passed as the first argument, it has a special meaning, see
115 (6)). This way, a colon ":" is handled like a "." (curdir) on Unix,
116 while an empty string "" is generally ignored (see
117 C<Unix-E<gt>canonpath()> ). Likewise, a "::" is handled like a ".."
118 (updir), and a ":::" is handled like a "../.." etc. E.g.
120 catdir("a",":",":","b") = ":a:b:"
121 catdir("a",":","::",":b") = ":a::b:"
125 If the first argument is an empty string "" or is a volume name, i.e. matches
126 the pattern /^[^:]+:/, the resulting path is B<absolute>.
130 Passing an empty string "" as the first argument to C<catdir()> is
131 like passingC<File::Spec-E<gt>rootdir()> as the first argument, i.e.
133 catdir("","a","b") is the same as
135 catdir(rootdir(),"a","b").
137 This is true on Unix, where C<catdir("","a","b")> yields "/a/b" and
138 C<rootdir()> is "/". Note that C<rootdir()> on Mac OS is the startup
139 volume, which is the closest in concept to Unix' "/". This should help
140 to run existing scripts originally written for Unix.
144 For absolute paths, some cleanup is done, to ensure that the volume
145 name isn't immediately followed by updirs. This is invalid, because
146 this would go beyond "root". Generally, these cases are handled like
147 their Unix counterparts:
150 Unix->catdir("","") = "/"
151 Unix->catdir("",".") = "/"
152 Unix->catdir("","..") = "/" # can't go beyond root
153 Unix->catdir("",".","..","..","a") = "/a"
155 Mac->catdir("","") = rootdir() # (e.g. "HD:")
156 Mac->catdir("",":") = rootdir()
157 Mac->catdir("","::") = rootdir() # can't go beyond root
158 Mac->catdir("",":","::","::","a") = rootdir() . "a:" # (e.g. "HD:a:")
160 However, this approach is limited to the first arguments following
161 "root" (again, see C<Unix-E<gt>canonpath()> ). If there are more
162 arguments that move up the directory tree, an invalid path going
163 beyond root can be created.
167 As you've seen, you can force C<catdir()> to create an absolute path
168 by passing either an empty string or a path that begins with a volume
169 name as the first argument. However, you are strongly encouraged not
170 to do so, since this is done only for backward compatibility. Newer
171 versions of File::Spec come with a method called C<catpath()> (see
172 below), that is designed to offer a portable solution for the creation
173 of absolute paths. It takes volume, directory and file portions and
174 returns an entire path. While C<catdir()> is still suitable for the
175 concatenation of I<directory names>, you are encouraged to use
176 C<catpath()> to concatenate I<volume names> and I<directory
179 $dir = File::Spec->catdir("tmp","sources");
180 $abs_path = File::Spec->catpath("MacintoshHD:", $dir,"");
184 "MacintoshHD:tmp:sources:" .
195 # take care of the first argument
197 if ($args[0] eq '') { # absolute path, rootdir
200 $first_arg = $self->rootdir;
202 } elsif ($args[0] =~ /^[^:]+:/) { # absolute path, volume name
204 $first_arg = shift @args;
205 # add a trailing ':' if need be (may be it's a path like HD:dir)
206 $first_arg = "$first_arg:" unless ($first_arg =~ /:\Z(?!\n)/);
208 } else { # relative path
210 if ( $args[0] =~ /^::+\Z(?!\n)/ ) {
211 # updir colon path ('::', ':::' etc.), don't shift
213 } elsif ($args[0] eq ':') {
214 $first_arg = shift @args;
216 # add a trailing ':' if need be
217 $first_arg = shift @args;
218 $first_arg = "$first_arg:" unless ($first_arg =~ /:\Z(?!\n)/);
222 # For all other arguments,
223 # (a) ignore arguments that equal ':' or '',
224 # (b) handle updir paths specially:
225 # '::' -> concatenate '::'
226 # '::' . '::' -> concatenate ':::' etc.
227 # (c) add a trailing ':' if need be
229 my $result = $first_arg;
231 my $arg = shift @args;
232 unless (($arg eq '') || ($arg eq ':')) {
233 if ($arg =~ /^::+\Z(?!\n)/ ) { # updir colon path like ':::'
234 my $updir_count = length($arg) - 1;
235 while ((@args) && ($args[0] =~ /^::+\Z(?!\n)/) ) { # while updir colon path
237 $updir_count += (length($arg) - 1);
239 $arg = (':' x $updir_count);
241 $arg =~ s/^://s; # remove a leading ':' if any
242 $arg = "$arg:" unless ($arg =~ /:\Z(?!\n)/); # ensure trailing ':'
248 if ( ($relative) && ($result !~ /^:/) ) {
249 # add a leading colon if need be
250 $result = ":$result";
254 # remove updirs immediately following the volume name
255 $result =~ s/([^:]+:)(:*)(.*)\Z(?!\n)/$1$3/;
263 Concatenate one or more directory names and a filename to form a
264 complete path ending with a filename. Resulting paths are B<relative>
265 by default, but can be forced to be absolute (but avoid this).
267 B<IMPORTANT NOTE:> Beginning with version 1.3 of this module, the
268 resulting path is relative by default and I<not> absolute. This
269 descision was made due to portability reasons. Since
270 C<File::Spec-E<gt>catfile()> returns relative paths on all other
271 operating systems, it will now also follow this convention on Mac OS.
272 Note that this may break some existing scripts.
274 The last argument is always considered to be the file portion. Since
275 C<catfile()> uses C<catdir()> (see above) for the concatenation of the
276 directory portions (if any), the following with regard to relative and
277 absolute paths is true:
280 catfile("file") = "file"
284 catfile("","") = rootdir() # (e.g. "HD:")
285 catfile("","file") = rootdir() . file # (e.g. "HD:file")
286 catfile("HD:","file") = "HD:file"
288 This means that C<catdir()> is called only when there are two or more
289 arguments, as one might expect.
291 Note that the leading ":" is removed from the filename, so that
293 catfile("a","b","file") = ":a:b:file" and
295 catfile("a","b",":file") = ":a:b:file"
297 give the same answer.
299 To concatenate I<volume names>, I<directory paths> and I<filenames>,
300 you are encouraged to use C<catpath()> (see below).
308 return $file unless @_;
309 my $dir = $self->catdir(@_);
316 Returns a string representing the current directory. On Mac OS, this is ":".
326 Returns a string representing the null device. On Mac OS, this is "Dev:Null".
336 Returns a string representing the root directory. Under MacPerl,
337 returns the name of the startup volume, since that's the closest in
338 concept, although other volumes aren't rooted there. The name has a
339 trailing ":", because that's the correct specification for a volume
346 # There's no real root directory on Mac OS. The name of the startup
347 # volume is returned, since that's the closest in concept.
350 my $system = Mac::Files::FindFolder(&Mac::Files::kOnSystemDisk,
351 &Mac::Files::kSystemFolderType);
352 $system =~ s/:.*\Z(?!\n)/:/s;
358 Returns the contents of $ENV{TMPDIR}, if that directory exits or the current working
359 directory otherwise. Under MacPerl, $ENV{TMPDIR} will contain a path like
360 "MacintoshHD:Temporary Items:", which is a hidden directory on your startup volume.
366 return $tmpdir if defined $tmpdir;
367 $tmpdir = $ENV{TMPDIR} if -d $ENV{TMPDIR};
368 unless (defined($tmpdir)) {
376 Returns a string representing the parent directory. On Mac OS, this is "::".
384 =item file_name_is_absolute
386 Takes as argument a path and returns true, if it is an absolute path.
387 If the path has a leading ":", it's a relative path. Otherwise, it's an
388 absolute path, unless the path doesn't contain any colons, i.e. it's a name
389 like "a". In this particular case, the path is considered to be relative
390 (i.e. it is considered to be a filename). Use ":" in the appropriate place
391 in the path if you want to distinguish unambiguously. As a special case,
392 the filename '' is always considered to be absolute. Note that with version
393 1.2 of File::Spec::Mac, this does no longer consult the local filesystem.
397 File::Spec->file_name_is_absolute("a"); # false (relative)
398 File::Spec->file_name_is_absolute(":a:b:"); # false (relative)
399 File::Spec->file_name_is_absolute("MacintoshHD:"); # true (absolute)
400 File::Spec->file_name_is_absolute(""); # true (absolute)
405 sub file_name_is_absolute {
406 my ($self,$file) = @_;
408 return (! ($file =~ m/^:/s) );
409 } elsif ( $file eq '' ) {
412 return 0; # i.e. a file like "a"
418 Returns the null list for the MacPerl application, since the concept is
419 usually meaningless under Mac OS. But if you're using the MacPerl tool under
420 MPW, it gives back $ENV{Commands} suitably split, as is done in
421 :lib:ExtUtils:MM_Mac.pm.
427 # The concept is meaningless under the MacPerl application.
428 # Under MPW, it has a meaning.
430 return unless exists $ENV{Commands};
431 return split(/,/, $ENV{Commands});
436 ($volume,$directories,$file) = File::Spec->splitpath( $path );
437 ($volume,$directories,$file) = File::Spec->splitpath( $path, $no_file );
439 Splits a path in to volume, directory, and filename portions.
441 On Mac OS, assumes that the last part of the path is a filename unless
442 $no_file is true or a trailing separator ":" is present.
444 The volume portion is always returned with a trailing ":". The directory portion
445 is always returned with a leading (to denote a relative path) and a trailing ":"
446 (to denote a directory). The file portion is always returned I<without> a leading ":".
447 Empty portions are returned as empty string ''.
449 The results can be passed to C<catpath()> to get back a path equivalent to
450 (usually identical to) the original path.
456 my ($self,$path, $nofile) = @_;
457 my ($volume,$directory,$file);
460 ( $volume, $directory ) = $path =~ m|^((?:[^:]+:)?)(.*)|s;
473 $volume = '' unless defined($volume);
474 $directory = ":$directory" if ( $volume && $directory ); # take care of "HD::dir"
476 # Make sure non-empty directories begin and end in ':'
477 $directory .= ':' unless (substr($directory,-1) eq ':');
478 $directory = ":$directory" unless (substr($directory,0,1) eq ':');
482 $file = '' unless defined($file);
484 return ($volume,$directory,$file);
490 The opposite of C<catdir()>.
492 @dirs = File::Spec->splitdir( $directories );
494 $directories should be only the directory portion of the path on systems
495 that have the concept of a volume or that have path syntax that differentiates
496 files from directories. Consider using C<splitpath()> otherwise.
498 Unlike just splitting the directories on the separator, empty directory names
499 (C<"">) can be returned. Since C<catdir()> on Mac OS always appends a trailing
500 colon to distinguish a directory path from a file path, a single trailing colon
501 will be ignored, i.e. there's no empty directory name after it.
503 Hence, on Mac OS, both
505 File::Spec->splitdir( ":a:b::c:" ); and
506 File::Spec->splitdir( ":a:b::c" );
510 ( "a", "b", "::", "c")
514 File::Spec->splitdir( ":a:b::c::" );
518 ( "a", "b", "::", "c", "::")
524 my ($self, $path) = @_;
526 my ($head, $sep, $tail, $volume, $directories);
528 return ('') if ( (!defined($path)) || ($path eq '') );
529 return (':') if ($path eq ':');
531 ( $volume, $sep, $directories ) = $path =~ m|^((?:[^:]+:)?)(:*)(.*)|s;
533 # deprecated, but handle it correctly
535 push (@result, $volume);
539 while ($sep || $directories) {
540 if (length($sep) > 1) {
541 my $updir_count = length($sep) - 1;
542 for (my $i=0; $i<$updir_count; $i++) {
543 # push '::' updir_count times;
544 # simulate Unix '..' updirs
545 push (@result, '::');
550 ( $head, $sep, $tail ) = $directories =~ m|^((?:[^:]+)?)(:*)(.*)|s;
551 push (@result, $head);
552 $directories = $tail;
561 $path = File::Spec->catpath($volume,$directory,$file);
563 Takes volume, directory and file portions and returns an entire path. On Mac OS,
564 $volume, $directory and $file are concatenated. A ':' is inserted if need be. You
565 may pass an empty string for each portion. If all portions are empty, the empty
566 string is returned. If $volume is empty, the result will be a relative path,
567 beginning with a ':'. If $volume and $directory are empty, a leading ":" (if any)
568 is removed form $file and the remainder is returned. If $file is empty, the
569 resulting path will have a trailing ':'.
575 my ($self,$volume,$directory,$file) = @_;
577 if ( (! $volume) && (! $directory) ) {
578 $file =~ s/^:// if $file;
582 my $path = $volume; # may be ''
583 $path .= ':' unless (substr($path, -1) eq ':'); # ensure trailing ':'
586 $directory =~ s/^://; # remove leading ':' if any
588 $path .= ':' unless (substr($path, -1) eq ':'); # ensure trailing ':'
592 $file =~ s/^://; # remove leading ':' if any
601 Takes a destination path and an optional base path and returns a relative path
602 from the base path to the destination path:
604 $rel_path = File::Spec->abs2rel( $path ) ;
605 $rel_path = File::Spec->abs2rel( $path, $base ) ;
607 Note that both paths are assumed to have a notation that distinguishes a
608 directory path (with trailing ':') from a file path (without trailing ':').
610 If $base is not present or '', then the current working directory is used.
611 If $base is relative, then it is converted to absolute form using C<rel2abs()>.
612 This means that it is taken to be relative to the current working directory.
614 Since Mac OS has the concept of volumes, this assumes that both paths
615 are on the $destination volume, and ignores the $base volume (!).
617 If $base doesn't have a trailing colon, the last element of $base is
618 assumed to be a filename. This filename is ignored (!). Otherwise all path
619 components are assumed to be directories.
621 If $path is relative, it is converted to absolute form using C<rel2abs()>.
622 This means that it is taken to be relative to the current working directory.
624 Based on code written by Shigio Yamaguchi.
629 # maybe this should be done in canonpath() ?
630 sub _resolve_updirs {
634 # resolve any updirs, e.g. "HD:tmp::file" -> "HD:file"
636 $proceed = ($path =~ s/^(.*):[^:]+::(.*?)\z/$1:$2/);
644 my($self,$path,$base) = @_;
647 if ( ! $self->file_name_is_absolute( $path ) ) {
648 $path = $self->rel2abs( $path ) ;
651 # Figure out the effective $base and clean it up.
652 if ( !defined( $base ) || $base eq '' ) {
655 elsif ( ! $self->file_name_is_absolute( $base ) ) {
656 $base = $self->rel2abs( $base ) ;
657 $base = _resolve_updirs( $base ); # resolve updirs in $base
660 $base = _resolve_updirs( $base );
664 my ( $path_dirs, $path_file ) = ($self->splitpath( $path ))[1,2] ;
666 # ignore $base's volume and file
667 my $base_dirs = ($self->splitpath( $base ))[1] ;
669 # Now, remove all leading components that are the same
670 my @pathchunks = $self->splitdir( $path_dirs );
671 my @basechunks = $self->splitdir( $base_dirs );
673 while ( @pathchunks &&
675 lc( $pathchunks[0] ) eq lc( $basechunks[0] ) ) {
680 # @pathchunks now has the directories to descend in to.
681 # ensure relative path, even if @pathchunks is empty
682 $path_dirs = $self->catdir( ':', @pathchunks );
684 # @basechunks now contains the number of directories to climb out of.
685 $base_dirs = (':' x @basechunks) . ':' ;
687 return $self->catpath( '', $self->catdir( $base_dirs, $path_dirs ), $path_file ) ;
692 Converts a relative path to an absolute path:
694 $abs_path = File::Spec->rel2abs( $path ) ;
695 $abs_path = File::Spec->rel2abs( $path, $base ) ;
697 Note that both paths are assumed to have a notation that distinguishes a
698 directory path (with trailing ':') from a file path (without trailing ':').
700 If $base is not present or '', then $base is set to the current working
701 directory. If $base is relative, then it is converted to absolute form
702 using C<rel2abs()>. This means that it is taken to be relative to the
703 current working directory.
705 If $base doesn't have a trailing colon, the last element of $base is
706 assumed to be a filename. This filename is ignored (!). Otherwise all path
707 components are assumed to be directories.
709 If $path is already absolute, it is returned and $base is ignored.
711 Based on code written by Shigio Yamaguchi.
716 my ($self,$path,$base) = @_;
718 if ( ! $self->file_name_is_absolute($path) ) {
719 # Figure out the effective $base and clean it up.
720 if ( !defined( $base ) || $base eq '' ) {
723 elsif ( ! $self->file_name_is_absolute($base) ) {
724 $base = $self->rel2abs($base) ;
729 # igonore $path's volume
730 my ( $path_dirs, $path_file ) = ($self->splitpath($path))[1,2] ;
732 # ignore $base's file part
733 my ( $base_vol, $base_dirs, undef ) = $self->splitpath($base) ;
736 $path_dirs = ':' if ($path_dirs eq '');
737 $base_dirs =~ s/:$//; # remove trailing ':', if any
738 $base_dirs = $base_dirs . $path_dirs;
740 $path = $self->catpath( $base_vol, $base_dirs, $path_file );
750 See the authors list in I<File::Spec>. Mac OS support by Paul Schinder
751 <schinder@pobox.com> and Thomas Wegner <wegner_thomas@yahoo.com>.