#!/bin/perl
## go - D. Musliner  	 - RCS $Revision: 3.6 $
## - fully automated LaTeX document generation routine.
## - checks to see if .bib or .tex source files (or included other files)
##		have been changed since last run (which made .aux).
## - reads in user's default info from .gorc in local or home directory.
## - see sub print_help for info.
## - -i option is coolest: note it will turn on slide mode automatically if
##	detects a \blackandwhite{} or \colorslides{} command.
##	- also automatically puts root_filename into .gorc, so you can run
##	  'go -i root_filename' once and only type 'go' forevermore.
##
## - NOTE portions of this code are designed to interface to the BiBDb 
## 	bibliographic database system, which has not yet been released 
##	outside of UM.
##-----------------------------------------------------------------------
##Copyright 1992 by David J. Musliner and The University of Michigan.
##
##                        All Rights Reserved
##
##Permission to use, copy, modify, and distribute this software and its
##documentation for any purpose and without fee is hereby granted,
##provided that the above copyright notice and this permission notice appear in
##all copies and modified versions.
##
##THE COPYRIGHT HOLDER(S) DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
##INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT 
##SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR 
##CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, 
##DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 
##TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
##PERFORMANCE OF THIS SOFTWARE.
##-----------------------------------------------------------------------
## The -pvc option is derived from ideas by
##		Bernd Nordhausen (bernd@iss.nus.sg) of National University of 
##		Singapore and Alex Lopez-Ortiz of University of Waterloo
##-----------------------------------------------------------------------

	## default document processing programs.
$latex  = 'latex';
$bibtex  = 'bibtex';
$slitex  = 'slitex';
$dviselect  = 'dviselect';
$dvips  = 'ndvips';
$lpr  = 'lpr';
$ps_previewer  = 'gs';                  # gs or ghostview
$dvi_previewer  = 'xtex';               # xtex
$makeindex  = 'makeindex';

	## default flag settings.
$bibtex_mode = 0;		# is there a bibliography needing bibtexing?
$index_mode = 0;		# is there an index needing makeindex run?
$bibdb_mode = 0;		# is the bibliography created by bibdb database manager?
$ran_bibextract = 0;		# T if have run bibextract already.

$root_filename = 'main';	# name of root LaTeX file

$sleep_time = 2;		# time to sleep b/w checks for file changes in -pvc mode

	## read in .gorc, either in current directory or user's home directory
	## - .gorc simply contains perl code, which can override defaults.
$rcfile = '.gorc';
if (!(-e $rcfile)) { $rcfile = "$ENV{HOME}/$rcfile"; }
if (-e $rcfile)
  {
	open(rcfile) || die "Couldn't open rc file [$rcfile]\n";
	while (<rcfile>) { eval; }
	close(rcfile);
  }

while ($_ = shift) 		## process command line args.
  {
	if (/^-c/) { $cleanup_mode = 1; }
	elsif (/^-f/) { $force_mode = 1; }
	elsif (/^-g/) { $go_mode = 1; }
	elsif (/^-h/) { &print_help; }
	elsif (/^-i/) { $auto_include_mode = 1; }
	elsif (/^-ps/) { $postscript_mode = 1; }
	elsif (/^-pvc/) { $preview_continuous_mode = 1; }
	elsif (/^-pv/) { $preview_mode = 1; $postscript_mode = 1;}
	elsif (/^-p/) { $print_mode = 1; $preview_mode = 0; }
	elsif (/^-s/) { $slide_mode = 1; }
	elsif (/^-/) { $lpr_options .= "$_ "; }
	elsif (/^[\d:\.\*]/) { $page_options .= "$_ "; }
	else { $root_filename = $_; }
  }

	## remove .tex from filename if was given.
if ($root_filename =~ /(\S+)\.tex$/) { $root_filename = $1; }

