#!/usr/bin/perl -w

#******************************************************************************
# dirdiff 0.0.2 -- diff all the files in two directories against each other
#
#   Copyright  1998 by Dustin Laurence. All rights reserved.
#
# To do:
#
#	* Allow specification of switches to be passed through to diff so you can
#	  do context diffs.
#
#******************************************************************************

# Try not to be a scripting language :-)

	use diagnostics;
	use English;
	use strict;

# Packages

#	use Cwd;

# Declare subroutines

	sub Usage;
	sub ValidateDir;
	sub EliminateInappropriate;

#******************************************************************************
# main
#
#*****************************************************************************/

# Right number of arguments?

	if ($#ARGV < 1 || $#ARGV > 2)
		{
		print STDERR "\nError: invalid number of arguments (", $#ARGV + 1, ")\n";
		Usage;
		}

# Get the arguments

	my $path1   = shift;
	ValidateDir $path1, 0;
	my $path2   = shift;
	ValidateDir $path2, 0;
	my $path3 = shift; # Will be undefined if there is no third arg
	ValidateDir $path3, 1;

# Open the directories

	if (! opendir DIR1, $path1)
		{
		print STDERR "\nError: cannot open directory $path1\n";
		exit 1;
		}

	if (! opendir DIR2, $path2)
		{
		print STDERR "\nError: cannot open directory $path2\n";
		exit 1;
		}

# Get the directory lists

	my @dirList1 = sort readdir DIR1; # Is sort necessary?
	my @dirList2 = sort readdir DIR2;

	closedir DIR1;
	closedir DIR2;

# Sort into appropriate files found in one, the other, or both lists

	EliminateInappropriate \@dirList1;
	EliminateInappropriate \@dirList2;

	my @both;
	my @only1;
	my @only2;

	while ($#dirList1 >= 0 || $#dirList2 >= 0)
		{
		if ($#dirList1 < 0)
			{
			$only2[++$#only2] = shift @dirList2;
			}
		elsif ($#dirList2 < 0)
			{
			$only1[++$#only1] = shift @dirList1;
			}
		elsif ($dirList1[0] lt $dirList2[0])
			{
			$only1[++$#only1] = shift @dirList1;
			}
		elsif ($dirList2[0] lt $dirList1[0])
			{
			$only2[++$#only2] = shift @dirList2;
			}
		else
			{
			$both[++$#both] = shift @dirList1;
			shift @dirList2;
			}

		EliminateInappropriate \@dirList1;
		EliminateInappropriate \@dirList2;
		}

# Handle duplicates

	if (!defined $path3)
		{
		print "\nDiffs of non-identical files appearing in both directories:\n";
		print "    < == $path1\n";
		print "    > == $path2\n\n";
		}
	else
		{
		print "\nSaving diffs of non-identical files in $path3/*.diff files\n";
		}

	my @identical;

	while ($#both >= 0)
		{
		my $nextFile = shift @both;
		my $diff = `diff $path1/$nextFile $path2/$nextFile`;

		if ($diff =~ m/\S/)
			{
			if (defined $path3)
				{
				`diff $path1/$nextFile $path2/$nextFile > $path3/$nextFile.diff`;
				}
			else
				{
				print "----- $nextFile -----\n";
				print `diff $path1/$nextFile $path2/$nextFile`;
				print "\n";
				}
			}
		else
			{
			$identical[$#identical+1] = $nextFile;
			}
		}

	if (!defined $path3)
		{
		print     "----- End Of Diffs -----\n";
		}

	print "\nIdentical files appearing in both directories:\n@identical\n";

# Handle non-duplicates

	print "\nFiles appearing only in directory $path1:\n@only1\n";
	print "\nFiles appearing only in directory $path2:\n@only2\n";

	exit 0;

#******************************************************************************
# EliminateInappropriate
#
#*****************************************************************************/

sub EliminateInappropriate
{
	my $listRef = shift;

	while (defined $$listRef[0] && -d $$listRef[0])
		{
		shift @$listRef;
		}
}

#******************************************************************************
# ValidateDir
#
#*****************************************************************************/

sub ValidateDir
{
	my $thisDir = shift;
	if (! defined $thisDir)
		{
		return;
		}

	my $isOutput = shift;

	if (! -d $thisDir)
		{
		print STDERR "\nError: argument $thisDir is not a directory.\n";
		exit 1;
		}

	if ($isOutput)
		{
		if (! -w $thisDir)
			{
			print STDERR "\nError: argument $thisDir is not writeable.\n";
			exit 1;
			}
		}
	else
		{
		if (! -r $thisDir)
			{
			print STDERR "\nError: argument $thisDir is not readable.\n";
			exit 1;
			}
		}
}

#******************************************************************************
# Usage
#
#*****************************************************************************/

sub Usage
{
	print STDERR "\nUsage: dirdiff <dir1> <dir2> [<outdir>]\n";
	print STDERR "\nDirdiff runs diff on every pair of files whose names appear in";
	print STDERR "\nboth dir1 and dir2.  If the optional third argument is present,";
	print STDERR "\nthen the outputs are placed in files in outdir whose names are";
	print STDERR "\nthe names of the files diffed, with \'.diff\' appended.  If";
	print STDERR "\noutdir is absent, then the outputs are sent to stdout one after";
	print STDERR "\nthe other, separated with headers containing the names of the";
	print STDERR "\nfiles diffed.";
	print STDERR "\n\nWARNING: existing files in outdir will be overwritten!\n";

	exit 1;
}
