#!/usr/bin/perl -w
#
# hss to html converter.
#
# Converts html source code (hss) to html files.
#
# Copyright (C) 1999-2000 S Morphet <smorphet@iee.org>
#  
# 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 2 of the License, 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.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software 
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#

# Modules
use POSIX qw(strftime);
use File::Copy 'cp'; 
use FileHandle;

# Function to print usage information.
sub DieUsage {
  print "\n";
  print "$id_name $id_version ($id_tag)\n" .
    "Usage: $id_name [options] inputfile\n\n";
  print "Options:\n";
  print "\t -v \t\t Be verbose.\n";
  print "\t -Dmacro[=defn]\t Define a macro, to 1, or defn, just like gcc.\n";
  print "\t -Idir \t\t Add dir to the list to be searched by #include.\n";

  print "\t -o file\t The name of the output file.\n";
  print "\t -x name \t The cross-ref filename.\n";

  print "\t -localroot dir\t The localroot directory for FULLREF.\n";
  print "\t -remoteroot dir The remoteroot directory for FULLREF.\n";
  print "\t -pwd dir \t The dir to use as pwd in FULLREF.\n";

  print "\t -k \t\t Keep intermediate files.\n";
  print "\t -b \t\t Don't remove blank lines.\n";
  print "\t -d \t\t Replace output file only if it is different.\n";
  print "\t -p \t\t Do not use line control.\n";

  print "\t -M \t\t Generate dependency information for make(1).\n";
  print "\t -MG \t\t Treat missing files as generated files.  Use with -M.\n";
  print "\t -MX \t\t Consider the cross-ref file as a possible dependency.\n".
    "\t\t\t Use with -M.\n";

  print "\t -def string\t The define string (default is \"#define\").\n";
  print "\t -defnmark mk\t The name marker in the define string.\n";
  print "\t -defdmark mk\t The definition marker in the define string.\n";
  
  print "\t -diffprog cmd\t The diff command string used by -d.\n";
  print "\t -diffomark mk\t The old name marker in the diff command string.\n";
  print "\t -diffnmark mk\t The new name marker in the diff command string.\n";
  
  print "\t -cpp command\t Command to run for the C preprocessor.\n";
  print "\t -cppopts opts\t Options to pass to the C preprocessor.\n";
  print "\t -cppoptsmark mk Marker for -cppopts insertion point.\n";
  print "\t -cppinmark mk\t Marker for cpp input file insertion point.\n";
  print "\t -cppoutmark mk\t Marker for cpp output file insertion point.\n";

  print "\t -intext ext\t The extension for intermediate files " .
    "(default .hxx).\n";
  print "\t -diffext ext\t The extension for intermediate output files " .
    "when\n\t\t\t the -d option is used. (default .dout).\n";
  print "\t -outext ext\t The extension for output files (default .html).\n";

  print "\t -htmlhdr string A header line for the HTML file.\n";
  print "\t -stamp \t Write a date stamp comment to the output file.\n";
  print "\t -version \t Print version info and exit.\n";
  print "\n";
  exit;
}


# Function to delete working files when program exits unexpectedly.
sub CleanUpFiles {
  my $delfile;

  # If we're keeping files, don't delete anything.  
  if( $keepfiles == 0 ) {
    unlink $interfile1;
    unlink $interfile2;
    unlink $outfile;
  }
}


# Function to load up the xref file.
sub LoadXRefFile {

  local $_;
  my %xrefhash;
  my @xrefline;
  my $name;

  my $hashexists = 0;

  print "Loading the x-ref file.\n" if $verbose;
  if( not open( XREF, "<$xreffile" )) {
    CleanUpFiles();
    # There's only one xref file, so it doesn't really matter if we
    # don't report the file/linenumber where the error occurs.
    die "$id_name: Unable to open x-ref file $xreffile.\n";
  }  

  # Build a hash for each page.
  while ( <XREF> ) {

    if( /<PAGE>/ ) {
      if( $hashexists ) {
	push( @xref_page_hash, {%xrefhash} );
      }

      # Start a new hash.
      %xrefhash = ();
      $hashexists = 1;

    }else{

      # Ignore blank lines.
      unless ( /^\s*$/ ) {

	# Add a line to the hash.
	s/^\s*//;  # Remove leading whitespace.
	@xrefline = split( /\s+/, $_ ); # Split into words.
	$name = $xrefline[0];           # Get the first word.
	shift @xrefline;
	
	$xrefhash{$name} = "@xrefline";
      }
    }
  }

  # Push the last hash.
  if( $hashexists ) {
    push( @xref_page_hash, {%xrefhash} );
  }

  close( XREF );
  $xrefopen = 1;
}


