File Coverage

blib/lib/File/PathConvert.pm
Criterion Covered Total %
statement 190 243 78.1
branch 73 116 62.9
condition 59 90 65.5
subroutine 14 15 93.3
pod 0 9 0.0
total 336 473 71.0


line stmt bran cond sub pod time code
1             #
2             # Copyright (c) 1996, 1997, 1998 Shigio Yamaguchi. All rights reserved.
3             # This program is free software; you can redistribute it and/or
4             # modify it under the same terms as Perl itself.
5             #
6             # File::PathConvert.pm
7             #
8              
9             package File::PathConvert;
10             require 5.002;
11              
12 6     6   82393 use strict ;
  6         15  
  6         277  
13              
14             BEGIN {
15 6     6   29 use Exporter ();
  6         12  
  6         119  
16 6     6   33 use vars qw($VERSION @ISA @EXPORT_OK);
  6         15  
  6         1132  
17 6     6   15 $VERSION = 0.9;
18 6         89 @ISA = qw(Exporter);
19 6         155 @EXPORT_OK = qw(setfstype splitpath joinpath splitdirs joindirs realpath abs2rel rel2abs $maxsymlinks $verbose $SL $resolved );
20             }
21              
22 6     6   34 use vars qw( $maxsymlinks $verbose $SL $resolved ) ;
  6         9  
  6         723  
23 6     6   30 use Cwd;
  6         11  
  6         50312  
