#!/usr/local/bin/perl

# -------------------------------------------------------------
#
#  Authors:     Stuart Robinson
#  Date:        7 May 2000 (w/ Bruce Ludlum) / 16 Feb 2005
#  Description: Does a grep and colorizes all matches so that 
#               they stand out. Supports usual grep options.
#
# -------------------------------------------------------------

use File::Find;
use Getopt::Long;
use strict;
use vars qw( $optCaseInsensitive
	     $optCount 
	     $optContext 
	     $optListFilesOnly 
	     $optPrintLineNumber 
	     $optPrintNonmatchesOnly 
	     $optPrintFilename );
use English (-no_match_vars);

my %matches;
my $pattern;

# Foreground color specifications
my %colors = (black      => 30,
	      blue       => 34,
	      cyan       => 36,
	      green      => 32,
	      magenta    => 35,
	      red        => 31,
	      white      => 37,
	      yellow     => 33);

main();


# -------------------------------------------------------------
# SUBROUTINES
# -------------------------------------------------------------

sub main {
    # Handle options
    GetOptions( 'i'         => \$optCaseInsensitive,
		'x=s'       => \$optContext,
		'context=s' => \$optContext,
		'c'         => \$optCount,
		'f'         => \$optPrintFilename,
		'l'         => \$optListFilesOnly,
		'n'         => \$optPrintLineNumber,
		'v'         => \$optPrintNonmatchesOnly );
    
    # Get pattern to be processed
    $pattern = shift @ARGV;
    if (not defined $pattern) {
	usage()
    }
    if (defined $optCaseInsensitive) {
	$pattern = '(?i)' . $pattern;
    }
    
    # Get files to be processed
    my @DIRLIST = @ARGV;
    @DIRLIST = qw(.) unless @DIRLIST;
    
    # Recurse through the files in a directory
    find(\&process_file, @DIRLIST);

    # Print count if option specified
    if ($optListFilesOnly) {
	for my $file (keys %matches) {
	    print "$file\n"
	}
    }
}

sub process_file {
    # Don't run this routine on files of the following types
    if ( -d $_ ) { return 1 };         		  	       # directories
    if ( -l $_ ) { return 1 };         		  	       # symlinks
    if ( $_ =~ /\~$/ ) { return 1};   		  	       # backup files
    
    # $_ is the file name relative to the current directory
    my $contents = slurp($_);
    my @lines = split("\n", $contents);
    
    # initialize file variables
    my $line;
    my $colorized_line;
    for (my $i = 0; $i <= $#lines; $i++) {
	my $line = $lines[$i];
	my $isMatch = $line =~ /$pattern/;
	if ($optCount && $isMatch) {
	    $matches{$_} = $matches{$_} + 1;
	} elsif ($optListFilesOnly) {
	    $matches{$_} = $matches{$_} + 1;
	    # TODO : Break!
	} elsif ($isMatch) {
	    process_line($_, 
			 $File::Find::name, 
			 $line, 
			 $i, 
			 \@lines);
	} elsif (defined $optPrintNonmatchesOnly) {
	    process_line($_, 
			 $File::Find::name, 
			 $line, 
			 $i, 
			 \@lines);
	}
    }

    if ($optCount) {
	print $matches{$_} . "\n";
    }
}

sub process_line {
    my $filename = shift;
    my $filepath = shift;
    my $line = shift;
    my $i = shift;
    my $r_lines = shift;

    my @lines = @{$r_lines};
    if (defined $optContext) {
	for (my $j = $i-$optContext; $j < $i; $j++) {
	    print_line($filename, $lines[$j], $j);
	}
	print_line($filename, $line, $i);
	for (my $j = $i+1; $j <= $i+$optContext; $j++) {
	    print_line($filename, $lines[$j], $j);
	}
 	print "--\n";
    } else {
	print_line($filename, $line, $i);
    }
}

sub print_line {
    my $filename = shift;
    my $line = shift;
    my $line_number = shift;

    my $colorized_line = $line;
    $colorized_line =~ s/($pattern)/\e\[$colors{'red'}m$1\e\[0m/g;
    my $colorized_filename = "\e\[$colors{'blue'}m" . $filename . "\e\[0m";
    my $colorized_line_number = "\e\[$colors{'yellow'}m" . $line_number++ . "\e\[0m";

    if (defined $optPrintLineNumber && defined $optPrintFilename) {
	print "$colorized_filename:$colorized_line_number:$colorized_line\n";
    } elsif (defined $optPrintLineNumber && (not defined $optPrintFilename)) {
	print "$colorized_line_number:$colorized_line\n";	    
    } elsif ((not defined $optPrintLineNumber) && defined $optPrintFilename) {
	print "$colorized_filename:$colorized_line\n";	    
    } else {
	print "$colorized_line\n";
    }
}

sub slurp {
    my $file = shift;
    open(FILE, "<$file") or die "Can't open $file for reading: $!";
    my $contents = do { local $/, <FILE> };
    close(FILE) or warn $!;
    return $contents;
}

sub usage {
print qq^
    USAGE
     $PROGRAM_NAME [OPTIONS] <REGEX> <FILE|DIR>

    OPTIONS
     -x NUM, --context=NUM
         Print  NUM lines of output context.  Places a line 
         with -- between matches.
     
     -c  Print only a count of the lines that contain the
         pattern. Incompatible with other options.

     -f  Print filename

     -i  Ignore upper/lower case distinction during comparis-
         ons.

     -l  Print the names of files with matching lines.

     -n  Precede each line by its line number in the file
         (first line is 1).

     -v  Print all lines except those that contain the pattern.

^;
  exit;
}
