#!/usr/bin/env python from __future__ import with_statement from contextlib import closing from optparse import OptionParser import sys import os import os.path import pty import re import select import subprocess import fnmatch import dircache import termios import signal class TexallOptionParser(OptionParser): def __init__(self): OptionParser.__init__(self, usage="%prog [options] DIR/FILE", version="%prog $Id$", description="Call LaTeX and friends to typeset all tex files in DIR/FILE. By default up-to-date files are skipped. Care is taken to run all required commands until the result is stable. By default, pdflatex is used, but use of pstricks forces dvi->ps->pdf. The utility program rmligs ist used to improve output for german language texts. Use \"texall -Sqfi batchmode file.tex\" to simulate the behaviour of (pdf)latex in non-interactive mode (useful for emacs).") self.add_option("-n", "--dry-run", action="store_false", dest="act", default=True, help="do not run any program") self.add_option("-f", "--force", action="store_true", dest="force", default=False, help="regenerate up-to-date files") self.add_option("-N", "--non-recursive", action="store_false", dest="recurse", default=True, help="disable recursion into subdirectories") self.add_option("-s", "--summary", dest="summary", help="summarise failures and processed files at the end of the run (one of failures (default), files, both, no)", choices=("no","files","failures","both"), default="failures") self.add_option("-v", "--verbose", action="count", dest="verbosity", default=1, help="explain what is going on") self.add_option("-q", "--quiet", action="store_const", dest="verbosity", const=0, help="suppress progress reports") self.add_option("-o", "--show-output", action="store_true", dest="showoutput", default=False, help="show the output of the called programs") self.add_option("-i", "--interaction", dest="interactionmode", choices=("batchmode", "nonstopmode", "scrollmode", "errorstopmode"), help="control the behaviour of latex (on of batchmode (default), nonstopmode (default for -o), scrollmode, errorstopmode)") self.add_option("-I", "--ignore", action="store", dest="ignorepattern", default=".*vorlage.*|\.\#.*|\..*\.rmligs\.tex", help="regular expression of filenames to ignore") self.add_option("-P", "--required-pattern", action="store", dest="requiredpattern", default='^\\\\documentclass|%%% TeX-master: t', help="regular expression that must match the file content") self.add_option("-T", "--preserve-tempfiles", action="store_false", dest="deletetempfiles", default=True, help="preserve temporary files (default: delete)") self.add_option("-K", "--no-kpathsea", action="store_true", dest="nokpse", default=False, help="do not call kpsewhich (might be faster)") self.add_option("-S", "--single-run", action="store_true", dest="singlerun", default=False, help="stop after the first LaTeX run, no matter if finished") def parse_args(self, args=None, values=None): options, arguments = OptionParser.parse_args(self, args, values) # provide default for interactionmode: if options.interactionmode == None: if options.showoutput: options.interactionmode = "nonstopmode" else: options.interactionmode = "batchmode" return options, arguments class AnalyserData: def __init__(self, name, filetype): self.name = name self.filetype = filetype class Analyser: """Abstract class for analysing of program output and file content""" def __init__(self, d): """Instance creation. d -- dictionary to store data in""" self.d = d def parse(self, text): """parse text and change own state accordingly. text -- the filecontent or line to analyse""" raise NotImplementedError def merge(self, d): """merge information gathered from an included file. d -- dictionary with information gathered from the included file""" raise NotImplementedError def finish(self, dirname): """final steps after all dependecy file information hat been merged. Only executed if this data object belongs to a main .tex file. dirname -- directory of current job""" pass class AnalyseTexRequired(Analyser): def __init__(self, d): d.required = False Analyser.__init__(self,d) def parse(self, text): if re_required.match(text): self.d.required = True def merge(self, d): pass # do not take over master file status class AnalyseTexDependencies(Analyser): def __init__(self, d): d.deps = [] d.grfdeps = [] Analyser.__init__(self, d) def parse(self, text): for m in re_inputinclude.finditer(text): self.d.deps.append((m.group(2), (".tex", ""))) m = re_documentclass.match(text) if m: cls = m.group(2) self.d.deps.append((cls, (".cls", ""))) for m in re_usepackage.finditer(text): stys = m.group(2) for sty in re_commawhitespace.split(stys): self.d.deps.append((sty, (".sty", ""))) for m in re_graphics.finditer(text): self.d.grfdeps.append(m.group(2)) def merge(self, d): self.d.deps.extend(d.deps) self.d.grfdeps.extend(d.grfdeps) def finish(self, _): if hasattr(self.d, "needsPS"): if self.d.needsPS: for f in self.d.grfdeps: self.d.deps.append((f, (".eps", ".jpg", ".png", ""))) else: for f in self.d.grfdeps: self.d.deps.append((f, (".pdf", ".jpg", ".png", ""))) # TODO: Analyser for \usebeamerXXXtheme class AnalyseTexBibfiles(Analyser): def __init__(self, d): d.bibfiles = [] Analyser.__init__(self, d) def parse(self, text): for m in re_bibliography.finditer(text): bibs = m.group(1) for bib in re_commawhitespace.split(bibs): if bib[-8:] != "-blx.bib" and bib[-4:] != "-blx": self.d.bibfiles.append(bib) def merge(self, d): self.d.bibfiles.extend(d.bibfiles) def finish(self, dirname): bibpaths = [] for bib in self.d.bibfiles: bibpath = texpath(dirname, bib, pathtype="bib",progname="bibtex") bibpath = strippath(bibpath, dirname) self.d.deps.append((bibpath, ("",))) bibpaths.append(bibpath) self.d.bibfiles = bibpaths class AnalyseTexNeedsPs(Analyser): def __init__(self, d): d.needsPS = False Analyser.__init__(self, d) def parse(self, text): if re_needsps.match(text): self.d.needsPS = True def merge(self, d): self.d.needsPS |= d.needsPS class AnalyseTexRmligs(Analyser): def __init__(self, d): d.rmligs = False Analyser.__init__(self, d) def parse(self, text): if re_rmligs.match(text): self.d.rmligs = True def merge(self, d): self.d.rmligs |= d.rmligs texanalysers = (AnalyseTexRequired, AnalyseTexDependencies, AnalyseTexBibfiles, AnalyseTexNeedsPs, AnalyseTexRmligs) class AnalyseLogErrors(Analyser): def __init__(self, d): d.errors = False Analyser.__init__(self, d) def parse(self, line): if re_error.match(line): self.d.errors = True class AnalyseLogRequests(Analyser): def __init__(self, d): d.requests = {} Analyser.__init__(self, d) self.inrequest = False def addrequest(self, name, priority): if self.d.requests.has_key(name): self.d.requests[name] = max(self.d.requests[name], priority) else: self.d.requests[name] = priority def parse(self, line): # line by line if self.inrequest: # strip newline character while line[-1] in ("\n", "\r"): line = line[:-1] if line[:7] == "binary=": self.reqbinary = line[7:] elif line[:7] == "option=": self.reqoptions.extend(line[7:].split(" ")) elif line[:7] == "infile=": self.reqinfile = line[7:] elif line == ":REQ": if self.reqbinary == None: self.reqbinary = self.reqname if self.reqbinary not in ("bibtex", "bibtex8"): error(self.d.name, "ignoring request to run %s for security reasons"%self.reqbinary, warning=True) self.inrequest = False return self.reqoptions[:0] = [self.reqbinary] if self.reqbinary == "bibtex8": self.reqoptions[1:1] = [ "--wolfgang" ] if self.reqinfile != None: self.reqoptions.append(self.reqinfile) self.addrequest(tuple(self.reqoptions), self.reqpri) self.inrequest=False else: error(self.d.name, "malformatted request ignored", warning=True) self.inrequest=False else: m = re_logmatcher.match(line) if m: if m.group() == 'LaTeX Warning: There were undefined references.': self.addrequest("latex",0) elif m.group() == 'LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right.': self.addrequest("latex",0) elif m.group(1)!=None: self.addrequest(m.group(2), int(m.group(1))) elif m.group(3)!=None: self.inrequest = True self.reqpri = int(m.group(3)) self.reqname = m.group(4) self.reqbinary = None self.reqoptions = [] self.reqinfile = None loganalysers = (AnalyseLogErrors, AnalyseLogRequests) analysecache = {} def analyseFile(dirname, filename, analyserclasses, parsetype, fulltext=False, cache=False, recurse=None, recursesystemfiles=False): pathname = os.path.join(dirname, filename) if cache and (parsetype, pathname) in analysecache: return analysecache[(parsetype, pathname)] data = AnalyserData(pathname, parsetype) analysers = [] for analyser in analyserclasses: analysers.append(analyser(data)) with closing(open(os.path.join(dirname, filename), "r")) as f: if fulltext: text = f.read() for a in analysers: a.parse(text) else: for line in f: for a in analysers: a.parse(line) if hasattr(data, "deps") and (recurse or (recurse==None and hasattr(data, "requrired") and data.required)): # recurse: for dep, extlist in data.deps: deppath = texpath(dirname, dep, extlist=extlist) if (not recursesystemfiles) and re_systemfile.match(deppath): # skip system files continue depname = strippath(deppath, dirname) depdata = analyseFile(dirname, depname, fulltext, cache, recurse=True) for a in analysers: a.merge(depdata) if hasattr(data, "required") and data.required: for a in analysers: a.finish(dirname) if cache: analysecache[(parsetype, pathname)] = data return data def analyseTex(dirname, filename): return analyseFile(dirname, filename, texanalysers, "tex", fulltext=True, cache=True) def analyseLog(dirname, filename): return analyseFile(dirname, filename, loganalysers, "log", cache=False) kpsecache = {} kpseproc = {} def texpath(dirname,filename,pathtype="tex",progname="latex",extlist=("",)): filename = os.path.expanduser(filename) # first try files in current directory for ext in extlist: if os.path.isfile(os.path.join(dirname,filename+ext)): return filename+ext if opts.nokpse: raise ValueError("file not found, not using kpsewhich: %s (extensions: %r)") # call kpsewhich to locate the file if not kpsecache.has_key((pathtype,progname)): kpsecache[(pathtype,progname)] = {} cache = kpsecache[(pathtype,progname)] for ext in extlist: lookupname = filename + ext if cache.has_key(lookupname): if cache[lookupname]: return cache[lookupname] else: continue else: if iswindows: kpse=subprocess.Popen(["kpsewhich", "-format", pathtype, "-progname", progname, lookupname], stdout=subprocess.PIPE) (out,err)=kpse.communicate() if kpse.wait() == 0: pathname = out[:-1] cache[lookupname] = pathname return pathname else: cache[lookupname] = None else: if kpseproc.has_key((pathtype, progname)): _, master = kpseproc[(pathtype, progname)] os.write(master, lookupname + "\n") else: master, slave = pty.openpty() #turn off echo attr = termios.tcgetattr(slave) attr[3] = attr[3] & ~termios.ECHO termios.tcsetattr(slave, termios.TCSANOW, attr) proc = subprocess.Popen(["kpsewhich", "-interactive", "-format", pathtype, "-progname", progname, lookupname], stdout=slave, stdin=slave) kpseproc[(pathtype, progname)] = (proc, master) if select.select([master], [], [], 0.1)[0]: pathname = os.read(master, 1024) else: pathname = None if pathname: while pathname[-1] in ("\n", "\r"): pathname = pathname[:-2] cache[lookupname] = pathname return pathname else: cache[lookupname] = None raise ValueError("file not found by kpsewhich: %s (extensions: %r)"%(filename,extlist)) errors = [] procfiles = [] errstr = {False: "ERROR", True: "WARNING"} def error(file, desc, warning=False): msg = "%s: %s: %s"%(errstr[warning], os.path.normpath(file), desc) if opts.verbosity: print msg errors.append(msg) null = file(os.path.devnull, "wb") # platform checking: iswindows = (sys.platform == "win32") # provide a (modified) copy of os.path.relpath from python 2.6 def relpath(pathname, start=os.path.curdir): """Return a relative version of a path""" if not iswindows: pathname = os.path.expanduser(pathname) start_list = os.path.abspath(start).split(os.path.sep) path_list = os.path.abspath(pathname).split(os.path.sep) # Work out how much of the filepath is shared by start and path. i = len(os.path.commonprefix([start_list, path_list])) rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] return os.path.join(*rel_list) else: start_list = os.path.abspath(start).split(os.path.sep) path_list = os.path.abspath(pathname).split(os.path.sep) if start_list[0].lower() != path_list[0].lower(): unc_path, rest = os.path.splitunc(pathname) unc_start, rest = os.path.splitunc(start) if bool(unc_path) ^ bool(unc_start): raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" % (pathname, start)) else: raise ValueError("path is on drive %s, start on drive %s" % (path_list[0], start_list[0])) # Work out how much of the filepath is shared by start and path. for i in range(min(len(start_list), len(path_list))): if start_list[i].lower() != path_list[i].lower(): break else: i += 1 rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] return os.path.join(*rel_list) def strippath(pathname, start): pathname = os.path.expanduser(pathname) pathname = os.path.abspath(os.path.join(start, pathname)) start = os.path.abspath(start) if pathname[:len(start)] == start: return pathname[len(start)+1:] else: return pathname binpath = os.path.expandvars("$PATH") if binpath == "$PATH": binpath = os.path.defpath binpath = binpath.split(os.path.pathsep) def isinpath(name): for p in binpath: if os.path.isfile(os.path.join(p, name)): return True return False if isinpath("rmligs"): rmligs_name = "rmligs" elif isinpath("rmligs-german"): rmligs_name = "rmligs-german" else: rmligs_name = "" error("rmligs", "Command not found. Please install rmligs (or rmligs-german) for optimal results", warning=True) def alltexfiles(arglist): """iterate over all .tex files that are not ignored by -I""" for a in arglist: if os.path.isdir(a): for directory, subdirs, files in os.walk(a): if not opts.recurse: subdirs[:] = [] for name in files: if re_texfile.match(name): if re_ignore.match(name): if opts.verbosity > 2: print "%s: skipped because of ignored name"%os.path.normpath(os.path.join(directory,name)) else: yield (directory, name) elif os.path.isfile(a): if re_texfile.match(a): (dirname, filename) = os.path.split(a) if dirname == "": dirname = "." yield (dirname, filename) else: error(a, "is no .tex file; skipped") elif os.path.isfile(a+".tex"): (dirname, filename) = os.path.split(a+".tex") if dirname == "": dirname = "." yield(dirname, filename) else: error(a, "file not found") def needsupdate(dirname, texname, data=None): jobname = texname[:-4] if data == None: data = analyseTex(dirname, texname) # skip non-main files if not data.required: if opts.verbosity > 2: print "%s: skipped because no main latex file"%os.path.normpath(os.path.join(dirname,texname)) return False # silent force if opts.force and not opts.verbosity: return True # check for nonexistant output file outname = jobname + ".pdf" if not os.path.isfile(os.path.join(dirname, outname)): return " (because .pdf does not exist)" # check for updated source and dependency files outdate = os.path.getmtime(os.path.join(dirname, outname)) if outdate < os.path.getmtime(os.path.join(dirname, texname)): return " (because .tex is newer than .pdf)" for filename, extlist in data.deps: try: deppath = texpath(dirname, filename, extlist=extlist) if outdate < os.path.getmtime(os.path.join(dirname, deppath)): return " (because %s is newer than .pdf)"%deppath except ValueError, e: if opts.verbosity > 2: error(os.path.join(dirname, texname), "ignored dependency: " + str(e), warning=True) # check for errors and requests logdata = analyseLog(dirname, jobname + ".log") if logdata.errors: return " (because of error(s) in .log file)" if logdata.requests: return " (because of request(s) in .log file)" # verbose force if opts.force: return " (because of --force)" # up to date if opts.verbosity > 2: print "%s: skipped because up-to-date"%os.path.normpath(os.path.join(dirname, texname)) return False MAXRUNS=5 def processTex(dirname, texname, data=None, reason=""): procfiles.append(os.path.normpath(os.path.join(dirname,texname))) if data == None: data = analyseTex(dirname, texname) if opts.verbosity: print "processing %s%s..."%(os.path.normpath(os.path.join(dirname,texname)),reason) # list of temporary files to be removed later tmplist = [] # support for pstricks/plain latex: if data.needsPS: tex = "latex" else: tex = "pdflatex" # detect usage of german/ngerman and generate temporary files with rmlig realname = texname if data.rmligs: realname = preparermligs(texname, dirname, tmplist) #strip .tex extension jobname=texname[:-4] # remove all outdated .bbl files (will be recreated by requests) bbls = bblfiles(dirname, jobname) for bib in data.bibfiles: bibpath = os.path.join(dirname, bib) bibtime = os.path.getmtime(bibpath) nextbbls = bbls[:] for bbl in bbls: bblpath = os.path.normpath(os.path.join(dirname, bbl)) bbltime = os.path.getmtime(bblpath) if (opts.force and not opts.singlerun) or bbltime < bibtime: reason = "" if opts.verbosity: if bbltime < bibtime: reason = " (because %s is newer than %s)"%(bib,bbl) else: reason = " (because of --force)" rmtempfile(bbl, dirname, force=True, reason=reason) nextbbls.remove(bbl) bbls = nextbbls runtex(tex, texname, realname, dirname) if not opts.singlerun: # check for undefined references and run requests numrun=0 reqs=opts.act # ignore requests if programs are not run while reqs: reqs = analyseLog(dirname, jobname+".log").requests if not reqs: break pri = reqs.values() pri.sort(reverse=True) lastp = pri[0] + 1 for p in pri: if p == lastp: continue lastp = p for req in reqs.keys(): rp = reqs[req] if rp == p: reason = "" if opts.verbosity: reason = " (because of request in .aux file, priority %d)"%rp if req == "latex": runtex(tex, texname, realname, dirname, reason=reason) elif isinstance(req, tuple): run(req, dirname, reason) else: error(os.path.join(dirname,jobname+".aux"), "unsupported request: %s"%req) numrun+=1 if numrun==MAXRUNS: error(os.path.join(dirname, texname), "does not stabilise after %i runs"%MAXRUNS) break # update index if it exists - no way of knowing if it was updated idx = jobname + ".idx" if os.path.isfile(os.path.join(dirname, idx)): run(["makeindex", jobname], dirname, reason=" (because .idx file might have changed)") runtex(tex, texname, realname, dirname, reason=" (because .ind file might have changed)") if data.needsPS: run(["dvips", jobname+".dvi"], dirname) run(["ps2pdf", jobname+".ps"], dirname) rmtempfile(tmplist, dirname) def bblfiles(dirname, jobname): files = dircache.listdir(dirname) return fnmatch.filter(files, "%s*.bbl"%jobname) def rmtempfile(filenames, dirname, force=False, reason=""): if (opts.deletetempfiles or force) and filenames: if not isinstance(filenames, list): filenames = [filenames] if opts.verbosity > 1: print " removing %s%s"%(", ".join(filenames), reason) if opts.act: for f in filenames: fullname = os.path.join(dirname, f) if os.path.isfile(fullname): os.remove(fullname) else: error(fullname, "could not be removed (does not exist)", warning=True) def run(arglist, dirname, reason="", inf=None, outf=None, stderr=None): """Run a command with optional io redirections. arglist -- list of progam name and arguments dirname -- the directory the command will be executed in reason -- reason for running this command, used in progress output inf -- file to be used as stdin, or alternativly a filter function for user input outf -- file to be used as stdout, or alternativly a filter function for program output stderr -- file to be used as stderr """ if opts.verbosity: redir ="" if hasattr(inf, "name") and inf.name != "" and inf.name[0] != "<": name = relpath(inf.name, dirname) redir = " < %s"%name print " running %s%s%s..."%(" ".join(arglist), redir, reason) if opts.act: master, slave = None, None if isinstance(inf, file): stdin = inf inf = None elif opts.showoutput and not iswindows: master, slave = pty.openpty() master = os.fdopen(master, "rw") stdin=slave elif inf or opts.showoutput: stdin=subprocess.PIPE else: stdin=null if isinstance(outf, file): stdout = outf outf = None elif opts.showoutput and not iswindows: if slave == None: master, slave = pty.openpty() master = os.fdopen(master, "rw") stdout=slave elif outf or opts.showoutput: stdout=subprocess.PIPE else: stdout=null try: proc = subprocess.Popen(arglist, stdin=stdin, stdout=stdout, stderr=stderr, cwd=dirname) if stdin == slave and stdin != None: childin = master else: childin = proc.stdin if stdout == slave and stdout != None: childout = master else: childout = proc.stdout selectlist = [] outlist = [] if childin != None: selectlist.append(sys.stdin) if childout != None: selectlist.append(childout) outlist.append(childout) while proc.poll() == None or select.select(outlist, [], [], 0)[0]: ready = select.select(selectlist, [], [], 0.1)[0] for f in ready: if f == childout: data = os.read(childout.fileno(), 1024) if data == "": # EOF outlist.remove(childout) selectlist.remove(childout) continue if outf: data = outf(data) if data != None: sys.stdout.write(data) sys.stdout.flush() if f == sys.stdin: if proc.poll() != None: # process terminated, does not take input anymore selectlist.remove(sys.stdin) continue data = os.read(sys.stdin.fileno(), 1024) if data == "": # EOF selectlist.remove(sys.stdin) continue if inf: data = inf(data) if data != None: childin.write(data) childin.flush() ret = proc.wait() if ret: error(dirname, "failed command: %s returned %i"%(" ".join(arglist), ret)) except (OSError, IOError), e: error(dirname, "failed command: %s: %s"%(" ".join(arglist),str(e))) def runtex(tex, texname, realname, dirname, reason=""): if not opts.showoutput: outf = None else: def outf(d): return re_rmligsfile.sub("\\1.tex", d) if texname != realname: run([tex, "-interaction", opts.interactionmode, "-jobname", texname[:-4], realname], dirname, outf=outf, reason=reason) else: run([tex, "-interaction", opts.interactionmode, texname], dirname, outf=outf, reason=reason) def preparermligs(texname, dirname, tmplist): try: texfile = open(os.path.join(dirname, texname)) except IOError, (errno, strerror): error(os.path.join(dirname, texname), "could not be read: %s. Not using rmligs."%strerror) return texname else: try: (subdir, texbasename) = os.path.split(texname) rmligsbasename = ".%s.rmligs.tex"%texbasename[:-4] rmligsname = os.path.join(subdir, rmligsbasename) if opts.act: rmligsfile = open(os.path.join(dirname, rmligsname), "w") else: rmligsfile = null except IOError, (errno, strerror): error(os.path.join(dirname, rmligsname), "could not be written: %s. Not using rmligs."%strerror) return texname else: tmplist.append(rmligsname) def rewriteinputrmligs(m): name=m.group(2) if not os.path.isfile(os.path.join(dirname,name)): name+=".tex" if os.path.isfile(os.path.join(dirname, name)): name = preparermligs(name, dirname, tmplist) else: error(os.path.join(dirname, texname), "%s: File could not be located. Not using rmligs."%name, warning=True) return "\\%s{%s}"%(m.group(1), name[:-4]) def filteroutput(d): rmligsfile.write(re_inputinclude.sub(rewriteinputrmligs, d)) return None run([rmligs_name, "-f"], dirname, inf=texfile, outf=filteroutput) texfile.close() if rmligsfile is not null: rmligsfile.close() return rmligsname # main program: def main(arguments): if not arguments: arguments = (".",) for dirname, texname in alltexfiles(arguments): data = analyseTex(dirname, texname) reason = needsupdate(dirname, texname, data) if reason: processTex(dirname, texname, data, reason) if opts.summary in [ "files", "both" ] : if procfiles: print "Processed the following files:" for f in procfiles: print " %s"%f else: print "Processed no files." if opts.summary in [ "failures", "both" ] : if errors: print "The following problems occured:" for f in errors: print " %s"%f if errors: sys.exit(1) try: if __name__ == "__main__": # setup global variables opts, programargs = TexallOptionParser().parse_args() else: opts, programargs = TexallOptionParser().parse_args([]) re_ignore = None if opts.ignorepattern != "": re_ignore = re.compile(opts.ignorepattern) re_required = re.compile(opts.requiredpattern) re_texfile = re.compile(".*\.tex$", re.I) re_inputinclude = re.compile('\\\\(input|include|@input)\\{([^}]*)\\}') re_usepackage = re.compile('\\\\usepackage(\\[.*?\\])?\\{([^}]*)\\}') re_documentclass = re.compile('\\\\documentclass(\\[.*?\\])?\\{([^}]*)\\}') re_graphics = re.compile('\\\\includegraphics(\\[.*?\\])?\\{([^}]*)\\}') if iswindows: re_systemfile = re.compile("^?:\\\\Program", re.I) else: re_systemfile = re.compile("^/usr/") re_bibliography = re.compile('\\\\bibliography\\{([^}]*)\\}') re_commawhitespace = re.compile('\\s*,\\s*') re_needsps = re.compile('\\\\usepackage(\\[.*?\\])?\\{[^}]*pstricks[^}]*\\}') re_rmligs = re.compile('\\\usepackage((\\[.*?\\])?\\{n?german\\}|\\[[^]]*?german[^]]*?\\]\\{babel\\})') re_logmatcher = re.compile("^LaTeX Warning: There were undefined references\.$|^LaTeX Warning: Label\(s\) may have changed\. Rerun to get cross-references right\.$|^REQ:(\d+):(\w+):REQ|^REQ:(\d+):(\w+):$") re_error = re.compile('^! ') re_rmligsfile = re.compile("\\.([^/ ]+)\\.rmligs\\.tex") if __name__ == "__main__": main(programargs) except KeyboardInterrupt: sys.exit(2) else: for proc, master in kpseproc.values(): os.close(master) os.kill(proc.pid, signal.SIGHUP)