This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
d3f60185155b1cb8826f6229062316ed7cce5f28
[perl5.git] / lib / File / Spec / VMS.pm
1 package File::Spec::VMS;
2
3 use strict;
4 use vars qw(@ISA);
5 require File::Spec::Unix;
6 @ISA = qw(File::Spec::Unix);
7
8 use Cwd;
9 use File::Basename;
10 use VMS::Filespec;
11
12 =head1 NAME
13
14 File::Spec::VMS - methods for VMS file specs
15
16 =head1 SYNOPSIS
17
18  require File::Spec::VMS; # Done internally by File::Spec if needed
19
20 =head1 DESCRIPTION
21
22 See File::Spec::Unix for a documentation of the methods provided
23 there. This package overrides the implementation of these methods, not
24 the semantics.
25
26 =over
27
28 =item eliminate_macros
29
30 Expands MM[KS]/Make macros in a text string, using the contents of
31 identically named elements of C<%$self>, and returns the result
32 as a file specification in Unix syntax.
33
34 =cut
35
36 sub eliminate_macros {
37     my($self,$path) = @_;
38     return '' unless $path;
39     $self = {} unless ref $self;
40     my($npath) = unixify($path);
41     my($complex) = 0;
42     my($head,$macro,$tail);
43
44     # perform m##g in scalar context so it acts as an iterator
45     while ($npath =~ m#(.*?)\$\((\S+?)\)(.*)#gs) { 
46         if ($self->{$2}) {
47             ($head,$macro,$tail) = ($1,$2,$3);
48             if (ref $self->{$macro}) {
49                 if (ref $self->{$macro} eq 'ARRAY') {
50                     $macro = join ' ', @{$self->{$macro}};
51                 }
52                 else {
53                     print "Note: can't expand macro \$($macro) containing ",ref($self->{$macro}),
54                           "\n\t(using MMK-specific deferred substitutuon; MMS will break)\n";
55                     $macro = "\cB$macro\cB";
56                     $complex = 1;
57                 }
58             }
59             else { ($macro = unixify($self->{$macro})) =~ s#/\z##; }
60             $npath = "$head$macro$tail";
61         }
62     }
63     if ($complex) { $npath =~ s#\cB(.*?)\cB#\${$1}#gs; }
64     $npath;
65 }
66
67 =item fixpath
68
69 Catchall routine to clean up problem MM[SK]/Make macros.  Expands macros
70 in any directory specification, in order to avoid juxtaposing two
71 VMS-syntax directories when MM[SK] is run.  Also expands expressions which
72 are all macro, so that we can tell how long the expansion is, and avoid
73 overrunning DCL's command buffer when MM[KS] is running.
74
75 If optional second argument has a TRUE value, then the return string is
76 a VMS-syntax directory specification, if it is FALSE, the return string
77 is a VMS-syntax file specification, and if it is not specified, fixpath()
78 checks to see whether it matches the name of a directory in the current
79 default directory, and returns a directory or file specification accordingly.
80
81 =cut
82
83 sub fixpath {
84     my($self,$path,$force_path) = @_;
85     return '' unless $path;
86     $self = bless {} unless ref $self;
87     my($fixedpath,$prefix,$name);
88
89     if ($path =~ m#^\$\([^\)]+\)\z#s || $path =~ m#[/:>\]]#) { 
90         if ($force_path or $path =~ /(?:DIR\)|\])\z/) {
91             $fixedpath = vmspath($self->eliminate_macros($path));
92         }
93         else {
94             $fixedpath = vmsify($self->eliminate_macros($path));
95         }
96     }
97     elsif ((($prefix,$name) = ($path =~ m#^\$\(([^\)]+)\)(.+)#s)) && $self->{$prefix}) {
98         my($vmspre) = $self->eliminate_macros("\$($prefix)");
99         # is it a dir or just a name?
100         $vmspre = ($vmspre =~ m|/| or $prefix =~ /DIR\z/) ? vmspath($vmspre) : '';
101         $fixedpath = ($vmspre ? $vmspre : $self->{$prefix}) . $name;
102         $fixedpath = vmspath($fixedpath) if $force_path;
103     }
104     else {
105         $fixedpath = $path;
106         $fixedpath = vmspath($fixedpath) if $force_path;
107     }
108     # No hints, so we try to guess
109     if (!defined($force_path) and $fixedpath !~ /[:>(.\]]/) {
110         $fixedpath = vmspath($fixedpath) if -d $fixedpath;
111     }
112
113     # Trim off root dirname if it's had other dirs inserted in front of it.
114     $fixedpath =~ s/\.000000([\]>])/$1/;
115     # Special case for VMS absolute directory specs: these will have had device
116     # prepended during trip through Unix syntax in eliminate_macros(), since
117     # Unix syntax has no way to express "absolute from the top of this device's
118     # directory tree".
119     if ($path =~ /^[\[>][^.\-]/) { $fixedpath =~ s/^[^\[<]+//; }
120     $fixedpath;
121 }
122
123 =back
124
125 =head2 Methods always loaded
126
127 =over
128
129 =item canonpath (override)
130
131 Removes redundant portions of file specifications according to VMS syntax
132
133 =cut
134
135 sub canonpath {
136     my($self,$path) = @_;
137
138     if ($path =~ m|/|) { # Fake Unix
139       my $pathify = $path =~ m|/\z|;
140       $path = $self->SUPER::canonpath($path);
141       if ($pathify) { return vmspath($path); }
142       else          { return vmsify($path);  }
143     }
144     else {
145       $path =~ s-\]\[--g;  $path =~ s/><//g;    # foo.][bar       ==> foo.bar
146       $path =~ s/([\[<])000000\./$1/;           # [000000.foo     ==> foo
147       return $path;
148     }
149 }
150
151 =item catdir
152
153 Concatenates a list of file specifications, and returns the result as a
154 VMS-syntax directory specification.  No check is made for "impossible"
155 cases (e.g. elements other than the first being absolute filespecs).
156
157 =cut
158
159 sub catdir {
160     my ($self,@dirs) = @_;
161     my $dir = pop @dirs;
162     @dirs = grep($_,@dirs);
163     my $rslt;
164     if (@dirs) {
165         my $path = (@dirs == 1 ? $dirs[0] : $self->catdir(@dirs));
166         my ($spath,$sdir) = ($path,$dir);
167         $spath =~ s/\.dir\z//; $sdir =~ s/\.dir\z//; 
168         $sdir = $self->eliminate_macros($sdir) unless $sdir =~ /^[\w\-]+\z/s;
169         $rslt = $self->fixpath($self->eliminate_macros($spath)."/$sdir",1);
170
171     # Special case for VMS absolute directory specs: these will have had device
172     # prepended during trip through Unix syntax in eliminate_macros(), since
173     # Unix syntax has no way to express "absolute from the top of this device's
174     # directory tree".
175     if ($spath =~ /^[\[<][^.\-]/s) { $rslt =~ s/^[^\[<]+//s; }
176     }
177     else {
178         if ($dir =~ /^\$\([^\)]+\)\z/s) { $rslt = $dir; }
179         else                            { $rslt = vmspath($dir); }
180     }
181     return $rslt;
182 }
183
184 =item catfile
185
186 Concatenates a list of file specifications, and returns the result as a
187 VMS-syntax file specification.
188
189 =cut
190
191 sub catfile {
192     my ($self,@files) = @_;
193     my $file = pop @files;
194     @files = grep($_,@files);
195     my $rslt;
196     if (@files) {
197         my $path = (@files == 1 ? $files[0] : $self->catdir(@files));
198         my $spath = $path;
199         $spath =~ s/\.dir\z//;
200         if ($spath =~ /^[^\)\]\/:>]+\)\z/s && basename($file) eq $file) {
201             $rslt = "$spath$file";
202         }
203         else {
204             $rslt = $self->eliminate_macros($spath);
205             $rslt = vmsify($rslt.($rslt ? '/' : '').unixify($file));
206         }
207     }
208     else { $rslt = vmsify($file); }
209     return $rslt;
210 }
211
212
213 =item curdir (override)
214
215 Returns a string representation of the current directory: '[]'
216
217 =cut
218
219 sub curdir {
220     return '[]';
221 }
222
223 =item devnull (override)
224
225 Returns a string representation of the null device: '_NLA0:'
226
227 =cut
228
229 sub devnull {
230     return "_NLA0:";
231 }
232
233 =item rootdir (override)
234
235 Returns a string representation of the root directory: 'SYS$DISK:[000000]'
236
237 =cut
238
239 sub rootdir {
240     return 'SYS$DISK:[000000]';
241 }
242
243 =item tmpdir (override)
244
245 Returns a string representation of the first writable directory
246 from the following list or '' if none are writable:
247
248     /sys$scratch
249     $ENV{TMPDIR}
250
251 =cut
252
253 my $tmpdir;
254 sub tmpdir {
255     return $tmpdir if defined $tmpdir;
256     foreach ('/sys$scratch', $ENV{TMPDIR}) {
257         next unless defined && -d && -w _;
258         $tmpdir = $_;
259         last;
260     }
261     $tmpdir = '' unless defined $tmpdir;
262     return $tmpdir;
263 }
264
265 =item updir (override)
266
267 Returns a string representation of the parent directory: '[-]'
268
269 =cut
270
271 sub updir {
272     return '[-]';
273 }
274
275 =item case_tolerant (override)
276
277 VMS file specification syntax is case-tolerant.
278
279 =cut
280
281 sub case_tolerant {
282     return 1;
283 }
284
285 =item path (override)
286
287 Translate logical name DCL$PATH as a searchlist, rather than trying
288 to C<split> string value of C<$ENV{'PATH'}>.
289
290 =cut
291
292 sub path {
293     my (@dirs,$dir,$i);
294     while ($dir = $ENV{'DCL$PATH;' . $i++}) { push(@dirs,$dir); }
295     return @dirs;
296 }
297
298 =item file_name_is_absolute (override)
299
300 Checks for VMS directory spec as well as Unix separators.
301
302 =cut
303
304 sub file_name_is_absolute {
305     my ($self,$file) = @_;
306     # If it's a logical name, expand it.
307     $file = $ENV{$file} while $file =~ /^[\w\$\-]+\z/s && $ENV{$file};
308     return scalar($file =~ m!^/!s             ||
309                   $file =~ m![<\[][^.\-\]>]!  ||
310                   $file =~ /:[^<\[]/);
311 }
312
313 =item splitpath (override)
314
315 Splits using VMS syntax.
316
317 =cut
318
319 sub splitpath {
320     my($self,$path) = @_;
321     my($dev,$dir,$file) = ('','','');
322
323     vmsify($path) =~ /(.+:)?([\[<].*[\]>])?(.*)/s;
324     return ($1 || '',$2 || '',$3);
325 }
326
327 =item splitdir (override)
328
329 Split dirspec using VMS syntax.
330
331 =cut
332
333 sub splitdir {
334     my($self,$dirspec) = @_;
335     $dirspec =~ s/\]\[//g;  $dirspec =~ s/\-\-/-.-/g;
336     my(@dirs) = split('\.', vmspath($dirspec));
337     $dirs[0] =~ s/^[\[<]//s;  $dirs[-1] =~ s/[\]>]\z//s;
338     @dirs;
339 }
340
341
342 =item catpath (override)
343
344 Construct a complete filespec using VMS syntax
345
346 =cut
347
348 sub catpath {
349     my($self,$dev,$dir,$file) = @_;
350     if ($dev =~ m|^/+([^/]+)|) { $dev =~ "$1:"; }
351     else { $dev .= ':' unless $dev eq '' or $dev =~ /:\z/; }
352     $dir = vmspath($dir);
353     "$dev$dir$file";
354 }
355
356
357 sub abs2rel {
358     my $self = shift;
359
360     return File::Spec::Unix::abs2rel( $self, @_ )
361         if ( join( '', @_ ) =~ m{/} ) ;
362
363     my($path,$base) = @_;
364
365     # Note: we use '/' to glue things together here, then let canonpath()
366     # clean them up at the end.
367
368     # Clean up $path
369     if ( ! $self->file_name_is_absolute( $path ) ) {
370         $path = $self->rel2abs( $path ) ;
371     }
372     else {
373         $path = $self->canonpath( $path ) ;
374     }
375
376     # Figure out the effective $base and clean it up.
377     if ( !defined( $base ) || $base eq '' ) {
378         $base = cwd() ;
379     }
380     elsif ( ! $self->file_name_is_absolute( $base ) ) {
381         $base = $self->rel2abs( $base ) ;
382     }
383     else {
384         $base = $self->canonpath( $base ) ;
385     }
386
387     # Split up paths
388     my ( undef, $path_directories, $path_file ) =
389         $self->splitpath( $path, 1 ) ;
390
391     $path_directories = $1
392         if $path_directories =~ /^\[(.*)\]\z/s ;
393
394     my ( undef, $base_directories, undef ) =
395         $self->splitpath( $base, 1 ) ;
396
397     $base_directories = $1
398         if $base_directories =~ /^\[(.*)\]\z/s ;
399
400     # Now, remove all leading components that are the same
401     my @pathchunks = $self->splitdir( $path_directories );
402     my @basechunks = $self->splitdir( $base_directories );
403
404     while ( @pathchunks && 
405             @basechunks && 
406             lc( $pathchunks[0] ) eq lc( $basechunks[0] ) 
407           ) {
408         shift @pathchunks ;
409         shift @basechunks ;
410     }
411
412     # @basechunks now contains the directories to climb out of,
413     # @pathchunks now has the directories to descend in to.
414     $path_directories = '-.' x @basechunks . join( '.', @pathchunks ) ;
415     $path_directories =~ s{\.\z}{} ;
416     return $self->catpath( '', $path_directories, $path_file ) ;
417 }
418
419
420 sub rel2abs($;$;) {
421     my $self = shift ;
422     return File::Spec::Unix::rel2abs( $self, @_ )
423         if ( join( '', @_ ) =~ m{/} ) ;
424
425     my ($path,$base ) = @_;
426     # Clean up and split up $path
427     if ( ! $self->file_name_is_absolute( $path ) ) {
428         # Figure out the effective $base and clean it up.
429         if ( !defined( $base ) || $base eq '' ) {
430             $base = cwd() ;
431         }
432         elsif ( ! $self->file_name_is_absolute( $base ) ) {
433             $base = $self->rel2abs( $base ) ;
434         }
435         else {
436             $base = $self->canonpath( $base ) ;
437         }
438
439         # Split up paths
440         my ( undef, $path_directories, $path_file ) =
441             $self->splitpath( $path ) ;
442
443         my ( $base_volume, $base_directories, undef ) =
444             $self->splitpath( $base ) ;
445
446         my $sep = '' ;
447         $sep = '.'
448             if ( $base_directories =~ m{[^.]\z} &&
449                  $path_directories =~ m{^[^.]}s
450             ) ;
451         $base_directories = "$base_directories$sep$path_directories" ;
452
453         $path = $self->catpath( $base_volume, $base_directories, $path_file );
454    }
455
456     return $self->canonpath( $path ) ;
457 }
458
459
460 =back
461
462 =head1 SEE ALSO
463
464 L<File::Spec>
465
466 =cut
467
468 1;