#!/usr/bin/perl -w
#===================================================================================
#
#         FILE:  checkrcs.pl
#
#        USAGE:  checkrcs.pl [-h] [-c|--check] [-d[r][s]|--documentation[r][s]] [Arbeitsverzeichnis]
#
#  DESCRIPTION:  
#
#      OPTIONS:  Siehe checkrcs.pl -h
#                -h Hilfetext anzeigen
#                -c Ueberpruefen der RCS-Repositories im Arbeitsverzeichnis
#                -d Erstellen einer Dokumentation fuer die RCS-Repositories im Arbeitsverzeichnis
# REQUIREMENTS:  Perl-Paket Getopt::Mixed
#                Perl-Paket Rcs
#         BUGS:  ---
#        NOTES:  ---
#       AUTHOR:  Dipl.Ing. Matthias Faulstich
#      COMPANY:  Fachhochschule Suedwestfalen
#      VERSION:  1.0
#      CREATED:  09.02.2005 09:39:47 CET
#     REVISION:  ---
#===================================================================================

use strict;

use Getopt::Mixed 'nextOption';
use Rcs;
use File::Basename; # Bestandteil der Perl-Basisinstallationen
use vars;

#---------------------------------------------------------------------------
#  Prototypen
#---------------------------------------------------------------------------
sub usage(;$$$);
sub analyseworkdir();
sub checkdir();
sub documentation(;$);

#---------------------------------------------------------------------------
#  Globale Variablen
#---------------------------------------------------------------------------
# Debugausgaben 
my $DEBUG=0;
# Liste der vorhandenen RCS-Repositories
my @RCSFILES;
# Liste der vorhandenen Origianldateien
my @ORIGINALFILES;
# Quelldateien mit RCS-Backup
my @ORIGINALFILESWITHRCS;
# RCS-Repositories zu den gefundenen Quelldateien
my @RCSFILESWITHORIGINAL;
# Standard-Startverzeichnis, wenn kein Standardverzeichnis angegeben wird
my $WORKDIR=`pwd`;
chomp($WORKDIR);
# Steuerung der Ausgabe von Getopt::Mixed Fehlermeldungen in usage()
my $INVALIDOPTION=0;



#===  FUNCTION  ====================================================================
#         NAME:  main (keine Funktion)
#  DESCRIPTION:  Verarbeitung der Parameter
# PARAMETER  1:  
#      CREATED:  09.02.2005 14:12:41 CET  /  AUTHOR:  
#===================================================================================
# Keine Parameter übergeben
if ( @ARGV == 0 ) {
	print("Hilfeaufruf mangels Parametern\n") if ($DEBUG);
	usage();
	exit(1);
}

# Erkennung, ob usage von Getopt::Mixed aufgerufen wurde
$INVALIDOPTION=1;

# Optionen bearbeiten
Getopt::Mixed::init("h help>h c d:s");
# Aufruf von usage() bei ungueltigen Opionen
$Getopt::Mixed::badOption=\&usage;
#Getopt::Mixed::getOptions();

$INVALIDOPTION=0;

# Umkopieren der Optionen in einen Hash, um die Option -h, falls
# irgendwo eingegeben, als erste und einzige Option zu bearbeiten
# vorkommt, allein und als erster
my %options;
while ((my $option, my $value) = nextOption()) {
	$options{$option}=$value;
}
Getopt::Mixed::cleanup();

# -h als erste und einzige Option behandeln, falls gegeben
if (grep( /^h/, keys(%options))){
	print("Hilfeaufruf wegen -h in Optionen\n") if ($DEBUG);
	usage();
}

# Optionales Arbeitsverzeichnis uebernehmen
if (@ARGV > 0){
	if (-d $ARGV[0]){
		$WORKDIR=$ARGV[0];
		chomp($WORKDIR)
	} else {
		printf("\n\n*** Ungueltiges Arbeitsvereichnis $ARGV[0] ***\n\n");
		usage();
	}
}
print "Arbeitsverzeichnis=$WORKDIR\n" if ($DEBUG);


# Sind Parameter vorhanden?
if ( (keys %options) == 0){
	printf("\n\n*** Keine Aktion fuer $WORKDIR gewaehlt ***\n\n");
	usage();
}

