#!/usr/bin/perl

=begin metadata

Name: cat
Description: concatenate and print files
Author: Abigail, perlpowertools@abigail.be
License: perl

=end metadata

=cut

use strict;

use File::Basename qw(basename);
use Getopt::Std qw(getopts);

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

our $VERSION = '1.3';
my $Program = basename($0);

my %opt;
getopts('benstuv', \%opt)  or do {
    warn "usage: $Program [-benstuv] [file ...]\n";
    exit EX_FAILURE;
};

my $ends              = $opt{'e'};
my $tabs              = $opt{'t'};
my $nonprinting       = $opt{'v'} || $ends || $tabs;
my $squeeze_empty     = $opt{'s'};
my $number_lines      = $opt{'n'} && !$opt{'b'};
my $number_non_blanks = $opt{'b'};

my $cook = grep {$_ || ()} @opt{'b', 'e', 'n', 's', 't', 'v'};
my $cat = $cook ? \&cook_file : \&raw_file;

# Unbuffer output for -u.
$| = 1 if $opt{'u'};

my $was_empty = 0;
my $count     = 0;

if (scalar(@ARGV) == 0) {
    push @ARGV, '-';
}
my $err = 0;
foreach my $f (@ARGV) {
    $err |= do_file($f);
}
exit ($err ? EX_FAILURE : EX_SUCCESS);

sub VERSION_MESSAGE {
    print "$Program version $VERSION\n";
    exit EX_SUCCESS;
}

sub do_file {
    my $name = shift;

    my $fh;
    if ($name eq '-') {
        $fh = *STDIN;
    } else {
        if (-d $name) {
            warn "$Program: $name: is a directory\n";
            return 1;
        }
        if (!open($fh, '<', $name)) {
            warn "$Program: $name: $!\n";
            return 1;
        }
    }
    $cat->($fh);

    if ($name ne '-' && !close($fh)) {
        warn "$Program: failed to close '$name': $!\n";
        return 1;
    }
    return 0;
}

sub raw_file {
    my $fh = shift;
    my $BUFSZ = 8192;
    my $buf;
    while (read $fh, $buf, $BUFSZ) {
	print $buf;
    }
}

sub cook_file {
    my $fh = shift;

    while (<$fh>) {
        if ($squeeze_empty) {
            my $is_empty = /^$/;
            if ($is_empty && $was_empty) {
                next;
            }
            $was_empty = $is_empty;
        }

        s/$/\$/ if $ends;
        if ($nonprinting) {
            s/([\x80-\xFF])/"M-" . ("\x7F" & $1)/ge;
            s/([\x00-\x08\x0B-\x1F])/"^" . chr (0100 + ord $1)/ge;
            s/\x7F/^?/g;
        }
        if ($tabs) {
            s/\x09/^I/g;
        }

        if ($number_lines || ($number_non_blanks && /\S/)) {
            printf "%6d\t", ++$count;
        }
        print;
    }
}

__END__

=pod

=head1 NAME

cat - concatenate and print files

=head1 SYNOPSIS

cat [-benstuv] [file ...]

=head1 DESCRIPTION

I<cat> reads and prints the files in order they are given. If no files
are given, I<standard input> is processed. A lone dash represents
I<standard input> as well.

=head2 OPTIONS

I<cat> accepts the following options:

=over 4

=item -b

Number all the non blank lines, starting at 1.

=item -e

Print a dollar sign (B<$>) at the end of each lines. Implies I<-v>.

=item -n

Number all the lines, starting at 1.

=item -s

The I<squeeze> option. Sequential empty lines are squeezed into a
single empty line.

=item -t

Display tabs as I<^I>. Implies I<-v>.

=item -u

Unbuffer output.

=item -v

Display non-printable characters in a printable way. Characters in the
range I<\000> - I<\037>, with the exception of tabs and linefeeds, are
printed as I<^X>, where I<X> is the symbol I<\0100> higher. I<DEL> is
printed as I<^?>. Characters whose highest bit is set are printed as
I<M->, followed by the representation of the character with the high
bit stripped.

=back

=head1 ENVIRONMENT

The working of I<cat> is not influenced by any environment variables.

=head1 BUGS

I<cat> has no known bugs.

=head1 STANDARDS

This I<cat> implementation is compliant with the B<IEEE Std1003.2-1992>
specification, also known as B<POSIX.2>.

This I<cat> implementation is compatible with the B<OpenBSD> implementation.

=head1 AUTHOR

The Perl implementation of I<cat> was written by Abigail, I<perlpowertools@abigail.be>.

=head1 COPYRIGHT and LICENSE

This program is copyright by Abigail 1999.

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

=cut