#!/usr/bin/perl

# align - filter to align columns of text
# Steve Kinzler, kinzler@cs.indiana.edu, Jun 00/Oct 01
# see website http://www.cs.indiana.edu/hyplan/kinzler/align/
# http://www.cs.indiana.edu/hyplan/kinzler/home.html#unix

$version = '1.3.0';

require 5.000;

$usage = <<EOF;
usage: $0 [ -s { s+ | s | t+ | t | _+ | _ | /patt } ]
       [ -j { s | t | _ | /string } ] [ -g # ] [ -t # ]
       [ -a { l | r | c }... ] [ file ... ]
	-s	split the input into columns defined by
		s+	one or more whitespace characters
		s	every whitespace character
		t+	one or more tab characters
		t	every tab character
		_+	one or more space characters
		_	every space character
		/patt	every occurance of the given Perl regexp pattern
	-j	join the output into columns separated by
		s	tabs and spaces as needed
		t	only tabs (with all left alignment only)
		_	only spaces
		/string	repetitions and a truncation of the given string
	-g	the minimum character places between output columns (gutter)
	-t	the number of character places taken by a full tab character
	-a	the alignment of each column with the last repeated as needed
		l	left
		r	right
		c	center
The default split method is determined by the following heuristic rules
applied in order to the entire input:
	s+	if there are any adjacent tab and space characters
	t+	if there are any two adjacent tab characters
	t	if there is any tab character
	_+	if there are any two adjacent space characters
	_	otherwise
The default join method is determined by the split method as follows:
	t	if t+ or t (and all left aligned)
	s	if s+ or s or /patt
	_	otherwise
The default gutter value is 1.
The default tab value is \$TABSTOP ($ENV{'TABSTOP'}), otherwise 8.
The default alignment is 'left' on all columns.
Visual alignment may not be achieved if the column text or join string
contains tabs, non-printing characters or other characters not occupying
one character place.
Version $version
EOF

require 'getopts.pl';
die $usage if ! &Getopts('hs:j:g:t:a:') || $opt_h;

$opt_g = ($opt_g ne '') ? $opt_g + 0 : 1;
$opt_t = ($opt_t ne '') ? $opt_t + 0 : $ENV{'TABSTOP'} + 0 || 8;
die "$0: invalid gutter value ($opt_g)\n" if $opt_g <  0;
die "$0: invalid tab value ($opt_t)\n"	  if $opt_t <= 0;

$opt_a =~ s/[^lrc]//g;
$opt_a =  $opt_a || 'l';

$opt_s =~ s/^$/DeFaUlT/	    ||
$opt_s =~ s/^[st]\+?$/\\$&/ ||
$opt_s =~ s/^_(\+?)$/ $1/   ||
$opt_s =~ /^[\t ]\+?$/	    ||
$opt_s =~ /^\//		    || die $usage;

$opt_j =~ s/^$/DeFaUlT/	    ||
$opt_j =~ /^[st_]?$/	    ||
$opt_j =~ s/^\t$/t/	    ||
$opt_j =~ s/^ $/_/	    ||
$opt_j =~ /^\/./	    || die $usage;

###############################################################################

chomp(@in = <>);

$opt_s = grep(/ \t|\t /, @in) ? '\s+' :
	 grep(/\t\t/,	 @in) ? '\t+' :
	 grep(/\t/,	 @in) ? '\t'  :
	 grep(/  /,	 @in) ? ' +'  :
				' '     if $opt_s eq 'DeFaUlT';

$opt_j = ($opt_s =~ /^(\\t|\t)/) ? 't' :
	 ($opt_s =~ /^(\\s|\/)/) ? 's' :
				   '_'  if $opt_j eq 'DeFaUlT';

$opt_j = 's' if $opt_a =~ /[^l]/ && $opt_j eq 't';

$opt_s =~ s/^\///;
$opt_j .= ':FlAg' unless $opt_j =~ s/^\///;

($tab, $tlen) = ($opt_j =~ /^[ts_]:FlAg$/) ? ("\t",   $opt_t)
					   : ($opt_j, length($opt_j));

foreach (@in) {
	@cols = split(/$opt_s/);
	@w = @widths; @widths = ();
	push(@widths, &max(shift @w, length(shift @cols))) while @cols;
	push(@widths, @w);
}

foreach (@in) {
	@cols = split(/$opt_s/);
	$llen = $hold = 0;
	@w    = @widths;
	@a    = split(//, $opt_a);

	while (@cols) {
		$a  = ($#a) ? shift @a : $a[0];
		$_  = shift @cols;
		$l  = length($_);
		$G  = shift(@w) - $l;
		$g  = ($a eq 'l') ? 0 : ($a eq 'r') ? $G : int($G / 2);
		$g += $hold + $opt_g if $#w < $#widths - 1;

		if ($opt_j eq 't:FlAg') {
			$ns = &min($g, $ex = ($llen + $g) % $tlen);
			$nt = int(($g - $ns + $tlen - .000001) / $tlen);
			$g += $tlen - $ex if $ns;
			$j  = $tab x $nt . (($ns) ? $tab : '');

		} elsif ($opt_j eq 's:FlAg') {
			$ns = &min($g, ($llen + $g) % $tlen);
			$nt = int(($g - $ns + $tlen - .000001) / $tlen);
			$j  = $tab x $nt . ' ' x $ns;

		} elsif ($opt_j eq '_:FlAg') {
			$j  = ' ' x $g;

		} else {
			$ns = $hold % $tlen;
			$nt = int($hold / $tlen);
			$j  = substr($tab, $tlen - $ns) . $tab x $nt;
			$ns = ($g - $hold) % $tlen;
			$nt = int(($g - $hold) / $tlen);
			$j .= $tab x $nt . substr($tab, 0, $ns);
		}

		print $j, $_;

		$llen += $l + $g;
		$hold  = ($a eq 'l') ? $G : ($a eq 'r') ? 0 : $G - int($G / 2);
	}

	print $/;
}

###############################################################################

sub max { ($_[0] >= $_[1]) ? $_[0] : $_[1] }
sub min { ($_[0] <= $_[1]) ? $_[0] : $_[1] }
