File::PathConvert - DEPRECATED: USE File::Spec and Cwd::abs_path().


File-PathConvert documentation Contained in the File-PathConvert distribution.

Index


Code Index:

NAME

Top

File::PathConvert - DEPRECATED: USE File::Spec and Cwd::abs_path().

SYNOPSIS

Top

    *** THIS MODULE (File::PathConvert) IS DEPRECATED. ****

    There are several known bugs, and it is not being actively
    maintained since all functionality is now available in
    modules (Cwd.pm and File::Spec) bundled in every Perl
    distribution of recent vintage.  This version is provided to
    fix a few bugs and to get the word out about the
    deprecation.

    Please use Cwd::abs_path() instead of
    File::PathConvert::realpath() and File::Spec methods instead
    of all other path manipulation functions in this module.

    If you use setfstype, you probably want to access a File::Spec::Foo
    module for the appropriate operating system.

    There is also a version of File::Spec on CPAN for backwards
    compatability.

    Thank you,

    Barrie Slaymaker <barries@slaysys.com>

BUGS

Top

realpath is not fully multiplatform.

abs2rel is broken in MacOS mode.

LIMITATIONS

Top

AUTHORS

Top

Barrie Slaymaker <barries@slaysys.com> Shigio Yamaguchi <shigio@wafu.netgate.net>


File-PathConvert documentation Contained in the File-PathConvert distribution.
#
# Copyright (c) 1996, 1997, 1998 Shigio Yamaguchi. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
#       File::PathConvert.pm
#

package File::PathConvert;
require 5.002;

use strict ;

BEGIN {
   use Exporter   ();
   use vars       qw($VERSION @ISA @EXPORT_OK);
   $VERSION       = 0.9;
   @ISA           = qw(Exporter);
   @EXPORT_OK     = qw(setfstype splitpath joinpath splitdirs joindirs realpath abs2rel rel2abs $maxsymlinks $verbose $SL $resolved );
}

use vars      qw( $maxsymlinks $verbose $SL $resolved ) ;
use Cwd;

#
# Initialize @EXPORT_OK vars
#
$maxsymlinks   = 32;       # allowed symlink number in a path
$verbose       = 0;        # 1: verbose on, 0: verbose off
$SL            = '' ;      # Separator char export
$resolved      = '' ;      # realpath() intermediate value export

#############################################################################
#
#  Package Globals
#

my $fstype        ; # A name indicating the type of filesystem currently in use
my $sep           ; # separator
my $sepRE         ; # RE to match spearator
my $notsepRE      ; # RE to match anything else
my $volumeRE      ; # RE to match the volume name
my $directoryRE   ; # RE to match the directory name
my $isrootRE      ; # RE to match root path: applied to directory portion only
#my $thisDir       ; # Name of this directory
my $thisDirRE     ; # Name of this directory
my $parentDir     ; # Name of parent directory
my $parentDirRE   ; # RE to match parent dir name
my $casesensitive ; # Set to non-zero for case sensitive name comprisions.  Only
                    # affects names, not any other REs, so $isrootRE for Win32
                    # must be case insensitive
my $idempotent    ; # Set to non-zero if '//' is equivalent to '/'.  This
                    # does not affect leading '//' and '\\' under Win32,
                    # but will fold '///' and '////', etc, in to '//' on this
                    # Win32



###########
#
# The following globals are regexs used in the indicated routines.  These
# are initialized by setfstype, so they don't need to be rebuilt each time
# the routine that uses them is called.

my $basenamesplitRE ; # Used in realpath() to split filenames.


###########
#
# This RE matches (and saves) the portion of the string that is just before
# the beginning of a name
#
my $beginning_of_name ;

#
# This whopper of an RE looks for the pattern "name/.." if it occurs
# after the beginning of the string or after the root RE, or after a separator.
# We don't assume that the isrootRE has a trailing separator.
# It also makes sure that we aren't eliminating '../..' and './..' patterns
# by using the negative lookahead assertion '(?!' ... ')' construct.  It also
# ignores 'name/..name'.
#
my $name_sep_parentRE ;

#
# Matches '..$', '../' after a root
my $leading_parentRE ;

#
# Matches things like '/(./)+' and '^(./)+'
#
my $dot_sep_etcRE ;

#
# Matches trailing '/' or '/.'
#
my $trailing_sepRE ;


#############################################################################
#
#     Functions
#