# Analyse des Arbeitsverzeichnisses
analyseworkdir();

# Verarbeitung der Parameter
foreach my $option (keys %options){
	
	if ($DEBUG){
		my $tmpval=$options{$option};
		$tmpval ="" if(! defined $tmpval);
		print ("main: Option=$option Value=$tmpval\n");
	
	
	
	}

	if  ($option eq 'c'){
		print("Option -c verarbeitet\n") if ($DEBUG);
		checkdir();
	} elsif ($option eq 'd'){
		documentation($options{$option});
	} else {
		print("Ungueltige Option verarbeitet\n") if ($DEBUG);
		usage();
	}
}	

exit 0;

#==  FUNCTION  ====================================================================
#
#         NAME:  usage
#
#  DESCRIPTION:  Zeigt den Hilfetext fuer dieses Skript an und beendet das Programm
#
#       AUTHOR:  
#      CREATED:  08.02.2005 11:56:26 CET
#
#---- PARAMETER  -------------------------------------------------------------------
#                Die Parameter sind optional und werden nur von Getopt::Mixed uebergeben, wenn
#                das Skrpt mit ungueltigen Parametern aufgerufen wird. Die Parameter und ihre 
#                Reihenfolge entsprechen dem Aufruf von &$Getopt::Mixed::badOption
#     
#        Number  Description
#
#           1 :  Position der ungueltigen Option in @ARGV
#           2 :  Die ungueltige Option
#           3 :  undefiniert oder der Grund, weshalb die Option nicht erkannt wurde
#===================================================================================
sub usage (;$$$){
	
	
	if ($INVALIDOPTION==1){
		print("\n\n*** Ungueltiger Parameter $_[1] ***\n") ;
		print("Ursache:\n$_[2]\n") if (defined $_[2]);
		print("\n");
	}

my $text = <<HELPTEXT;
Aufruf: checkrcs.pl [-h] [-c|--check] [-d[r][s]|--documentation[r][s]] [Arbeitsverzeichnis]

Dieses Skript erleichtert die Verwaltung von RCS-Repositories unterhalb eines Verzeichnisses. 
Über die mit ci eingegebenen Beschreibungen und die Aenderungen seit dem Einpflegen der Basisversion kann
eine Dokumentation ueber die Aenderugen erstellt werden.
Dies dient besonders zur Verfolgung von Aenderugen in Konfigurationsdateien (z.B. in /etc)

Wenn ein Arbeitsverzeichnis uebergeben wird, werden die RCS-Repositories dieses Verzeichnisses
verwendet. Das Standardverzeichnis, falls kein Arbeitsverzeichnis angegeben wird, ist /etc.

-h --help
Anzeige dieses Hilfetextes

Im Folgenden sind mit RCS-Repositories Dateien mit der Endung ",v" gemeint, die mit dem
ci-Kommando von RCS erzeugt werden.

-c 
(check)
* Findet RCS-Repositories unterhalb von <Startverzeichnis>, zu denen keine Originale vorhanden sind
* Vergleicht RCS-Repositories mit den Originalen und listet Originale mit veralteten RCS-Repositories
* Startverzeichnis " /etc, wenn kein Startverzeichnis angegeben wird

-d[s][r]
(documentation)
Gibt fuer alle Dateien im Startverzeichnis,die eine entsprechende RCS-Repository besitzen
* die Kommentare fuer die Aenderungen und
* die Differenzen zwischen der 1. Version und dem momentanen Stand aus
Wird -d kein weiterer Parameter mitgegeben (s oder r), wird s als Standard verwendet
[s] (summarize) (Standardeinstellung, wenn kein Parameter angegeben wird)
Gibt die Ergebnisse als Zusammenfassung an:
1. Liste der Revisionen
2. Liste aller Aenderungen
[r] (revision)
Gibt die Aenderungen fuer jede Revision an
- Beschreibung der Revision
- Aenderungen der Revision

HELPTEXT

	printf("\n$text\n");
	
	# Beenden des Skripts
	Getopt::Mixed::cleanup();
	# Abbruch bei ungueltigem Parameter
	if ($INVALIDOPTION==1){
		exit 1;
	} else {
		exit 0;
	}
}