if ($auto_include_mode) 
  { 
		# get search paths.
	$psfigsearchpath = '.';
	$TEXINPUTS = $ENV{'TEXINPUTS'};
	if (!$TEXINPUTS) { $TEXINPUTS = '.'; }
	$BIBINPUTS = $ENV{'BIBINPUTS'};
	if (!$BIBINPUTS) { $BIBINPUTS = $TEXINPUTS; }

		# reset all saved flags that old .gorc might have set
		# and that we can automatically detect should/not be set.
		# Note we dont reset bibdb_mode b/c have no way to detect if it
		# is correct or not: preserve that over repeated -i invocations.
  	$slide_mode =0;
  	$postscript_mode =0;
  	$landscape_mode =0;
  	$bibtex_mode =0;
	$index_mode =0;

  	$includes = ''; 
  	&scan_for_includes("$root_filename.tex"); 
  	&update_gorc;
  }

if ($cleanup_mode) { &cleanup; }

if ($cleanup_mode || $auto_include_mode) { exit; }

	## put root tex file into list of includes.
$includes .= " $root_filename.tex";

warn "Go: Processing document in [$root_filename.tex]\n";

	## before munging, save existing .aux file.
	## - if latex bombs, kill .aux, restore this backup to get back most
	## useful bib/ref info.
system("cp -p $root_filename.aux $root_filename.aux.bak > /dev/null 2>&1");

#************************************************************
# note for page options with slitex, we will create a duplicate root
# file and insert the appropriate \onlyslides command, rather than
# using dviselect to choose pages, since that way we will get headers
# properly used even if dont print first slide.

if ($slide_mode) 
  { 
	if ($page_options)
  	  {
		$page_options =~ tr/:/-/;
		warn "Go: Selected pages will appear in $root_filename"."_partial.*\n";
		@split_page_options = split(' ',$page_options);
		@sorted_page_options = sort increasing @split_page_options;
		$page_options = join(',',@sorted_page_options);
		print "$page_options\n";
			# now set root filename to new partial one and
			# reset page options to null, so process it entirely.
		$partial_rootname = "$root_filename"."_partial";
  		if (!open(rootfile,"$root_filename.tex")) 
    		  { 
		    die "Go: could not open input file [$root_filename.tex]\n";
		  }
  		if (!open(partial_rootfile,">$partial_rootname.tex")) 
    		  { 
		    die "Go: could not open output file [$partial_rootname.tex]\n";
		  }
  		while(<rootfile>)
    		  {
			if (/begin\{document\}/)
			  {
				print partial_rootfile;
				print partial_rootfile "  \\onlyslides{$page_options}\n";
			  }
			else { print partial_rootfile; }
    		  }
		close partial_rootfile;
		close rootfile;
		$root_filename = $partial_rootname;
		$includes .= " $partial_rootname.tex";
		$page_options = '';
  	  }
	&make_slitex_dvi; 
	&make_final_dvi;
  	&make_postscript;
  	if ($preview_mode) { &make_preview; }
  	if ($preview_continuous_mode) { &make_preview_continuous; }
  	&make_printout;
  	exit;
  }
else
  {
	if ($page_options) 	# fix up page options, b/c dviselect uses : for 
  	  { 			# ranges, I always use - by mistake, so...
		$page_options =~ tr/-/:/;
		warn "Go: Selected pages will appear in $root_filename"."_partial.*\n";
	  }
	&make_latex_dvi;
	&make_final_dvi;
  	&make_postscript;
  	if ($preview_mode) { &make_preview; }
  	if ($preview_continuous_mode) { &make_preview_continuous; }
  	&make_printout;
  	exit;
  }

#************************************************************
#### Subroutines

