The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#
#===============================================================================
#
#         FILE:  RelDir.pm
#
#  DESCRIPTION:  This module provides a mechanism to determine the relative
#                path between two directory names.
#
#        FILES:  ---
#         BUGS:  ---
#        NOTES:  ---
#       AUTHOR:   (Dave Roberts), <droberts@cpan.org>
#      COMPANY:
#      VERSION:  1.0
#      CREATED:  15/04/2010 20:30:39 GMT
#     REVISION:  $Revision: 1.0 $
#===============================================================================

use strict;
use warnings;
package File::RelDir;
use Carp;
require Exporter;

our @ISA = qw(Exporter);
our %EXPORT_TAGS = ( 'all' => [ qw( ) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = qw(
  
);

our $VERSION = "0.1";
sub Version {
return $VERSION;
}

sub New {
  if ($#_ != 1){
     printf "_  %s\n",$#_;
     carp "Usage: File::Repl->New(\$dir)";
  }
  my $class = shift;
  my($r);
  $r->{dira} = $_[0];
  ($r->{patha},$r->{w32a})=&_dc($r->{dira});
  bless  $r, $class;
  return $r;
}

sub _dc {   # Directory Check - sanity check the directory name
	my($dir) = @_;
	my($c) = $dir =~ s/\\/\//g;  # use forward slash as directory seperator
        if ($c>0){
		return $dir,1;   # return win32 status if \ directory seperators
	}elsif ($dir =~ m/^[a-z]{1}:/){
		return $dir,2;   # return win32 status if drive letter leads path
	}else{
		return $dir,0;
	}
	carp "sub _dc failed";
}

sub Path {
    my($r,$dirb,$w);
  if ( scalar(@_) eq 2 ) {
    ($r,$dirb) =@_;
}else{
     carp "Usage: \$ref->Path(\$dir)";
}
  $r->{dirb} = $dirb;
  ($r->{pathb},$r->{w32b})=&_dc($r->{dirb});
  # Set windows flag - drives case insensitive testing
  $w = $r->{w32a} || $r->{w32b} || 0;

# split both paths by dir seperators
   my(@dira,@dirb,$i,$j,$relpath);
   @dira = split(/\//,$r->{patha});
   @dirb = split(/\//,$r->{pathb});
   unless( $dira[0] eq $dirb[0] ){
	my($err) = "different file systems or not absolute paths";
	# for unix these values will be null ("")
	# for win32 a drive letter (need to deal with case differences here
	#if ( $dira[0] =~ m/^[a-z]{1}:$/i ){  # test logic for a drive letter
	if ( $dira[0] =~ m/^[a-z]{1}:$/i ){  # test logic for a drive letter
	   unless (lc($dira[0]) eq lc($dirb[0])){
		   carp $err;
		   return 0;
	   }
	}else{
	   carp $err;
		   return 0;
        }
}
my($min) = $#dira;
$min = $#dirb if ($#dirb < $min);
$j=-1;

for($i=0;$i<=$min;$i++){
	if (($w == 0) & ($dira[$i] eq $dirb[$i])){
		#	print "segment $i identical ( $dira[$i] eq $dirb[$i] )\n";
		$j=$i;
	}elsif (($w >= 1) & (lc($dira[$i]) eq lc($dirb[$i]))){
		#	print "segment $i identical ( $dira[$i] eq $dirb[$i] )\n";
		$j=$i;
	}else{
		#print "segment $i differ\n";
		last;
	}
}
if ($j >= 0){
  $i = $#dira - $j;
    $relpath = "." if ($i == 0);
    $relpath = sprintf "../" x $i unless ($i == 0);
    for ($i=$j+1;$i<=$#dirb;$i++){
      $relpath .= "/" unless ($relpath =~ m/\/$/);
      $relpath .= $dirb[$i];
    }

}else{
   carp "different file systems or not absolute paths";
}
$relpath =~ s/\//\\/g if ($r->{w32a} == 1);
return $relpath;

}

sub Diff ($$) {
# provide a relartive pathname from direcrtory a to directory b
  my ($dira, $dirb) = @_;
  my($ref)=File::RelDir->New($dira);
  return $ref->Path($dirb);
  }
1;

__END__

=head1 NAME

File::RelDir - Perl module that returns relative path between two directories

=head1 SYNOPSIS

   use File::RelDir;

   $ref=File::RelDir->New("/tmp/here");

   $relative_path = $ref->Path("/tmp/there");

   #returns "../there"

   $ref=File::RelDir->New('d:\users\me\excel');

   $relative_path = $ref->Path('d:\users\me\word');

   #returns '../word'

   $relative_path = File::RelDir->Diff('d:\users\me\excel',  e:\users\me\word');

   #returns undef

   $relative_path = File::RelDir->Diff('d:\users\me\excel',  d:\users\me\word');

   #returns '../word'


=head1 DESCRIPTION

The File:RelDir provides a mechanism to determine the relative path between two directory structures.  It honours case sensitivity unless one (or both) the paths compared appears to be a windows path, when it becomes case insensitive.

It returns undef when no similarity between the directories supplied is determined.

=head1 METHODS

=over 2

=item B<New(DirA)>

The B<New> method establishes a reference based on the directory name provided.

=item B<Path(DirB)>

The B<Path> method returns the relative path to the directory name supplied, or undef in the event the
two directories are not compatible.

The case of the relative path is retained (wether a windows style case insensitive directory comparison was performed). The directory seperator will be forward slash based (/), unless a backslash (\) based
directory path is provided to the B<New> call. 

=item B<Diff(DirA,DirB)>

The B<Diff> method provides a simpler method of achieving the same end for the occasional relative path, use B<New> and B<Path> if you have multiple calls on the same base directory.

=back

=head1 REQUIRED MODULES

None.

=head1 AUTHOR

Dave Roberts <droberts@cpan.org>

=head1 SUPPORT

You can send bug reports and suggestions for improvements on this module
to me at droberts@cpan.org. However, I can't promise to offer
any other support for this package.

=head1 COPYRIGHT

This module is Copyright © 2012 Dave Roberts. All rights reserved.

This script is free software; you can redistribute it and/or modify it
under the same terms as Perl itself. This script is distributed in the
hope that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. The copyright holder of this script can not be held liable
for any general, special, incidental or consequential damages arising
out of the use of the script.

=head1 CHANGE HISTORY