aboutsummaryrefslogtreecommitdiff
path: root/texall
diff options
context:
space:
mode:
Diffstat (limited to 'texall')
-rwxr-xr-xtexall347
1 files changed, 347 insertions, 0 deletions
diff --git a/texall b/texall
new file mode 100755
index 0000000..4c5ff3c
--- /dev/null
+++ b/texall
@@ -0,0 +1,347 @@
+#!/usr/bin/env python
+
+from optparse import OptionParser
+import sys
+import os
+import os.path
+import re
+import subprocess
+
+parser = OptionParser(usage="%prog [-v] DIR/FILE [...]", version="%prog $Id: $")
+parser.add_option("-n", "--dry-run",
+ action="store_false", dest="act", default=True,
+ help="do not run any program")
+parser.add_option("-f", "--force",
+ action="store_true", dest="force", default=False,
+ help="regenerate up-to-date files")
+parser.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")
+parser.add_option("-v", "--verbose",
+ action="count", dest="verbosity", default=1,
+ help="give reasons for actions, may be repeated")
+parser.add_option("-q", "--quiet",
+ action="store_false", dest="progress", default=True,
+ help="suppress progress reports")
+parser.add_option("-i", "--ignore",
+ action="store", dest="ignorepattern", default=".*vorlage.*|\.\#.*",
+ help="regular expression of filenames to ignore")
+parser.add_option("-p", "--required-pattern",
+ action="store", dest="requiredpattern", default='^\\\\documentclass|%%% TeX-master: t',
+ help="regular expression that must match the file content")
+(options, args) = parser.parse_args()
+
+interactionmode = "batchmode"
+if options.verbosity>2:
+ interactionmode = "nonstopmode"
+
+re_ignore = None
+if options.ignorepattern != "":
+ re_ignore = re.compile(options.ignorepattern)
+
+re_required = re.compile(options.requiredpattern)
+
+re_texfile = re.compile(".*\.tex$", re.I)
+
+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 options.progress:
+ print msg
+ errors.append(msg)
+
+# iterate over all .tex files that are not ignored by -i or -p
+def alltexfiles(args):
+ for a in args:
+ if os.path.isfile(a):
+ if re_texfile.match(a):
+ yield os.path.split(a)
+ else:
+ error(a, "is no tex file; skipped")
+ elif os.path.isdir(a):
+ for dir, subdirs, files in os.walk(a):
+ for name in files:
+ if re_texfile.match(name):
+ if re_ignore.match(name):
+ if options.verbosity > 1:
+ print "%s: skipped because of ignored name"%os.path.normpath(os.path.join(dir,name))
+ continue
+ for m in texgrep(re_required, dir, name):
+ yield (dir, name)
+ break
+ else:
+ if options.verbosity > 1:
+ print "%s: skipped because no main latex file"%os.path.normpath(os.path.join(dir,name))
+
+def texgrep(matcher, dirname, filename, recurse=False):
+ try:
+ f = open(os.path.join(dirname,filename))
+ content = f.read()
+ for m in matcher.finditer(content):
+ yield m
+ if recurse:
+ for dep in dependencies(dirname, filename, texonly=True):
+ for m in texgrep(matcher, "", dep, recurse=False):
+ yield m
+ except IOError, (errno, strerror):
+ error(os.path.join(dirname, filename), "could not be read: %s"%strerror)
+ except StopIteration:
+ f.close()
+ else:
+ f.close()
+
+RECURSIONDEPTH=10
+re_inputinclude = re.compile('\\\\(input|include|@input)\\{([^}]*)\\}')
+re_usepackage = re.compile('\\\\usepackage(\\[.*?\\])?\\{([^}]*)\\}')
+re_graphics = re.compile('\\\\includegraphics(\\[.*?\\])?\\{([^}]*)\\}')
+graphics_ext = ["", ".pdf", ".eps", ".png", ".jpg"]
+re_bibliography = re.compile('\\\\bibliography\\{([^}]*)\\}')
+re_commawhitespace = re.compile('\\s*,\\s*')
+def dependencies(dirname, filename, texonly=False, recurse=RECURSIONDEPTH):
+ if not recurse:
+ return
+ try:
+ f = open(os.path.join(dirname,filename))
+ content = f.read()
+ for m in re_inputinclude.finditer(content):
+ texs = m.group(2)
+ for tex in re_commawhitespace.split(texs):
+ if not os.path.isfile(os.path.join(dirname,tex)):
+ tex+=".tex"
+ if os.path.isfile(os.path.join(dirname,tex)):
+ yield os.path.normpath(os.path.join(dirname,tex))
+ for m in dependencies(dirname, tex, texonly, recurse-1):
+ yield m
+ for m in re_usepackage.finditer(content):
+ stys = m.group(2)
+ for sty in re_commawhitespace.split(stys):
+ if not os.path.isfile(os.path.join(dirname,sty)):
+ sty+=".sty"
+ if os.path.isfile(os.path.join(dirname,sty)):
+ yield os.path.normpath(os.path.join(dirname,sty))
+ for m in dependencies(dirname, sty, texonly, recurse-1):
+ yield m
+ if not texonly:
+ for m in re_graphics.finditer(content):
+ names = m.group(2)
+ for name in re_commawhitespace.split(names):
+ for ext in graphics_ext:
+ if os.path.isfile(os.path.join(dirname,name+ext)):
+ yield os.path.normpath(os.path.join(dirname,name+ext))
+ for m in re_bibliography.finditer(content):
+ bibs = m.group(1)
+ for bib in re_commawhitespace.split(bibs):
+ if bib[-4:] != ".bib":
+ bib += ".bib"
+ if bib[-8:] != "-blx.bib":
+ yield os.path.normpath(os.path.join(dirname,os.path.expanduser(bib)))
+
+ except IOError, (errno, strerror):
+ error(os.path.join(dirname, filename), "could not be read: %s"%strerror)
+ except StopIteration:
+ f.close()
+ else:
+ f.close()
+
+def bibfiles(dirname, texname):
+ for m in texgrep(re_bibliography, dirname, texname, recurse=True):
+ bibs = m.group(1)
+ for bib in re_commawhitespace.split(bibs):
+ if bib[-4:] != ".bib":
+ bib += ".bib"
+ if bib[-8:] != "-blx.bib":
+ yield os.path.normpath(os.path.join(dirname,os.path.expanduser(bib)))
+
+def outdatedtexfiles(args):
+ for dirname, texname in alltexfiles(args):
+ #strip .tex extension
+ jobname=texname[:-4]
+ pdfname=jobname+".pdf"
+ reason = ""
+ # check if an update is needed:
+ if options.force and not options.verbosity:
+ yield (dirname, texname, reason)
+ elif not os.path.isfile(os.path.join(dirname,pdfname)):
+ if options.verbosity:
+ reason = " (because .pdf does not exist)"
+ yield (dirname, texname, reason)
+ else:
+ pdftime = os.path.getmtime(os.path.join(dirname, pdfname))
+ if pdftime < os.path.getmtime(os.path.join(dirname, texname)):
+ if options.verbosity:
+ reason = " (because .tex is newer than .pdf)"
+ yield (dirname, texname, reason)
+ else:
+ for f in dependencies(dirname, texname):
+ if pdftime < os.path.getmtime(f):
+ if options.verbosity:
+ reason = " (because %s is newer than .pdf)"%f
+ yield (dirname, texname, reason)
+ break
+ else:
+ r = haderrors(dirname, jobname)
+ if r:
+ if options.verbosity:
+ reason = r
+ yield (dirname, texname, reason)
+ elif options.force:
+ reason = " (because of --force)"
+ yield (dirname, texname, reason)
+ elif options.verbosity > 1:
+ print "%s: skipped because up-to-date"%os.path.normpath(os.path.join(dirname, texname))
+
+re_nopdftex = re.compile('\\\\usepackage(\\[.*?\\])?\\{[^}]*pstricks[^}]*\\}')
+def detecttextype(dirname, texname):
+ for m in texgrep(re_nopdftex, dirname, texname, recurse=True):
+ return "latex"
+ return "pdflatex"
+
+MAXRUNS=5
+def processtexfiles(args):
+ for dirname, texname, reason in outdatedtexfiles(args):
+ procfiles.append(os.path.normpath(os.path.join(dirname,texname)))
+ if options.progress:
+ print "processing %s%s..."%(os.path.normpath(os.path.join(dirname,texname)),reason)
+ # TODO: add support for pstricks/plain latex
+ tex = detecttextype(dirname, texname)
+ run([tex, "-interaction", interactionmode, texname], dirname)
+ #strip .tex extension
+ jobname=texname[:-4]
+
+ # run bibtex if any bibfile changed:
+ for bib in bibfiles(dirname, texname):
+ bbl = os.path.normpath(os.path.join(dirname, jobname+".bbl"))
+ if options.force or not os.path.isfile(bbl) or os.path.getmtime(bbl) < os.path.getmtime(bib):
+ reason = ""
+ if options.verbosity:
+ if not os.path.isfile(bbl):
+ reason = " (because .bbl does not exist yet)"
+ elif os.path.getmtime(bbl) < os.path.getmtime(bib):
+ reason = " (because %s is newer than .bbl)"%bib
+ else:
+ reason = " (because of --force)"
+ run(["bibtex8", "--wolfgang", jobname], dirname, reason=reason)
+ run([tex, "-interaction", interactionmode, texname], dirname, reason=" (because of updated .bbl)")
+ break
+
+ # check for undefined references and run requests
+ numrun=0
+ reqs=options.act
+ while reqs:
+ reqs = requests(os.path.join(dirname, jobname+".log"))
+ pri = reqs.values()
+ pri.sort(reverse=True)
+ for p in pri:
+ for req in reqs.keys():
+ rp = reqs[req]
+ if rp == p:
+ reason = ""
+ if options.verbosity:
+ reason = " (because of request in .aux file, priority %d)"%rp
+ if req == "latex":
+ run([tex, "-interaction", interactionmode, texname], dirname, reason=reason)
+ elif req == "bibtex":
+ run(["bibtex8", "--wolfgang", jobname], dirname, reason=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)")
+ run([tex, "-interaction", interactionmode, texname], dirname, reason=" (because .ind file might have changed)")
+
+ if tex == "latex":
+ run(["dvips", jobname+".dvi"], dirname)
+ run(["ps2pdf", jobname+".ps"], dirname)
+
+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+):")
+def requests(logpath):
+ reqs = {}
+ def addrequest(name, priority):
+ if reqs.has_key(name):
+ reqs[name] = max(reqs[name], priority)
+ else:
+ reqs[name] = priority
+ try:
+ log = open(logpath)
+ for line in log:
+ m = re_logmatcher.match(line)
+ if m:
+ if m.group() == 'LaTeX Warning: There were undefined references.':
+ addrequest("latex",0)
+ elif m.group() == 'LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right.':
+ addrequest("latex",0)
+ elif m.group(1)!=None:
+ addrequest(m.group(2), int(m.group(1)))
+ # TODO: add support for multiple bibliographies, see biblatex.pdf Sec. 2.4.4
+ except IOError, (errno, strerror):
+ error(logpath, "could not be read: %s"%strerror)
+ else:
+ log.close()
+ return reqs
+
+re_error = re.compile('^! ')
+def haderrors(dirname, jobname):
+ logpath = os.path.join(dirname, jobname+".log")
+ if not os.path.isfile(logpath):
+ return None
+ try:
+ f = open(logpath)
+ for line in f:
+ if re_error.match(line):
+ return " (because of error in .log file)"
+ if re_logmatcher.match(line):
+ return " (because of request in .log file)"
+ except IOError, (errno, strerror):
+ error(logpath, "could not be read: %s"%strerror)
+ else:
+ f.close()
+ return False
+
+def run(arglist, dirname, reason=""):
+ if options.verbosity>2:
+ # use default
+ output=None
+ else:
+ output=file("/dev/null")
+ if options.progress:
+ print " running %s%s..."%(" ".join(arglist),reason)
+ if options.act:
+ ret = subprocess.call(arglist, stdout=output, stderr=output, cwd=dirname)
+ if ret:
+ error(dirname, "failed command: %s"%(" ".join(arglist)))
+
+
+# main program:
+try:
+ if args:
+ processtexfiles(args)
+ else:
+ processtexfiles(["."])
+
+ if options.summary in [ "files", "both" ] :
+ if procfiles:
+ print "Processed the following files:"
+ for f in procfiles:
+ print " %s"%f
+ else:
+ print "Processed no files."
+ if options.summary in [ "failures", "both" ] :
+ if errors:
+ print "The following problems occured:"
+ for f in errors:
+ print " %s"%f
+
+ if errors:
+ sys.exit(1)
+
+except KeyboardInterrupt:
+ sys.exit(2)