#
# setfstype: takes the name of an operating system and sets up globals that
#            allow the other functions to operate on multiple OSs.  See 
#            %fsconfig for the sets of settings.
#
#            This is run once on module load to configure for the OS named
#            in $^O.
#
# Interface:
#       i)     $osname, as in $^O or plain english: "MacOS", "DOS, etc.
#              This is _not_ usually case sensitive.
#       r)     Name of recognized name on success else undef.  Note that, as
#              shipped, 'unix' is the default is nothing else matches.
#       go)    $fstype and lots of internal parameters and regexs.
#       x)     Dies if a parameter required in @fsconfig is missing.
#
#
# There are some things I couldn't figure a way to parameterize by setting
# globals. $fstype is checked for filesystem type-specific logic, like 
# VMS directory syntax.
#
# Setting up for a particular OS type takes two steps: identify the OS and
# set all of the 'atomic' global variables, then take some of the atomic
# globals which are regexps and build composite values from them.
#
# The atomic regexp terms are generally used to build the larger composite
# regexps that recognize and break apart paths.  This leads to 
# two important rules for the atomic regexp terms:
#
# (1) Do not use '(' ... ')' in the regex terms, since they are used to build
# regexs that use '(' ... ')' to parse paths.
#
# (2) They must be built so that a '?' or other quantifier may be appended.
# This generally means using the '(?:' ... ')' or '[' ... ']' to group
# multicharacter patterns.  Other '(?' ... ')' may also do.
#
# The routines herein strive to preserve the
# original separator and root settings, and, it turns out, never need to
# prepend root to a string (although they do need to insert separators on
# occasion).  This is good, since the Win32 root expressions can be like
# '/', '\', 'A:/', 'a:/', or even '\\' or '//' for UNC style names.
#
# Note that the default root and default notsep are not used, and so are 
# undefined.
#
# For DOS, MacOS, and VMS, we assume that all paths handed in are on the same
# volume.  This is not a significant limitation except for abs2rel, since the
# absolute path is assumed to be on the same volume as the base path.
#
sub setfstype($;) {
   my( $osname ) = @_ ;

   # Find the best match for OS and set up our atomic globals accordingly
   if ( $osname =~ /^(?:(ms)?(dos|win(32|nt)?))/i )
   {
      $fstype           = 'Win32' ;
      $sep              = '/' ;
      $sepRE            = '[\\\\/]' ;
      $notsepRE         = '[^\\\\/]' ;
      $volumeRE         = '(?:^(?:[a-zA-Z]:|(?:\\\\\\\\|//)[^\\\\/]+[\\\\/][^\\\\/]+)?)' ;
      $directoryRE      = '(?:(?:.*[\\\\/](?:\.\.?$)?)?)' ;
      $isrootRE         = '(?:^[\\\\/])' ;
#      $thisDir          = '.' ;
      $thisDirRE        = '\.' ;
      $parentDir        = '..' ;
      $parentDirRE      = '(?:\.\.)' ;
      $casesensitive    = 0 ;
      $idempotent       = 1 ;
   }
   elsif ( $osname =~ /^MacOS$/i )
   {
      $fstype           = 'MacOS' ;
      $sep              = ':' ;
      $sepRE            = '\:' ;
      $notsepRE         = '[^:]' ;
      $volumeRE         = '(?:^(?:[^:]+:)?)' ;
      $directoryRE      = '(?:(?:.*:)?)' ;
      $isrootRE         = '(?:^(?=[^:].*:)?)' ;
#      $thisDir          = '' ;
      $thisDirRE        = 'cantpossiblymatchthis' ;
      $parentDir        = '' ;
      $parentDirRE      = '(?=(?<=:):)' ;
      $casesensitive    = 0 ;
      $idempotent       = 0 ;
   }
   elsif ( $osname =~ /^VMS$/i )
   {
      $fstype           = 'VMS' ;
      $sep              = '.' ;
      $sepRE            = '[\.\]]' ;
      $notsepRE         = '[^\.\]]' ;
      # volume is node::volume:, where node:: and volume: are optional 
      # and node:: cannot be present without volume.  node can include
      # an access control string in double quotes.
      # Not supported:
      #     quoted full node names
      #     embedding a double quote in a string ("" to put " in)
      #     support ':' in node names
      #     foreign file specifications
      #     task specifications
      #     UIC Directory format (use the 6 digit name for it, instead)
      $volumeRE         = '(?:^(?:(?:[\w\$-]+(?:"[^"]*")?::)?[\w\$-]+:)?)' ;
      $directoryRE      = '(?:(?:\[.*\])?)' ;

      # Root is the lack of a leading '.', unless string is empty, which
      # means 'cwd', which is relative.
      $isrootRE         = '(?:^[^\.])' ;
#      $thisDir          = '' ;
      $thisDirRE        = '\[\]' ;
      $parentDir        = '-' ;
      $parentDirRE      = '-' ;
      $casesensitive    = 0 ;
      $idempotent       = 0 ;
   }
   elsif ( $osname =~ /^URL$/i )
   {
      # URL spec based on RFC2396 (ftp://ftp.isi.edu/in-notes/rfc2396.txt)
      $fstype           = 'URL' ;
      $sep              = '/' ;
      $sepRE            = '/' ;
      $notsepRE         = '[^/]' ;
      # Volume= scheme + authority, both optional
      $volumeRE         = '(?:^(?:[a-zA-Z][a-zA-Z0-9+-.]*:)?(?://[^/?]*)?)' ;

      # Directories do _not_ include the query component: we pretend that 
      # anything after a "?" is the filename or part of it.  So a '/'
      # terminates and is part of the directory spec, while a '?' or '#'
      # terminate and are not part of the directory spec.
      #
      # We pretend that ";param" syntax does not exist
      #
      $directoryRE      = '(?:(?:[^?#]*/(?:\.\.?(?:$|(?=[?#])))?)?)' ;
      $isrootRE         = '(?:^/)' ;
#      $thisDir          = '.' ;
      $thisDirRE        = '\.' ;
      $parentDir        = '..' ;
      $parentDirRE      = '(?:\.\.)' ;
      # Assume case sensitive, since many (most?) are.  The user can override 
      # this if they so desire.
      $casesensitive    = 1 ;
      $idempotent       = 1 ;
   }
   else
   { 
      $fstype           = 'Unix' ;
      $sep              = '/' ;
      $sepRE            = '/' ;
      $notsepRE         = '[^/]' ;
      $volumeRE         = '' ;
      $directoryRE      = '(?:(?:.*/(?:\.\.?$)?)?)' ;
      $isrootRE         = '(?:^/)' ;
#      $thisDir          = '.' ;
      $thisDirRE        = '\.' ;
      $parentDir        = '..' ;
      $parentDirRE      = '(?:\.\.)' ;
      $casesensitive    = 1 ;
      $idempotent       = 1 ;
   }

   # Now set our composite regexps.

   # Maintain old name for backward compatibility
   $SL= $sep ;

   # Build lots of REs used below, so they don't need to be built every time
   # the routines that use them are called.
   $basenamesplitRE   = '^(.*)' . $sepRE . '(' . $notsepRE . '*)$' ;

   $leading_parentRE  = '(' . $isrootRE . '?)(?:' . $parentDirRE . $sepRE . ')*(?:' . $parentDirRE . '$)?' ;
   $trailing_sepRE    = '(.)' . $sepRE . $thisDirRE . '?$' ;

   $beginning_of_name = '(?:^|' . $isrootRE . '|' . $sepRE . ')' ;

   $dot_sep_etcRE     = 
      '(' . $beginning_of_name . ')(?:' . $thisDirRE . $sepRE . ')+';

   $name_sep_parentRE = 
      '(' . $beginning_of_name . ')'
      . '(?!(?:' . $thisDirRE . '|' . $parentDirRE . ')' . $sepRE . ')'
      . $notsepRE . '+' 
      . $sepRE . $parentDirRE 
      . '(?:' . $sepRE . '|$)'
      ;

   if ( $verbose ) {
      print( <<TOHERE )  ;
fstype        = "$fstype"
sep           = "$sep"
sepRE         = /$sepRE/
notsepRE      = /$notsepRE/
volumeRE      = /$volumeRE/
directoryRE   = /$directoryRE/
isrootRE      = /$isrootRE/
thisDirRE     = /$thisDirRE/
parentDir     = "$parentDir"
parentDirRE   = /$parentDirRE/
casesensitive = "$casesensitive"
TOHERE
#thisDir       = "$thisDir"
   }

   return $fstype ;
}