#************************************************************
sub make_latex_dvi
{
  $changed_dvi = 0 ;		# flag if anything changed.
	## get initial last modified times.
  $tex_mtime = &get_latest_mtime($includes);
  $aux_mtime = &get_mtime("$root_filename.aux");
  $bbl_mtime = &get_mtime("$root_filename.bbl");

  	## - if no dvi file, or .aux older than tex file or bib file, run latex.
  if ( $go_mode || !(-e "$root_filename.dvi") || ($aux_mtime < $tex_mtime) ||
     ($aux_mtime < $bbl_mtime) || !(-e "$root_filename.aux"))
    {
	warn "------------\nRunning first $latex\n------------\n";
	$return = system("$latex $root_filename"); 
	$changed_dvi = 1;

  	if (!$force_mode && $return)
    	{ &exit_msg('Latex encountered an error',1); }

  	if  ($index_mode)
      {
        warn "------------\nRunning $makeindex\n------------\n";
        $return = system("$makeindex $root_filename");

  		if (!$force_mode && $return)
    		{ &exit_msg('Makeindex encountered an error'); }
      }
    }

  $bib_mtime = &get_latest_mtime($bib_files);
  	## if no .bbl or .bib changed since last bibtex run, run bibtex.
  if ($bibtex_mode && (&check_for_bad_citation || !(-e "$root_filename.bbl") ||
		 ($bbl_mtime < $bib_mtime)))
    {
	warn "------------\nRunning $bibtex\n------------\n";
	$return = system("$bibtex $root_filename"); 
	$bbl_mtime = &get_mtime("$root_filename.bbl");
    }

  if ($bibtex_mode && &check_for_bibtex_errors)
    {
 	if ($bibdb_mode)
	  {
		warn "------------\nRunning bibextract\n------------\n";
		$return = system("bibextract $root_filename"); 
  		$bib_mtime = &get_latest_mtime($bib_files);
		warn "------------\nRunning $bibtex\n------------\n";
		$return = system("$bibtex $root_filename"); 
		$bbl_mtime = &get_mtime("$root_filename.bbl");
		$ran_bibextract = 1;
	  }
	elsif (!$force_mode)
   	  { 
			# touch a .bib file so that will rerun bibtex to fix errors.
		@split_bib_files = split(' ',$bib_files);
		system("touch $split_bib_files[0]");
		&exit_msg('Bibtex reported an error'); 
	  }
    }

  if ($ran_bibextract && &check_for_bibtex_errors && !$force_mode)
      { 
			# touch a .bib file so that will rerun bibtex to fix errors.
		@split_bib_files = split(' ',$bib_files);
		system("touch $split_bib_files[0]");
		&exit_msg('Bibtex reported an error'); 
	  }

  ## now, if need to, rerun latex up to twice to generate valid .dvi 
  ## w/ citations resolved.

  $dvi_mtime = &get_mtime("$root_filename.dvi");
  if ( ($dvi_mtime <= $bbl_mtime) || &check_for_reference_change )
    {
	warn "------------\nRunning second $latex\n------------\n";
	$return = system("$latex $root_filename"); 
	$changed_dvi = 1;
    }

  if (!$force_mode && $return)
    { &exit_msg('Latex encountered an error',1); }

  if (&check_for_reference_change)
    {
	warn "------------\nRunning third $latex\n------------\n";
	$return = system("$latex $root_filename"); 
	$changed_dvi = 1;
    }

  if (!$force_mode && &check_for_bad_citation)
    { &exit_msg('Latex could not resolve all citations or labels'); }

  return(1);
}


sub make_slitex_dvi
{
  $tex_mtime = &get_latest_mtime($includes);
  $dvi_mtime = &get_mtime("$root_filename.dvi");
  if ( $go_mode || !(-e "$root_filename.dvi") || ($dvi_mtime < $tex_mtime) )
    { 
	warn "------------\nRunning $slitex\n------------\n";
	$return = system("$slitex $root_filename"); 
    }
  if (!$force_mode && $return)
    { &exit_msg('Slitex encountered an error'); }
}

#************************************************************
# arg1 = name

sub find_process_id
{
  @ps_output = `ps -x`;
  shift(@ps_output);	# discard the header line from ps.
  foreach (@ps_output)
    {
	s/\s+/ /g;	# compress multiple spaces.
	($pid,$tt,$stat,$time,@command) = split(' ',$_);
	if ($command[0] eq $_[0]) 
	  { 
		warn "Go: Reattached to existing previewer, pid=$pid\n";
		return($pid); 
	  }
    }
  return(0);
}

#************************************************************
# run a one-shot postscript previewer.
sub make_preview
{
	if ($page_options)
	  { exec("$ps_previewer $root_filename"."_partial.ps"); }
	else  
	  { exec("$ps_previewer $root_filename.ps"); }
}

#************************************************************
# - launch a previewer, then run main guts of go (make_latex_dvi) every few
# 	seconds and send SIGUSR1 to the previewer if a change is made to dvi.