#===  FUNCTION  ====================================================================
#
#         NAME:  analyseworkdir
#
#  DESCRIPTION:  Analysiert das Verzeichnis $WORKDIR und legt
#                - Alle RCS-Repositories in der Datei mit dem Namen in RCSFILES 
#                - Alle Dateien, die keine RCS-Repositories sind (Originaldateien), in der Datei mit dem Namen in ORIGINALFILES 
#                - Alle Originaldatein mit einer RCS-Repository in der Datei mit dem Namen in $RCSFILESWITHORIGINAL
#
#       AUTHOR:  
#      CREATED:  08.02.2005 14:04:36 CET
#
#---- PARAMETER  -------------------------------------------------------------------
#        Number  Description
#          
#---- GLOBAL VARIABLES  ------------------------------------------------------------
#     This variables must be predefined before calling this funtion
#        Name 
#        WORKDIR              : Standardverzeichnis, wenn kein Startvereichnis als Parameter uebergeben wird
#        RCSFILES             : Dateiname fuer die Liste aller RCS-Repositories im Startverzeichnis
#        ORIGINALFILES        : Dateiname fuer die Liste aller Dateien im Startverzeichnis, die KEINE RCS-Repositories sind
#        ORIGINALFILESWITHRCS : Dateiname fuer Liste aller Dateien, zu denen es eine RCS-Repository gibt
#        RCSFILESWITHORIGINAL : Dateiname fuer Liste aller RCS-Repositories, zu denen es eine Originaldatei gibt
#===================================================================================
sub analyseworkdir(){
	
	@ORIGINALFILESWITHRCS=qw();
	@RCSFILESWITHORIGINAL=qw();
			
	printf("Ueberrpuefe RCS-Repositories in $WORKDIR\n");
	
	# Alle vorhandenen RCS-Repositories im Startverzeichnis finden
	printf ("Suche RCS-Repositories...");
	my @tmp=`find $WORKDIR -type f -regex \".*,v\$\"`;
	foreach (@tmp){
		/^\s*(.*)/;
		if (defined $1){
			my $file=$1;
			chomp($file);
			push(@RCSFILES, $file);
		}
	}
	my $cnt=@RCSFILES;
	print("\r$cnt RCS-Repositories gefunden\n");

	# Alle Dateien im Startverzeichnis finden, die KEINE RCS-Repositories sind
	printf ("Suche Originaldateien...");
	@tmp=`find $WORKDIR -type f -regex \".*[^,][^v]\$\"`;
	foreach (@tmp){
		/^\s*(.*)/;
		if (defined $1){
			my $file=$1;
			chomp($file);
			push(@ORIGINALFILES, $file);
		}
	}
	$cnt=@ORIGINALFILES;
	print("\r$cnt Originaldateien gefunden\n");

	# RCS-Repositories finden, die keine entsprechenden Quelldateien haben
	print("Suche Originale zu RCS-Repositories in $WORKDIR");
	my $actline=0;
	
	# Vergleiche alle Nicht-RCS-Repositories im Startverzeichnis mit allen
	# darin befindlichen RCS-Repositories
	
	foreach my $orgfile_withpath (@ORIGINALFILES){
		chomp($orgfile_withpath);
		$actline++;
		
		my $orgfilename = basename($orgfile_withpath, qw());
		my $orgfilepath = dirname($orgfile_withpath, qw());
		
		my $searchrcsfile=$orgfilename.",v";
		foreach my $rcsfile_withpath (@RCSFILES) {
			# Quelldatei und RCS-Repository in gleichem Verzeichnis
			if ($rcsfile_withpath eq "$orgfilepath/$searchrcsfile" ){

				# Quelldatei mit RCS Backup im gleichen Verzeichnis gefunden
				push (@ORIGINALFILESWITHRCS, $orgfile_withpath);
				push (@RCSFILESWITHORIGINAL, $rcsfile_withpath);
				next;
			}	
	
			# RCS-Repository in RCS Unterverzeichnis
			if ($rcsfile_withpath eq "$orgfilepath/RCS/$searchrcsfile"){

				# Quelldatei mit RCS Backup in RCS Unterverzeichnis gefunden
				push(@ORIGINALFILESWITHRCS, $orgfile_withpath);
				push(@RCSFILESWITHORIGINAL, $rcsfile_withpath);
				next;
			}
		}
	}
	# Anzeige aufrawumen
	`setterm -cursor on`;
	print("\r                                                                       \r");
	
	print Dumper(@ORIGINALFILESWITHRCS) if ($DEBUG);
}
#===  FUNCTION  ====================================================================
#
#         NAME:  checkdir
#
#  DESCRIPTION:  Überprüft die RCS-Repositories und die zugehörigen Originale
#								 Findet RCS-Repositories in $RCSFILES zu denen keine Originale vorhanden sind
#		             Vergleicht RCS-Repositories mit den Originalen und listet Originale mit veralteten RCS-Repositories
#								 Die Ausgabe erfolgt als Text auf stdout
#       AUTHOR:  
#      CREATED:  08.02.2005 11:57:16 CET
#
#---- PARAMETER  -------------------------------------------------------------------
#        Number  Description
#           1 :  
#
#---- GLOBAL VARIABLES  ------------------------------------------------------------
#     This variables must be predefined before calling this funtion
#        Name 
#        WORKDIR              : Standardverzeichnis, wenn kein Startvereichnis als Parameter uebergeben wird
#        RCSFILES             : Dateiname fuer die Liste aller RCS-Repositories im Startverzeichnis
#        ORIGINALFILES        : Dateiname fuer die Liste aller Dateien im Startverzeichnis, die KEINE RCS-Repositories sind
#        ORIGINALFILESWITHRCS : Dateiname fuer Liste aller Dateien, zu denen es eine RCS-Repository gibt
#        RCSFILESWITHORIGINAL : Dateiname fuer Liste aller RCS-Repositories, zu denen es eine Originaldatei gibt
#===================================================================================
sub  checkdir() {
	
	my $bindir=`which rcs`;
	chomp($bindir);
	print ("\n");

	# Liste RCS-Repositories ohne Quelldateien
	my $rcsfile;
	my $cnt=0;
	print("RCS-Repositories ohne Quelldateien:\n");
	foreach $rcsfile (@RCSFILES){
		my $match=0;
		foreach my $rcswithoriginal (@RCSFILESWITHORIGINAL){
			if ( $rcswithoriginal eq $rcsfile ){
				$match=1;
				last;
			}
		}
		
		if ( $match == 0 ){
			print("$rcsfile\n");
			$cnt++;
		}
	}
	
	if ( $cnt == 1 ){
		print("1 RCS-Repository ohne Quelldatei gefunden\n\n\n");
	} else {
		print("$cnt RCS-Repositories ohne Quelldateien gefunden\n\n\n");
	}

	# Vergleich, ob RCS Dateien von Quelldateien mit RCS Datei noch aktuell sind
	print("Quelldateien mit veralteten RCS-Repositories\n");
	my $actpath=`pwd`;
	chomp($actpath);
	$cnt=0;
	foreach my $orgfile_withpath (@ORIGINALFILESWITHRCS){
		# Dateiname und Pfad isolieren
		
		my $orgfilename = basename($orgfile_withpath, qw());
		my $orgfilepath = dirname($orgfile_withpath, qw());

		# Passende RCS-Repository heraussuchen
		# Behandlung nicht vorhandener RCSDateien geschenkt, da bereits gecheckt
		$rcsfile = "";
		foreach my $file (@RCSFILESWITHORIGINAL){

			if ($file=~/$orgfilename,v$/){
				$rcsfile=$file;
				last;
			}
		}
		if ($rcsfile eq ""){
			printf "Finde kein RCS-Repository zu $orgfilename\n";
			next;
		}
		
		my $rcs= new Rcs();
		$rcs->bindir(dirname($bindir));
		$rcs->workdir($orgfilepath);
		$rcs->file($orgfilename);
		$rcs->rcsdir(dirname($rcsfile), qw());
		if ($rcs->rcsdiff()){
			print("$orgfile_withpath\n");
			$cnt++;
		}
	}
	if ( $cnt == 1 ){
		print("1 Quelldatei mit veraltetem RCS-Repository gefunden\n\n");
	} else {
		print("$cnt Quelldateien mit veralteten RCS-Repositories gefunden\n\n");
	}
}