setfstype( $^O ) ;


#
# splitpath: Splits a path into component parts: volume, dirpath, and filename.
#
#           Very much like File::Basename::fileparse(), but doesn't concern
#           itself with extensions and knows about volume names.
#
#           Returns ($volume, $directory, $filename ).
#
#           The contents of the returned list varies by operating system.
#
#           Unix:
#              $volume: always ''
#              $directory: up to, and including, final '/'
#              $filename: after final '/'
#
#           Win32:
#              $volume: drive letter and ':', if present
#              $directory and $filename are like on Unix, but '\' and '/' are
#              equivalent and the $volume is not in $directory..
#
#           VMS:
#              $volume: up to and including first ":"
#              $directory: "[...]" component
#              $filename: the rest.
#              $nofile is ignored
#
#           URL:
#              $volume: up to ':', then '//stuff/morestuff'.  No trailing '/'.
#              $directory: after $volume, up to last '/'
#              $filename: the rest.
#              $nofile is ignored
#
# Interface:
#       i)     $path
#       i)     $nofile: if true, then any trailing filename is assumed to
#              belong to the directory for non-VMS systems.
#       r)     list of ( $volume, $directory, $filename ).
#
sub splitpath {
   my( $path, $nofile )= @_ ;
   my( $volume, $directory, $file ) ;
   if ( $fstype ne 'VMS' && $fstype ne 'URL' && $nofile ) {
      $path =~ m/($volumeRE)(.*)$/ ;
      $volume   = $1 ;
      $directory= $2 ;
      $file     = '' ;
   }
   else {
      $path =~ m/($volumeRE)($directoryRE)(.*)$/ ;
      $volume   = $1 ;
      $directory= $2 ;
      $file     = $3 ;
   }

   # For Win32 UNC, force the directory portion to be non-empty. This is
   # because all UNC names are absolute, even if there's no trailing separator
   # after the sharename.
   #
   # This is a bit of a hack, necesitated by the implementation of $isrootRE,
   # which is only applied to the directory portion.
   #
   # A better long term solution might be to make the isroot test a member 
   # function in the future, object-oriented version of this.
   #
   $directory = $1
     if ( $fstype eq 'Win32' && $volume =~ /^($sepRE)$sepRE/ && $directory eq '' ) ;

   return ( $volume, $directory, $file ) ;
}