sub make_preview_continuous
{
		# get the value of SIGUSR1, defaults to SPARC value.
  if (!(do 'signal.ph')) { eval 'sub SIGUSR1 {30;}'; }
		# note we only launch a previewer if one isnt already running... 
		# otherwise we'll send reopen signals to the existing previewer.
  unless (($previewer_pid = &find_process_id($dvi_previewer)) || 
			($previewer_pid = fork))
	{ 		# in forked child, close off from parent so previewer runs
			# on after parent 'go' dies.
	    setpgrp($$,0);
	    exec("$dvi_previewer $root_filename.dvi"); 
	}
	
  while (1)		# loop forever, rebuilding .dvi as necessary.
	{
		sleep($sleep_time);
		&make_latex_dvi;
		if ($changed_dvi) { kill &SIGUSR1,$previewer_pid; }
	}
}

#************************************************************
sub make_printout
{
  return if (!$print_mode);
  warn "------------\nPrinting with [$lpr]\n------------\n";
  if ($page_options)
    { system("$lpr $lpr_options $root_filename"."_partial.ps"); }
  else 
    { system("$lpr $lpr_options $root_filename.ps"); }
}

#************************************************************
sub make_final_dvi
{
  if (!$page_options)
    { 
	warn "\n------------\n";
	warn "[$root_filename.dvi] for [$root_filename.tex] is up to date\n"; 
	return;
    }
  warn "------------\nRunning $dviselect\n------------\n";
  system("$dviselect -i $root_filename.dvi -o $root_filename"."_partial.dvi $page_options");
}

#************************************************************
sub make_postscript
{
  return if (!$postscript_mode && !$print_mode);
  $ps_mtime = &get_mtime("$root_filename.ps");
  $dvi_mtime = &get_mtime("$root_filename.dvi");
  if ( ($ps_mtime < $dvi_mtime) || $page_options )
    {
	warn "------------\nRunning $dvips\n------------\n";
	if ($page_options)
	  { system("$dvips $root_filename"."_partial.dvi"); }
	else { system("$dvips $root_filename"); }
    }
  else
    { 
	warn "\n------------\n";
	warn "[$root_filename.ps] for [$root_filename.tex] is up to date\n"; 
    }
}

#************************************************************
sub get_mtime
{
  local ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,
			$ctime,$blksize,$blocks) = stat($_[0]);
  $mtime;
}

#************************************************************
sub check_for_reference_change
{
  local($logfile) = "$root_filename.log";
  open(logfile) || die "Could not open log file to check for reference check\n";
  while(<logfile>)
    { 
	  if (/Rerun to get/) { return 1; } 
	}
  0;
}

#************************************************************
sub check_for_bad_reference
{
  local($logfile) = "$root_filename.log";
  open(logfile) || die "Could not open log file to check for bad reference\n";
  while (<logfile>)
    {
	  if (/LaTeX Warning: Reference[^\001]*undefined./) { return 1; } 
    }
  0;
}
#************************************************************
	# check for citation which latex couldnt resolve.
sub check_for_bad_citation
{
  local($logfile) = "$root_filename.log";
  open(logfile) || die "Could not open log file to check for bad citation\n";
  while (<logfile>)
    {
	if (/LaTeX Warning: Citation[^\001]*undefined./) { return 1; }
    }
  0;
}

#************************************************************
	# check for citation which bibtex didnt find.
sub check_for_bibtex_errors
{
  local($logfile) = "$root_filename.blg";
  open(logfile) || die "Could not open bibtex log file error check\n";
  while (<logfile>)
    {
	if (/Warning--/) { return 1; }
	if (/error message/) { return 1; }
    }
  0;
}

