#/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.

# Author: Lutz Prechelt (prechelt@ira.uka.de),
#         23.03.91
# Change: Lutz Prechelt, 02.07.91
#
# Usage: see message at "die" below.

$infinity = 10000;

$precontext  = 2;
$postcontext = 2;
$paragraphmode    = 0;
$withlinenumber   = 0;
$withfilename     = 0;
$wrong_option     = 0;
$reversemode      = 0;
$delimiterstring  = "-----------\n";
# $endpara = '\S.*\n';

sub showline {
  if ($withfilename != 0) {
    printf ("\"%s\"", $ARGV);
  }
  if ($withfilename != 0 && $withlinenumber != 0) {
    print ",";
  }
  if ($withlinenumber != 0) {
    printf ("%4d", $.);
  }
  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] =~ /^-p$/) {
    $paragraphmode = 1;
    shift;
  }
  elsif ($ARGV[0] =~ /^-n$/) {
    $withlinenumber = 1;
    shift;
  }
  elsif ($ARGV[0] =~ /^-h$/) {
    $withfilename = 1;
    shift;
  }
  elsif ($ARGV[0] =~ /^-v$/) {
    $reversemode = 1;
    shift;
  }
  elsif ($ARGV[0] =~ /^-e$/) { # 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] [-h] [-n] [-d string] 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
   -5,12  means display 5 lines before the match and 12 lines after
   -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)
   -h  means toggle display filename before every line
   -n  means display line number before every line
   -d string  means use string as the output delimiter string
   pattern  is a Perl regular expression (you better quote it !)
Exiting";
}


if (length (@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;


# now 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 ($reversemode == 1 ? $cur !~ /$pat/o : $cur =~ /$pat/o) {   #match found
    if ($lastoutput <= $postcontext) {
      &showline ($cur);
    }
    else {
      print $delimiterstring if ($seq++ && $precontext + $postcontext > 0);
      foreach $line (@ary) {
        &showline ($line);
      }
    }
    $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];
}