# Function to get a page number for a XRef.
# Arguments:
#   xrefname
#   xrefval
# Returns the page number (index into the xref array).
# Precondition: LoadXRefFile has been run succesfully.
sub LookupXRef {
  my( $xrefname, $xrefval ) = @_;

  print "Searching for $xrefname = $xrefval.\n" if $verbose;
  
  my $pnum = 0;
  foreach $page ( @xref_page_hash ) {
    my $lookup = $$page{$xrefname};

    if( defined( $lookup ) ) {
      # Allow things in quotes too.
      if( ($xrefval eq $lookup) or
	  ($xrefval eq "\"$lookup\"") ) {
	return $pnum;
      }
    }
    $pnum++;
  }
  
  print "Search failed.\n" if $verbose;
  return -1;
}


# Function to get a string for an xref entry on a certain page.
# Arguments: 
#   key
#   page
sub LookupXRefEntry {
  my ($key, $pagenum) = @_;
  
  my $hashref = $xref_page_hash[$pagenum];
  return $$hashref{$key};
}


# Function to run the c preprocessor on a file.
# Arguments: 
#   input filename
#   output filename
sub RunCpp {
  my ($infile, $outfile) = @_;

  my $command = $cppcommand;
  my $rc;

  # Replace markers with options and filenames.
  $command =~ s/$cppoptsmark/$cppopts/;
  $command =~ s/$cppinmark/$infile/;
  $command =~ s/$cppoutmark/$outfile/;
  
  print "Running C preprocessor: $infile -> $outfile.\n" if $verbose;
  print "Command is $command\n"  if $verbose;

  # Flush verbose output before running an external program.
  STDOUT->flush();

  $rc = system "$command";

  if( $rc ) {
    CleanUpFiles();
    die "$id_name: C preprocessor failed for $infile.\n";
  }
}


# Function to turn a local relative pathname into a remote one.
# Arguments:
#   $relname    - relative pathname.
#   $localroot  - local path to root of project.
#   $remoteroot - remote path to root of project.
sub LocalPathToRemote{
  my( $relname, $localroot, $remoteroot, $filename, $linenumber ) = @_;

  # Make full name out of remote name.
  # If a directory has been specified for the pwd, use that, else
  # use the proper pwd.
  my $fullname;
  if( $pwdset ) {
    $fullname = $pwdstring;
  }else{
    $fullname = `pwd`;
  }

  chomp( $fullname );
  $fullname .= "/$relname";

  # Split the parts of the path into a list.
  my @dirs = split( "/", $fullname );
  my @optdirs = ();
  
  foreach $dir ( @dirs ) {
    
    # If we get a dot, just ignore it.
    if( $dir eq "." )  { next; }
    
    # For a doubledot, pop the previous dir (go up one) and
    # ignore the doubledot.
    if( $dir eq ".." ) { pop @optdirs; next; }
    
    # Push the directory onto the end of the list.
    push @optdirs, $dir;
  }
  
  $fullname = join( "/", @optdirs );

  # Check that the fullname begins with the local root.
  # If it doesn't the file specified by the relative path may not
  # be under the local root.
  if( $fullname !~ /^$localroot/ ) {

    die "$id_name: $filename:$linenumber\n" .
        "\tConverting relative path to remote - the full local path:\n" .
	"\t$fullname\n" .
	"\tdoes not exist under the local project root:\n" .
	"\t$localroot\n";
  }

  # Now replace the localroot part of the path with the remote root.  
  ($fullname = $fullname) =~ s/$localroot/$remoteroot/;

  return $fullname;
}