#************************************************************
# cleanup
# - erases all generated files, exits w/ no other processing.
sub cleanup
{
  unlink("$root_filename.aux");
  unlink("$root_filename.aux.bak");
  unlink("$root_filename.bbl");
  unlink("$root_filename.blg");
  unlink("$root_filename.log");
  unlink("$root_filename.bbe");	      ## bibdb imprecise citation translations.
  unlink("$root_filename.dvi");
  unlink("$root_filename.ps");
  unlink("$root_filename.ind");
  unlink("$root_filename.idx");
  unlink("$root_filename.ilg");
  unlink("$root_filename.toc");
  unlink("$root_filename"."_partial.dvi");
  unlink("$root_filename"."_partial.ps");
		# .aux files are also made for \include'd files
  foreach $include (split(' ',$includes))
    { 
	$include =~ s/\.[^\.]*$/.aux/;
	unlink($include);
    }
}

#************************************************************
sub print_help
{
  warn "Go: Automatic LaTeX document generation routine\n\n";
  warn "Usage: go [go_options] [lpr_options] [dviselect_options] [filename]\n\n";
  warn "  Go_options:\n";
  warn "  	-c 	- clean up (remove) all nonessential files\n";
  warn "  	-f 	- force continued processing past errors\n";
  warn "  	-g 	- process regardless of file timestamps\n";
  warn "  	-h 	- print help\n";
  warn "  	-i 	- scan for includes & put defaults in .gorc\n";
  warn "  	-p 	- print document after generating postscript\n";
  warn "  	-ps 	- generate postscript\n";
  warn "  	-pv 	- preview document\n";
  warn "  	-pvc 	- preview document and continuously update\n\n";
  warn "  filename = the root filename of LaTeX document\n\n";
  warn "  Other options starting with - are passed to lpr (if using -p).\n";
  warn "  Space-separated page lists & colon-separated ranges are passed to dviselect.\n";
  warn "  Set default root filename in .gorc with \$root_filename=\'filename\'; or use -i.\n";
  exit;
}

#************************************************************
# - stats all files listed in first arg, returns most recent modify time of all.

sub get_latest_mtime
{
  local($return_mtime) = 0;
  foreach $include (split(' ',$_[0]))
    {
  	  $include_mtime = &get_mtime($include);
	  if ($include_mtime >  $return_mtime) { $return_mtime = $include_mtime; }
    }
  $return_mtime;
}

#************************************************************
# - looks recursively for included & inputted and psfig'd files and puts
#   them into $includes.
# - note only primitive comment removal: cannot deal with escaped %s, but then,
#	when would they occur in any line important to GO??

sub scan_for_includes
{
  local(*FILE);
  if (!open(FILE,$_[0])) 
    { warn "Go: could not open input file [$_[0]]\n"; return; }
  while(<FILE>)
    {
	($_,$junk) = split('%',$_);		# primitive comment removal.
	if (/\\include[{\s]+([^\001}]*)[\s}]/)
	  {
		$full_filename = $1;
		if ($1 =~ m/\./)
		  { $full_filename = &find_file($full_filename,$TEXINPUTS); }
 		else
		  { $full_filename = &find_file("$full_filename.tex",$TEXINPUTS); }
		$includes .= "$full_filename ";
		warn "	Found include for file [$full_filename]\n";
		&scan_for_includes($full_filename);
	  }
	elsif (/\\input[{\s]+([^\001}]*)[\s}]/)
	  {
		$full_filename = $1;
		if ($1 =~ m/\./)
		  { $full_filename = &find_file($full_filename,$TEXINPUTS); }
 		else
		  { $full_filename = &find_file("$full_filename.tex",$TEXINPUTS); }
		$includes .= "$full_filename ";
		warn "	Found input for file [$full_filename]\n";
		&scan_for_includes($full_filename);
	  }
	elsif (/\\blackandwhite{([^\001}]*)}/ || /\\colorslides{([^\001}]*)}/)
	  {
		$slide_mode = 1;
		$postscript_mode = 1;
		$full_filename = $1;
		if ($1 =~ m/\./)
		  { $full_filename = &find_file($full_filename,$TEXINPUTS); }
 		else
		  { $full_filename = &find_file("$full_filename.tex",$TEXINPUTS); }
		$includes .= "$full_filename ";
		warn "	Found slide input for file [$full_filename]\n";
		&scan_for_includes($full_filename);
	  }
	elsif (/\\psfig{file=([^,}]+)/ || /\\psfig{figure=([^,}]+)/)
	  {
		$full_filename = &find_file($1,$psfigsearchpath);
		$includes .= "$full_filename ";
		warn "	Found psfig for file [$full_filename]\n";
	  }
	elsif ( /\\epsfbox{([^}]+)}/ || /\\epsfbox\[[^\]]*\]{([^}]+)}/ ||
	        /\\epsffile{([^}]+)}/ || /\\epsffile\[[^\]]*\]{([^}]+)}/  )
	  {
		$full_filename = &find_file($1,$TEXINPUTS);
		$includes .= "$full_filename ";
		warn "	Found epsf for file [$full_filename]\n";
	  }
	elsif (/\\documentstyle[^\000]+landscape/)
	  {
		$landscape_mode = 1;
		warn "	Detected landscape mode\n";
	  }
	elsif (/\\bibliography{([^}]+)}/)
	  {
		$bib_files = $1;
		$bib_files =~ tr/,/ /;
		$bib_files = &find_file_list($bib_files,'.bib',$BIBINPUTS);
		warn "	Found bibliography files [$bib_files]\n";
		$bibtex_mode = 1;
	  }
	elsif (/\\psfigsearchpath{([^}]+)}/)
	  { $psfigsearchpath = $1; }
	elsif (/\\makeindex/)
	  {
		$index_mode = 1;
		warn "	Detected index mode\n";
	  }
    }
}

