#!/usr/bin/perl

=begin metadata

Name: expand
Description: convert tabs to spaces
Author: Thierry Bezecourt, thbzcrt@worldnet.fr
License: perl

=end metadata

=cut


#
# A Perl implementation of expand(1) and unexpand(1) for the Perl Power
# Tools project by Thierry Bezecourt <thbzcrt@worldnet.fr>.
#
# I don't use Text::Tabs, because :
# - it doesn't handle tags which are set at specified places on the line
# - it doesn't recognize backspace characters
#
# Please see the pod documentation at the end of this file.
#
# 99/03/07 : first version
#

use strict;

use File::Basename qw(basename);

use constant EX_SUCCESS => 0;
use constant EX_FAILURE => 1;

my $Program = basename($0);

my %line_desc;
my $tabstop = 8;
my @tabstops;
my @files;

while (@ARGV && $ARGV[0] =~ /\A\-(.+)/) {
    my $val = $1;
    if ($val eq '-') {
        shift @ARGV;
        last;
    }
    @tabstops = split /,/, $val;
    usage(1) if grep /\D/, @tabstops; # only integer arguments are allowed
    shift @ARGV;
}

@files = @ARGV;

# $tabstop is used only if multiple tab stops have not been defined
if(scalar @tabstops == 0) {
    $tabstop = 8;
} elsif(scalar @tabstops == 1) {
    $tabstop = $tabstops[0];
} else {
    my $howfar = 1;
    my %tabs = map { $_ => 1 } @tabstops;
    for (my $i = $tabstops[$#tabstops]-1; $i >= 0; $i--) {
        # how far is the $i-th column from the next tab
	$line_desc{$i} = $howfar;
	$howfar = 0 if defined $tabs{$i};
	$howfar++;
    }
}

for my $file (@files) {
    my $in;
    unless (open $in, '<', $file) {
	warn "$Program: couldn't open '$file' for reading: $!\n";
	exit EX_FAILURE;
    }
    while (<$in>) {
	expand_line($_);
    }
    close $in;
}
unless (@files) {
    while (<>) {
	expand_line($_);
    }
}
exit EX_SUCCESS;

sub usage {
    warn "usage: $Program [-tabstop] [-tab1,tab2,...] [file ...]\n";
    exit EX_FAILURE;
}

sub expand_line {
    my $line = shift;
    my $incr;
    my $curs = 0;

    for my $c (split //, $line) {
	if($c eq "\b") {  # backspace
	    print "\b";
	    $curs-- if $curs;
	} elsif($c eq "\t") {
	    if(scalar @tabstops > 0) {
		if(defined($line_desc{$curs})) {
		    $incr = $line_desc{$curs};
		} else { # Jupiter, and beyond the infinite
		    $incr = $tabstop;
		}
	    } else {
		$incr = $curs%$tabstop ? ($tabstop - $curs%$tabstop)
		    : $tabstop;
	    }
	    print ' ' x $incr;
	    $curs += $incr;
	} else {
	    print $c;
	    $curs++;
        }
    }
}

__END__

=head1 NAME

expand - convert tabs to spaces

=head1 SYNOPSIS

expand [B<-tabstop>] [B<-tab1,tab2,...>] [B<file> ...]

=head1 DESCRIPTION

I<expand> processes the named files or the standard input writing the
standard output with tabs changed into blanks.  Backspace characters
are preserved into the output and decrement the column count for tab
calculations.  I<expand> is useful for pre-processing character files
(before sorting, looking at specific columns, etc.) that contain tabs.

=head1 OPTIONS

=over 4

=item -tabstop

Tabs are set B<tabstop> spaces apart instead of the default 8.

=item -tab1,tab2,...

Tabs are set at the specific column numbers B<tab1>, B<tab2> and so on.

=back

=head1 AUTHOR

=for html
The Perl implementation was written by <A
href="mailto:thbzcrt@worldnet.fr">Thierry B&eacute;zecourt</A> for the
<A href="http://language.perl.com/ppt/">Perl Power Tools project</A>,
March 1999.

=for html <!--

The Perl implementation was written by Thierry Bezecourt,
I<thbzcrt@worldnet.fr>.  Perl Power Tools project, March 1999.

=for html -->

This documentation comes from the BSD expand(1) man page.

=head1 COPYRIGHT and LICENSE

This program is free and open software. You may use, modify, distribute,
and sell this program (and any modified variants) in any way you wish,
provided you do not restrict others from doing the same.

=cut