# This is the main function that gets called recursively to perform the
# preprocessing pass.  The first argument is the filename to open, which
# may include a path.  The second argument is the filename without
# a path (suitable for printing in messages).
sub PreprocessWithIncludes {
  my ($filename, $printablefilename) = @_;
  my $incname;
  my $pincname;
  my $idir;
  my $openok;
  local *INFILE;
  my $linecount = 1;

  # Open the file.  Failure to open causes an error UNLESS we're 
  # doing -M -MG.
  $openok = open INFILE, "<$filename";

  # Failure to open the file is not an error _if_ we're doing makedepgen
  if( (not $openok) and (not $makedepgen) ) {
    CleanUpFiles();
    die "$id_name: Unable to open $printablefilename.\n";
  }

  # Add dependencies to the list.
  if( $makedep ) {
    push( @deplist, $filename );
  }

  # If we couldn't open the file, and we've got this far, it means
  # we're doing makedep.  We can't read the file, so we just return.
  return if( not $openok );

  # Reading an ordinary file.
  # If using cpp line control, output the file name and line number
  # for the file that has just been opened.
  if( $linecontrol ) {
    print OUTFILE "#line $linecount \"$printablefilename\"\n";
  }

  while( <INFILE> ) {

    # Check for include.
    # If producing dependency information, check for IMPORT too.
    if( /^\s*(\#\s*include)\s+[<\"]\s*(.*?)\s*[>\"]/ or
	($makedep and /<\s*(IMPORT)\s*=\s*[<\"]\s*(.*?)\s*[>\"]>/) ) {

      my $isimport;

      # We need to know if we're dealing with include or import, because
      # we mustn't open the imported file.
      if( $1 eq "IMPORT" ) {
	$isimport = 1;
      }else{
	$isimport = 0;
      }
	
      # Attempt to read the file using each of the dirs in @includes.
      $incname = "";
      foreach $idir (@includes) {
	if( -f "$idir/$2" ) { 
	  $incname = "$idir/$2";
	  $pincname = "$2";
	  last; 
	}
      }

      if( $incname eq "" ) {
	# The file wasn't found on the include path.

	# If not doing makedepgen it's an error.
	if( not $makedepgen ) {
	  my $ipath;
	  CleanUpFiles();
	  $ipath = join (":", @includes);
	  die "$id_name: $printablefilename:$linecount\n" .
	    "\tUnable to find included file $2 in include path:\n" .
	    "\t$ipath\n";
	} else {
	  # It must be makedepgen.  Just add the filename to the
	  # dependency list without trying to open it.
	  push( @deplist, $2 );
	}
      }else{
	# The file was found.
	
	if( not $isimport ) {
	  # Open the include file and process it.
	  PreprocessWithIncludes( "$incname", "$pincname" );
	}else{
	  # Imported files don't get opened until the post-processing
	  # stage.  Here we just add the filename to the dependency list.
	  # (IMPORT is only detected when processing dependencies.)
	  push( @deplist, $pincname );
	}

	# Increment the line counter for the #include line.
	$linecount += 1;

	# If using cpp line control, output the file name and line number
	# for the file that we've just returned to.
	if( $linecontrol ) {
	  print OUTFILE "#line $linecount \"$printablefilename\"\n";
	}
	
	# End of #include processing.
      }

    } else {
      # Process an ordinary line (i.e. not #include).

      # Check that the line ends in newline.  If it doesn't it must
      # be the last line in a file.
      if( not $_ =~ /\n/ ) {
	print "$id_name: $printablefilename:$linecount\n" .
	  "\tWarning - File does not end in newline.\n";
	$_ = "$_\n";
      }

      if( not $makedep ) {
	# If doing makedep we don't do any of this work...

	# Look for references.
	# We do this before the preprocessor, because we know that it'll
	# try to expand all the _C_ macros which the crossref table is built
	# from.
	# REF sp. colon sp. OUTPUT sp. comma sp. NAME sp. EQ. 
	#     sp. TOKEN sp. paren
	if( /<\s*REF\s*:\s*(.*?)\s*,\s*(.*?)\s*=\s*(.*?)\s*>/ ) {
	  # regex returns OUTPUT, NAME and TOKEN as $1 $2 $3.
	  # Look for a page in the xref file which defines NAME=TOKEN and
	  # Return the corresponding definition.
	  
	  $match = $&;
	  
	  print "Processing cross reference returning key $1 " . 
	    "for page matching $2=$3.\n" if $verbose;
	  
	  $refret = $1;
	  $refmatchname = $2;
	  $refmatchval = $3;
	  
	  # Load up the references first time they are used.
	  LoadXRefFile() if ($xrefopen == 0);
	  
	  # Process the reference here.
	  my $pagenum = LookupXRef( $refmatchname, $refmatchval );
	  
	  if( $pagenum < 0 ) {
	    CleanUpFiles();
	    die "$id_name: $printablefilename:$linecount\n" .
	      "\tUnable to find cross reference entry for " .
	      "$refmatchname = $refmatchval.\n";
	  }
	  
	  # Look for the corresponding string.
	  $replacement = LookupXRefEntry( $refret, $pagenum );
	  if( not defined $replacement ) {
	    CleanUpFiles();
	    die "$id_name: $printablefilename:$linecount\n" .
	      "\tUnable to find cross reference match for $refret on page " .
	      "$pagenum\n";
	  }
	  
	  # Make the replacement. Use quotemeta to make sure the brackets etc.
	  # in the REF() don't upset anything.
	  s/\Q$match/$replacement/;
	  
	  # Redo the line, because there might be more macros...
	  redo;
	}
	
	# Write the processed line into the output file.
	print OUTFILE $_;

	# Increment the line counter.
	$linecount += 1;

      }else{
	# Makedepend.
	
	# If we're including the xref file in the dependency output,
	# and we haven't seen it yet, look for a <REF> tag, and output
	# the xref file name when we find it.
	if( $makedepref and not $makedepreffound ) {
	  
	  # Don't check for full <REF> syntax, any <REF> will do.
	  if( /<\s*REF.*?>/ ) {
	    print "Found <REF> in input: adding dep $xreffile\n" if $verbose;

	    # If not doing makedepgen, we need to make sure the file exists.
	    if( (not $makedepgen) and not -e $xreffile )
	      {
		CleanUpFiles();
		die "$id_name: " .
		  "Unable to open cross reference file $xreffile.\n";
	      }
	    
	    # Add the file to the deplist.
	    $makedepreffound = 1;
	    push( @deplist, $xreffile );
	  }
	}
	# End of makedepend processing for ordinary lines.
      }
      # End of processing for ordinary lines.
    }
    # End of while <INFILE>
  }  
  
  close INFILE;
}


# Preprocess file.
# Arguments:
#   input filename
#   output filename
sub Preprocess
{
  my( $infile, $outfile ) = @_;
  my $defline;

  if( not $makedep ) {
    # Normal mode.
    print "Preprocessing: $infile -> $outfile.\n" if $verbose;

    # Open the output file.  We'll open the input file inside
    # PreprocessWithIncludes.
    if( not open ( OUTFILE, ">$outfile" )) {
      CleanUpFiles();
      die "$id_name: Could not open $outfile for output.\n";
    }

    # Write defines supplied on the command line into the file so 
    # that they will be available to the C preprocessor.
    while( @defpair = each %defines ) {
      
      $defline = $definition;
      $defline =~ s/$defnamemark/$defpair[0]/;
      if( $defpair[1] ne "" ) {
	$defline =~ s/$defdefmark/$defpair[1]/;
      }else{
	$defline =~ s/$defdefmark//;
      }
      print OUTFILE "$defline\n";
    }
    
    # Preprocess, with include file processing.
    PreprocessWithIncludes( "$infile", "$infile" );
    
    # Close the output file.  The input file was closed in the
    # PreprocessWithIncludes subroutine.
    close( OUTFILE );
  } else {
    # Make-depend mode - just read the input files.
    print "Dependency checking: $infile -> $outfile.\n" if $verbose;

    PreprocessWithIncludes( "$infile", "$infile" );
  }
}


# Postprocess file to make html.
# Arguments:
#   input filename
#   output filename
sub Postprocess
{
  my( $infile, $outfile ) = @_;
  my $idir;
  my $incname;

  my $currentfilename;
  my $currentlinenumber;

  # If using line control, the infile will contain lines line
  # '# nn "filename" fff
  # nn is the line number.
  # fff are multiple flags, which are ignored here.
  #
  # This is the cpp _output_ format, without the word 'line'.
  # The cpp _input_ format uses #line, and is what we added to
  # the .1.hxx file in preprocessing.

  print "Postprocessing: $infile -> $outfile.\n" if $verbose;
 
  # Open the input and output files.
  if( not open ( INFILE, "<$infile" )) {
    CleanUpFiles();
    die "$id_name: Could not open $infile for input.\n";
  }

  if( not open ( OUTFILE, ">$outfile" )) {
    CleanUpFiles();
    die "$id_name: Could not open $outfile for output.\n";
  }

  # Add a banner to the html file.
  print OUTFILE "$htmlheader\n" if $htmlheaderdef;
  print OUTFILE "<!-- Guava tools by Steve Morphet 1999-2000. -->\n";

  if( $datestampoutput ) {
    print OUTFILE "<!-- Generated at $datestring. -->\n";
  }

  $currentfilename = $infile;
  $currentlinenumber = 1;

  # Copy lines from hss to hxx.
  while( <INFILE> ){

    # Check for a line control code.
    if( $linecontrol ) {
      if( /^\#\s*([0-9]*)\s*\"(.*)\"\s*/ )
	{
	  # Update filename and line number counters.
	  $currentlinenumber = $1;
	  $currentfilename = $2;
	  #print "Line control $currentfilename line $currentlinenumber.\n";
	  next;
	}
    }
    #print "Processing line $currentlinenumber\n";

    # Process blank lines.
    if( $removeblanks and /^\s*$/ ) {
      # Increment the current line number for the line we just skipped.
      $currentlinenumber += 1;
      next;
    }

    # Process IMPORT.  A bit like #include, but it just imports the
    # text without any processing.
    if( /<\s*IMPORT\s*=\s*[<\"]\s*(.*?)\s*[>\"]>/ ) {

      local $_;

      # Attempt to read the file using each of the dirs in @includes.
      $incname = "";
      foreach $idir (@includes) {
	if( -f "$idir/$1" ) { 
	  $incname = "$idir/$1";
	  last; 
	}
      }

      if( $incname eq "" ) {
	my $ipath;
	CleanUpFiles();
	$ipath = join (":", @includes);
	die "$id_name: $currentfilename:$currentlinenumber\n" .
	  "\tUnable to find imported file $1 in include path:\n" .
	  "\t$ipath\n";
      }

      # Open the file.
      if( not open IMPORT, "<$incname" ) {
	CleanUpFiles();
	die "$id_name: $currentfilename:$currentlinenumber\n" .
	  "\tUnable to open imported file $incname.\n";
      }

      # Copy the imported file to the output.
      while( <IMPORT> ) {
	print OUTFILE $_;
      }
      close IMPORT;

      # Increment the current line number for the IMPORT line.
      $currentlinenumber += 1;
      next;
    }

    # Insert the current date.
    if( /<\s*DATE\s*(=\s*\"(.*?)\"){0,1}\s*>/ ) {
      my $date;

      if( defined $2 ) {
	
	$date = strftime( "$2", @datecodes );
	
	# If the format contained <TH> replace it with th, st, nd or rd
	# as appropriate.
	if( $datecodes[3] == 1 or $datecodes[3] == 21 ) {
	  $day_th = "st";
	} elsif ($datecodes[3] == 2 or $datecodes[3] == 22 ) {
	  $day_th = "nd";	  
	} elsif ($datecodes[3] == 3 or $datecodes[3] == 23 ) {
	  $day_th = "rd";	  
	} else {
	  $day_th = "th";	  
	}

	$date =~ s/<TH>/$day_th/g;

      } else {
	$date = $datestring;
      }

      my $match = $&;
      s/\Q$match/$date/;
      redo;
    }

    # Process FULLREF, depending on whether LOCAL is defined.
    if( /<\s*FULLREF\s*=\s*"(.*?)"\s*>/ ) {

      my $match = $&;
      my $relref = $1;

      print "Processing FULLREF(\"$relref\") for " if $verbose;
      if( defined $defines{'LOCAL'} ) {	

	print "local target\n" if $verbose;

	# Just reoutput the argument of the FULLREF macro.
	s/\Q$match/"$relref"/;
	print "Replacing FULLREF(\"$relref\") with $relref.\n" if $verbose;

      } else {

	print "non-local target\n" if $verbose;
	
	# Ensure we have localroot and remoteroot specified either on the
	# command line, or by defines.
	if( $localroot_cmdline_def ) {
	  $localroot = $localroot_cmdline;
	}elsif( defined $defines{'LOCALROOT'} ) {
	  $localroot = $defines{'LOCALROOT'};
	}else{
	  CleanUpFiles();
	  die "$id_name: $currentfilename:$currentlinenumber\n" .
	    "\tProcessing FULLREF for non-local target:  The " . 
	    "local directory root must\n\tbe specified either with " .
	    "-localroot or -DLOCALROOT on the command line.\n";
	}

	if( $remoteroot_cmdline_def ) {
	  $remoteroot = $remoteroot_cmdline;
	}elsif( defined $defines{'REMOTEROOT'} ) {
	  $remoteroot = $defines{'REMOTEROOT'};
	}else{
	  CleanUpFiles();
	  die "$id_name: $currentfilename:$currentlinenumber\n" .
	    "\tProcessing FULLREF for non-local target:  The " . 
	    "remote directory root must\n\tbe specified either with " .
	    "-remoteroot or -DREMOTEROOT on the command line.\n";
	}
	
	# Convert the FULLREF argument to a remote path.
	$remoteref = LocalPathToRemote( $relref, $localroot, $remoteroot,
				        $currentfilename,
				        $currentlinenumber );
	print "Replacing FULLREF(\"$relref\") with $remoteref.\n" if $verbose;

	s/\Q$match/"$remoteref"/;	
      }

      # Redo the line, because there might be more macros...
      redo;
    }

    # Macros to insert apostrophes and quotes.  Even with -traditional,
    # the c preprocessor gets upset if they're used in macros.
    s/<APOS>/\'/g; 
    s/<QUOTE>/\"/g; 
    
    # Macro to absorb spaces.  The C preprocessor tends to leave spaces
    # all over the place which may not be what is needed. Double spaces
    # are fine in HTML, of course.
    s/\s*<NOSP>\s*//g; 

    # If you need to force a space somewhere...
    # For example, to ensure that you _don't_ have a double space
    # somewhere, use <NOSP><SP>.  <SP> replacement must happen after
    # <NOSP> for this to work.
    s/<SP>/ /g;

    # Write the processed line into the output file.
    print OUTFILE $_;

    # Increment the current line number for the line we just output.
    $currentlinenumber += 1;
  }
  
  # Close the files.
  close( OUTFILE );
  close( INFILE );
}


#########################
## Main program start. ##
#########################

# Process arguments specified on command line.
$keepfiles = 0;
$verbose = 0;
$removeblanks = 1;

#Filename extensions.
$hxxext = ".hxx";
$htmlext = ".html";
$diffext = ".dout";

$datestampoutput = 0;

$outfile = "";
$outfileset = 0;

$xreffile = "index.xref";
$xrefopen = 0;

$defnamemark = "%NAME%";
$defdefmark = "%DEF%";
$definition = "#define %NAME% %DEF%";

$cppopts = "";

$localroot_cmdline_def = 0;
$remoteroot_cmdline_def = 0;

$pwdstring = "";
$pwdset = 0;

%defines = ();

$htmlheader = "";
$htmlheaderdef = 0;

# Makedep is set by -M.
# Makedepgen is set by -MG, and modifies the behaviour of makedep.
# -MG has to be used with -M, which seems a bit odd, but is consistent
# with the way gcc does it.
$makedep = 0;
$makedepgen = 0;
$makedepref = 0;
$makedepreffound = 0;
@deplist = ();

# Get a single timestamp that can be reused throughout the program.
$exectime = time;
$datestring = localtime($exectime);
@datecodes = (localtime($exectime));

# Get information from rcs.
# Spaces before the closing quotes are preserved by RCS and stop
# emacs syntax highlighting thinking there's a variable called $'
$programid = '$Id: hss2html,v 1.36 2000/06/17 15:50:28 sdm Exp $ ';
$programid =~ /\$Id: (.*?),v (.*?) (.*?) /;
$id_name = $1;
$id_version = $2;
$id_date = $3;

$tagname   = '$Name: Guava-1_0_3 $ ';
$tagname   =~ /\$Name: (.*?) /;
if( $1 ne "" ) {
  $id_tag = $1;
}else{
  $id_tag = "none";
}

# Flag to see whether the user has specified a cpp command line.
$usersetcpp = 0;

# Default markers for the CPP command.  The 'cppoptmark; marker will
# be replaced with the text of the -cppopts argument.  The string
# should be such that the name of the output file can be appended,
# followed by the name of the input file.  For that reason, the string
# should end in -o or equivalent.
$cppoptsmark = "%OPTS%";
$cppinmark = "%INFILE%";
$cppoutmark = "%OUTFILE%";

# Flag set if using line control
$linecontrol = 1;

# Include search dirs, because cpp never gets the chance...
@includes = ();

# Output difference testing.
$diffout = 0;
$diffoldmarker = "%OLD%";
$diffnewmarker = "%NEW%";
# The -b option to GNU diff ignores whitespace in the input.  -q
# outputs only whether the files differ.  We expect an output value of
# zero if there are no differences, and non zero if there are
# differences.  It seems that there's no way with GNU diff to suppress
# _all_ output (with -q it still tells us on stdout if the files
# differ) so we send the output to /dev/null.
$diffprog = "diff -b -q %OLD% %NEW% >/dev/null";

# Process command line arguments.
$numargs = $#ARGV;
while( ($numargs >= 0) and $ARGV[0] =~ /^-/ )
  {  
    if( $ARGV[0] eq "-k" ) {
      $keepfiles = 1;
    }
    elsif( $ARGV[0] eq "-v" ) {
      $verbose += 1;
    }
    elsif( $ARGV[0] eq "-b" ) {
      $removeblanks = 0;
    }
    elsif( $ARGV[0] eq "-d" ) {
      $diffout = 1;
    }
    elsif( $ARGV[0] eq "-p" ) {
      $linecontrol = 0;
    }
    elsif( $ARGV[0] eq "-stamp" ) {
      $datestampoutput = 1;
    }
    elsif( $ARGV[0] eq "-M" ) {
      $makedep = 1;
    }
    elsif( $ARGV[0] eq "-MG" ) {
      $makedepgen = 1;
    }
    elsif( $ARGV[0] eq "-MX" ) {
      $makedepref = 1;
    }
    elsif( $ARGV[0] eq "-def" ) {
      shift;
      $numargs -= 1;
      $definition = $ARGV[0];
    }
    elsif( $ARGV[0] eq "-defnmark" ) {
      shift;
      $numargs -= 1;
      $defnamemark = $ARGV[0];
    }
    elsif( $ARGV[0] eq "-defdmark" ) {
      shift;
      $numargs -= 1;
      $defdefmark = $ARGV[0];
    }
    elsif( $ARGV[0] eq "-diff" ) {
      shift;
      $numargs -= 1;
      $diffprog = $ARGV[0];
    }
    elsif( $ARGV[0] eq "-diffomark" ) {
      shift;
      $numargs -= 1;
      $diffoldmarker = $ARGV[0];
    }
    elsif( $ARGV[0] eq "-diffnmark" ) {
      shift;
      $numargs -= 1;
      $diffnewmarker = $ARGV[0];
    }
    elsif( $ARGV[0] =~ "^-I" ) {
      $_ = $ARGV[0];

      # Strip trailing slash.
      s/(.*)\/$/$1/;

      /^-I(.*)/;
      if($1 eq ""){
	die "$id_name: Include directory is blank.  Use -Idir " .
	  "without a space, perhaps?\n";
      }
      push( @includes, $1 );
    }    
    elsif( $ARGV[0] =~ /^-D/ ) {
      # Definitions might have a value or not.
      if( $ARGV[0] =~ "=" ) {
	$_ = $ARGV[0];
	/^-D(.*?)=(.*)/;
	$name = $1;
	$value = $2;
      }else{
	$_ = $ARGV[0];
	/^-D(.*)/;
	$name = $1;
	$value = "1";
      }
      # Add the name and value to a hash.
      $defines{$name} = $value;
    }
    elsif( $ARGV[0] eq "-cpp" ) {
      shift;
      $numargs -= 1;
      $usersetcpp = 1;
      $cppcommand = $ARGV[0];
    }
    elsif( $ARGV[0] eq "-cppoptsmark" ) {
      shift;
      $numargs -= 1;
      $cppoptsmark = $ARGV[0];
    }
    elsif( $ARGV[0] eq "-cppinmark" ) {
      shift;
      $numargs -= 1;
      $cppinmark = $ARGV[0];
    }
    elsif( $ARGV[0] eq "-cppoutmark" ) {
      shift;
      $numargs -= 1;
      $cppoutmark = $ARGV[0];
    }
    elsif( $ARGV[0] eq "-cppopts" ) {
      shift;
      $numargs -= 1;
      $cppopts = $ARGV[0];
    }
    elsif( $ARGV[0] eq "-o" ) {
      shift;
      $numargs -= 1;
      $outfile = $ARGV[0];
      $outfileset = 1;
    }
    elsif( $ARGV[0] eq "-x" ) {
      shift;
      $numargs -= 1;
      $xreffile = $ARGV[0];
    }
    elsif( $ARGV[0] eq "-pwd" ) {
      shift;
      $numargs -= 1;
      $pwdstring = $ARGV[0];
      $pwdset = 1;
    }
    elsif( $ARGV[0] eq "-localroot" ) {
      shift;
      $numargs -= 1;
      $localroot_cmdline_def = 1;
      $localroot_cmdline = "$ARGV[0]";
    }
    elsif( $ARGV[0] eq "-remoteroot" ) {
      shift;
      $numargs -= 1;
      $remoteroot_cmdline_def = 1;
      $remoteroot_cmdline = "$ARGV[0]";
    }
    elsif( $ARGV[0] eq "-intext" ) {
      shift;
      $numargs -= 1;
      $hxxext = ".$ARGV[0]";
    }
    elsif( $ARGV[0] eq "-outext" ) {
      shift;
      $numargs -= 1;
      $htmlext = ".$ARGV[0]";
    }
    elsif( $ARGV[0] eq "-diffext" ) {
      shift;
      $numargs -= 1;
      $diffext = ".$ARGV[0]";
    }
    elsif( $ARGV[0] eq "-htmlhdr" ) {
      shift;
      $numargs -= 1;
      $htmlheader = "$ARGV[0]";
      $htmlheaderdef = 1;
    }
    elsif( $ARGV[0] eq "-version" ) {
      print "$id_name, version $id_version, release $id_tag, $id_date.\n";
      exit;
    }
    else {
      print "Unknown option $ARGV[0]\n";
      DieUsage;
    }
    
    $numargs -= 1;
    shift;
  }

DieUsage if( $numargs != 0 );
$inputfile = $ARGV[0];

# Are we using line control?
if( $verbose ) {
  if( $linecontrol ) {
    print "Using line control.\n";
  } else {
    print "Not using line control.\n";
  }
}

# Build the default cpp command line if the user has not specified
# one.
if( not $usersetcpp ) {

  # If not using line control, give the -P flag to cpp.
  if( $linecontrol ) {
    $cppnolc = "";
  } else {
    $cppnolc = "-P";
  }

  # Not every gcc installation has cpp, so we use gcc with the -E
  # option to do the same job.
  $cppcommand = "gcc -x c -traditional -E $cppnolc $cppoptsmark " .
    "-o $cppoutmark $cppinmark";
}

# Check that the options marker can be found in the CPP command string.
unless( $cppcommand =~ /$cppoptsmark/ ) {
  die "$id_name: Options marker \"$cppoptsmark\" not " .
    "found in CPP command string.\n";
}
unless( $cppcommand =~ /$cppinmark/ ) {
  die "$id_name: Input file marker \"$cppinmark\" not " .
    "found in CPP command string.\n";
}
unless( $cppcommand =~ /$cppoutmark/ ) {
  die "$id_name: Output file marker \"$cppoutmark\" not " .
    "found in CPP command string.\n";
}

# Check the markers are present in the define string 
unless( $definition =~ /$defnamemark/ ) {
  die "$id_name: Name marker \"$defnamemark\" not found " .
    "in definition string.\n";
}
unless( $definition =~ /$defdefmark/ ) {
  die "$id_name: Definition marker \"$defdefmark\" not " .
    "found in definition string.\n";
}

# Check the markers are present in the diffprog string.
unless( $diffprog =~ /$diffoldmarker/ ) {
  die "$id_name: Old name marker \"$diffoldmarker\" not found in " .
    "diffprog string.\n";
}
unless( $diffprog =~ /$diffnewmarker/ ) {
  die "$id_name: New name marker \"$diffnewmarker\" not found in " .
    "diffprog string.\n";
}

# Create output and intermediate filenames from the input name.

# Pattern searches for a dot followed by a number of non-dot characters
# at the end of the string.
if( $inputfile =~ /\.[^\.]*$/ ) {

  # Input file has extension, replace it to create output filenames.
  if( $outfileset == 0 ) {
    ($outfile = $inputfile) =~ s/\.[^\.]*$/$htmlext/; 
  }
  ($interdiff  = $inputfile) =~ s/\.[^\.]*$/$diffext/;
  ($interfile1 = $inputfile) =~ s/\.[^\.]*$/\.1$hxxext/;
  ($interfile2 = $inputfile) =~ s/\.[^\.]*$/\.2$hxxext/;

} else {

  # Input file has no extension, so append extensions to create output
  # filenames.
  if( $outfileset == 0 ) {
    $outfile = "$inputfile$htmlext";
  }

  $interdiff  = "$inputfile$diffext";
  $interfile1 = "$inputfile.1$hxxext";
  $interfile2 = "$inputfile.2$hxxext";
}


# If the output filename has not got an extension now, it must have
# been specified without one.
if( not ($outfile =~ /\./) ) {
  print "Adding extension to output " .
    "file name $outfile > " if $verbose;
  $outfile .= "$htmlext";
  print "$outfile.\n" if $verbose;
}

# Add the directory of the input file name to the include path.
if( $inputfile =~ /^(.*)\/(.*)/ ) {
  # If the input file isn't in the current dir, add the path to @includes.
  push( @includes, "$1" );
}else{
  # Else, put "." into the include path.
  push( @includes, "." );
}

# Display file names.
if( $verbose ) {
  print "input file name = $inputfile\n";
  print "intermediate file names = $interfile1, $interfile2\n";
  if( $diffout ){
    print "intermediate output name for diff = $interdiff\n";
  }
  print "output file name = $outfile\n";
}

# If not doing makedep, make sure makedepgen and makedepref aren't
# used either.
if( (not $makedep) && $makedepgen ) { 
  die "$id_name: -MG cannot be used without -M.\n"; 
}
if( (not $makedep) && $makedepref ) { 
  die "$id_name: -MX cannot be used without -M.\n"; 
}


# Preprocess the input file.  Includes will be expanded first, and
# cross references are expanded.
Preprocess( "$inputfile", "$interfile1" );

if( $makedep ) {
  # Output dependency information and exit.
  my ($i, $d);
  my %seen;

  # gcc removes the pathname from the dependent file name, so we should too.
  $outfile =~ s/^.*\///;

  # Write the target filename.
  print "$outfile: ";

  # Remove duplicates from the list, preserving the order.
  # From the Perl Cookbook, pp102.
  %seen = ();
  @uniquedeps = grep { ! $seen{$_} ++ } @deplist;

  # Write out each dependency.
  foreach $dep (@uniquedeps) {
    
    # Strip leading './' from names.
    if( $dep =~ /^.\/(.*)/ ){
	$dep = $1;
     }
    print "$dep ";
  }

  print "\n";
  exit;
}

# Run the C preprocessor to make hxx.  All includes should have been 
# processed already.
RunCpp( "$interfile1", "$interfile2" );

# Postprocess the file to make an html file.  No need to process includes,
# because there shouldn't be any.
# If doing diffout, use the alternate filename.
if( $diffout ) {
  Postprocess( "$interfile2", "$interdiff" );
}else{
  Postprocess( "$interfile2", "$outfile" );
}

if( $diffout ) {
  $filesdiffer = 0;

  if( not -e $outfile ) {

    # Output file does not exist, so no need to compare.
    print "Output file does not exist yet in diff comparison.\n" if $verbose;
    $filesdiffer = 1;

  } else {

    # Replace the diff markers with filenames.
    $diffprog =~ s/$diffnewmarker/$interdiff/;
    $diffprog =~ s/$diffoldmarker/$outfile/;
  
    # Run the diff program.
    print "Running \"$diffprog\"\n" if $verbose;
    $filesdiffer = system "$diffprog";
  }
  
  if( $filesdiffer ) {
    print "File has changed.  Replacing original.\n" if $verbose;
    cp( "$interdiff", "$outfile" );
  } else {
    print "Files are identical.  Keeping original.\n" if $verbose;
  }

}

if( $keepfiles == 0 ) {
  print "Removing intermediate file $interfile1\n" if $verbose;
  unlink $interfile1;

  print "Removing intermediate file $interfile2\n" if $verbose;
  unlink $interfile2;

  print "Removing comparison output file $interdiff\n" if $verbose;
  unlink $interdiff;
}
