#!/usr/local/bin/perl
#
#    Copyright (C) 1991 by Lutz Prechelt, Karlsruhe
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 1, or (at your option)
#    any later version.
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#    If you don't have a copy of the GNU General Public License write to
#    Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

# Version:     2,  Patchlevel 0
# Author:      Lutz Prechelt (prechelt@ira.uka.de), 23.03.91
# Correction   by Sridhar Vasudevan, 03.07.91
# Last Change: Lutz Prechelt, 1995/04/05
#
# Usage: see message at "die" below.

#---------- constants:
$infinity         = 10000;

#---------- Variables and their Defaults:
$delimiterstring  = "\n";
$matches          = 0;
$precontext       = 2;
$postcontext      = 2;
$wrong_option     = 0;

#---------- boolean Options:
$paragraphmode    = 0;
$count_matches    = 0;
$ignorecase       = 0;
$withlinenumber   = 0;
$withfilename     = 0;
$reversemode      = 0;

# $endpara = '\S.*\n';

#---------- Subroutines:

sub match_found {
  #bad style: uses the global variables 
  #           $ignorecase, $reversemode, $cur, $pat
  if ($ignorecase != 0) {
    ($reversemode == 1 ? $cur !~ /$pat/io : $cur =~ /$pat/io);
  }
  else {
    ($reversemode == 1 ? $cur !~ /$pat/o : $cur =~ /$pat/o);
  }
}

sub showline {
  if ($withfilename != 0) {
    printf ("\"%s\"", $ARGV);
  }
  if ($withfilename != 0 && $withlinenumber != 0) {
    print ",";
  }
  if ($withlinenumber != 0) {
    printf ("%4d", $_[1]);
  }
  if ($withfilename != 0 || $withlinenumber != 0) {
    print ": ";
  }
  print ($_[0]);
}


#---------- Process the Options:
do {
  $something_done = 1;
  if ($ARGV[0] =~ /^-(\d+)$/) {
    $precontext = $postcontext = $1;
    shift;
  }
  elsif ($ARGV[0] =~ /^-(\d+)[\,\+\/\;](\d+)$/) {
    $precontext  = $1;
    $postcontext = $2;
    shift;
  }
  elsif ($ARGV[0] =~ /^-d$/ && $#ARGV > 0) {
    $delimiterstring = $ARGV[1];
    $delimiterstring =~ s/\\n/\n/o;
    shift; shift;
  }
  elsif ($ARGV[0] =~ /^-d(.*)$/) {
    $delimiterstring = $1;
    $delimiterstring =~ s/\\n/\n/o;
    shift;
  }
  elsif ($ARGV[0] =~ /^-c$/) {
    $count_matches = 1;
    shift;
  }
  elsif ($ARGV[0] =~ /^-h$/) {
    $withfilename = 1;
    shift;
  }
  elsif ($ARGV[0] =~ /^-i$/) {
    $ignorecase = 1;
    shift;
  }
  elsif ($ARGV[0] =~ /^-n$/) {
    $withlinenumber = 1;
    shift;
  }
  elsif ($ARGV[0] =~ /^-p$/) {
    $paragraphmode = 1;
    shift;
  }
  elsif ($ARGV[0] =~ /^-v$/) {
    $reversemode = 1;
    shift;
  }
  elsif (($ARGV[0] =~ /^-e$/) || ($ARGV[0] =~ /^--$/)) {
    # end options (for expressions starting with - )
    $something_done = 0;
    shift;
  }
  elsif ($ARGV[0] =~ /^-/) {
    printf ("don't know option '%s'\n", $ARGV[0]);
    $wrong_option = 1;
    $something_done = 0;
    shift;
  }
  else {
    $something_done = 0;
  }
} while ($something_done);


#---------- Usage message:
if ($#ARGV == -1 || $wrong_option) {
  die "
   Usage: cgrep [-pre[,post]] [-p] [-v] [-c] [-h] [-n] [-d string] 
                [-e] pattern [file...]

   cgrep is a context grep. It displays more than the one matching line for
   every match (2 before and 2 after as default).

   -3   means display 3 lines before and 3 lines after the match (e.g.)
   -5,12  means display 5 lines before the match and 12 lines after (e.g.)
   -p   means display only as much of the context as belongs to the
        current paragraph. (paragraphs bounded by empty lines)
   -v   means invert search (display nomatches)
   -c   means display number of matching lines at the end of run
   -h   means toggle display filename before every line
   -i   means ignore case when matching the regexp
   -n   means display line number before every line
   -d string  means use string as the output delimiter string
   --   (or -e) means end options (i.e. now comes the pattern, 
                                   for patterns starting with - )
   pattern  is a Perl regular expression (you better quote it !)
Exiting";
}


if ($#ARGV > 1) {
  $withfilename = !$withfilename;
}


#---------- Get the pattern and protect the delimiter.
$pat = shift;
$pat =~ s#/#\\/#g;


#---------- current line will always be at end of array,
#           i.e. $ary[$currentpre]
$_ = <>;
push(@ary,$_);
$currentpre = 0;

#---------- do the search
# use @ary as a silo, shifting and pushing.
# the length of the @ary at any time is $currentpre + 1
# the current line is @ary[$currentpre],
# the postcontext is not held in @ary.
$seq = 0;
$lastoutput = $infinity;  #last output is infinitely many lines ago
$cur = @ary[0];       #current line
while ($cur) {  #as long as there is something to look at
  if (&match_found()) {
    $matches++;
    if ($lastoutput <= $postcontext) {
      &showline ($cur, $.);
    }
    else {
      print $delimiterstring if ($seq++ && $precontext + $postcontext > 0);
      $lineno = $. - $#ary;
      foreach $line (@ary) {
        &showline ($line, $lineno++);
      }
    }
    $lastoutput = 0;
  }
  elsif (($cur !~ /\S.*\n/o && $paragraphmode == 1) || eof) { 
    # paragraph/file end
    for (; $currentpre >= 0; $currentpre--) {
      shift (@ary);
    }
    $lastoutput = $infinity;
    close (ARGV) if (eof);
  }
  elsif ($lastoutput <= $postcontext) {  #another line of postcontext
    &showline ($cur, $.);
  }
  #goto next line of input:
  $lastoutput++;
  $_ = <> if $_;
  push(@ary,$_); 
  if ($currentpre < $precontext) {
    $currentpre++;
  }
  else {
    shift(@ary);
  }
  $cur = $ary[$currentpre];
}


#---------- perhaps display number of matches:
if ($count_matches != 0) {
  printf ("%d\n", $matches);
}



