File::Spec bugs (spotted by Hack Kampbjorn <hack@hackdata.com>)
[p5sagit/p5-mst-13.2.git] / lib / File / Spec / Win32.pm
CommitLineData
270d1e39 1package File::Spec::Win32;
2
cbc7acb0 3use strict;
c27914c9 4use Cwd;
cbc7acb0 5use vars qw(@ISA);
6require File::Spec::Unix;
7@ISA = qw(File::Spec::Unix);
8
270d1e39 9=head1 NAME
10
11File::Spec::Win32 - methods for Win32 file specs
12
13=head1 SYNOPSIS
14
cbc7acb0 15 require File::Spec::Win32; # Done internally by File::Spec if needed
270d1e39 16
17=head1 DESCRIPTION
18
19See File::Spec::Unix for a documentation of the methods provided
20there. This package overrides the implementation of these methods, not
21the semantics.
22
23=over
24
cbc7acb0 25=item devnull
270d1e39 26
cbc7acb0 27Returns a string representation of the null device.
270d1e39 28
cbc7acb0 29=cut
270d1e39 30
cbc7acb0 31sub devnull {
32 return "nul";
33}
270d1e39 34
cbc7acb0 35=item tmpdir
270d1e39 36
cbc7acb0 37Returns a string representation of the first existing directory
38from the following list:
270d1e39 39
cbc7acb0 40 $ENV{TMPDIR}
41 $ENV{TEMP}
42 $ENV{TMP}
43 /tmp
44 /
45
46=cut
270d1e39 47
cbc7acb0 48my $tmpdir;
49sub tmpdir {
50 return $tmpdir if defined $tmpdir;
270d1e39 51 my $self = shift;
cbc7acb0 52 foreach (@ENV{qw(TMPDIR TEMP TMP)}, qw(/tmp /)) {
53 next unless defined && -d;
54 $tmpdir = $_;
55 last;
270d1e39 56 }
cbc7acb0 57 $tmpdir = '' unless defined $tmpdir;
58 $tmpdir = $self->canonpath($tmpdir);
59 return $tmpdir;
60}
61
46726cbe 62sub case_tolerant {
63 return 1;
64}
65
cbc7acb0 66sub file_name_is_absolute {
67 my ($self,$file) = @_;
1b1e14d3 68 return scalar($file =~ m{^([a-z]:)?[\\/]}is);
270d1e39 69}
70
71=item catfile
72
73Concatenate one or more directory names and a filename to form a
74complete path ending with a filename
75
76=cut
77
78sub catfile {
cbc7acb0 79 my $self = shift;
270d1e39 80 my $file = pop @_;
81 return $file unless @_;
82 my $dir = $self->catdir(@_);
cbc7acb0 83 $dir .= "\\" unless substr($dir,-1) eq "\\";
270d1e39 84 return $dir.$file;
85}
86
87sub path {
270d1e39 88 my $path = $ENV{'PATH'} || $ENV{'Path'} || $ENV{'path'};
89 my @path = split(';',$path);
cbc7acb0 90 foreach (@path) { $_ = '.' if $_ eq '' }
91 return @path;
270d1e39 92}
93
94=item canonpath
95
96No physical check on the filesystem, but a logical cleanup of a
97path. On UNIX eliminated successive slashes and successive "/.".
98
99=cut
100
101sub canonpath {
0994714a 102 my ($self,$path) = @_;
1b1e14d3 103 $path =~ s/^([a-z]:)/\u$1/s;
270d1e39 104 $path =~ s|/|\\|g;
f505c983 105 $path =~ s|([^\\])\\+|$1\\|g; # xx////xx -> xx/xx
cbc7acb0 106 $path =~ s|(\\\.)+\\|\\|g; # xx/././xx -> xx/xx
1b1e14d3 107 $path =~ s|^(\.\\)+||s unless $path eq ".\\"; # ./xx -> xx
108 $path =~ s|\\\z||
109 unless $path =~ m#^([A-Z]:)?\\\z#s; # xx/ -> xx
cbc7acb0 110 return $path;
270d1e39 111}
112
c27914c9 113=item splitpath
114
115 ($volume,$directories,$file) = File::Spec->splitpath( $path );
116 ($volume,$directories,$file) = File::Spec->splitpath( $path, $no_file );
117
118Splits a path in to volume, directory, and filename portions. Assumes that
119the last file is a path unless the path ends in '\\', '\\.', '\\..'
120or $no_file is true. On Win32 this means that $no_file true makes this return
121( $volume, $path, undef ).
122
123Separators accepted are \ and /.
124
125Volumes can be drive letters or UNC sharenames (\\server\share).
126
0994714a 127The results can be passed to L</catpath> to get back a path equivalent to
c27914c9 128(usually identical to) the original path.
129
130=cut
131
132sub splitpath {
133 my ($self,$path, $nofile) = @_;
134 my ($volume,$directory,$file) = ('','','');
135 if ( $nofile ) {
136 $path =~
0994714a 137 m{^( (?:[a-zA-Z]:|(?:\\\\|//)[^\\/]+[\\/][^\\/]+)? )
c27914c9 138 (.*)
1b1e14d3 139 }xs;
c27914c9 140 $volume = $1;
141 $directory = $2;
142 }
143 else {
144 $path =~
0994714a 145 m{^ ( (?: [a-zA-Z]: |
146 (?:\\\\|//)[^\\/]+[\\/][^\\/]+
c27914c9 147 )?
148 )
1b1e14d3 149 ( (?:.*[\\\\/](?:\.\.?\z)?)? )
c27914c9 150 (.*)
1b1e14d3 151 }xs;
c27914c9 152 $volume = $1;
153 $directory = $2;
154 $file = $3;
155 }
156
157 return ($volume,$directory,$file);
158}
159
160
161=item splitdir
162
163The opposite of L</catdir()>.
164
165 @dirs = File::Spec->splitdir( $directories );
166
167$directories must be only the directory portion of the path on systems
168that have the concept of a volume or that have path syntax that differentiates
169files from directories.
170
171Unlike just splitting the directories on the separator, leading empty and
172trailing directory entries can be returned, because these are significant
173on some OSs. So,
174
175 File::Spec->splitdir( "/a/b/c" );
176
177Yields:
178
179 ( '', 'a', 'b', '', 'c', '' )
180
181=cut
182
183sub splitdir {
184 my ($self,$directories) = @_ ;
185 #
186 # split() likes to forget about trailing null fields, so here we
187 # check to be sure that there will not be any before handling the
188 # simple case.
189 #
1b1e14d3 190 if ( $directories !~ m|[\\/]\z| ) {
c27914c9 191 return split( m|[\\/]|, $directories );
192 }
193 else {
194 #
195 # since there was a trailing separator, add a file name to the end,
196 # then do the split, then replace it with ''.
197 #
198 my( @directories )= split( m|[\\/]|, "${directories}dummy" ) ;
199 $directories[ $#directories ]= '' ;
200 return @directories ;
201 }
202}
203
204
205=item catpath
206
207Takes volume, directory and file portions and returns an entire path. Under
208Unix, $volume is ignored, and this is just like catfile(). On other OSs,
209the $volume become significant.
210
211=cut
212
213sub catpath {
214 my ($self,$volume,$directory,$file) = @_;
215
216 # If it's UNC, make sure the glue separator is there, reusing
217 # whatever separator is first in the $volume
218 $volume .= $1
1b1e14d3 219 if ( $volume =~ m@^([\\/])[\\/][^\\/]+[\\/][^\\/]+\z@s &&
220 $directory =~ m@^[^\\/]@s
c27914c9 221 ) ;
222
223 $volume .= $directory ;
224
225 # If the volume is not just A:, make sure the glue separator is
226 # there, reusing whatever separator is first in the $volume if possible.
1b1e14d3 227 if ( $volume !~ m@^[a-zA-Z]:\z@s &&
228 $volume =~ m@[^\\/]\z@ &&
0994714a 229 $file =~ m@[^\\/]@
c27914c9 230 ) {
231 $volume =~ m@([\\/])@ ;
232 my $sep = $1 ? $1 : '\\' ;
233 $volume .= $sep ;
234 }
235
236 $volume .= $file ;
237
238 return $volume ;
239}
240
241
242=item abs2rel
243
244Takes a destination path and an optional base path returns a relative path
245from the base path to the destination path:
246
247 $rel_path = File::Spec->abs2rel( $destination ) ;
248 $rel_path = File::Spec->abs2rel( $destination, $base ) ;
249
250If $base is not present or '', then L</cwd()> is used. If $base is relative,
251then it is converted to absolute form using L</rel2abs()>. This means that it
252is taken to be relative to L<cwd()>.
253
254On systems with the concept of a volume, this assumes that both paths
0994714a 255are on the $destination volume, and ignores the $base volume.
c27914c9 256
257On systems that have a grammar that indicates filenames, this ignores the
258$base filename as well. Otherwise all path components are assumed to be
259directories.
260
261If $path is relative, it is converted to absolute form using L</rel2abs()>.
262This means that it is taken to be relative to L</cwd()>.
263
264Based on code written by Shigio Yamaguchi.
265
266No checks against the filesystem are made.
267
268=cut
269
270sub abs2rel {
271 my($self,$path,$base) = @_;
272
273 # Clean up $path
274 if ( ! $self->file_name_is_absolute( $path ) ) {
275 $path = $self->rel2abs( $path ) ;
276 }
277 else {
278 $path = $self->canonpath( $path ) ;
279 }
280
281 # Figure out the effective $base and clean it up.
282 if ( ! $self->file_name_is_absolute( $base ) ) {
283 $base = $self->rel2abs( $base ) ;
284 }
285 elsif ( !defined( $base ) || $base eq '' ) {
286 $base = cwd() ;
287 }
288 else {
289 $base = $self->canonpath( $base ) ;
290 }
291
292 # Split up paths
293 my ( $path_volume, $path_directories, $path_file ) =
294 $self->splitpath( $path, 1 ) ;
295
296 my ( undef, $base_directories, undef ) =
297 $self->splitpath( $base, 1 ) ;
298
299 # Now, remove all leading components that are the same
300 my @pathchunks = $self->splitdir( $path_directories );
301 my @basechunks = $self->splitdir( $base_directories );
302
303 while ( @pathchunks &&
304 @basechunks &&
305 lc( $pathchunks[0] ) eq lc( $basechunks[0] )
306 ) {
307 shift @pathchunks ;
308 shift @basechunks ;
309 }
310
311 # No need to catdir, we know these are well formed.
312 $path_directories = CORE::join( '\\', @pathchunks );
313 $base_directories = CORE::join( '\\', @basechunks );
314
5cefc38b 315 # $base_directories now contains the directories the resulting relative
316 # path must ascend out of before it can descend to $path_directory. So,
c27914c9 317 # replace all names with $parentDir
5cefc38b 318
319 #FA Need to replace between backslashes...
320 $base_directories =~ s|[^\\]+|..|g ;
c27914c9 321
322 # Glue the two together, using a separator if necessary, and preventing an
323 # empty result.
5cefc38b 324
325 #FA Must check that new directories are not empty.
326 if ( $path_directories ne '' && $base_directories ne '' ) {
c27914c9 327 $path_directories = "$base_directories\\$path_directories" ;
328 } else {
329 $path_directories = "$base_directories$path_directories" ;
330 }
331
0994714a 332 # It makes no sense to add a relative path to a UNC volume
1b1e14d3 333 $path_volume = '' unless $path_volume =~ m{^[A-Z]:}is ;
0994714a 334
c27914c9 335 return $self->canonpath(
0994714a 336 $self->catpath($path_volume, $path_directories, $path_file )
c27914c9 337 ) ;
338}
339
340=item rel2abs
341
342Converts a relative path to an absolute path.
343
1d7cb664 344 $abs_path = File::Spec->rel2abs( $destination ) ;
345 $abs_path = File::Spec->rel2abs( $destination, $base ) ;
c27914c9 346
347If $base is not present or '', then L<cwd()> is used. If $base is relative,
348then it is converted to absolute form using L</rel2abs()>. This means that it
349is taken to be relative to L</cwd()>.
350
351Assumes that both paths are on the $base volume, and ignores the
352$destination volume.
353
354On systems that have a grammar that indicates filenames, this ignores the
355$base filename as well. Otherwise all path components are assumed to be
356directories.
357
358If $path is absolute, it is cleaned up and returned using L</canonpath()>.
359
360Based on code written by Shigio Yamaguchi.
361
362No checks against the filesystem are made.
363
364=cut
365
366sub rel2abs($;$;) {
367 my ($self,$path,$base ) = @_;
368
c27914c9 369 if ( ! $self->file_name_is_absolute( $path ) ) {
370
1d7cb664 371 if ( !defined( $base ) || $base eq '' ) {
c27914c9 372 $base = cwd() ;
373 }
1d7cb664 374 elsif ( ! $self->file_name_is_absolute( $base ) ) {
375 $base = $self->rel2abs( $base ) ;
376 }
c27914c9 377 else {
378 $base = $self->canonpath( $base ) ;
379 }
380
c27914c9 381 my ( undef, $path_directories, $path_file ) =
382 $self->splitpath( $path, 1 ) ;
383
384 my ( $base_volume, $base_directories, undef ) =
385 $self->splitpath( $base, 1 ) ;
386
387 $path = $self->catpath(
388 $base_volume,
389 $self->catdir( $base_directories, $path_directories ),
390 $path_file
391 ) ;
392 }
393
394 return $self->canonpath( $path ) ;
395}
396
270d1e39 397=back
398
cbc7acb0 399=head1 SEE ALSO
400
401L<File::Spec>
270d1e39 402
cbc7acb0 403=cut
404
4051;