24              
25             #
26             # Initialize @EXPORT_OK vars
27             #
28             $maxsymlinks = 32; # allowed symlink number in a path
29             $verbose = 0; # 1: verbose on, 0: verbose off
30             $SL = '' ; # Separator char export
31             $resolved = '' ; # realpath() intermediate value export
32              
33             #############################################################################
34             #
35             # Package Globals
36             #
37              
38             my $fstype ; # A name indicating the type of filesystem currently in use
39             my $sep ; # separator
40             my $sepRE ; # RE to match spearator
41             my $notsepRE ; # RE to match anything else
42             my $volumeRE ; # RE to match the volume name
43             my $directoryRE ; # RE to match the directory name
44             my $isrootRE ; # RE to match root path: applied to directory portion only
45             #my $thisDir ; # Name of this directory
46             my $thisDirRE ; # Name of this directory
47             my $parentDir ; # Name of parent directory
48             my $parentDirRE ; # RE to match parent dir name
49             my $casesensitive ; # Set to non-zero for case sensitive name comprisions. Only
50             # affects names, not any other REs, so $isrootRE for Win32
51             # must be case insensitive
52             my $idempotent ; # Set to non-zero if '//' is equivalent to '/'. This
53             # does not affect leading '//' and '\\' under Win32,
54             # but will fold '///' and '////', etc, in to '//' on this
55             # Win32
56              
57              
58              
59             ###########
60             #
61             # The following globals are regexs used in the indicated routines. These
62             # are initialized by setfstype, so they don't need to be rebuilt each time
63             # the routine that uses them is called.
64              
65             my $basenamesplitRE ; # Used in realpath() to split filenames.
66              
67              
68             ###########
69             #
70             # This RE matches (and saves) the portion of the string that is just before
71             # the beginning of a name
72             #
73             my $beginning_of_name ;
74              
75             #
76             # This whopper of an RE looks for the pattern "name/.." if it occurs
77             # after the beginning of the string or after the root RE, or after a separator.
78             # We don't assume that the isrootRE has a trailing separator.
79             # It also makes sure that we aren't eliminating '../..' and './..' patterns
80             # by using the negative lookahead assertion '(?!' ... ')' construct. It also
81             # ignores 'name/..name'.
82             #
83             my $name_sep_parentRE ;
84              
85             #
86             # Matches '..$', '../' after a root
87             my $leading_parentRE ;
88              
89             #
90             # Matches things like '/(./)+' and '^(./)+'
91             #
92             my $dot_sep_etcRE ;
93              
94             #
95             # Matches trailing '/' or '/.'
96             #
97             my $trailing_sepRE ;
98              
99              
100             #############################################################################
101             #
102             # Functions
103             #
104              
105              
106             #
107             # setfstype: takes the name of an operating system and sets up globals that
108             # allow the other functions to operate on multiple OSs. See
109             # %fsconfig for the sets of settings.
110             #
111             # This is run once on module load to configure for the OS named
112             # in $^O.
113             #
114             # Interface:
115             # i) $osname, as in $^O or plain english: "MacOS", "DOS, etc.
116             # This is _not_ usually case sensitive.
117             # r) Name of recognized name on success else undef. Note that, as
118             # shipped, 'unix' is the default is nothing else matches.
119             # go) $fstype and lots of internal parameters and regexs.
120             # x) Dies if a parameter required in @fsconfig is missing.
121             #
122             #
123             # There are some things I couldn't figure a way to parameterize by setting
124             # globals. $fstype is checked for filesystem type-specific logic, like
125             # VMS directory syntax.
126             #
127             # Setting up for a particular OS type takes two steps: identify the OS and
128             # set all of the 'atomic' global variables, then take some of the atomic
129             # globals which are regexps and build composite values from them.
130             #
131             # The atomic regexp terms are generally used to build the larger composite
132             # regexps that recognize and break apart paths. This leads to
133             # two important rules for the atomic regexp terms:
134             #
135             # (1) Do not use '(' ... ')' in the regex terms, since they are used to build
136             # regexs that use '(' ... ')' to parse paths.
137             #
138             # (2) They must be built so that a '?' or other quantifier may be appended.
139             # This generally means using the '(?:' ... ')' or '[' ... ']' to group
140             # multicharacter patterns. Other '(?' ... ')' may also do.
141             #
142             # The routines herein strive to preserve the
143             # original separator and root settings, and, it turns out, never need to
144             # prepend root to a string (although they do need to insert separators on
145             # occasion). This is good, since the Win32 root expressions can be like
146             # '/', '\', 'A:/', 'a:/', or even '\\' or '//' for UNC style names.
147             #
148             # Note that the default root and default notsep are not used, and so are
149             # undefined.
150             #
151             # For DOS, MacOS, and VMS, we assume that all paths handed in are on the same
152             # volume. This is not a significant limitation except for abs2rel, since the
153             # absolute path is assumed to be on the same volume as the base path.
154             #
155             sub setfstype($;) {
156 19     19 0 2456 my( $osname ) = @_ ;
157              
158             # Find the best match for OS and set up our atomic globals accordingly
159 19 100       382 if ( $osname =~ /^(?:(ms)?(dos|win(32|nt)?))/i )
    50          
    100          
    100          
160             {
161 3         11 $fstype = 'Win32' ;
162 3         9 $sep = '/' ;
163 3         9 $sepRE = '[\\\\/]' ;
164 3         9 $notsepRE = '[^\\\\/]' ;
165 3         7 $volumeRE = '(?:^(?:[a-zA-Z]:|(?:\\\\\\\\|//)[^\\\\/]+[\\\\/][^\\\\/]+)?)' ;
166 3         7 $directoryRE = '(?:(?:.*[\\\\/](?:\.\.?$)?)?)' ;
167 3         7 $isrootRE = '(?:^[\\\\/])' ;
168             # $thisDir = '.' ;
169 3         6 $thisDirRE = '\.' ;
170 3         57 $parentDir = '..' ;
171 3         6 $parentDirRE = '(?:\.\.)' ;
172 3         6 $casesensitive = 0 ;
173 3         6 $idempotent = 1 ;
174             }
175             elsif ( $osname =~ /^MacOS$/i )
176             {
177 0         0 $fstype = 'MacOS' ;
178 0         0 $sep = ':' ;
179 0         0 $sepRE = '\:' ;
180 0         0 $notsepRE = '[^:]' ;
181 0         0 $volumeRE = '(?:^(?:[^:]+:)?)' ;
182 0         0 $directoryRE = '(?:(?:.*:)?)' ;
183 0         0 $isrootRE = '(?:^(?=[^:].*:)?)' ;
184             # $thisDir = '' ;
185 0         0 $thisDirRE = 'cantpossiblymatchthis' ;
186 0         0 $parentDir = '' ;
187 0         0 $parentDirRE = '(?=(?<=:):)' ;
188 0         0 $casesensitive = 0 ;
189 0         0 $idempotent = 0 ;
190             }
191             elsif ( $osname =~ /^VMS$/i )
192             {
193 4         9 $fstype = 'VMS' ;
194 4         9 $sep = '.' ;
195 4         11 $sepRE = '[\.\]]' ;
196 4         8 $notsepRE = '[^\.\]]' ;
197             # volume is node::volume:, where node:: and volume: are optional
198             # and node:: cannot be present without volume. node can include
199             # an access control string in double quotes.
200             # Not supported:
201             # quoted full node names
202             # embedding a double quote in a string ("" to put " in)
203             # support ':' in node names
204             # foreign file specifications
205             # task specifications
206             # UIC Directory format (use the 6 digit name for it, instead)
207 4         9 $volumeRE = '(?:^(?:(?:[\w\$-]+(?:"[^"]*")?::)?[\w\$-]+:)?)' ;
208 4         8 $directoryRE = '(?:(?:\[.*\])?)' ;
209              
210             # Root is the lack of a leading '.', unless string is empty, which
211             # means 'cwd', which is relative.
212 4         7 $isrootRE = '(?:^[^\.])' ;
213             # $thisDir = '' ;
214 4         10 $thisDirRE = '\[\]' ;
215 4         27 $parentDir = '-' ;
216 4         6 $parentDirRE = '-' ;
217 4         10 $casesensitive = 0 ;
218 4         14 $idempotent = 0 ;
219             }
220             elsif ( $osname =~ /^URL$/i )
221             {
222             # URL spec based on RFC2396 (ftp://ftp.isi.edu/in-notes/rfc2396.txt)
223 3         7 $fstype = 'URL' ;
224 3         8 $sep = '/' ;
225 3         7 $sepRE = '/' ;
226 3         7 $notsepRE = '[^/]' ;
227             # Volume= scheme + authority, both optional
228 3         5 $volumeRE = '(?:^(?:[a-zA-Z][a-zA-Z0-9+-.]*:)?(?://[^/?]*)?)' ;
229              
230             # Directories do _not_ include the query component: we pretend that
231             # anything after a "?" is the filename or part of it. So a '/'
232             # terminates and is part of the directory spec, while a '?' or '#'
233             # terminate and are not part of the directory spec.
234             #
235             # We pretend that ";param" syntax does not exist
236             #
237 3         5 $directoryRE = '(?:(?:[^?#]*/(?:\.\.?(?:$|(?=[?#])))?)?)' ;
238 3         6 $isrootRE = '(?:^/)' ;
239             # $thisDir = '.' ;
240 3         7 $thisDirRE = '\.' ;
241 3         5 $parentDir = '..' ;
242 3         5 $parentDirRE = '(?:\.\.)' ;
243             # Assume case sensitive, since many (most?) are. The user can override
244             # this if they so desire.
245 3         26 $casesensitive = 1 ;
246 3         6 $idempotent = 1 ;
247             }
248             else
249             {
250 9         67 $fstype = 'Unix' ;
251 9         17 $sep = '/' ;
252 9         17 $sepRE = '/' ;
253 9         17 $notsepRE = '[^/]' ;
254 9         15 $volumeRE = '' ;
255 9         19 $directoryRE = '(?:(?:.*/(?:\.\.?$)?)?)' ;
256 9         169 $isrootRE = '(?:^/)' ;
257             # $thisDir = '.' ;
258 9         16 $thisDirRE = '\.' ;
259 9         17 $parentDir = '..' ;
260 9         13 $parentDirRE = '(?:\.\.)' ;
261 9         17 $casesensitive = 1 ;
262 9         19 $idempotent = 1 ;
263             }
264              
265             # Now set our composite regexps.
266              
267             # Maintain old name for backward compatibility
268 19         44 $SL= $sep ;
269              
270             # Build lots of REs used below, so they don't need to be built every time
271             # the routines that use them are called.
272 19         58 $basenamesplitRE = '^(.*)' . $sepRE . '(' . $notsepRE . '*)$' ;
273              
274 19         68 $leading_parentRE = '(' . $isrootRE . '?)(?:' . $parentDirRE . $sepRE . ')*(?:' . $parentDirRE . '$)?' ;
275 19         46 $trailing_sepRE = '(.)' . $sepRE . $thisDirRE . '?$' ;
276              
277 19         64 $beginning_of_name = '(?:^|' . $isrootRE . '|' . $sepRE . ')' ;
278              
279 19         61 $dot_sep_etcRE =
280             '(' . $beginning_of_name . ')(?:' . $thisDirRE . $sepRE . ')+';
281              
282 19         96 $name_sep_parentRE =
283             '(' . $beginning_of_name . ')'
284             . '(?!(?:' . $thisDirRE . '|' . $parentDirRE . ')' . $sepRE . ')'
285             . $notsepRE . '+'
286             . $sepRE . $parentDirRE
287             . '(?:' . $sepRE . '|$)'
288             ;
289              
290 19 50       71 if ( $verbose ) {
291 0         0 print( <
292             fstype = "$fstype"
293             sep = "$sep"
294             sepRE = /$sepRE/
295             notsepRE = /$notsepRE/
296             volumeRE = /$volumeRE/
297             directoryRE = /$directoryRE/
298             isrootRE = /$isrootRE/
299             thisDirRE = /$thisDirRE/
300             parentDir = "$parentDir"
301             parentDirRE = /$parentDirRE/
302             casesensitive = "$casesensitive"
303             TOHERE
304             #thisDir = "$thisDir"
305             }
306              
307 19         39 return $fstype ;
308             }
309              
310              
311             setfstype( $^O ) ;
312              
313              
314             #
315             # splitpath: Splits a path into component parts: volume, dirpath, and filename.
316             #
317             # Very much like File::Basename::fileparse(), but doesn't concern
318             # itself with extensions and knows about volume names.
319             #
320             # Returns ($volume, $directory, $filename ).
321             #
322             # The contents of the returned list varies by operating system.
323             #
324             # Unix:
325             # $volume: always ''
326             # $directory: up to, and including, final '/'
327             # $filename: after final '/'
328             #
329             # Win32:
330             # $volume: drive letter and ':', if present
331             # $directory and $filename are like on Unix, but '\' and '/' are
332             # equivalent and the $volume is not in $directory..
333             #
334             # VMS:
335             # $volume: up to and including first ":"
336             # $directory: "[...]" component
337             # $filename: the rest.
338             # $nofile is ignored
339             #
340             # URL:
341             # $volume: up to ':', then '//stuff/morestuff'. No trailing '/'.
342             # $directory: after $volume, up to last '/'
343             # $filename: the rest.
344             # $nofile is ignored
345             #
346             # Interface:
347             # i) $path
348             # i) $nofile: if true, then any trailing filename is assumed to
349             # belong to the directory for non-VMS systems.
350             # r) list of ( $volume, $directory, $filename ).
351             #
352             sub splitpath {
353 176     176 0 13400 my( $path, $nofile )= @_ ;
354 176         179 my( $volume, $directory, $file ) ;
355 176 100 100     1092 if ( $fstype ne 'VMS' && $fstype ne 'URL' && $nofile ) {
      100        
356 62         367 $path =~ m/($volumeRE)(.*)$/ ;
357 62         116 $volume = $1 ;
358 62         109 $directory= $2 ;
359 62         94 $file = '' ;
360             }
361             else {
362 114         12434 $path =~ m/($volumeRE)($directoryRE)(.*)$/ ;
363 114         253 $volume = $1 ;
364 114         157 $directory= $2 ;
365 114         311 $file = $3 ;
366             }
367              
368             # For Win32 UNC, force the directory portion to be non-empty. This is
369             # because all UNC names are absolute, even if there's no trailing separator
370             # after the sharename.
371             #
372             # This is a bit of a hack, necesitated by the implementation of $isrootRE,
373             # which is only applied to the directory portion.
374             #
375             # A better long term solution might be to make the isroot test a member
376             # function in the future, object-oriented version of this.
377             #
378 176 50 100     774 $directory = $1
      66        
379             if ( $fstype eq 'Win32' && $volume =~ /^($sepRE)$sepRE/ && $directory eq '' ) ;
380              
381 176         575 return ( $volume, $directory, $file ) ;
382             }
383              
384              
385             #
386             # joinpath: joins the results of splitpath(). Not really necessary now, but
387             # good to have:
388             #
389             # - API completeness
390             # - Self documenting code
391             # - Future handling of other filesystems
392             #
393             # For instance, if you leave the ':' or the '[' and ']' out of VMS $volume
394             # and $directory strings, this patches it up. If you leave out the '['
395             # and provide the ']', or vice versa, it is not cleaned up. This is
396             # because it's useful to automatically insert both '[' and ']', but if you
397             # leave off only one, it's likely that there's a bug elsewhere that needs
398             # looking in to.
399             #
400             # Automatically inserts a separator between directory and filename if needed
401             # for non-VMS OSs.
402             #
403             # Automatically inserts a separator between volume and directory or file
404             # if needed for Win32 UNC names.
405             #
406             sub joinpath($;$;$;) {
407 121     121 0 13288 my( $volume, $directory, $filename )= @_ ;
408              
409             # Fix up delimiters for $volume and $directory as needed for various OSs
410 121 100       273 if ( $fstype eq 'VMS' ) {
411 12 50 66     66 $volume .= ':'
412             if ( $volume ne '' && $volume !~ m/:$/ ) ;
413              
414 12 100 100     87 $directory = join( '', ( '[', $directory, ']' ) )
415             if ( $directory ne '' && $directory !~ m/^\[.*\]$/ ) ;
416             }
417             else {
418             # Add trailing separator to directory names that require it and
419             # need it. URLs always require it if there are any directory
420             # components.
421 109 100 100     985 $directory .= $sep
      66        
      100        
422             if ( $directory ne ''
423             && ( $fstype eq 'URL' || $filename ne '' )
424             && $directory !~ m/$sepRE$/
425             ) ;
426              
427             # Add trailing separator to volume for UNC and HTML volume
428             # names that lack it and need it.
429             # Note that if a URL volume is a scheme only (ends in ':'),
430             # we don't require a separator: it's a relative URL.
431 109 50 66     1005 $volume .= $sep
      66        
      100        
      33        
      66        
432             if ( ( ( $fstype eq 'Win32' && $volume =~ m#^$sepRE{2}# )
433             || ( $fstype eq 'URL' && $volume =~ m#[^:/]$# )
434             )
435             && $volume !~ m#$sepRE$#
436             && $directory !~ m#^$sepRE#
437             && ( $directory ne '' || $filename ne '' )
438             ) ;
439             }
440              
441 121         839 return join( '', $volume, $directory, $filename ) ;
442             }
443              
444              
445             #
446             # splitdirs: Splits a string containing directory portion of a path
447             # in to component parts. Preserves trailing null entries, unlike split().
448             #
449             # "a/b" should get you [ 'a', 'b' ]
450             #
451             # "a/b/" should get you [ 'a', 'b', '' ]
452             #
453             # "/a/b/" should get you [ '', 'a', 'b', '' ]
454             #
455             # "a/b" returns the same array as 'a/////b' for those OSs where
456             # the seperator is idempotent (Unix and DOS, at least, but not VMS).
457             #
458             # Interface:
459             # i) directory path string
460             #
461             sub splitdirs($;) {
462 23     23 0 6983 my( $directorypath )= @_ ;
463              
464 23 100       85 $directorypath =~ s/^\[(.*)\]$/$1/
465             if ( $fstype eq 'VMS' ) ;
466              
467             #
468             # split() likes to forget about trailing null fields, so here we
469             # check to be sure that there will not be any before handling the
470             # simple case.
471             #
472 23 100       276 return split( $sepRE, $directorypath )
473             if ( $directorypath !~ m/$sepRE$/ ) ;
474              
475             #
476             # since there was a trailing separator, add a file name to the end, then
477             # do the split, then replace it with ''.
478             #
479 6         13 $directorypath.= "file" ;
480 6         49 my( @directories )= split( $sepRE, $directorypath ) ;
481 6         13 $directories[ $#directories ]= '' ;
482              
483 6         29 return @directories ;
484             }
485              
486             #
487             # joindirs: Joins an array of directory names in to a string, adding
488             # OS-specific delimiters, like '[' and ']' for VMS.
489             #
490             # Note that empty strings '' are no different then non-empty strings,
491             # but that undefined strings are skipped by this algorithm.
492             #
493             # This is done the hard way to preserve separators that are already
494             # present in any of the directory names.
495             #
496             # Could this be made faster by using a join() followed
497             # by s/($sepRE)$sepRE+/$1/g?
498             #
499             # Interface:
500             # i) array of directory names
501             # o) string representation of directory path
502             #
503             sub joindirs {
504 23     23 0 7324 my $directory_path ;
505              
506 23   100     257 $directory_path = shift
507             while ( ! defined( $directory_path ) && @_ ) ;
508              
509 23 100       47 if ( ! defined( $directory_path ) ) {
510 5         7 $directory_path = '' ;
511             }
512             else {
513 18         22 local $_ ;
514              
515 18         35 for ( @_ ) {
516 52 50       100 next if ( ! defined( $_ ) ) ;
517              
518 52 50 33     623 $directory_path .= $sep
519             if ( $directory_path !~ /$sepRE$/ && ! /^$sepRE/ ) ;
520              
521 52         350 $directory_path .= $_ ;
522             }
523             }
524              
525 23 100       64 $directory_path = join( '', '[', $directory_path, ']' )
526             if ( $fstype eq 'VMS' ) ;
527              
528 23         116 return $directory_path ;
529             }
530              
531              
532             #
533             # realpath: returns the canonicalized absolute path name
534             #
535             # Interface:
536             # i) $path path
537             # r) resolved name on success else undef
538             # go) $resolved
539             # resolved name on success else the path name which
540             # caused the problem.
541             $resolved = '';
542             #
543             # Note: this implementation is based 4.4BSD version realpath(3).
544             #
545             # TODO: Speed up by using Cwd::abs_path()?
546             #
547             sub realpath($;) {
548 17     17 0 20258 ($resolved) = @_;
549 17         132022 my($backdir) = cwd();
550 17         722 my($dirname, $basename, $links, $reg);
551              
552 17         415 $resolved = regularize($resolved);
553             LOOP:
554             {
555             #
556             # Find the dirname and basename.
557             # Change directory to the dirname component.
558             #
559 17 100       30 if ($resolved =~ /$sepRE/) {
  24         302  
560 20         322 ($dirname, $basename) = $resolved =~ /$basenamesplitRE/ ;
561 20 100       63 $dirname = $sep if ( $dirname eq '' );
562 20         34 $resolved = $dirname;
563 20 50       446 unless (chdir($dirname)) {
564 0 0       0 warn("realpath: chdir($dirname) failed: $! (in ${\cwd()}).") if $verbose;
  0         0  
565 0         0 chdir($backdir);
566 0         0 return undef;
567             }
568             } else {
569 4         21 $dirname = '';
570 4         24 $basename = $resolved;
571             }
572             #
573             # If it is a symlink, read in the value and loop.
574             # If it is a directory, then change to that directory.
575             #
576 24 100       77 if ( $basename ne '' ) {
577 22 100       574 if (-l $basename) {
    100          
578 7 50       94 unless ($resolved = readlink($basename)) {
579 0 0       0 warn("realpath: readlink($basename) failed: $! (in ${\cwd()}).") if $verbose;
  0         0  
580 0         0 chdir($backdir);
581 0         0 return undef;
582             }
583 7         17 $basename = '';
584 7 50       24 if (++$links > $maxsymlinks) {
585 0 0       0 warn("realpath: too many symbolic links: $links.") if $verbose;
586 0         0 chdir($backdir);
587 0         0 return undef;
588             }
589 7         17 redo LOOP;
590             } elsif (-d _) {
591 11 50       112 unless (chdir($basename)) {
592 0 0       0 warn("realpath: chdir($basename) failed: $! (in ${\cwd()}).") if $verbose;
  0         0  
593 0         0 chdir($backdir);
594 0         0 return undef;
595             }
596 11         26 $basename = '';
597             }
598             }
599             }
600             #
601             # Get the current directory name and append the basename.
602             #
603 17         172321 $resolved = cwd();
604 17 100       292 if ( $basename ne '' ) {
605 4 50       43 $resolved .= $sep if ($resolved ne $sep);
606 4         18 $resolved .= $basename
607             }
608 17         804 chdir($backdir);
609 17         994 return $resolved;
610             } # end sub realpath
611              
612              
613             #
614             # abs2rel: make a relative pathname from an absolute pathname
615             #
616             # Interface:
617             # i) $path absolute path(needed)
618             # i) $base base directory(optional)
619             # r) relative path of $path
620             #
621             # Note: abs2rel doesn't check whether the specified path exist or not.
622             #
623             sub abs2rel($;$;) {
624 56     56 0 24716 my($path, $base) = @_;
625 56         151 my($reg );
626              
627 56         92 my( $path_volume, $path_directory, $path_file )= splitpath( $path,'nofile');
628 56 100       218 if ( $path_directory !~ /$isrootRE/ ) {
629 9 50       20 warn("abs2rel: nothing to do: '$path' is relative.") if $verbose;
630 9         41 return $path;
631             }
632              
633 47 50 33     546 $base = cwd()
634             if ( !defined( $base ) || $base eq '' ) ;
635              
636 47         106 my( $base_volume, $base_directory, $base_file )= splitpath( $base,'nofile');
637             # check for a filename, since the nofile parameter does not work for OSs
638             # like VMS that have explicit delimiters between the dir and file portions
639 47 50 66     112 warn( "abs2rel: filename '$base_file' passed in \$base" )
640             if ( $base_file ne '' && $verbose ) ;
641              
642 47 50       163 if ( $base_directory !~ /$isrootRE/ ) {
643             # Make $base absolute
644 0         0 my( $cw_volume, $cw_directory, $dummy ) = splitpath( cwd(), 'nofile' ) ;
645             # maybe we should warn if $cw_volume ne $base_volume and both are not ''
646 0 0 0     0 $base_volume= $cw_volume
647             if ( $base_volume eq '' && $cw_volume ne '' ) ;
648 0         0 $base_directory = join( '', $cw_directory, $sep, $base_directory ) ;
649             }
650              
651 47         262 $path_directory = regularize( $path_directory );
652 47         79 $base_directory = regularize( $base_directory );
653             # Now, remove all leading components that are the same, so 'name/a'
654             # 'name/b' become 'a' and 'b'.
655 47         190 my @pathchunks = split($sepRE, $path_directory);
656 47         195 my @basechunks = split($sepRE, $base_directory);
657              
658 47 100       217 if ( $casesensitive )
659             {
660 30   100     149 while (@pathchunks && @basechunks && $pathchunks[0] eq $basechunks[0])
      100        
661             {
662 41         44 shift @pathchunks ;
663 41         350 shift @basechunks ;
664             }
665             }
666             else {
667 17   100     82 while ( @pathchunks
      100        
668             && @basechunks
669             && lc( $pathchunks[0] ) eq lc( $basechunks[0] )
670             )
671             {
672 28         30 shift @pathchunks ;
673 28         136 shift @basechunks ;
674             }
675             }
676              
677             # No need to use joindirs() here, since we know that the arrays
678             # are well formed.
679 47         95 $path_directory= join( $sep, @pathchunks );
680 47         73 $base_directory= join( $sep, @basechunks );
681              
682             # Convert $base_directory from absolute to relative
683 47 100       80 if ( $fstype eq 'VMS' ) {
684 2 100       8 $base_directory= $sep . $base_directory
685             if ( $base_directory ne '' ) ;
686             }
687             else {
688 45         139 $base_directory=~ s/^$sepRE// ;
689             }
690              
691             # $base_directory now contains the directories the resulting relative path
692             # must ascend out of before it can descend to $path_directory. So,
693             # replace all names with $parentDir
694 47         249 $base_directory =~ s/$notsepRE+/$parentDir/g ;
695              
696             # Glue the two together, using a separator if necessary, and preventing an
697             # empty result.
698 47 100 100     157 if ( $path_directory ne '' && $base_directory ne '' ) {
699 19         33 $path_directory = "$base_directory$sep$path_directory" ;
700             } else {
701 28         99 $path_directory = "$base_directory$path_directory" ;
702             }
703              
704 47         81 $path_directory = regularize( $path_directory ) ;
705              
706             # relative URLs should have no name in the volume, only a scheme.
707 47 100       94 if ( $fstype eq 'URL' ) {
708 17         26 $path_volume =~ s#/.*## ;
709             # NOTE: if case insensitive URLs are ever done, this next comparison
710             # needs to be tweaked.
711 17         21 my $path_file_file = $path_file ;
712 17         19 $path_file_file =~ s/#.*// ;
713 17         22 my $base_file_file = $base_file ;
714 17         18 $base_file_file =~ s/#.*// ;
715 17 100       38 $path_file =~ s/[^#]+//
716             if ( $path_file_file eq $base_file_file ) ;
717             }
718              
719             # MacOS relative URLs should have no volume.
720 47 50       80 $path_volume =''
721             if ( $fstype eq 'MacOS' ) ;
722 47         89 return joinpath( $path_volume, $path_directory, $path_file ) ;
723             }
724              
725             #
726             # rel2abs: make an absolute pathname from a relative pathname
727             #
728             # Assumes no trailing file name on $base. Ignores it if present on an OS
729             # like $VMS.
730             #
731             # Interface:
732             # i) $path relative path (needed)
733             # i) $base base directory (optional)
734             # r) absolute path of $path
735             #
736             # Note: rel2abs doesn't check if the paths exist.
737             #
738             sub rel2abs($;$;) {
739 0     0 0 0 my( $path, $base ) = @_;
740 0         0 my( $reg );
741              
742 0         0 my( $path_volume, $path_directory, $path_file )= splitpath( $path, 'nofile' ) ;
743 0 0       0 if ( $path_directory =~ /$isrootRE/ ) {
744 0 0       0 warn( "rel2abs: nothing to do: '$path' is absolute" )
745             if $verbose;
746 0         0 return $path;
747             }
748              
749 0 0 0     0 warn( "rel2abs: volume '$path_volume' passed in relative path: \$path" )
750             if ( $path_volume ne '' && $verbose ) ;
751              
752 0 0 0     0 $base = cwd()
753             if ( !defined( $base ) || $base eq '' ) ;
754              
755 0         0 my( $base_volume, $base_directory, $base_file )= splitpath( $base, 'nofile' ) ;
756             # check for a filename, since the nofile parameter does not work for OSs
757             # like VMS that have explicit delimiters between the dir and file portions
758 0 0 0     0 warn( "rel2abs: filename '$base_file' passed in \$base" )
759             if ( $base_file ne '' && $verbose ) ;
760              
761 0 0       0 if ( $base_directory !~ /$isrootRE/ ) {
762             # Make $base absolute
763 0         0 my( $cw_volume, $cw_directory, $dummy ) = splitpath( cwd(), 'nofile' ) ;
764             # maybe we should warn if $cw_volume ne $base_volume and both are not ''
765 0 0 0     0 $base_volume= $cw_volume
766             if ( $base_volume eq '' && $cw_volume ne '' ) ;
767 0         0 $base_directory = join( '', $cw_directory, $sep, $base_directory ) ;
768             }
769              
770 0         0 $path_directory = regularize( $path_directory );
771 0         0 $base_directory = regularize( $base_directory );
772              
773 0         0 my $result_directory ;
774             # Avoid using a separator if either directory component is empty.
775 0 0 0     0 if ( $base_directory ne '' && $path_directory ne '' ) {
776 0         0 $result_directory= joindirs( $base_directory, $path_directory ) ;
777             }
778             else {
779 0         0 $result_directory= "$base_directory$path_directory" ;
780             }
781              
782 0         0 $result_directory = regularize( $result_directory );
783              
784 0         0 return joinpath( $base_volume, $result_directory, $path_file ) ;
785             }
786              
787             #
788             # regularize a path.
789             #
790             # Removes dubious and redundant information.
791             # should only be called on directory portion on OSs
792             # with volumes and with delimeters that separate dir names from file names,
793             # since the separators can take on different semantics, like "\\" for UNC
794             # under Win32, or '.' in filenames under VMS.
795             #
796             sub regularize {
797 158     158 0 292 my( $in )= $_[ 0 ] ;
798              
799             # Combine idempotent separators. Do this first so all other REs only
800             # need to match one separator. Use the first sep found instead of
801             # sepRE to preserve slashes on Win32.
802 158 100       1127 $in =~ s/($sepRE)$sepRE+/$1/g
803             if ( $idempotent ) ;
804              
805             # We do this after deleting redundant separators in order to be consistent.
806             # If a Win32 path ended in \/, we want to be sure that the \ is returned,
807             # no the /.
808 158 100       2432 my $trailing_sep = $in =~ /($sepRE)$sepRE*$/ ? $1 : '';
809              
810             # Delete all occurences of 'name/..(/|$)'. This is done with a while
811             # loop to get rid of things like 'name1/name2/../..'. We chose the pattern
812             # name/../ as the target instead of /name/.. so as to preserve 'rootness'.
813 158         1418 while ($in =~ s/$name_sep_parentRE/$1/g) {}
814            
815             # Get rid of ./ in '^./' and '/./'
816 158         1017 $in =~ s/$dot_sep_etcRE/$1/g ;
817              
818             # Get rid of trailing '/' and '/.' unless it would leave an empty string
819 158         1183 $in =~ s/$trailing_sepRE/$1/ ;
820              
821             # Get rid of '../' constructs from absolute paths
822 158 100       968 $in =~ s/$leading_parentRE/$1/
823             if ( $in =~ /$isrootRE/ ) ;
824              
825             # # Default to current directory if it's now empty.
826             # $in = $thisDir if $_[0] eq '' ;
827             #
828             # Restore trailing separator if it was lost. We do this to preserve
829             # the 'dir-ness' of the path: paths that ended in a separator on entry
830             # should leave with one in case the caller is using trailing slashes to
831             # indicate paths to directories.
832 158 100 100     828 $in .= $trailing_sep
833             if ( $trailing_sep ne '' && $in !~ /$sepRE$/ ) ;
834              
835 158         816 return $in ;
836             }
837              
838             1;
839              
840             __END__