#!/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 () { 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() { 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() { 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 () { 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 () { 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 () { 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() { ($_,$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; }