#************************************************************
# - puts root name and includes into local .gorc automatically

sub update_gorc
{
  $rcfile = '>.gorc';
  open(rcfile) || die "Go: Unable to open .gorc for updating\n";
  print rcfile '$root_filename = \'' . "$root_filename';\n";
  print rcfile '$includes = \'' . "$includes';\n";
  print rcfile '$bib_files = \'' . "$bib_files';\n";
  if ($slide_mode) { print rcfile '$slide_mode = 1;' . "\n"; }
  if ($postscript_mode) { print rcfile '$postscript_mode = 1;' . "\n"; }
  if ($bibtex_mode) { print rcfile '$bibtex_mode = 1;' . "\n"; }
  if ($bibdb_mode) { print rcfile '$bibdb_mode = 1;' . "\n"; }
  if ($landscape_mode) { print rcfile '$landscape_mode = 1;' . "\n"; }
  if ($preview_mode) { print rcfile '$preview_mode = 1;' . "\n"; }
  if ($index_mode) { print rcfile '$index_mode = 1;' . "\n"; }

  print rcfile "\$latex  = \'$latex\';\n";
  print rcfile "\$bibtex  = \'$bibtex\';\n";
  print rcfile "\$slitex  = \'$slitex\';\n";
  print rcfile "\$dviselect  = \'$dviselect\';\n";
  print rcfile "\$dvips  = \'$dvips\';\n";
  print rcfile "\$lpr  = \'$lpr\';\n";
  print rcfile "\$ps_previewer  = \'$ps_previewer\';\n";
  print rcfile "\$dvi_previewer  = \'$dvi_previewer\';\n";
  print rcfile "\$makeindex  = \'$makeindex\';\n";

  close rcfile;
  warn "	Updated .gorc\n";
}

#************************************************************
# given filename and path, return full name of file, or die if none found.

sub find_file
{
  foreach $dir (split(':',$_[1]))
    { if (-e "$dir/$_[0]") { return("$dir/$_[0]"); } }
  die "GO ERROR: Could not find file [$_[0]] in path [$_[1]]\n";
}

#************************************************************
# given space sep list of filenames, a file suffix, and a path, return list of 
# full names of files, or die w/ warning if not found.

sub find_file_list
{
  local($return_list) = '';
  foreach $file (split(' ',$_[0]))
    { $return_list .= &find_file("$file$_[1]",$_[2]) . " "; }
  $return_list;
}

#************************************************************
sub increasing { $a - $b; }

#************************************************************
sub exit_msg
  {
	warn "\n------------\n";
	warn "Go: $_[0].\n";
	warn "-- Use the -f option to force complete processing.\n";
	if ($_[1])
	  {
		warn "Go: restoring last $root_filename.aux file\n";
		system("cp -p $root_filename.aux.bak $root_filename.aux > /dev/null 2>&1");
	  }
	exit;
  }