#!/usr/bin/perl
use strict;

# Copyright 2004 Stephen Samuel.
# You are free to redistribute this under the Gnu Public License V2.0 or later
# as long as this notice, license and the SCO note remain intact.
#
# see http://www.gnu.org/licenses/gpl.html  for the details of the GPL.
#   (( essentially, you're free to redistribute and modify it as long as this
#      copyright notice remains, and others have GPL rights, and access, to the 
#      (possibly modified) source 
#   ))
#  
# SCO note:   Since the SCO Group has publicly and legally repudiated acceptence
# applicability and enforcability of the GPL, the GPL license does *NOT* apply
# to the SCO Group,  or anybody associated with or working for them or with them
# unless they conspicuously reverse that stance in a legally binding manner 
# before first use of this software.
#
# 
# $Header: /home/samuel/bin/RCS/redent,v 1.4 2004/03/22 01:58:53 samuel Exp samuel $
# $Log: redent,v $
# Revision 1.4  2004/03/22 01:58:53  samuel
# license change
#
# Revision 1.3  2004/03/22 00:01:18  samuel
# First public release
#
#
# redent takes a shell script on standard input, and attempts to re-indent it
# so that a human can follow the logic.
# Originally written to reformat the configure script for ethereal, it should
# handle *most* (but *not all*) reasonably sane shell scripts.
#
# 

# Does not handle quoted doublequotes inside of double quotes
#   ( "like \"this" )
# Presumption: multi-line quoted strings can be safely indented.

my $debug=0;
# $debug=1;

print "";
$|=1;
print "";

my %shift_tab=(
	"if" => 4,
	"then" => 0,
	"else" => 0,
	"elif" => 0,
	"fi" => -4,
	"until" => 4,
	"while" => 4,
	"do" => 0,
	"done" => -4,
	"for"	=> 4,
	"case" => 4,
	"in"	=> 0,
	"esac" => -4,
	"select" => 4,
	";;" => 0
);
my $keywords;
$keywords=join("|",keys(%shift_tab));
printf ":: keywords << %s >>\n",$keywords if $debug;
	

my $big=1000;


my $line;
my $inhere=undef;
my $inquote=0;
my $notab=0;
my $indent=0;
my $quote_indent;
my $tindent=0;
my $myindent;
my $quote;
my $rq = '`'; # rightquote char (for sanity sake )
my $quote_shift=2;
while(<>){
	chomp;
	$myindent=$indent;
	my $save;
	# inside of single or double quotes;
	# if( /<<\\?(\w+)\s*$/){
	# 	$inhere=$1;
	# };
	if(defined $inhere){
		print '@@' if $debug;
		print "$_\n" ;
		$inhere=undef if(/^$inhere$/);
		print "--@@"  if $debug && !  defined $inhere ;
		next;
	};
	if( $inquote ){
		# Check for here documents
		if(/<<./){
			m/<<\s?[\\"']?([_\w]+)/;
			$inhere=$1;
			print "::+@@ <<$1>>\n" if $debug;
		};
		print "<$quote>" if $debug;
		# scan for backslashed strings ... but not if single quoted (?!')
		if( s/^(([^$quote]|(?:(?<!\\)(\\\\)*(?!')\\$quote))*((?<!\\)(\\\\)*)$quote)//) {
			print "::-<$quote> <<$1>>\n" if $debug ;
			tabto($indent+$quote_shift,$1,$quote_indent);
			$notab=1;
			$quote_indent=undef;
			$inquote=0;
		}else{
			tabto($indent+4,"$_\n",$quote_indent);
			next;
		}
	};
	$line=$_;
	$line =~ s/^\s*#.*//;   # take out comment lines.
	$line =~ s/(\S)#+/${1}_/;  # neutralize  hash-chars in the middle of words
	## bughunt while( $line =~ s/^([^#'"$rq]*)((#.*)|((['"$rq])[^\5]*\5))/${1}^/ ){ 
	$line =~ s/\\\\/_/g;   # neutralize double backslashes 
	$line =~ s/\\["'#$rq]/_/g; # neutralize backslashed quotes and hashes
	while( $line =~ s/^([^#'"$rq]*)((#.*)|((['"$rq]).*?\5))/${1}^/ ){ 
	# neutralize quoted text;
	    if( $debug){
		my( $a, $b, $c, $d,$e);
		$a=$1; $b=$2; $c=$3;$d=$4; $e=$5;
		printf "}}\n<<%s>>  1<%s> 2<%s> 3<%s> 4<%s> 5<%s>\n",$line,xx($a),xx($b),xx($c),xx($d),xx($e);
	    }
	};
	
	$line =~ s/(^|\s*)#.*//;
	if ( $line =~ s/((['"$rq]).*)// ){
		$inquote=1 ;
		$quote=$2;
		print "::+<$2> <<$1>>\n" if $debug;
			$quote_indent=count_indent($_)+$quote_shift unless /^trap '/; # special case for trap string
	};
		# backindent conditional interims
	if(/^\s*($keywords)\b/ && $shift_tab{$1}<=0){
			$tindent -=4 ;   
			print"<<'$1'\n" if $debug;
	}
	# Here documents
	if($line =~ /<<./){
		m/<<\s?[\\"']?([_\w]+)/;
		$inhere=$1;
		print "::+@@ <<$1>>\n" if $debug;
	};
# not gonna parse the case thing... just scan to skip  wierd --word-if  type constructs
	while( $line =~ s/(^|[\s;:])($keywords)\b(?!-)/ / ){
		$indent += $shift_tab{$2};
		print "::$2 , $shift_tab{$2} , $indent\n" if $debug;
	};
	if( $notab){
		print "$_\n";
	}else{
		if( /^\s*$/ ){
			print "$_\n" ;
		}else{
			tabto($myindent+$tindent, "$_\n" ,$quote_indent);
		}
	};
	$tindent=0;
	$notab=0;

};

print "\n==============\n============\n" if $debug;


sub max{
	my ($a,$b)=@_;
	return $a>$b? $a : $b 
};

sub min{
	my ($a,$b)=@_;
	return $a<$b? $a : $b 
};

# tabto($depth,$line[,$pre])
#  peels off the first $pre spaces (defaults to 1000) 
#  and then indents the line by $depth spaces.  Uses 8-width tabs for indentation 
sub tabto{
	my $depth=$_[0];
	my $line=$_[1];
	my $eol= "\n"x chomp($line);
	my $pre;
	my $spaces;
	if(defined $_[2]){
		$pre=$_[2];
	}else{
		$pre=1000;  # an insane depth
	};
	$line =~ s/^(\s*)//;
	$spaces= $notab?0: (max(count_indent($1.".")-$pre,0) + $depth) ;
	printf "%s%s%s%s", "\t"x  ($spaces/8) , " "x ($spaces%8) , $line,$eol;
}

sub count_indent{
	my $line=$_[0];
	my $spaces;
	$line =~ /^(\s*)/;
	return length($spaces=$1);
}


sub expandtab{
	my $str=$_[0];
	my @parts=split(/\t/ , $str);
	my ( $res,$rem ,$bit,$more);
	$res="";
	$more=0;
	foreach $bit (split(/\t/, $_[0] ) ){
		$res .= (" "x $more) . $bit;; 
		$more=8- (length($res) % 8 );
	}
	return($res);
}

sub xx{
	if(defined($_[0]) ){ return $_[0] }else{return("UNDEF")};
};