#
# joinpath: joins the results of splitpath().  Not really necessary now, but
# good to have:
#
#     - API completeness
#     - Self documenting code
#     - Future handling of other filesystems
#
# For instance, if you leave the ':' or the '[' and ']' out of VMS $volume
# and $directory strings, this patches it up.  If you leave out the '['
# and provide the ']', or vice versa, it is not cleaned up.  This is
# because it's useful to automatically insert both '[' and ']', but if you
# leave off only one, it's likely that there's a bug elsewhere that needs
# looking in to.
#
# Automatically inserts a separator between directory and filename if needed
# for non-VMS OSs.
#
# Automatically inserts a separator between volume and directory or file 
# if needed for Win32 UNC names.
#
sub joinpath($;$;$;) {
   my( $volume, $directory, $filename )= @_ ;

   # Fix up delimiters for $volume and $directory as needed for various OSs
   if ( $fstype eq 'VMS' ) {
      $volume .= ':'
         if ( $volume ne '' && $volume !~ m/:$/ ) ;

      $directory = join( '', ( '[', $directory, ']' ) )
         if ( $directory ne '' && $directory !~ m/^\[.*\]$/ ) ;
   }
   else {
      # Add trailing separator to directory names that require it and
      # need it.  URLs always require it if there are any directory
      # components.
      $directory .= $sep
         if (  $directory ne '' 
            && ( $fstype eq 'URL' || $filename ne '' )
            && $directory !~ m/$sepRE$/ 
            ) ;

      # Add trailing separator to volume for UNC and HTML volume
      # names that lack it and need it.
      # Note that if a URL volume is a scheme only (ends in ':'),
      # we don't require a separator: it's a relative URL.
      $volume .= $sep
         if (  (  ( $fstype eq 'Win32' && $volume =~ m#^$sepRE{2}# )
               || ( $fstype eq 'URL'   && $volume =~ m#[^:/]$#     )
               )
            && $volume    !~ m#$sepRE$#  
            && $directory !~ m#^$sepRE#      
            && ( $directory ne '' || $filename ne '' )
            ) ;
   }

   return join( '', $volume, $directory, $filename ) ;
}


#
# splitdirs: Splits a string containing directory portion of a path
# in to component parts.  Preserves trailing null entries, unlike split().
#
# "a/b" should get you [ 'a', 'b' ]
#
# "a/b/" should get you [ 'a', 'b', '' ]
#
# "/a/b/" should get you [ '', 'a', 'b', '' ]
#
# "a/b" returns the same array as 'a/////b' for those OSs where
# the seperator is idempotent (Unix and DOS, at least, but not VMS).
#
# Interface:
#     i) directory path string
#
sub splitdirs($;) {
   my( $directorypath )= @_ ;

   $directorypath =~ s/^\[(.*)\]$/$1/
      if ( $fstype eq 'VMS' ) ;

   #
   # split() likes to forget about trailing null fields, so here we
   # check to be sure that there will not be any before handling the
   # simple case.
   #
   return split( $sepRE, $directorypath )
      if ( $directorypath !~ m/$sepRE$/ ) ;

   #
   # since there was a trailing separator, add a file name to the end, then
   # do the split, then replace it with ''.
   #
   $directorypath.= "file" ;
   my( @directories )= split( $sepRE, $directorypath ) ;
   $directories[ $#directories ]= '' ;

   return @directories ;
}

#
# joindirs: Joins an array of directory names in to a string, adding
# OS-specific delimiters, like '[' and ']' for VMS.
#
# Note that empty strings '' are no different then non-empty strings,
# but that undefined strings are skipped by this algorithm.
#
# This is done the hard way to preserve separators that are already
# present in any of the directory names.
#
# Could this be made faster by using a join() followed 
# by s/($sepRE)$sepRE+/$1/g?
#
# Interface:
#     i) array of directory names
#     o) string representation of directory path
#
sub joindirs {
   my $directory_path ;

   $directory_path = shift
      while ( ! defined( $directory_path ) && @_ ) ;

   if ( ! defined( $directory_path ) ) {
      $directory_path = '' ;
   }
   else {
      local $_ ;

      for ( @_ ) {
        next if ( ! defined( $_ ) ) ;

        $directory_path .= $sep
           if ( $directory_path !~ /$sepRE$/ && ! /^$sepRE/ ) ;

        $directory_path .= $_ ;
      }
   }

   $directory_path = join( '', '[', $directory_path, ']' )
      if ( $fstype eq 'VMS' ) ;

   return $directory_path ;
}


#
# realpath: returns the canonicalized absolute path name
#
# Interface:
#       i)      $path   path
#       r)              resolved name on success else undef
#       go)     $resolved
#                       resolved name on success else the path name which
#                       caused the problem.
$resolved = '';
#
#       Note: this implementation is based 4.4BSD version realpath(3).
#
# TODO: Speed up by using Cwd::abs_path()?
#
sub realpath($;) {
    ($resolved) = @_;
    my($backdir) = cwd();
    my($dirname, $basename, $links, $reg);

    $resolved = regularize($resolved);
LOOP:
    {
        #
        # Find the dirname and basename.
        # Change directory to the dirname component.
        #
        if ($resolved =~ /$sepRE/) {
            ($dirname, $basename) = $resolved =~ /$basenamesplitRE/ ;
            $dirname = $sep if ( $dirname eq '' );
            $resolved = $dirname;
            unless (chdir($dirname)) {
                warn("realpath: chdir($dirname) failed: $! (in ${\cwd()}).") if $verbose;
                chdir($backdir);
                return undef;
            }
        } else {
            $dirname = '';
            $basename = $resolved;
        }
        #
        # If it is a symlink, read in the value and loop.
        # If it is a directory, then change to that directory.
        #
        if ( $basename ne '' ) {
            if (-l $basename) {
                unless ($resolved = readlink($basename)) {
                    warn("realpath: readlink($basename) failed: $! (in ${\cwd()}).") if $verbose;
                    chdir($backdir);
                    return undef;
                }
                $basename = '';
                if (++$links > $maxsymlinks) {
                    warn("realpath: too many symbolic links: $links.") if $verbose;
                    chdir($backdir);
                    return undef;
                }
                redo LOOP;
            } elsif (-d _) {
                unless (chdir($basename)) {
                    warn("realpath: chdir($basename) failed: $! (in ${\cwd()}).") if $verbose;
                    chdir($backdir);
                    return undef;
                }
                $basename = '';
            }
        }
    }
    #
    # Get the current directory name and append the basename.
    #
    $resolved = cwd();
    if ( $basename ne '' ) {
        $resolved .= $sep if ($resolved ne $sep);
        $resolved .= $basename
    }
    chdir($backdir);
    return $resolved;
} # end sub realpath


#
# abs2rel: make a relative pathname from an absolute pathname
#
# Interface:
#       i)      $path   absolute path(needed)
#       i)      $base   base directory(optional)
#       r)              relative path of $path
#
#       Note:   abs2rel doesn't check whether the specified path exist or not.
#
sub abs2rel($;$;) {
    my($path, $base) = @_;
    my($reg );

    my( $path_volume, $path_directory, $path_file )= splitpath( $path,'nofile');
    if ( $path_directory !~ /$isrootRE/ ) {
        warn("abs2rel: nothing to do: '$path' is relative.") if $verbose;
        return $path;
    }

    $base = cwd()
        if ( !defined( $base ) || $base eq '' ) ;

    my( $base_volume, $base_directory, $base_file )= splitpath( $base,'nofile');
    # check for a filename, since the nofile parameter does not work for OSs
    # like VMS that have explicit delimiters between the dir and file portions
    warn( "abs2rel: filename '$base_file' passed in \$base" )
       if ( $base_file ne '' && $verbose ) ;

    if ( $base_directory !~ /$isrootRE/ ) {
        # Make $base absolute
        my( $cw_volume, $cw_directory, $dummy ) = splitpath( cwd(), 'nofile' ) ;
        # maybe we should warn if $cw_volume ne $base_volume and both are not ''
        $base_volume= $cw_volume
           if ( $base_volume eq '' && $cw_volume ne '' ) ;
        $base_directory = join( '', $cw_directory, $sep, $base_directory ) ;
    }

    $path_directory = regularize( $path_directory );
    $base_directory = regularize( $base_directory );
    # Now, remove all leading components that are the same, so 'name/a'
    # 'name/b' become 'a' and 'b'.
    my @pathchunks = split($sepRE, $path_directory);
    my @basechunks = split($sepRE, $base_directory);

    if ( $casesensitive ) 
    {
        while (@pathchunks && @basechunks && $pathchunks[0] eq $basechunks[0]) 
        {
            shift @pathchunks ;
            shift @basechunks ;
        }
    }
    else {
        while (  @pathchunks 
              && @basechunks 
              && lc( $pathchunks[0] ) eq lc( $basechunks[0] ) 
              ) 
        {
            shift @pathchunks ;
            shift @basechunks ;
        }
    }

    # No need to use joindirs() here, since we know that the arrays
    # are well formed.
    $path_directory= join( $sep, @pathchunks );
    $base_directory= join( $sep, @basechunks );

    # Convert $base_directory from absolute to relative
    if ( $fstype eq 'VMS' ) {
        $base_directory= $sep . $base_directory
            if ( $base_directory ne '' ) ;
    }
    else {
        $base_directory=~ s/^$sepRE// ;
    }

    # $base_directory now contains the directories the resulting relative path 
    # must ascend out of before it can descend to $path_directory.  So, 
    # replace all names with $parentDir
    $base_directory =~ s/$notsepRE+/$parentDir/g ;

    # Glue the two together, using a separator if necessary, and preventing an
    # empty result.
    if ( $path_directory ne '' && $base_directory ne '' ) {
        $path_directory = "$base_directory$sep$path_directory" ;
    } else {
        $path_directory = "$base_directory$path_directory" ;
    }

    $path_directory = regularize( $path_directory ) ;

    # relative URLs should have no name in the volume, only a scheme.
    if ( $fstype eq 'URL' ) {
        $path_volume =~ s#/.*## ;
        # NOTE: if case insensitive URLs are ever done, this next comparison
        # needs to be tweaked.
        my $path_file_file = $path_file ;
        $path_file_file =~ s/#.*// ;
        my $base_file_file = $base_file ;
        $base_file_file =~ s/#.*// ;
        $path_file =~ s/[^#]+//
            if ( $path_file_file eq $base_file_file ) ;
    }

    # MacOS relative URLs should have no volume.
    $path_volume =''
        if ( $fstype eq 'MacOS' ) ;
    return joinpath( $path_volume, $path_directory, $path_file ) ;
}

#
# rel2abs: make an absolute pathname from a relative pathname
#
# Assumes no trailing file name on $base.  Ignores it if present on an OS
# like $VMS.
#
# Interface:
#       i)      $path   relative path (needed)
#       i)      $base   base directory  (optional)
#       r)              absolute path of $path
#
#       Note:   rel2abs doesn't check if the paths exist.
#
sub rel2abs($;$;) {
    my( $path, $base ) = @_;
    my( $reg );

    my( $path_volume, $path_directory, $path_file )= splitpath( $path, 'nofile' ) ;
    if ( $path_directory =~ /$isrootRE/ ) {
        warn( "rel2abs: nothing to do: '$path' is absolute" ) 
            if $verbose;
        return $path;
    }

    warn( "rel2abs: volume '$path_volume' passed in relative path: \$path" )
        if ( $path_volume ne '' && $verbose ) ;

    $base = cwd()
        if ( !defined( $base ) || $base eq '' ) ;

    my( $base_volume, $base_directory, $base_file )= splitpath( $base, 'nofile' ) ;
    # check for a filename, since the nofile parameter does not work for OSs
    # like VMS that have explicit delimiters between the dir and file portions
    warn( "rel2abs: filename '$base_file' passed in \$base" )
        if ( $base_file ne '' && $verbose ) ;

    if ( $base_directory !~ /$isrootRE/ ) {
        # Make $base absolute
        my( $cw_volume, $cw_directory, $dummy ) = splitpath( cwd(), 'nofile' ) ;
        # maybe we should warn if $cw_volume ne $base_volume and both are not ''
        $base_volume= $cw_volume
            if ( $base_volume eq '' && $cw_volume ne '' ) ;
        $base_directory = join( '', $cw_directory, $sep, $base_directory ) ;
    }

    $path_directory = regularize( $path_directory );
    $base_directory = regularize( $base_directory );

    my $result_directory ;
    # Avoid using a separator if either directory component is empty.
    if ( $base_directory ne '' && $path_directory ne '' ) {
        $result_directory= joindirs( $base_directory, $path_directory ) ;
    }
    else {
        $result_directory= "$base_directory$path_directory" ;
    }

    $result_directory = regularize( $result_directory );

    return joinpath( $base_volume, $result_directory, $path_file ) ;
}

#
# regularize a path.  
#
#    Removes dubious and redundant information. 
#    should only be called on directory portion on OSs
#    with volumes and with delimeters that separate dir names from file names,
#    since the separators can take on different semantics, like "\\" for UNC
#    under Win32, or '.' in filenames under VMS.
#
sub regularize {
    my( $in )= $_[ 0 ] ;

    # Combine idempotent separators.  Do this first so all other REs only
    # need to match one separator. Use the first sep found instead of
    # sepRE to preserve slashes on Win32.
    $in =~ s/($sepRE)$sepRE+/$1/g
        if ( $idempotent ) ;

    # We do this after deleting redundant separators in order to be consistent.
    # If a Win32 path ended in \/, we want to be sure that the \ is returned,
    # no the /.
    my $trailing_sep = $in =~ /($sepRE)$sepRE*$/ ? $1 : '';

    # Delete all occurences of 'name/..(/|$)'.  This is done with a while
    # loop to get rid of things like 'name1/name2/../..'. We chose the pattern
    # name/../ as the target instead of /name/.. so as to preserve 'rootness'.
    while ($in =~ s/$name_sep_parentRE/$1/g) {}
   
    # Get rid of ./ in '^./' and '/./'
    $in =~ s/$dot_sep_etcRE/$1/g ;

    # Get rid of trailing '/' and '/.' unless it would leave an empty string
    $in =~ s/$trailing_sepRE/$1/ ;

    # Get rid of '../' constructs from absolute paths
    $in =~ s/$leading_parentRE/$1/
      if ( $in =~ /$isrootRE/ ) ;

#    # Default to current directory if it's now empty.
#    $in = $thisDir if $_[0] eq '' ;
#
    # Restore trailing separator if it was lost. We do this to preserve
    # the 'dir-ness' of the path: paths that ended in a separator on entry
    # should leave with one in case the caller is using trailing slashes to
    # indicate paths to directories.
    $in .= $trailing_sep
        if ( $trailing_sep ne '' && $in !~ /$sepRE$/ ) ;

    return $in ;
}

1;

__END__

#    use File::PathConvert qw(realpath abs2rel rel2abs setfstype splitpath 
#      joinpath splitdirs joindirs $resolved);
#
#    $relpath = abs2rel($abspath);
#    $abspath = abs2rel($abspath, $base);
#
#    $abspath = rel2abs($relpath);
#    $abspath = rel2abs($relpath, $base);
#
#    $path = realpath($logpath) || die "resolution stopped at $resolved";
#
#    ( $volume, $directory, $filename )= splitpath( $path ) ;
#    ( $volume, $directory, $filename )= splitpath( $path, 'nofile' ) ;
#
#    $path= joinpath( $volume, $directory, $filename ) ;
#
#    @directories= splitdirs( $directory ) ;
#    $directory= joindirs( @directories ) ;
#
#=head1 DESCRIPTION
#
#File::PathConvert provides functions to convert between absolute and
#relative paths, and from logical paths to physical paths on a variety of
#filesystems, including the URL 'filesystem'.
#
#Paths are decomposed internally in to volume, directory, and, sometimes
#filename portions as appropriate to the operation and filesystem, then
#recombined.  This preserves the volume and filename portions so that they may
#be returned, and prevents them from interfering with the path conversions.  
#
#Here are some examples of path decomposition.  A '****' in a column indicates
#the column is not used in C<abs2rel> and C<rel2abs> functions for that
#filesystem type.
#
#
#    FS      VOLUME                  Directory       filename
#    ======= ======================= =============== =============
#    URL     http:                   /a/b/           c?query
#            http://fubar.com        /a/b/           c?query
#            //p.d.q.com             /a/b/c/         ?query
#
#    VMS     Server::Volume:         [a.b]           c
#            Server"access spec"::   [a.b]           c
#            Volume:                 [a.b]           c
#
#    Win32   A:                      \a\b\c          ****
#            \\server\Volume         \a\b\c          ****
#            \\server\Volume         \a/b/c          ****
#
#    Unix    ****                    \a\b\c          ****
#
#    MacOS   Volume::                a:b:c           ****
#
#Many more examples abound in the test.pl included with this module.
#
#Only the VMS and URL filesystems indicate if the last name in a path is a
#directory or file.  For other filesystems, all non-volume names are assumed to
#be directory names.  For URLs, the last name in a path is assumed to be a
#filename unless it ends in '/', '/.', or '/..'.   
#
#Other assumptions are made as well, especially MacOS and VMS. THESE MAY CHANGE
#BASED ON PROGRAMMER FEEDBACK!
#
#The conversion routines C<abs2rel>, C<rel2abs>, and C<realpath> are the 
#main focus of this package.  C<splitpath> and C<joinpath> are provided to 
#allow volume oriented filesystems (almost anything non-unixian, actually)
#to be accomodated.  C<splitdirs> and C<joindirs> provide directory path
#grammar parsing and encoding, which is especially useful for VMS.
#
#=over 4
#
#=item setfstype
#
#This is called automatically on module load to set the filesystem type
#according to $^O. The user can call this later set the filesystem type
#manually.  If the name is not recognized, unix defaults are used.  Names
#matching /^URL$/i, /^VMS$/i, /^MacOS$/i, or /^(ms)?(win|dos)/32|nt)?$/i yield
#the appropriate (hopefully) filesystem settings.  These strings may be
#generalized in the future.
#
#Examples:
#
#    File::PathConvert::setfstype( 'url' ) ; 
#    File::PathConvert::setfstype( 'Win32' ) ;
#    File::PathConvert::setfstype( 'HAL9000' ) ; # Results in Unix default
#
#=item abs2rel
#
#C<abs2rel> converts an absolute path name to a relative path:
#converting /1/2/3/a/b/c relative to /1/2/3 returns a/b/c
#
#    $relpath= abs2rel( $abspath ) ;
#    $relpath= abs2rel( $abspath, $base ) ;
#
#If $abspath is already relative, it is returned unchanged.  Otherwise the
#relative path from $base to $abspath is returned.  If $base is undefined the
#current directory is used.
#
#The volume and filename portions of $base are ignored if present.  
#If $abspath and $base are on different volumes, the volume from $abspath is
#used.
#
#No filesystem calls are made except for getting the current working directory
#if $base is undefined, so symbolic links are not checked for or resolved, and
#no check is done for existance.
#
#Examples
#
#    # Unix
#    'a/b/c' == abs2rel( 'a/b/c', $anything )
#    'a/b/c' == abs2rel( '/1/2/3/a/b/c', '/1/2/3' )
#
#    # DOS
#    'a\\b/c' == abs2rel( 'a\\b/c', $anything )
#    'a\\b/c' == abs2rel( '/1\\2/3/a\\b/c', '/1/2/3' )
#
#    # URL
#    'http:a/b/c'           == abs2rel( 'http:a/b/c', $anything ) 
#    'http:a/b/c'           == abs2rel( 'http:/1/2/3/a/b/c',
#                                       'ftp://t.org/1/2/3/?z' )
#    'http:a/b/c?q'         == abs2rel( 'http:/1/2/3/a/b/c/?q',
#                                       'ftp://t.org/1/2/3?z'  )
#    'http://s.com/a/b/c?q' == abs2rel( 'http://s.com/1/2/3/a/b/c?q',
#                                       'ftp://t.org/1/2/3/?z')
#
#=item rel2abs
#
#C<rel2abs> makes converts a relative path name to an absolute path: 
#converting a/b/c relative to /1/2/3 returns /1/2/3/a/b/c.
#
#    $abspath= rel2abs( $relpath ) ;
#    $abspath= rel2abs( $relpath, $base ) ;
#
#If $relpath is already absolute, it is returned unchanged.  Otherwise $relpath
#is taken to be relative to $base and the resulting absolute path is returned.
#If $base is not supplied, the current working directory is used.
#
#The volume portion of $relpath is ignored.  The filename portion of $base is
#also ignored. The volume from $base is returned if present. The filename
#portion of $abspath is returned if present.
#
#No filesystem calls are made except for getting the current working directory
#if $base is undefined, so symbolic links are not checked for or resolved, and
#no check is done for existance.
#
#C<rel2abs> will not return a path of the form "./file".
#
#Examples
#
#    # Unix
#    '/a/b/c'       == rel2abs( '/a/b/c', $anything )
#    '/1/2/3/a/b/c' == rel2abs( 'a/b/c', '/1/2/3' )
#
#    # DOS
#    '\\a\\b/c'                == rel2abs( '\\a\\b/c', $anything )
#    '/1\\2/3\\a\\b/c'         == rel2abs( 'a\\b/c', '/1\\2/3' )
#    'C:/1\\2/3\\a\\b/c'       == rel2abs( 'D:a\\b/c', 'C:/1\\2/3' )
#    '\\\\s\\v/1\\2/3\\a\\b/c' == rel2abs( 'D:a\\b/c', '\\\\s\\v/1\\2/3' )
#
#    # URL
#    'http:/a/b/c?q'            == rel2abs( 'http:/a/b/c?q', $anything )
#    'ftp://t.org/1/2/3/a/b/c?q'== rel2abs( 'http:a/b/c?q',
#                                           'ftp://t.org/1/2/3?z' )
#
#
#=item realpath
#
#C<realpath> makes a canonicalized absolute pathname and
#resolves all symbolic links, extra ``/'' characters, and references
#to /./ and /../ in the path.
#C<realpath> resolves both absolute and relative paths.
#It returns the resolved name on success, otherwise it returns undef
#and sets the valiable C<$File::PathConvert::resolved> to the pathname
#that caused the problem.
#
#All but the last component of the path must exist.
#
#This implementation is based on 4.4BSD realpath(3).  It is not tested under
#other operating systems at this time.
#
#If '/sys' is a symbolic link to '/usr/src/sys':
#
#    chdir('/usr');
#    '/usr/src/sys/kern' == realpath('../sys/kern');
#    '/usr/src/sys/kern' == realpath('/sys/kern');
#
#=item splitpath
#
#To be written...
#
#=item joinpath
#
#To be written...
#
#Note that C<joinpath( splitpath( $path ) )> usually yields path.  URLs
#with directory components ending in '/.' or '/..' will be fixed 
#up to end in '/./' and '/../'.
#
#=item splitdirs
#
#To be written...
#
#=item joindirs
#
#
#=back