#===  FUNCTION  ====================================================================
#         NAME:  documentation
#  DESCRIPTION:  Erstellt eine Dokumentation fuer Anderungen aus den Log-Messages und
#                den Differenzen
# PARAMETER  1:  
#      CREATED:  14.02.2005 10:22:24 CET  /  AUTHOR:  
#===================================================================================
sub documentation(;$){

	# Methode 
	# s: summarize = Alle Aenderungen zusammengefasst
	# r: revisionswise = Aenderungen werden der jeweiligen Revision zugeordnet
	my $method= shift;
	if ( ! defined $method || $method eq ""){
		$method='s';
	}

	my $bindir=`which rcs`;
	chomp($bindir);
	print ("\n");
	foreach my $orgfile_withpath (@ORIGINALFILESWITHRCS){
		
		# Dateiname und Pfad isolieren
		my $orgfilename = basename($orgfile_withpath, qw());
		my $orgfilepath = dirname($orgfile_withpath, qw());

		# Passende RCS-Repository heraussuchen
		# Behandlung nicht vorhandener RCSDateien geschenkt, da bereits gecheckt
		my $rcsfile = "";
		foreach my $file (@RCSFILESWITHORIGINAL){

			if ($file=~/$orgfilename,v$/){
				$rcsfile=$file;
				last;
			}
		}
		if ($rcsfile eq ""){
			printf "Finde kein RCS-Repository zu $orgfilename\n";
			next;
		}
		
		my $rcs= new Rcs();
		$rcs->bindir(dirname($bindir));
		$rcs->workdir($orgfilepath);
		$rcs->file($orgfilename);
		$rcs->rcsdir(dirname($rcsfile), qw());
		
		print Dumper($rcs)if ($DEBUG);
		print("$orgfile_withpath:\n\n");
		
		
		my @revisions=$rcs->revisions();
		my %comments=$rcs->comments();
		my $revision;
		my @diffs;
		my $diff;
	
		my @orderedrevisions=sort {$a <=> $b } @revisions ;
		my $maxrev="$orderedrevisions[@orderedrevisions-1]";
		my $minrev="$orderedrevisions[0]";
		
		#---------------------------------------------------------------------------
		# Liste der Aenderungen, methode r: revisionwise 
		# Aenderungen werden der jeweiligen Revision zugeordnet
		#---------------------------------------------------------------------------
		if ($method eq "r"){
			my $prevrev=$minrev;
			foreach $revision (@orderedrevisions){
				print("Rev. $revision: $comments{$revision}");
				if ($revision ne $minrev){
					@diffs=$rcs->rcsdiff("-r$revision", "-r$prevrev");
					if (@diffs > 0){
						print("Änderungen: Revision $prevrev - Revision $revision\n");
						foreach $diff (@diffs){
							print("$diff");
						}
					}

				}
				$prevrev=$revision;
				print("\n");
			}
			print("\n\n");
		}

		#---------------------------------------------------------------------------
		# Liste der Aenderungen, methode s: summarize
		# Alle Änderungen zusammengefasst
		#---------------------------------------------------------------------------
		if ($method eq 's'){
			
			# Auflistung der Revisionsbeschreibungen	
			if (@orderedrevisions != 1){
				print("Versionsbeschreibungen:\n");
			} else {
				print("Versionsbeschreibung:\n");
			}
			
			foreach $revision (@orderedrevisions){
					print("Rev. $revision: $comments{$revision}");
			}
			
			# Ausgabe der Aenderungen, falls es mehr als 1 Version gibt
			if ($maxrev ne $minrev){
				print("Änderungen:\n");
				@diffs=$rcs->rcsdiff("-r$maxrev", "-r$minrev");
				foreach $diff (@diffs){
					print("$diff");
				}
			}
			print("\n\n");
		}
	}
}

