From e0c6872cf40896c7be36b11dcc744620f10adf1d Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Mon, 2 Sep 2019 13:46:59 +0900 Subject: Initial commit --- support/tex-it/texit.pl | 2686 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2686 insertions(+) create mode 100644 support/tex-it/texit.pl (limited to 'support/tex-it/texit.pl') diff --git a/support/tex-it/texit.pl b/support/tex-it/texit.pl new file mode 100644 index 0000000000..8c4c428e8a --- /dev/null +++ b/support/tex-it/texit.pl @@ -0,0 +1,2686 @@ +#! /usr/local/bin/perl -- # -*-Perl-*- + +######################################################################## +# TeXit: a Perl script for running TeX. +# +# Copyright (C) 1993 by Norman Walsh. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# Comments, questions, suggestions, and even flames welcome. +# +# See "display_usage" at the bottom of this file for information +# about how to use TeXit. +######################################################################## + +($VERSION = '$Revision: 1.36 $ ') =~ s/.*: (\d+.\d+).*/$1/; +print "This is TeXit v$VERSION. "; +print "Copyright (C) 1993 by Norman Walsh.\n"; + +($revision, $patchlevel) = &perl_version_info(); + +if ($revision lt "4.0.1.7" || + ($revision eq "4.0.1.7" && $patchlevel < 35)) { + print "***********************************", + "***********************************\n"; + print " WARNING: Your Perl is old. TeXit may or may not work.\n"; + print " In particular, options may not be interpreted correctly.\n"; + print "***********************************", + "***********************************\n"; +} + +######################################################################## +# Load standard library routines for handling abbreviations. I use +# this for the command line options. +# +require "abbrev.pl"; + +######################################################################## +# Initialize associative arrays of format information. +# +# &setup_format (common-name, printable-name, parser-name, format-name); +# +# Where: +# +# common-name : The (unique) internal (to TeXit) name of the format +# printable-name : What we tell the user this format is called +# parser-name : Which "parse__log" routine gets called +# format-name : What is the '&' filename of this format +# +&setup_format ("amslatex", "AMSLaTeX", "latex", "lplain", "" ); +&setup_format ("amstex", "AMSTeX", "default", "amstex", "" ); +&setup_format ("foiltex", "FoilTeX", "latex", "fltplain", "" ); +&setup_format ("lamstex", "LAMSTeX", "default", "lamstex", "" ); +&setup_format ("latex", "LaTeX", "latex", "lplain", "" ); +&setup_format ("lollipop", "Lollipop", "default", "lollipop", "" ); +&setup_format ("nfss2ltx", "LaTeX (NFSS2)", "latex", "nfss2ltx", "" ); +&setup_format ("plain", "Plain TeX", "default", "plain", "" ); +&setup_format ("slitex", "SliTeX", "latex", "splain", "" ); +&setup_format ("texinfo", "Texinfo", "texinfo", "plain", "" ); +&setup_format ("latex2e", "LaTeX2e", "latex", "latex2e", "2E" ); + +######################################################################## +# Global defaults. These should be set to reasonable values when the +# script is installed. The values below are reasonable for many Unix +# installations. +# +# To override these defaults, when TeXit begins, it loads each of the +# following files, if they exist: +# +# A system-wide init file (if configured to do so) +# ~/.texitrc (.texitrc in the users home directory) +# /path/.texitrc (/path is the path of the tex file being processed) +# foo (for each "-initfile foo" specified on the command line) +# +# They are loaded in that order and each can over-ride the values of +# the preceding. Or any of the defaults shown below: +# +$TEXITRC = ".texitrc"; # Filename for init files ~/.texitrc ... + +$DEFAULTFORMAT = "plain"; # Default format to use... +$DEFAULTLATEXFORMAT = "latex"; # Default LaTeX format to use... +$VERBOSE = 0; # Lots of messages... +$TEXITFASTEXIT = 1; # Empty line at prompt == quit +$ONCE = 0; # Run once, no prompting? (-once) +$GOTOMENU = 0; # Go right to menu? (-menu) +$CONFIRMPRINT = 1; # Ask before printing? +$CLEANONEXIT = 0; # Cleanup automatically on exit? +$CLEANPROMPT = 1; # Should we ask first? (-clean) +$SETXTITLE = 0; # Should we alter the X11 title/icon name? + +%DEPENDSON = (); # what does the DVI file depend on? + +%WARNINGS = (); # are there any warnings parse__log + # didn't recognize? +%DEFAULTCMD = (); # no default commands +%AUTOCMD = (); # no automatic commands +$AUTOMAX = 1; # how many automatic commands can occur + # in a row? + +$TEXERROR = 1; # Return code from TEX that indicates an + # an error. Under emTeX, make this 2. + # emTeX returns 1 for overfull boxes, etc. + # which arent really errors. +$ERRORLINE = 0; # What line did the TeX error occur on... +$ERRORFILE = ""; # What file was being processed... +%Parens = (); +$ParenLevel = 0; + +$ENV_TEXINPUTS = "TEXINPUTS"; # Name of TEXINPUTS environment variable +$ENV_BIBINPUTS = "BIBINPUTS"; # Name of BIBINPUTS environment variable +$ENV_TEXFMTS = "TEXFORMATS";# Name of TEXFORMATS environment variable + +$TEXRETRIES = 4; # How many retries before we give up? + +$TEXHEADERSIZE = 20; # number of lines at the top of each + # document that TeXit searches for + # formatting clues. + +$QuitReply = "e[x]it"; # To leave TeXit +$TeXReply = "[t]ex"; # To run TeX +$CleanupReply = "[c]leanup"; # To cleanup files +$BibTeXReply = "[b]ibtex"; # To run BibTeX +$GhostviewReply= "[g]hostview";# To run Ghostview +$PrintReply = "[p]rint"; # To print +$ViewReply = "[v]iew"; # To view +$StatusReply = "[?]status"; # To get a status report from TeXit + +$TEX = "virtex"; # TeX, the program +$BIBTEX = "bibtex"; # BibTeX +$MAKEINDEX = "makeindex"; # MakeIndex +$VIEW = "xdvi"; # A previewer +$GSVIEW = "ghostview"; # Ghostview, another previewer + +# The following commands are used to run each of the above programs. +# See the 'run' subroutine for a description of the %-variable +# substitution that is performed. Also, note that Perl $VARIABLE +# substitution is performed too. +# +# The $TEXCMDs are stored in an associative array because not all +# format files can be run with the same command line. +# +$TEXCMD{"default"} = '$TEX "%F %o \\input{%t}"'; +$TEXCMD{"plain"} = '$TEX "%F %o \\input %t"'; +$TEXCMD{"texinfo"} = '$TEX "%F %o \\input %t"'; +$BIBTEXCMD = '$BIBTEX %o %s'; +$MAKEINDEXCMD = '$MAKEINDEX %o %s'; +$VIEWCMD = '$VIEW %o %d'; +$GSVIEWCMD = '$GSVIEW %o %s.ps'; + +# Default options for each of the above commands. These options +# are _always_ used (where %o occurs in the command) if they are +# set, so choose wisely. They come before any additional options +# specified at the TeXit prompt. +# +$TEXOPTS{"default"} = ""; +$BIBTEXOPTS = ""; +$MAKEINDEXOPTS = ""; +$VIEWOPTS = ""; +$GSVIEWOPTS = ""; + +######################################################################## +# Printer initialization. It works like this: the user selects a +# printer queue "Q". Then $PRQUEUE{"Q"} determines the printer _type_. +# Ordinarily, the user never knows about the type, but it is used +# to lookup the DVI driver and printer commands. In particular: +# +# $PRQUEUE{"Q"} = "TYPE" +# +# $DVICMD{"TYPE"} = DVI driver command (with substitutions) +# $DVIOPT{"TYPE"} = Default options for driver +# $DVIQUERY{"TYPE"} = Ask for additional DVI options? +# If $DVIQUERY{"TYPE"} is defined, TeXit will ask +# the user for additional dvi driver options (using +# the value of $DVIQUERY{"TYPE"} as the prompt). +# These options are added to any options entered +# at the prompt. +# +# $PRINTCMD{"Q"} = How to print resulting file (with substitution) +# $PRINTOPT{"Q"} = Default options for driver +# $PRINTQUERY{"Q"} = Ask for additional printer options? See above. +# $PRINTCONFIRM{"Q"}= Yes or No? Overrides $CONFIRMPRINT... +# +# Ordinarily, all of the above variables should be set for each queue +# and it is an error for them to be unset. However, the following +# exception is made: if the $PRINTCMD{"Q"} isn't set, then the file +# is run through a DVI driver but is not printed. +# +# Note: Ghostview is implemented this way, but it is also hard coded +# later on as an option in it's own right because we can be a little +# more intelligent about it. +# +# It would clearly have been possible to do configuration in other +# ways, in particular by using a single print command with different +# printer options ("lpr -P%p" for example). But that has disadvantages +# too and it doesn't save much time or space. +# +$PRQUEUE{"Local"} = "ps"; # A 'local' printer +$PRQUEUE{"File"} = "ps"; # Just save the PS file +$PRQUEUE{"ghostview"} = "ps"; # Ghostview + +$PRINTCMD{"Local"} = 'lpr %o %s.ps'; +$PRINTOPT{"Local"} = ""; + +$PRINTCMD{"File"} = ""; +$PRINTOPT{"File"} = ""; + +$PRINTCMD{"ghostview"} = '$GSVIEW %o %s.ps'; +$PRINTOPT{"ghostview"} = ""; + +$DVICMD{"ps"} = 'dvips %o %d'; +$DVIOPT{"ps"} = ""; + +######################################################################## +# The $CleanupReply option looks for a list of files to delete in the +# list "@cleanup_files". These are usually set by perl commands in the +# document since there aren't any default _files_ to delete. +# +# It also looks for a list of extensions in the list "@cleanup_" +# If the file . exists, it is deleted during cleanup +# +@cleanup_files = (); +@cleanup_plain = ('log'); +@cleanup_texinfo = ('log', 'aux', 'cp','fn','ky','pg','toc','tp','vr'); +@cleanup_latex = ('log', 'aux', 'blg', 'ilg', 'lot', 'lof', 'toc'); +@cleanup_nfss2ltx = ('log', 'aux', 'blg', 'ilg', 'lot', 'lof', 'toc'); +@cleanup_latex2e = ('log', 'aux', 'blg', 'ilg', 'lot', 'lof', 'toc'); + +######################################################################## +# The system default, user default, and directory default initfiles +# have to be kept around until we're ready to load them. That's what +# the @TEXITINIT list is for. Ordinarily this is initialized empty. +# But if you always want to load a system-wide init file, perhaps +# "/usr/local/lib/texitrc" then you can initialize TEXITINIT to +# include that file. +# +@TEXITINIT = (); + +# Unfortunately, we want to run the system default files before the +# files specified with -initfile on the command line, but we don't +# find out what the name of the directory default is until after we've +# parsed the command line. To circumvent this problem, we store +# initfiles from the command line in a seperate array. +# +@TEXITCMDLINE = (); # leave this empty + +######################################################################## +# Locate the user-default init file and store it on the TEXITINIT list. +# Some installations might not have a HOME environment variable to +# contain the home directory, so we let them specify a TEXIT env. var. +# instead. +# +$_ = $ENV{"TEXIT"}; +$_ = $ENV{"HOME"} if $_ eq ""; +$_ .= "/" if $_ && $_ !~ /.*\/$/; +$texitrc = $_ . $TEXITRC; +print "Looking for user-init file: $texitrc\n" if $VERBOSE; +unshift(@TEXITINIT, $texitrc) if -r $texitrc; + +######################################################################## +# make STDOUT unbuffered so we can print messages w/o newlines in them +# +select(STDOUT); $| = 1; + +######################################################################## +# Get ready to parse the arguments... +# +# The associative array "cmdlineopts" is built to hold all of the +# possible, unambiguous command line options. It will later be used +# to get back the full name (i.e. $cmdlineopts{"ini"} = "initfile"). +# +{ + local (@formatlist) = keys (%format); + %cmdlineopts = (); + &abbrev (*cmdlineopts, + 'initfile', 'verbose', 'once', 'menu', 'clean', + 'bibtex', 'makeindex', 'tex', + @formatlist); +} + +# Build the case statement for parsing the options. This code builds +# an associative array and 'eval's it to get the cases. It's a little +# obscure, but it works. +# +# If a valid option is not found in the case statement, it is assumed +# to be the name of a format file. This saves a whole bunch of cases. +# Note: the lines inbetween &case and ENDCASE _are not_ evaluated +# by the Perl compiler at compile time...they are 'eval'ed at runtime. +# +&case (*casefoo,<<'ENDCASE'); + initfile $texitrc = $ARGV[0] || "no file"; shift; + verbose $VERBOSE = 1; + once $ONCE = 1; + menu $GOTOMENU = 1; + clean $CLEANPROMPT = 0; + tex $runtexfirst = 1; + bibtex $runbibtexfirst = 1; + makeindex $runmakeindexfirst = 1; +ENDCASE + +# make sure a few things are undefined +undef ($TEXFORMAT); +undef ($texitrc); +undef ($runtexfirst); +undef ($runbibtexfirst); +undef ($runmakeindexfirst); + +# Now look at the arguments, keep scanning through them as long as +# they begin with a hyphen. Anything after that has to be a filename +# +while ($ARGV[0] =~ /^-(.*)/ && shift) { + ($option = $1) =~ tr/A-Z/a-z/; + $option = $cmdlineopts{$option}; # find long opt + die "Unrecognized or ambiguous option: -$1.\n" + if $option eq ""; + + if (defined($casefoo{$option})) { + # Ok, it's one of the arms of the case statement + eval $casefoo{$option}; + if (defined($texitrc)) { + # The only arm that isn't self-contained is the -initfile arm. + # For that option, we have to check for the initfile and save + # it for later. + die "You forgot the file name after \"-$1\".\n" + if $texitrc eq "no file"; + + die "Initfile does not exist: $texitrc\n" + if ! -r $texitrc; + + print "Looking for -initfile: $texitrc\n" if $VERBOSE; + unshift(@TEXITCMDLINE, $texitrc); + undef ($texitrc); + } + } else { + # It's not one of the arms of the case, so it must be a format. + $TEXFORMAT = $option; + } +} + +######################################################################## +# What's left on the command line must be the name of the file that +# contains the document to process. Anything else must be options to +# TeX. Make sure they begin with an escape (you can put \relax +# in front if you need to). Note: this verifies that the user +# escaped the shell command line correctly. +# +$texfile = $ARGV[0] || &display_usage(); # display_usage doesn't return +shift; + +if (defined($ARGV[0])) { + local ($texescape, $printescape); + $texescape = "\\\\"; + $texescape = "@" if $TEXFORMAT eq "texinfo"; + eval "\$printescape = \"$texescape\""; + if ($ARGV[0] !~ /^$texescape.*/) { + print "TeX options must begin with a \"$printescape\". "; + die "Use ${printescape}relax if necessary.\n"; + } + + $CMDLINEOPTS = join (" ", @ARGV); +} + +######################################################################## +# Locate the requested TeX file. It's either in the current (or +# specified) directory or no path was specified and it's on the +# TEXINPUTS path. +# +$qualifiedtexfile = &cleanup_texfilename($texfile); +if (! -e $qualifiedtexfile) { + if ($texfile !~ /[\/\\]/) { # no path... + $qualifiedtexfile = &find_on_path("$ENV{$ENV_TEXINPUTS}", + "$texfile"); + $qualifiedtexfile = &find_on_path("$ENV{$ENV_TEXINPUTS}", + "$texfile" . ".tex") + if $qualifiedtexfile eq ""; + + die "Cannot find \"$texfile[.tex]\" on $ENV_TEXINPUTS path.\n" + if $qualifiedtexfile eq ""; + + $texfile = $qualifiedtexfile; + } else { + die "Cannot find \"$texfile[.tex]\".\n"; + } +} else { + $texfile = $qualifiedtexfile; +} + +######################################################################## +# Maybe the file has an emacs-style "local variables" definition that +# describes the file. Here, we only care about the local variables +# "TeX-master" and "mode". These are the same variables AUC-TeX uses. +# The TeX-master file means that _this_ file is some sub-part of a +# larger document. The larger document is pointed to by TeX-master. +# The "mode" is what format this is in (Plain TeX, LaTeX, ... etc.) +# +# Alternatively, if you don't use Emacs, you can get the same logic +# out of TeXit by putting: +# +# % Master: file +# % Mode: format % or Format: format +# +# somewhere near the top (within the first $TEXHEADERSIZE lines) of +# your document. In this case, the line must begin with a percent +# sign and there must be nothing else on the line. The Master: and +# Mode: commands must each appear on a line of their own. If you +# specify both a Master: and a TeX-master in the local variables, the +# TeX-master will win (simply because it comes later in the file). +# The same goes for mode:. +# +# Other than that, TeXit tries to be smart about what format to use, +# looking for things that are clues to the format. Unfortunately, +# TeXit is easy to fool, so you may need a - on the command +# line sometimes. +# +&get_local_vars ($texfile); + +# If this file has a master, load the local vars from the master file +# as well...these override variables from the initial file, but that's +# probably for the best anyway... +# +# TeX-master = "t" is special, it means _this_ file is the master file +# (i.e. it's `true' that this is the master file). +# +# If there isn't a master file, then $texfile is the master. +# +$texmaster = $texvars{"TeX-master"}; +if (defined($texmaster) && $texmaster ne "t") { + $texmaster = &cleanup_texfilename($texmaster); + print "Processing $texfile ... superceded by master file.\n"; + die "Cannot read master file: $texmaster" if (! -r $texmaster); + &get_local_vars ($texmaster); +} else { + $texmaster = $texfile; +} + +if ($texvars{"mode"} eq "") { + if ($TEXFORMAT eq "") { + print "\nI can't figure out what format this file uses, ", + "and you didn't say, so \nI'm going to guess that ", + "it's a \"$formatname{$DEFAULTFORMAT}\" file. Use ", + "the \"-format\" \noption if this is a bad guess.\n\n"; + + $texvars{"mode"} = $DEFAULTFORMAT; + } else { + $texvars{"mode"} = $TEXFORMAT; + } +} + +######################################################################## +# Split the master filename up into a path, name, and extension +# +($TEXFILEPATH,$TEXFILENAME,$TEXFILEEXT) = &splitfn($texmaster,".tex"); +$DEPENDSON{$texmaster} = 1; + +######################################################################## +# Build up the default array of commands. +# +# Prompt with "key" if $CMDPROMPT{key} exists and the test $CMDTEST{key} +# succeeds. A null test succeeds by default. The string enclosed in +# square brackets in the key is what the user has to enter to select +# that option. +# +# TeXit `eval's $CMDPROMPT{key} if the user selects the option "key". +# +# Use single quotes when building the command to be evaluated _and_ +# the test so that $VAR substitution isn't performed prematurely! +# +# The commands to run tex, cleanup the directory, and quit are used +# elsewhere. Change the definitions of $xxxReply to make them different. +# But don't change what they _mean_!!! +# + +$RETEX = "last ASKLOOP"; +$REASK = "next ASKLOOP"; + +$CMDPROMPT{$TeXReply} = '$RETEX'; +$CMDPROMPT{$CleanupReply} = '&cleanup("$opts"); $REASK'; +$CMDPROMPT{$QuitReply} = 'print "Thank you for using TeXit!\n\n";exit(0)'; +$CMDPROMPT{$StatusReply} = '&showstatus("$opts"); $REASK'; + +$CMDTEST{$TeXReply} = ''; +$CMDTEST{$CleanupReply} = '&files_to_cleanup()'; +$CMDTEST{$QuitReply} = ''; +$CMDTEST{$StatusReply} = ''; + +# It would be ok to change these... +# +$CMDPROMPT{$BibTeXReply} = '&bibtexcmd("$opts"); $RETEX'; +$CMDPROMPT{$GhostviewReply} = '&ghostviewcmd("$opts"); $REASK'; +$CMDPROMPT{$PrintReply} = '&printcmd("$opts"); $REASK'; +$CMDPROMPT{$ViewReply} = '&viewcmd("$opts"); $REASK'; + +$CMDTEST{$BibTeXReply} = '"$BIBTEX" && -r &auxfile("aux")'; +$CMDTEST{$GhostviewReply} = '"$GSVIEW" && -r &auxfile("dvi")'; +$CMDTEST{$PrintReply} = '"%PRQUEUE" && -r &auxfile("dvi")'; +$CMDTEST{$ViewReply} = '"$VIEW" && -r &auxfile("dvi")'; + +######################################################################## +# Look for a directory-local init file. Then load all the init files +# that we've found. Load the global one, then the user-global one, +# then the directory-local one then all the user specified ones, in +# order. +# +# Note: init files are just bits of Perl code so they can really do +# anything you want. But they have to be legal Perl. Usually they +# just set some global vars. +# +# Yes, "require_hack" is ugly. But read the comments in that routine +# and you'll see why it was necessary. +# +$texitrc = $TEXFILEPATH . $TEXITRC; +print "Looking for local init file: $texitrc\n" if $VERBOSE; +unshift (@TEXITINIT, $texitrc) if -r $texitrc; + +while (@TEXITINIT) { + $texitrc = pop(@TEXITINIT); + if (-r $texitrc) { + &require_hack ($texitrc); + } else { + warn "Can't read initfile: $texitrc\n"; + } +} + +while (@TEXITCMDLINE) { + $texitrc = pop(@TEXITCMDLINE); + if (-r $texitrc) { + &require_hack ($texitrc); + } else { + warn "Can't read initfile: $texitrc\n"; + } +} + +undef($texitrc); + +######################################################################## +# Now that all the initialization code has been run, build up an +# abbreviation list for the printer queues. +# +{ + local ($queue,@prlist); + + foreach $queue (keys %PRQUEUE) { + unshift(@prlist, $queue); + } + + %pr_queue_names = (); + &abbrev (*pr_queue_names, @prlist); +} + +######################################################################## +# Pick a format. Tell the user if we don't agree about the format +# they demanded. Shift the format to lowercase for using as a key in +# the %format associative array. +# +# Bail out if we can't find the format in the %format. +# +print "Processing $TEXFILEPATH$TEXFILENAME.$TEXFILEEXT ... "; +if ($TEXFORMAT ne "" && $TEXFORMAT ne $texvars{"mode"}) { + print "it looks like a $formatname{$texvars{mode}} file to me,\n"; + print "but you said it was a $formatname{$TEXFORMAT} file. "; + print "Suit yourself.\n"; +} elsif ($TEXFORMAT eq "") { + print "it looks like a $formatname{$texvars{mode}} file to me.\n"; + if ($texvars{"mode"} eq "latex" && $DEFAULTLATEXFORMAT) { + $TEXFORMAT = $DEFAULTLATEXFORMAT; + print "(Using $formatname{$TEXFORMAT} variant of LaTeX.)\n"; + } +} else { + print "using $formatname{$TEXFORMAT}, like you said.\n"; +} + +$TEXFORMAT = $texvars{"mode"} if $TEXFORMAT eq ""; +$TEXFORMAT =~ tr/A-Z/a-z/; + +die "Don't know what format file to use for \"$TEXFORMAT\".\n" + if $format{$TEXFORMAT} eq ""; + +######################################################################## +# Let's make sure we can process this format... +# +{ + local ($path) = $ENV{$ENV_TEXFMTS} || "/usr/local/lib/tex/formats"; + local ($formatfile) = $format{$TEXFORMAT} . ".fmt"; + local ($formatpath) = &find_on_path($path, $formatfile); + + if ($formatpath eq "") { + print "\nCan't find the format file for "; + print "$formatname{$TEXFORMAT} on the $ENV_TEXFMTS path.\n"; + die "Can't continue without \"$formatfile\".\n"; + } +} + +# If the user has specified an alternate inputs environment for this +# format, temporarily set the environment for TeX... +if ($altinputenv{$TEXFORMAT} + && $ENV{"$ENV_TEXINPUTS$altinputenv{$TEXFORMAT}"}) { + $ENV{"$ENV_TEXINPUTS"} = $ENV{"$ENV_TEXINPUTS$altinputenv{$TEXFORMAT}"}; + print "Adjusting $ENV_TEXINPUTS environment variable for this format.\n"; +} + +######################################################################## +# Evaluate the PLC commands for this document. +# +# PLC commands are bits of Perl that usually look something like this: +# +# % &makeindex('-o driver.ind driver.idx') +# % if &missing('driver.ind') || &changed('driver.idx'); +# % &bibtex('driver') if &newer('jigsaw.bib','driver.bbl'); +# +# This says run MakeIndex if "driver.ind" is missing or "driver.idx" +# changes over the course of a TeX run. And run BibTeX if the +# bibliography file is newer than the BBL file (it'll also get run +# automatically if any citations are unresolved). +# +# $first_time is true now so that &missing, &dependson, &changed, and +# &newer all return false regardless of the condition tested. This +# way we can initialize some globals before we run TeX for the first +# time (to see if files change on the first run, for example). +# +# VERBOSE is forced off temporarily so that no stupid messages are +# printed, even if the user wants them. Note the dynamic scoping +# nature of Perl. +# + +{ + local ($VERBOSE) = 0; + local ($first_time) = 1; + local ($line, @the_rules); + + @the_rules = @default_rules; + foreach $line (@the_rules) { + eval $line; + die "Invalid rule in \@default_rules: $line\n" if $@; + } + + eval "\@the_rules = \@${TEXFORMAT}_rules"; + foreach $line (@the_rules) { + eval $line; + die "Invalid rule in \@${TEXFORMAT}_rules: $line\n" if $@; + } + + foreach $line (@PLCRULES) { + eval $line; + die "Invalid PLC rule: $line\n" if $@; + } +} + +{ + # Now print out the rules, if the user wants to see them... + + local ($line, @the_rules); + + if ($VERBOSE && @default_rules) { + print "Default rules:\n"; + foreach $line (@default_rules) { + print " $line\n"; + } + print "\n"; + } + + eval "\@the_rules = \@${TEXFORMAT}_rules"; + if ($VERBOSE && @the_rules) { + print "$TEXFORMAT rules:\n"; + foreach $line (@the_rules) { + print " $line\n"; + } + print "\n"; + } + + if ($VERBOSE && @PLCRULES) { + print "Your document defined the following rules:\n"; + foreach $line (@PLCRULES) { + print " $line\n"; + } + print "\n"; + } +} + +######################################################################## +# If the user asked us to run MakeIndex or BibTeX first, run them now. +# +if ($runbibtexfirst) { + if (-r &auxfile("aux")) { + &bibtexcmd(""); + } else { + print "There's no AUX file to run BibTeX against.\n"; + } +} +undef($runbibtexfirst); + +if ($runmakeindexfirst) { + if (-r &auxfile("idx")) { + &makeindexcmd(""); + } else { + print "There's no IDX file to run MakeIndex against.\n"; + } +} +undef($runmakeindexfirst); + +######################################################################## +# Ok, now it's time to run TeX. Run TeX repeatedly until an error +# occurs, the dependencies don't change or we've tried more than four +# times. If it doesn't work in four tries, it never will. +# +$opts = ""; # user specified options after the prompt +$changed = 0; # did the file change on this run? +$runcount = 0; # how many times have we run TeX in a row? + +# Think about BibTeX: every time we run it, it changes the BBL file and +# potentially causes us to decide to run TeX again. But if the list of +# missing citations doesn't change after the first run, running again +# won't help. So we keep track of the missing citations between runs +# and only run BibTeX when it changes. +# +%missingcites = (); # last missing cite list + +# If not -menu, assume we'll TeX first... +# +$changed = !$GOTOMENU; + +# If the tex file and the dvi file and the log file all exist, maybe +# we don't actually have to run TeX first...maybe the user just wants +# to print or preview or something and they're used to typing "texit". +# +if (!$GOTOMENU && !$runtexfirst) { # if the user doesn't insist... + $changed = &dependencies_changed () + if (-r $texmaster && -r &auxfile("dvi") && -r &auxfile("log")); + %missingcites = (); # the results from an old log don't count +} +undef($runtexfirst); + +$autocount = 0; # No automatic commands yet... +$lastcmd = $TeXReply; # assume we ran tex... +TEXLOOP: for(;;) { # don't stop till the user says quit. + if ($changed) { + $ERRORLINE = 0; # No errors occurred... + $ERRORFILE = ""; # + $rc = &texcmd($opts); # run TeX + # If the return code is less than TEXERROR, set it to zero. + # This canonicalizes the test for success when running a program + $rc = 0 if $rc < $TEXERROR; + + # 9 Sep 93: the former test at this location failed to handle + # a document that _required_ four tries to process correctly. + $changed = 0; + $changed = 1 if $rc == 0 && &dependencies_changed (); + next TEXLOOP if $changed && $runcount < $TEXRETRIES; + } else { + $rc = 0; + } + + if ($rc != 0) { + warn "TeX failed. All bets are off.\n\n"; + ($ERRORFILE, $ERRORLINE) = &find_error_line(); + } + + if ($runcount >= $TEXRETRIES && $changed) { + print "\nI've tried $TEXRETRIES times and it still "; + print "isn't right...I give up.\n\n"; + exit 1 if $ONCE; + } else { + if ($ONCE) { + &cleanup("") if $CLEANONEXIT && !$CLEANPROMPT && !$rc; + exit $rc; + } + } + + $changed = 1; # assume it'll change before we get back to top... + + ASKLOOP: for (;;) { + # User can change anything while we're sitting at the prompt, so + # reset runcount and citation list. + $runcount = 0; + %missingcites = (); + + &set_x_titles("TeXit"); + + if ($rc == 0 + && $AUTOCMD{$lastcmd} ne "" + && $CMDPROMPT{$AUTOCMD{$lastcmd}} ne "" + && $autocount < $AUTOMAX) { + $cmd = $AUTOCMD{$lastcmd}; + $opts = ""; + $autocount++; + } else { + $rc = 0; + $autocount = 0; + ($cmd, $opts) = &get_command(); + } + + if ($cmd eq "") { + $cmd = $QuitReply; + $cmd = $DEFAULTCMD{$lastcmd} if $DEFAULTCMD{$lastcmd} ne ""; + $cmd = "redo" if !$TEXITFASTEXIT && $DEFAULTCMD{$lastcmd} eq ""; + + # Unless the last thing we did was cleanup, re-running TeX + # supercedes other commands if the DVI file is gone or any + # dependent files are newer... + if ($lastcmd ne $CleanupReply) { + if (! -r &auxfile("dvi")) { + print "\n", &auxfile("dvi"), " disappeared.\n"; + $cmd = $TeXReply; + } elsif (&dependent_files_are_newer()) { + print "\nA file that the document ", + "depends on has changed.\n"; + $cmd = $TeXReply; + } + } + } + + next if $cmd eq "redo"; + + # Special case for $QuitReply if CLEANONEXIT is true: + # + if ($cmd eq $QuitReply && $CLEANONEXIT) { + $AUTOCMD{$CleanupReply} = $QuitReply; # exit w/o asking next + $CLEANONEXIT = 0; # Prevent reexecution of this code... + $cmd = $CleanupReply; + $opts = ""; + } + + if (($cmd eq $QuitReply) && $SETXTITLE) { + local ($host, $cwd); + chop($host = `hostname`); + chop($cwd = `pwd`); + &set_xterm_title("$host: $cwd"); + &set_xicon_title("$host"); + } + + $lastcmd = $cmd; + + # Ok, now it's time to 'eval' the command. + # + # First, copy it and protect any quotations in it from premature + # replacement. Note: the user has to protect backslashes by + # escaping them (i.e. in order to get "\" use "\\"). This is + # necessary so that the user can use a backslash to protect + # dollar signs for variable assignment. + # + # Then eval it once to do $-var substitution. + # + # Then eval it again to really do the work... + # + $thecmd = $CMDPROMPT{$cmd}; + $thecmd =~ s/\"/\\"/g; # protect double quotes + eval "\$thecmd = \"$thecmd\""; + eval ( $thecmd ); + die "Invalid command defined for $cmd: $CMDPROMPT{$cmd}\n" if $@; + } +} + +######################################################################## +# These subroutines execute the expected commands. They are here, with +# nice names, so that the PLC commands can use them and be easy to read. +# Internally, different routines are used so that %-var substitution is +# preformed. Note that it _isn't_ preformed here. +# +# &tex("foo") ==> $TEX foo +# +sub tex { + local ($opts) = @_; + local ($exopts); + + $exopts = join(" ",$TEXOPTS{$TEXFORMAT},$CMDLINEOPTS,$opts); + + &run($TEX . $exopts, ""); +} + +sub bibtex { + local ($opts) = @_; + + &run($BIBTEX . " " . $BIBTEXOPTS . " " . $opts, ""); +} + +sub makeindex { + local ($opts) = @_; + + &run($MAKEINDEX . " " . $MAKEINDEXOPTS . " " . $opts, ""); +} + +######################################################################## +# These are the subroutines used internally where all we want to pass +# in are the _additional_ options. +# +# &texcmd("foo") => $TEXCMD with $TEXOPTS and "foo" inserted as options +# and all other %-var substitution performed. +# +sub texcmd { + local ($opts) = @_; + local ($rc, $extex, $exopt); + + $extex = $TEXCMD{"default"}; + $extex = $TEXCMD{$TEXFORMAT} if $TEXCMD{$TEXFORMAT} ne ""; + $exopt = join (" ", $TEXOPTS{$TEXFORMAT}, $CMDLINEOPTS, $opts); + + $rc = &run ($extex, $exopt); + $runcount++; + $rc; +} + +sub bibtexcmd { + local ($opts) = @_; + local ($rc); + + if (! -r &auxfile("aux")) { + print "Cannot run BibTeX, no AUX file." if $VERBOSE; + $rc = -1; + } else { + $rc = &run ($BIBTEXCMD, $BIBTEXOPTS . " " . $opts); + } + + $rc; +} + +sub makeindexcmd { + local ($opts) = @_; + + &run($MAKEINDEXCMD, $MAKEINDEXOPTS . " " . $opts); +} + +sub viewcmd { + local ($opts) = @_; + + &run ($VIEWCMD, $VIEWOPTS . " " . $opts); +} + +sub printcmd { + local ($opts) = @_; + local ($queueopt, $dviuseropts, $prtuseropts); + local ($rc, $type); + local ($dvicmd, $dviopt, $prtcmd, $prtopt); + local ($doit,$queue); + local ($confirm) = $CONFIRMPRINT; + + $queueopt = ""; + + # parse the user-specified options for a printer queue and options + if ($opts =~ /^\s*(\S+)\s*(.*)$/) { + $queueopt = $1; + $opts = $2; + } + + # if the queue name starts with "-" or "/", assume that it's an option, + # not a queue name... + if ($queueopt =~ /^(-|\/)/) { + $opts = "$queueopt $opts"; + $queueopt = ""; + } + + # if no queue is specified, use the default printer queue + $queueopt = $DEFAULT_PRINTER if $queueopt eq ""; + + if ($queueopt eq "") { + print "You must specify a printer queue (use \"p \").\n"; + return(0); + } + + # Special case, if the queue is "?", list the queues we know about. + if ($queueopt eq "?") { + &print_long_list("\nThe following queues are available: ", + sort(keys(%PRQUEUE))); + print "\n"; + return (0); + } + + $queue = $pr_queue_names{$queueopt}; + + if (!defined ($PRQUEUE{$queue})) { + warn "There is no printer queue named \"$queueopt\".\n"; + return 0; + } + + ($dviuseropts, $prtuseropts) = split (/\|/, $opts); + + $type = $PRQUEUE{$queue}; + + if (!defined ($DVICMD{$type})) { + warn "There is no DVI driver for \"$type\" printers.\n"; + return 0; + } + + $dvicmd = $DVICMD{$type}; + $dviopt = $DVIOPT{$type}; + + if ($DVIQUERY{$type} ne "") { + print $DVIQUERY{$type}; + $_ = scalar (); + $dviuseropts .= " " . $_; + } + + $prtcmd = $PRINTCMD{$queue}; + $prtopt = $PRINTOPT{$queue}; + + $rc = &run ($dvicmd, $dviopt . " " . $dviuseropts); + + warn "DVI driver failed.\n" if $rc; + + $doit = ($prtcmd ne ""); # really print it... + + if (defined($PRINTCONFIRM{$queue})) { + undef $confirm; + $confirm = 0 if "0" =~ /^$PRINTCONFIRM{$queue}/i; + $confirm = 0 if "no" =~ /^$PRINTCONFIRM{$queue}/i; + $confirm = 0 if "false" =~ /^$PRINTCONFIRM{$queue}/i; + $confirm = 1 if "1" =~ /^$PRINTCONFIRM{$queue}/i; + $confirm = 1 if "yes" =~ /^$PRINTCONFIRM{$queue}/i; + $confirm = 1 if "true" =~ /^$PRINTCONFIRM{$queue}/i; + if (! defined($confirm)) { + print "\nI don't know what `$PRINTCONFIRM{$queue}' means, "; + print "I'll assume that it means "; + print "`yes'.\n" if $CONFIRMPRINT; + print "`no'.\n" if !$CONFIRMPRINT; + $confirm = $CONFIRMPRINT; + } + } + + # Wait! If the driver failed or there's a queue other than + # Ghostview selected, ask and make sure. + # + if ($rc || ($doit && ($queue ne "ghostview") && $confirm)) { + print "Really print to $queue? "; + $_ = scalar (); chop; + $doit = 0 if !/^y/i; + } + + if ($doit) { + if ($PRINTQUERY{$queue} ne "") { + print $PRINTQUERY{$queue}; + $_ = scalar (); + $prtuseropts .= " " . $_; + } + $rc = &run ($prtcmd, $prtopt . " " . $prtuseropts); + } else { + print "Print command aborted.\n" if ($prtcmd ne ""); + $rc = -1; + } + + $rc; +} + +######################################################################## +# Ghostview is a little special. The print commands always have to +# run the DVI driver because they don't know what kind of file might +# be produced, but with Ghostview, we know that it'll be the master +# document name with '.ps' on the end. So, if that exists and is +# newer than the DVI file, don't bother running the DVI driver again. +# +# Note also that Ghostview knows what type it is, so the type doesn't +# have to be in the printer queue. This means you can remove the +# Ghostview printer from the queue list even if you don't have any +# other PS printers. +# +# Oh, also, we always run the DVI driver again if the user entered +# DVI driver options at the prompt... +# +sub ghostviewcmd { + local ($opts) = @_; + local ($queue, $dviuseropts, $prtuseropts); + local ($rc, $type); + local ($dvicmd, $dviopt, $prtcmd, $prtopt); + local ($doit); + local ($psfile) = &auxfile("ps"); + + ($dviuseropts, $prtuseropts) = split (/\|/, $opts); + + $type = "ps"; + + if (!defined ($DVICMD{$type})) { + warn "There is no DVI driver for \"$type\" output.\n"; + return 0; + } + + $dvicmd = $DVICMD{$type}; + $dviopt = $DVIOPT{$type}; + + if ((! -r $psfile || (&mtime($psfile) < &mtime(&auxfile("dvi")))) + || $dviuseropts ne "") { + $rc = &run ($dvicmd, $dviopt . " " . $dviuseropts); + } else { + print "\nPostScript file already exists.\n"; + $rc = 0; + } + + $doit = 1; + if ($rc) { + warn "DVI driver failed.\n"; + print "Preview anyway? "; + $_ = scalar (); chop; + $doit = 1 if /^y/i; + } + + if ($doit) { + $rc = &run ($GSVIEWCMD, $GSVIEWOPTS . " " . $prtuseropts); + } + + $rc; +} + +######################################################################## +# Add new files to the @cleanup_files array. This call makes sure that +# duplicate filenames are not added. It simplifies the TeXit rules +# for extending the list of files to clean up. Simply saying: +# +# push(@cleanup_files, 'one', 'two'); +# +# is a bad idea because it'll add "one" and "two" each time the document +# is processed. On the other hand, expecting users to type the rule: +# +# push(@cleanup_files, 'one', 'two') if $first_time; +# +# is unrealistic. So now they can just call cleanup_files... +# +sub cleanup_files { + local (@newfiles) = @_; + local ($file, $files, $newfile, $add); + + $add = 1; + foreach $newfile (@newfiles) { + foreach $file (@cleanup_files) { + $add = 0; + last if $file eq $newfile; + $add = 1; + } + push (@cleanup_files, $newfile) if ($add); + } +} + +######################################################################## +# Go out there and find all the files that have to be cleaned up. +# Return them in a list. +# +sub files_to_cleanup { + local ($opts) = @_; + local (%thefiles); + local ($file, @filelist, $ext, @extlist); + + @filelist = (); + + # throw them all in an associative array first to + # avoid duplicates. + foreach $file (@cleanup_files) { + $file =~ s/\\/\//; # \ -> / + $file = $TEXFILEPATH . $file if $file !~ /\//; + $thefiles{$file} = 1; + } + + # find the list of extensions in "@cleanup_". + eval "\@extlist = \@cleanup_$TEXFORMAT"; + if (@extlist) { + local ($file); + foreach $ext (@extlist) { + $file = &auxfile($ext); + $thefiles{$file} = 1; + } + } + + # now put them in a regular array + foreach $file (keys %thefiles) { + if (-e $file) { + unshift(@filelist, $file); + } + } + + # return that array + + @filelist; +} + +######################################################################## +# Delete any files that have to be cleaned up... +# +sub cleanup { + local ($opts) = @_; + local (@filelist, $_); + + @filelist = &files_to_cleanup(); + + # if there are any files to be deleted, ask the user to confirm + # and then blow 'em away... + if (@filelist) { + &print_long_list ("\nDelete: ", sort @filelist); + print "\n"; + if ($CLEANPROMPT) { + print "Really delete these files? "; + $_ = scalar (); chop; + } else { + $_ = "yes"; + } + + if (/^y/i) { + print "\n", unlink(@filelist), " files deleted.\n"; + } else { + print "\nCleanup aborted.\n"; + } + + print "\n"; + } +} + +######################################################################## +# The workhorse. This routine takes a command string and an options +# string. The command string is subjected to the following +# interpolation: +# +# IN ORDER TO HANDLE SOME TEX WEIRDNESS WITH FILENAMES, THIS MACRO ASSUMES +# THAT YOU PASSED '%t' TO THE TEX PROGRAM, IF YOU PASS '%s', THIS CODE +# WILL MAKE INCORRECT SUBSTITUTIONS FOR SOME VARIABLES! +# +# 1) It is "eval"ed so that Perl variables in the +# string are replaced with their values, i.e. +# "$TEX foo" -> "virtex foo" if $TEX="virtex". +# +# 2) %-substitution is performed. The following +# substitutions are currently supported: +# +# %F = format name (&lplain) +# %s = base document name (myfile) +# [ this used to mean /path/file w/o an extension, it now +# means the name of the DVI file w/o the .dvi extension ] +# %t = tex document name (/path/file.tex) +# %d = dvi document name (myfile.dvi) +# %e = error line, if there was one, otherwise 0 +# %E = file containing error, if there was one +# %o = the options passed into run +# +# %S, %T, and %D are the same as %s, %t, and %d except that +# pathname components are seperated by backslashes. This is a +# hack for MS-DOS and OS/2. +# +# TeX has some funny rules about path names: +# +# - The DVI file always goes in the current directory, +# the path is ignored. +# +# - To form the dvi filename, TeX strips off the last extension that +# *you* provide and appends .dvi. For example: +# +# You say: You mean: Dvi file: +# ------------- ------------ ------------ +# test.foo test.foo.tex test.dvi +# test.foo test.foo test.dvi +# test.foo.tex test.foo.tex test.foo.dvi +# test.foo.bar test.foo.bar test.foo.dvi +# +sub run { + local ($cmd,$opts) = @_; + local ($format) = "&" . $format{$TEXFORMAT}; + local ($texbase, $dvifile, $texfile); + local ($Texbase, $Dvifile, $Texfile); + local ($rc,$title); + + $texfile = $TEXFILEPATH . $TEXFILENAME . "." . $TEXFILEEXT; + $texbase = $TEXFILENAME; + $dvifile = &auxfile ("dvi"); + + ($Texbase = $texbase) =~ s#/#\\#g; + ($Texfile = $texfile) =~ s#/#\\#g; + ($Dvifile = $dvifile) =~ s#/#\\#g; + + # $cmd may be of the form "$TEX ..." and we want to + # expand the definition of $TEX, etc. + $cmd =~ s/\\/\\\\/g; # protect \'s + $cmd =~ s/\"/\\\"/g; # protect "'s + eval "\$cmd = \"$cmd\""; + + # Now substitute for `%' options... + $cmd =~ s/\%F/$format/g; + $cmd =~ s/\%s/$texbase/g; + $cmd =~ s/\%t/$texfile/g; + $cmd =~ s/\%d/$dvifile/g; + $cmd =~ s/\%S/$Texbase/g; + $cmd =~ s/\%T/$Texfile/g; + $cmd =~ s/\%D/$Dvifile/g; + $cmd =~ s/\%e/$ERRORLINE/g if $ERRORLINE; + $cmd =~ s/\%E/$ERRORFILE/g if $ERRORLINE; + $cmd =~ s/\%o/$opts/g; + + $cmd =~ s/\s+/ /g; # remove extranous whitespace, (display hack). + + $title = (split(/\s/, $cmd))[0]; + &set_xterm_title("TeXit: $title"); + &set_xicon_title("$title"); + + print "\n$cmd\n"; + $rc = system "$cmd"; + + if ($rc > 0) { + local (@parms); + @parms = split (/\s+/, $cmd); + print "\nNote: $parms[0] ended with return code $rc.\n"; + } + + $rc; +} + +######################################################################## +# Gets a command from the user by building a prompt up from the +# %CMDPROMPT and %CMDTEST arrays. A particular entry is only allowed +# if an eval of it's %CMDTEST returns true. +# +# &get_command() returns a two element list. The first element is +# index into the %CMDPROMPT array, the second element is any additional +# arguments supplied by the user. +# +sub get_command { + local (%commands, $_, $options); + local ($promptstring, $promptprefix); + local ($line, $thetest, $result); + + %commands = (); + $promptstring = ""; + $promptprefix = "Again? "; + $line = $promptprefix; + + # look for a stupid configuration error where a CMDTEST has + # gotten misspelled. + foreach $cmdname (keys %CMDTEST) { + die "$cmdname is defined as a CMDTEST but not as a CMDPROMPT.\n" + if !defined($CMDPROMPT{$cmdname}); + } + + foreach $cmdname (sort keys %CMDPROMPT) { + $thetest = $CMDTEST{$cmdname}; + $result = 1; + if ($thetest ne "") { + eval "\$result = $thetest"; + die "Bad CMDTEST: $thetest\n" if $@; + } + + if ($result) { + local ($cmdkey) = $cmdname; + $cmdkey =~ s/^.*\[(.*)\].*$/$1/; + die "No command string in square brackets: $cmdname\n" + if $cmdkey eq ""; + die "Duplicate command: $cmdname = $commands{$cmdkey}\n" + if defined($commands{$cmdkey}); + $commands{$cmdkey} = $cmdname; + if ($cmdname !~ /^!.*/) { + if (length($line . $cmdname) < 60) { + $line .= $cmdname . ", "; + } else { + $promptstring .= $line . "\n"; + $line = " " x length($promptprefix) . $cmdname . ", "; + } + } + } + } + + $line =~ s/(.*),\s*$/$1/; # remove the last ", "... + $promptstring .= $line . ": "; + + for (;;) { + ($cmdkey, $options) = ("", ""); + print "\nProcessing: $TEXFILEPATH$TEXFILENAME.$TEXFILEEXT\n", + $promptstring; + $_ = scalar(); chop; + ($cmdkey, $options) = ($1, $2) if /\s*(\S+)\s*(.*)/; + last if $commands{$cmdkey} ne "" || $_ eq ""; + print "\nI don't understand the command: $cmdkey\n\n"; + } + + ($commands{$cmdkey}, $options); +} + +######################################################################## +# Parse the TeX file looking for a local variables section. Build +# the associative array "%texvars" to hold the variables. +# +sub get_local_vars { + local($texfile) = @_; + local(@parms, $in_locals, $near_top, $in_plc, $plc_line); + + open (TEXFILE, $texfile) || die "Can't read $texfile.\n"; + + $in_locals = 0; # have we found the start of the local vars? + $in_plc = 0; # are we in the PLC commands? + $near_top = $TEXHEADERSIZE; # are we in the "header" area? + + @PLCRULES = (); # throw away any rules we've already found + + while () { + chop; + + if ($near_top) { + $texvars{"TeX-master"} = $1 if /\%+\s*master:\s*(\S+)\s*$/i; + $texvars{"mode"} = $1 if /\%+\s*format:\s*(\S+)\s*$/i; + $texvars{"mode"} = $1 if /\%+\s*mode:\s*(\S+)\s*$/i; + + if (!defined($texvars{"mode"})) { + $texvars{"mode"} = "latex" if (/^\\documentstyle/); + $texvars{"mode"} = "latex2e" if (/^\\documentclass/); + $texvars{"mode"} = "slitex" if (/^\\documentstyle.*\{slides\}/); + $texvars{"mode"} = "foiltex" if (/^\\documentstyle.*\{foils\}/); + $texvars{"mode"} = "amstex" if (/^\\document\s*$/); + $texvars{"mode"} = "lollipop" if (/^\\Define\w+:\w+/); + $texvars{"mode"} = "texinfo" if (/^\@node/); + $texvars{"mode"} = "texinfo" if (/^\@ifinfo/); + $texvars{"mode"} = "texinfo" if (/input texinfo/); + } + + $near_top--; + } + + $in_locals = 0 if $in_locals && /End:\s*$/; + + if ($in_locals) { + local($_) = $_; + s/^\W*//; # remove leading whitespace + @parms = split(/[: \t\"\']+/); + $var = $parms[0]; + shift(@parms); + $val = join(" ",@parms); + $texvars{$var} = $val if !defined($texvars{$var}); + } + + # the [V] makes sure Emacs doesn't get confused when editing + # texit.pl + $in_locals = 1 if /Local [V]ariables:\s*$/; + + if ($in_plc && /End\s+TeXit\s+rules:\s*$/) { + push (@PLCRULES, $plc_line) if $plc_line ne ""; + undef($plc_line); + $in_plc = 0; + } + + if ($in_plc) { + local ($line) = $_; + local ($lastline) = /.*;\s*$/; # ends with a semicolon? + $line =~ s/\s*\%*\s*(.*)/$1/; # trim leading whitespace + $line =~ s/(.*)([^\\])(\#)(.*)$/$1$2/; # trim trailing comments + $line = "" if $line =~ /^\#/; # _only_ a comment? + $plc_line .= $line . " " if $line ne ""; + if ($lastline) { + push (@PLCRULES, $plc_line); + $plc_line = ""; + } + } + + $in_plc = 1 if /Start\s+TeXit\s+rules:\s*$/; + } + + warn "Unended local variables section?\n" if $in_locals; + die "Unended TeXit rules section!\n" if $in_plc; + + close (TEXFILE); +} + +######################################################################## +# Cleanup the TeX filename. Add the extension ".tex" if it doesn't +# already have an extension. +# +sub cleanup_texfilename { + local($texfile) = @_; + local($path,$base,$ext) = &splitfn($texfile,".tex"); + + $ext = "tex" if ($ext eq ""); + + $path . $base . "." . $ext; +} + +######################################################################## +# Break a filename into it's path, basename, and extension components. +# The path returned always ends with a slash. "./" is returned if the +# file has no path. If the filename passed in does not exist, the +# default extension passed in is tried (actually, is assumed to be +# correct). +# +sub splitfn { + local ($filename, $defext) = @_; + local ($path, $basename, $ext) = ("", "", ""); + + $filename =~ tr/\\/\//; # translate \ into / + + $filename = $filename . $defext if ! -r $filename; + + if ($filename =~ /\//) { + ($path = $filename) =~ s/\/[^\/]*$//; + ($basename = $filename) =~ s/.*\///; + } + else { + $path = "."; + $basename = $filename; + } + + if ($basename =~ /\./) { + ($ext = $basename) =~ s/.*\.//; + $basename =~ s/\.[^.]*$//; + } + + ($path . "/",$basename,$ext); +} + +######################################################################## +# Print a %done message when scanning LOGFILE +# +sub percent_done { + local ($logfile) = @_; + + if ($logfile) { + $LOGSIZE = -s $logfile; + $LOGPERC = 0; + $LOGPREV = 0; + } else { + $LOGPERC = int (tell(LOGFILE) / $LOGSIZE * 100.0); + printf "Parsing log file...%d%% done\r", $LOGPERC + if $LOGPERC > $LOGPREV; + $LOGPREV = $LOGPERC; + } +} + +######################################################################## +# Search the log file looking for the (first) line that contains an +# error. +# +sub find_error_line { + local ($logfile) = &auxfile("log"); + local ($prevtext, $curfile, $paren, $rest, $temp); + local ($line) = 0; + + &init_parse_log_filenames(); + + $curfile = ""; + + if (-r $logfile) { + &percent_done($logfile); + open (LOGFILE, $logfile); + while () { + chop; + &percent_done(); + + ($paren, $rest, $temp) = &parse_log_filenames($_); + while ($paren eq "(" || $paren eq ")") { + $curfile = $temp; + ($paren, $rest, $temp) = &parse_log_filenames($rest); + } + + if ($prevtext =~ /^! .*$/ + && /^l\.([0-9]+) /) { + $line = $1; + } + + last if $line; + + $prevtext = $_; + } + close (LOGFILE); + } + + ($curfile, $line); +} + +######################################################################## +# Look through the log file for warnings. +# +sub parse_default_log { + # only looks for new dependency files... + local ($paren, $rest, $tempfile, $curfile); + local ($logfile) = &auxfile("log"); + + &init_parse_log_filenames(); + + if (-r $logfile) { + &percent_done ($logfile); + open (LOGFILE, $logfile); + while () { + chop; + &percent_done (); + + ($paren, $rest, $tempfile) = &parse_log_filenames($_); + while ($paren eq "(" || $paren eq ")") { + $curfile = $tempfile; + printf("Adding dependency for `%s'.\n", $curfile) + if !defined($DEPENDSON{$curfile}) && $VERBOSE; + $DEPENDSON{$curfile} = 1; + ($paren, $rest, $tempfile) = &parse_log_filenames($rest); + } + } + } + close (LOGFILE); +} + +######################################################################## +# Look through the log file for the LaTeX warnings. +# +sub parse_latex_log { + local ($newbib, $misbib, $newref, $badref, $badfont) = (0, 0, 0, 0, 0); + local ($hastoc, $haslof, $haslot) = (0, 0, 0); + local ($font, $reference, $citation); + local ($inputfile, $warning); + local ($logfile) = &auxfile("log"); + local ($auxfile) = &auxfile("aux"); + local ($paren, $rest, $curfile, $tempfile); + + # These are global values. + undef (%citelist); # undefined citations + undef (%reflist); # undefined references + undef (%fontlist); # NFSS substituted fonts + undef (%refmultlist); # multiply defined references + undef (%WARNINGS); # Unrecognized warning messages + + &init_parse_log_filenames(); + + if (-r $logfile) { + &percent_done ($logfile); + open (LOGFILE, $logfile); + while () { + chop; + &percent_done (); + + $warning = 0; + + ($paren, $rest, $tempfile) = &parse_log_filenames($_); + while ($paren eq "(" || $paren eq ")") { + $curfile = $tempfile; + printf("Adding dependency for `%s'.\n", $curfile) + if !defined($DEPENDSON{$curfile}) && $VERBOSE; + $DEPENDSON{$curfile} = 1; + ($paren, $rest, $tempfile) = &parse_log_filenames($rest); + } + + if (/LaTeX Warning: Citation/) { + $newbib = 1; + ($citation = $_) =~ s/.*`(.*)'.*/$1/; # '` + $citelist{$citation} = 1; + $warning = 1; + } + + if (/LaTeX Warning: Reference/) { + ($reference = $_) =~ s/.*\`(.*)\'.*/$1/; + $reflist{$reference} = 1; + $badref = 1; + $warning = 1; + } + + if (/LaTeX Warning: Label `(.*)' multiply defined/) { # '` + $refmultlist{$1} = 1; + $badref = 1; + $warning = 1; + } + + if (/Warning: Font\/shape.*undefined/) { + ($font = $_) =~ s/.*\`(\S+)\s*\'.*/$1/; + $fontlist{$font} = 1; + $badfont = 1; + $warning = 1; + } + + if (/Warning: Font\/shape.*not available/) { + ($font = $_) =~ s/.*\`(\S+)\s*\'.*/$1/; + $fontlist{$font} = 1; + $badfont = 1; + $warning = 1; + } + + if (/LaTeX Warning: Label\(s\) may have changed/) { + $newref = 1; + $warning = 1; + } + + # LaTeX2e + if (/LaTeX Warning: There are unresolved references./) { + $warning = 1; + } + + if (/LaTeX Warning: Marginpar on page .* moved./ + || /Warning: Using `.*' instead on input line [0-9]+./ # '` + || /Warning: Using external font `.*' instead on input/ # '` + || /Warning: Substituting `.*' instead on input/) { # '` + $warning = 1; # don't bother to show these warnings. + } + + if ((/Warning:/ && !$warning) + || /NFSS Info: \*\*\*/) { + $WARNINGS{$_} = $curfile; + } + + if (/No file .*\.bbl\./) { + $misbib = 1; + } + + # see if the file should have TOC, LOF, and LOT files... + $hastoc = 1 if /tf\@toc/; + $haslof = 1 if /tf\@lof/; + $haslot = 1 if /tf\@lot/; + } + close (LOGFILE); + } + + if (-r $auxfile) { + open (AUXFILE, $auxfile); + while () { + chop; + + if (/\\bibstyle\{(.+)\}/) { + local ($name, $fullname); + $name = $1 . ".bst"; + $fullname = &find_on_path($ENV{"$ENV_TEXINPUTS"}, $name); + if ($fullname ne "" + && -r $fullname + && !defined($DEPENDSON{$fullname})) { + printf("Adding dependency for `%s'.\n", $fullname) + if !defined($DEPENDSON{$fullname}) && $VERBOSE; + $DEPENDSON{$fullname} = 1; + } + } + + if (/\\bibdata\{(.+)\}/) { + local (@filelist, $fullname, $name); + + @filelist = split(/,/, $1); + while (($name = shift @filelist)) { + $name = $name . ".bib"; + $fullname = &find_on_path($ENV{"$ENV_BIBINPUTS"}, $name); + if ($fullname ne "" + && -r $fullname + && !defined($DEPENDSON{$fullname})) { + printf("Adding dependency for `%s'.\n", $fullname) + if !defined($DEPENDSON{$fullname}) && $VERBOSE; + $DEPENDSON{$fullname} = 1; + } + } + } + } + close (AUXFILE); + } + + ($newbib, $misbib, $newref, $badref, $badfont, $hastoc, $haslof, $haslot); +} + +######################################################################## +# Look through the log file for the Texinfo warnings. +# +sub parse_texinfo_log { + local ($newref, $badref) = (0, 0); + local ($logfile) = &auxfile("log"); + local ($ref); + local ($paren, $rest, $tempfile, $curfile); + + undef (%reflist); # undefined references + + &init_parse_log_filenames(); + + if (-r $logfile) { + &percent_done ($logfile); + open (LOGFILE, $logfile); + while () { + chop; + &percent_done (); + + ($paren, $rest, $tempfile) = &parse_log_filenames($_); + while ($paren eq "(" || $paren eq ")") { + $curfile = $tempfile; + printf("Adding dependency for `%s'.\n", $curfile) + if !defined($DEPENDSON{$curfile}) && $VERBOSE; + $DEPENDSON{$curfile} = 1; + ($paren, $rest, $tempfile) = &parse_log_filenames($rest); + } + + if (/Undefined cross reference \`(.*)\'./) { + $badref = 1; + $ref = $1; + $ref = $1 if $ref =~ /(.*)-[^\-]*/; + $reflist{$ref} = 1; + } + + $newref = 1 + if /Cross reference values unknown; you must run TeX again./; + } + close (LOGFILE); + } + + ($newref, $badref); +} + +######################################################################## +# This routine aids in the parsing of log files be locating filenames +# within the log. Filenames included in a document always appear as +# '(filename...' in the log. +# +# NOTE: if unbalanced parenthesis occur in the log file (because someone +# printed them in a \typeout command or some such), all bets are +# off! +# +# FURTHER NOTE: this routine returns the top of the filename stack +# whenever a close paren is seen, so you will get the +# same files more than once... +# +# This routine uses the global variables %ParenLevel and %Parens which +# it initializes appropriately... +# +# ********************************************************************** +# THIS ROUTINE IS NOT RE-ENTRANT! IT REQUIRES STATE SAVED ACROSS CALLS! +# ********************************************************************** +# +sub init_parse_log_filenames { + %Parens = (); + $ParenLevel = 0; +} + +sub parse_log_filenames { + local ($rest) = @_; + local ($paren, $curfile); + local (@result) = (); + + undef ($curfile); + + while ($rest =~ /^[^()]*(\(|\))(.*)/) { + $paren = $1; + $rest = $2; + + ($curfile = $rest) =~ s/\s*(\S+)/$1/; # leading whitesp + $curfile =~ s/^([^() ]+).*/$1/; # trailing ( and ) + + if ($paren eq "(") { + $ParenLevel++; + if (-r $curfile) { + $Parens{$ParenLevel} = $curfile; + } else { + $Parens{$ParenLevel} = '*'; + } + } else { + $ParenLevel--; + $curfile = $Parens{$ParenLevel}; + } + + last if -r $curfile; + + undef ($curfile); + } + + if ($curfile) { + @result = ("$paren", "$rest", "$curfile"); + } + + @result; +} + +######################################################################## +# This routine is called after each run of TeX to see if TeX needs to +# be run again. TeX needs to be run twice when documents contain +# cross references, lists of tables, or other things which cannot be +# completely determined in a single pass. Sometimes TeX needs to be +# run three times. +# +# Since different formats print different messages in the log file, +# there is no universal way to determine if TeX needs to be run again. +# +# This routine calls "&parse_FORMAT_log()" to extract information from +# the log file and "&interpret_FORMAT_log()" to decide what to do with +# the information. "&parse_FORMAT_log()" should return a list, this +# list is passed to "&interpret_FORMAT_log()". +# +# FORMAT = $parsename{$TEXFORMAT}. +# +# Currently only "default" and "latex" FORMATs exist. The default +# format just looks to see if the DVI file still exists or if the +# master file is newer than the DVI file. +# +# The latex format does a lot more. +# +# If you write new &parse_FORMAT_log()/&interpret_FORMAT_log() routines, +# remember to do the same checking that &interpret_default_log() does +# and remember to run the users PLC commands at an appropriate place. +# Copying and modifying the latex versions is probably the best way +# to start. +# +sub dependencies_changed { + local (@parselist, $sub); + + $changed = 0; + + eval "\@parselist = &parse_$parsename{$TEXFORMAT}_log();"; + eval "\$changed = &interpret_$parsename{$TEXFORMAT}_log(\@parselist);"; + + $changed; +} + +######################################################################## +# Called by the interpret_FORMAT_routines, this function returns +# true if the dvi file is missing or the master document is more +# recent than the dvi file... +# +sub document_is_newer { + local ($changed) = 0; + + if (! -r &auxfile("dvi")) { + print &auxfile("dvi"), " is missing.\n" if $VERBOSE; + $changed = 1; + } elsif (&mtime(&auxfile("dvi")) < &mtime($texmaster)) { + print "$texmaster is newer than ", &auxfile("dvi"), ".\n" + if $VERBOSE; + $changed = 1; + } + + $changed; +} + +######################################################################## +# Called by the interpret_FORMAT_routines, this function runs the +# @default_rules, @FORMAT_rules, and user rules... +# Input: $changed +# Output: new value of $changed +# +sub run_rules { + local ($changed) = @_; + local ($line, @the_rules); + + @the_rules = @default_rules; + foreach $line (@the_rules) { + print "Rule: $line\n" if $VERBOSE; + eval $line; + die "Invalid rule: $line\n" if $@; + } + + eval "\@the_rules = \@${TEXFORMAT}_rules"; + foreach $line (@the_rules) { + print "Rule: $line\n" if $VERBOSE; + eval $line; + die "Invalid rule: $line\n" if $@; + } + + foreach $line (@PLCRULES) { + print "Rule: $line\n" if $VERBOSE; + eval $line; + die "Invalid rule: $line\n" if $@; + } + + $changed; +} + +######################################################################## +# Input: the list of values returned by parse_default_log(). +# Output: prints informative messages. +# Returns: 1 if TeX should be run again... +# +sub interpret_default_log { + local ($changed) = 0; + + $changed = &document_is_newer(); + $changed = &run_rules($changed); + $changed = 1 if &dependent_files_are_newer(); + + print "\nAs far as I can tell, the document is up to date.\n" + if !$changed; + + $changed; +} + +######################################################################## +# Input: the list of values returned by parse_latex_log(). +# Output: prints informative messages. +# Returns: 1 if TeX should be run again... +# +sub interpret_latex_log { + local ($newbib, $misbib, $newref, $badref, $badfont, + $hastoc, $haslof, $haslot) = @_; + local ($changed) = 0; + local ($warning); + + $changed = &document_is_newer(); + + # If the document should have TOC, LOF, or LOT files but they + # don't actually exist, then the document has to be TeX'd + # again. + if (($hastoc && ! -r &auxfile("toc")) + || ($haslof && ! -r &auxfile("lof")) + || ($haslot && ! -r &auxfile("lot"))) { + $changed = 1; + print "Table of contents disappeared.\n" + if $hastoc && ! -r &auxfile("toc"); + print "List of figures disappeared.\n" + if $haslof && ! -r &auxfile("lof"); + print "List of tables disappeared.\n" + if $haslot && ! -r &auxfile("lot"); + } + + # If there are unknown citations... + if ($newbib) { + local ($wasdiff) = 0; + + &print_long_list ("Missing citations: ", keys(%citelist)); + + # calculate the intersection between the currently missing + # citations and the ones that were missing last time. If they + # are different, run BibTeX. Since missingcites begins empty, + # this will force BibTeX to be run at least once if there are + # any missing citations. + { + local (%mark, @diff, $_); + grep($mark{$_}++, keys(%missingcites)); + @diff = grep(!$mark{$_}, keys(%citelist)); + if (@diff) { + $rc = &bibtexcmd (""); + print "\n"; + $changed = 1; + $wasdiff = 1; + } + } + + if (%missingcites) { + print "Bibliography database(s) may be incomplete.\n"; + if (! $wasdiff) { + print "The same citations are missing. ", + "Running BibTeX again won't help.\n"; + } + } + print "\n"; + + %missingcites = %citelist; + } + + if ($misbib && ! $newbib) { + $rc = &bibtexcmd(""); + print "\n"; + } + + # If there are unknown references... + if ($badref) { + if (%reflist) { + &print_long_list ("Missing references: ", + keys(%reflist)); + print "\n"; + } + if (%refmultlist) { + &print_long_list ("Multiply defined references: ", + keys(%refmultlist)); + print "\n"; + # Retry once if the error is found on the first pass + # because the error persists in the AUX file through + # one run after the user fixes the problem. + $newref = 0 if $runcount > 1; + } + } + + # Just FYI about substituted fonts... + if ($badfont) { + &print_long_list ("FYI: substituted fonts: ", + keys(%fontlist)); + print "\n"; + } + + # If there are Warnings that TeXit doesn't understand... + if (%WARNINGS) { + print "\nThe following (unrecognized) warnings appear ", + "in the log file:\n"; + foreach $warning (keys %WARNINGS) { + if ($warning =~ /input line ([0-9]+)./) { + printf "%s: %d: %s\n", $WARNINGS{$warning}, $1, $warning; + } else { + print $WARNINGS{$warning}, ": ", $warning, "\n"; + } + } + } + + # The file has changed if there are new references... + $changed = $changed || $newref; + + $changed = &run_rules($changed); + $changed = 1 if &dependent_files_are_newer(); + + if (!$changed) { + if ($newbib || $badref) { + print "\nThe document is still incorrect, "; + print "but TeXing again won't help.\n"; + print "Some citations cannot be found. " + if $newbib; + print "Some references cannot be found or cannot be resolved." + if $badref; + print "\n\n"; + } else { + print "\nAs far as I can tell, the document is up to date.\n"; + } + } else { + print "\n"; + } + + $changed; +} + +######################################################################## +# Input: the list of values returned by parse_texinfo_log(). +# Output: prints informative messages. +# Returns: 1 if TeX should be run again... +# +sub interpret_texinfo_log { + local ($newref, $badref) = @_; + local ($changed) = 0; + + # If there are unknown references... + if ($badref) { + if (%reflist) { + print "\n"; + &print_long_list ("Missing references: ", + keys(%reflist)); + print "\n"; + } + } + + $changed = &document_is_newer() || $newref; + $changed = &run_rules($changed); + $changed = 1 if &dependent_files_are_newer(); + + if (!$changed) { + if ($badref) { + print "The document is still incorrect, "; + print "but TeXing again won't help.\n"; + print "Some references cannot be found or cannot be resolved." + if $badref; + print "\n\n"; + } else { + print "As far as I can tell, the document is up to date.\n"; + } + } + + $changed; +} + +######################################################################## +# Input: an extension, `ext' +# Output: the filename $TEXFILEPATH . $TEXFILENAME . `.ext' +# Note: auxilliary files are always placed in the current directory, +# the path is ignored. This is a change from v1.27 to be more +# compatible with TeX. +# +sub auxfile { + local($ext) = @_; + local($dot); + + $dot = ""; + $dot = "." if $ext ne "" && $ext !~ /^\./; + + $TEXFILENAME . $dot . $ext; +} + +######################################################################## +# Input: none +# Output: true if any file in %DEPENDSON is newer than DVI file +# +sub dependent_files_are_newer { + local($file, $rerun); + + $rerun = 0; + if (-r &auxfile("dvi")) { + foreach $file (keys %DEPENDSON) { + if (-r $file) { + if (&mtime(&auxfile("dvi")) < &mtime($file)) { + print "$file newer than DVI file? Yes.\n" + if $VERBOSE; + $rerun = 1; + } else { + print "$file newer than DVI file? No.\n" + if $VERBOSE; + } + } else { + print "$file newer than DVI file? No (doesn't exist).\n" + if $VERBOSE; + } + } + } + + $rerun; +} + +######################################################################## +# Input: list of files +# Output: None. Sets the %DEPENDSON list... +# +sub dependson { + local(@filelist) = @_; + local($file); + + foreach $file (@filelist) { + $DEPENDSON{$file} = 1; + } +} + +######################################################################## +# Input: list of files +# Output: true if any file is missing +# +sub missing { + local(@filelist) = @_; + local($missing, $file); + + $missing = 0; + + foreach $file (@filelist) { + if (defined($ifmissing{$file})) { + if (! -r $file) { + print "Missing: $file? Yes.\n" if $VERBOSE; + $missing = 1; + } else { + print "Missing: $file? No.\n" if $VERBOSE; + } + } else { + $ifmissing{$file} = 1; + } + } + + $missing && (!$first_time); +} + +######################################################################## +# Input: list of files +# Output: waits until they all exist (and are readable) +# +sub waitfor { + local(@filelist) = @_; + local(@missing, $file); + + @missing = (); + + foreach $file (@filelist) { + push (@missing, $file) if (! -r $file); + } + + if ($VERBOSE && @missing) { + &print_long_list ("\nWaiting for: ",@filelist); + print "\n"; + } + + foreach $file (@missing) { + while (! -r $file) { + sleep 1; + } + } +} + +######################################################################## +# Input: list of files +# Output: true if any file has changed. A file has changed if its +# checksum is different from the preceding call. Files that +# are missing both times are currently the same. +# +sub changed { + local(@filelist) = @_; + local($changed, $savecheck, $file); + + $changed = 0; + + foreach $file (@filelist) { + if (defined($ifchanged{$file})) { + $savecheck = &checksum($file); + if ($ifchanged{$file} != $savecheck) { + print "Changed: $file? Yes.\n" if $VERBOSE; + $ifchanged{$file} = $savecheck; + $changed = 1; + } else { + print "Changed: $file? No.\n" if $VERBOSE; + } + } else { + $ifchanged{$file} = &checksum($file); + } + } + + $changed && (!$first_time); +} + +######################################################################## +# Calculate a checksum for a file. +# +sub checksum { + local($file) = @_; + local($/, $checksum); + + undef $/; + + if (-r $file) { + open (CHECKSUM, $file); + $checksum = unpack("%32C*", ); + close (CHECKSUM); + } else { + $checksum = "none"; + } + + $checksum; +} + +######################################################################## +# Input: two lists of files. The lists must be quoted strings so that +# they can be seperated easily. The two strings are split on +# "/, /" so they _can_ include multiple files. +# Output: true if _any_ of the first list of files is newer than _any_ +# of the second list of files _or_ any of the second list of +# files is missing. +# +sub newer { + local ($newfile, $oldfile) = @_; + local (@newfiles,@oldfiles); + local ($file, $missing, $newer); + + @newfiles = split(/[, ]/, $newfile); + @oldfiles = split(/[, ]/, $oldfile); + + die "Bad call to &newer(). No files in `new' list.\n" + if !@newfiles; + die "Bad call to &newer(). No files in `old' list.\n" + if !@oldfiles; + + $newer = 0; + + # Find the oldest old file. All must exist. + undef $oldfile; + foreach $file (@oldfiles) { + $newer = 1, $missing = $file, last if ! -r $file; + $oldfile = $file + if (! $oldfile || (&mtime($file) < &mtime($oldfile))); + } + + # Find the newest new file. + undef $newfile; + foreach $file (@newfiles) { + $newfile = $file + if (-r $file + && (! $newfile || (&mtime($file) > &mtime($newfile)))); + } + + warn "None of the `new' files exist: @newfiles\n" + if ! $newfile; + + $newer = $newer + || ($newfile && (&mtime($newfile) > &mtime($oldfile))); + + if ($newer) { + if ($oldfile) { + print "$newfile newer than $oldfile? Yes.\n" + if $VERBOSE; + } else { + print "$missing doesn't exist, so $newfile is newer.\n" + if $VERBOSE; + } + } else { + print "$newfile newer than $oldfile? No.\n" + if $VERBOSE; + } + + $newer && (!$first_time); +} + +######################################################################## +# This routine prints a message followed by a potentially long list of +# items, seperated by spaces. It will never allow "word wrap" to occur +# in the middle of a word. There has to be a better way, using Perl's +# report generation to do this, but I haven't looked yet. +# +sub print_long_list { + local ($message,@thelist) = @_; + local ($line) = $message; + local ($item, $displaystring) = ("", ""); + + foreach $item (@thelist) { + if (length($line . $item) < 73) { + $line .= $item . ", "; + } else { + $displaystring .= $line . "\n"; + $line = $item . ", "; + } + } + + $line =~ s/(.*),\s*$/$1/; # remove the last ", "... + $displaystring .= $line . "\n"; + + print $displaystring; +} + +######################################################################## +# Shows the current status of TeXit. This is kindof a debugging option +# and kindof a 'satisfy my curiousity' option. It isn't really very +# complete yet. +sub showstatus { + local($opts) = @_; + + print "\n"; + + if ($opts eq "?" || $opts eq "") { + print "Status options:\n"; + print " rules = rules\n"; + print " dep = document dependencies\n"; + print " = value of Perl variable \n"; + } elsif ($opts eq "rules") { + local ($line, @the_rules); + + @the_rules = @default_rules; + foreach $line (@the_rules) { + print "Default rule: $line\n"; + } + + eval "\@the_rules = \@${TEXFORMAT}_rules"; + foreach $line (@the_rules) { + print "$TEXFORMAT rule: $line\n"; + } + + foreach $line (@PLCRULES) { + print "Document rule: $line\n"; + } + } elsif ($opts eq "dep") { + local ($line); + + print "This document depends on the following files:\n"; + foreach $line (keys %DEPENDSON) { + print "\t$line\n"; + } + } else { + local ($arg, $svalue, %pvalue, @avalue, $line, $isdef); + + $isdef = 0; + + eval "\$arg = defined(\$$opts);"; + if ($arg) { + eval "\$svalue = \$$opts;"; + print "\$$opts = $svalue\n"; + $isdef = 1; + } + + eval "\$arg = defined(\%$opts);"; + if ($arg) { + print "\%$opts:\n"; + eval "\%pvalue = \%$opts;"; + foreach $line (keys %pvalue) { + print "$line = $pvalue{$line}\n"; + } + $isdef = 1; + } + + eval "\$arg = defined(\@$opts);"; + if ($arg) { + print "\@$opts:\n"; + eval "\@avalue = \@$opts;"; + foreach $line (@avalue) { + print "$line\n"; + } + $isdef = 1; + } + + if (! $isdef) { + print "$opts is undefined.\n"; + } + } +} + +######################################################################## +# Setup the $format{}, $formatname{}, and $parsename{} associative +# arrays for finding the format file name, printable name, and parser +# name for each format. +sub setup_format { + local ($common_name, $printable_name, + $parser_name, $format_name, $alt_inputenv) = @_; + + $format{$common_name} = $format_name; + $formatname{$common_name} = $printable_name; + $parsename{$common_name} = $parser_name; + $altinputenv{$common_name} = $alt_inputenv; +} + +######################################################################## +# Builds an associative array for a pascal-like case statement. This +# is a bit obscure and relies on Perl's ability to evaluate strings +# that look like code at runtime. +sub case { + local(*assoc,$_) = @_; + for (split(/\n/)) { + /^\s*(\S+)\s+(.*)/; + for (eval $1) { + $assoc{$_} = $2; + } + } +} + +######################################################################## +# Return Perl version info... +# +sub perl_version_info { + local($savestar) = $*; + + $* = 1; + + $] =~ /^.*Revision: ([\d.]+) .*\nPatch level: (\d+).*$/; + $revision = $1; + $patchlevel = $2; + + $* = $savestar; + + ($revision, $patchlevel); +} + +######################################################################## +# This hack gets around problems with MS-DOS and OS/2 ports of Perl. +# Some ports get confused when presented with "require d:/path/file" +# because they think "d:/path/file" should appear somewhere in @INC +# because it doesn't begin with a slash. Sigh. This code isn't as +# nice as "require" but it works... +# +sub require_hack { + local($file) = @_; + local($line); + + print "Loading init file: $file\n" if $VERBOSE; + + $line = ""; + open (INIT, $file); + while () { + $line = $line . $_; + if (/;\s*$/) { + eval $line; + die "Error in init file: $file at line $..\n" if $@; + $line = ""; + } + } + close (INIT); + + die "Missing \";\" at end of init file: $file.\n" + if ($line !~ /^\s*$/); +} + +######################################################################## +# This hack gets around problems with (at least) the OS/2 port of Perl. +# The OS/2 port (maybe the MS-DOS port?) have one _hell_ of an annoying +# bug. The test "-M file" returns a large positive number instead of +# a negative number for files that are newer than the start-time of the +# script (someone probably did an unsigned computation where a signed +# computation was required...) The result is that -M file1 > -M file2 +# if file2 existed when the script began and file1 did not. Really +# bad. +# +# Caveat, on a working system: +# +# -M file1 > -M file2 ==> &mtime(file1) < &mtime(file2) +# +sub mtime { + local ($file) = @_; + local ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, + $atime, $mtime, $ctime, $blksize, $blocks); + + if (-r $file) { + ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, + $atime, $mtime, $ctime, $blksize, $blocks) = stat($file); + } else { + $mtime = -1; + } + + $mtime; +} + +######################################################################## +# This helpful little routine locates a file on a path. The path can +# be ":" or ";" delimited. If the file is found, it's fully qualified +# name is returned, otherwise the null string is returned. If the +# input path contains "/" or "\" then either it is returned (if the file +# specified exists) or the empty string is returned, the path _is not_ +# searched. +# +sub find_on_path { + local($path, $file) = @_; + local($dir, $filename); + + $filename = ""; + + if ($file =~ /\/|\\/) { + $filename = $file if -e $file; + } else { + foreach $dir (split(/;|:/,$path)) { + $dir =~ s/\\/\//g; + $filename = $dir . "/" . $file; + last if -e $filename; + $filename = ""; + } + } + $filename; +} + +######################################################################## +# Sometimes it is helpful to have a .texitrc file (especially in a +# working directory) update the paths used by TeX. These two routines +# are a big help... +# +sub del_from_path { + local($path,$dir) = @_; + local(@paths,$outpath,$curdir,$place); + + @paths = split(/$PATHSEP/,$path); + + $outpath = ""; + for ($place = 0; $place <= $#paths; $place++) { + $curdir = @paths[$place]; + $outpath = sprintf("%s$PATHSEP%s", $outpath, $curdir) + if $curdir && $curdir ne $dir; + } + + $outpath = $1 if $outpath =~ /^$PATHSEP+([^$PATHSEP].*)$/; + $outpath = $1 if $outpath =~ /^(.*[^$PATHSEP])$PATHSEP+$/; + + $outpath; +} + +sub add_to_path { + local($path,$dir,$front) = @_; + local(@paths,$outpath,$curdir,$place); + + $path = &del_from_path($path,$dir); # remove it if it's already there + + if ($front) { + $path = $dir . $PATHSEP . $path; + } else { + $path = $path . $PATHSEP . $dir; + } + + $path; +} + +######################################################################## +# For texit running under X11, this routine changes the title to reflect +# what's going on now...works for the iconized xterm too! +# +sub set_xterm_title { + local ($title) = @_; + + print STDOUT "]2;$title" if $SETXTITLE; +} + +sub set_xicon_title { + local ($title) = @_; + + print STDOUT "]1;$title" if $SETXTITLE; +} + +sub set_x_titles { + local ($title) = @_; + &set_xterm_title ($title); + &set_xicon_title ($title); +} + +######################################################################## +# I'm sure this could be improved...but real docs should be written +# anyway so there's no need to go overboard here. That's my excuse, +# in any event. +# +sub display_usage { + local (@formatlist, $item); + + print "#" x 72, "\n"; + print <<'EOF'; +Usage: texit texfile[.tex] +Where may be: + - process `texfile' using the named format. + -once process document once and exit. Returns 0 + if the document seems ok after processing. + -clean suppress prompt message when cleaning up files. + -menu skip the initial run of TeX and go straight to the menu. + -verbose display additional status messages. + -initfile init load the file "init" during initialization. + -tex force an initial run of TeX, even if it doesn't + seem necessary. + -bibtex start by running BibTeX (instead of TeX). + -makeindex start by running MakeIndex. + +TeXit tries to figure out the format of your document, so you don't +have to specify it. Of course, you have to specify it if TeXit guesses +wrong. Better yet, stick in an Emacs-style local var section to define +the format or a "% Format: format" line at the top of your document. + +You only have to specify enough of the option string to make it +unambiguous (e.g. "-v" is good enough for -verbose). + +EOF + + foreach $item (keys %format) { + unshift(@formatlist, $item) if !defined($casefoo{$item}); + } + + &print_long_list("TeXit recognizes the following format names: ", + sort(@formatlist)); + print "#" x 72, "\n"; + exit 2; +} + -- cgit v1.2.3