From 33b6bc5f024a0533ceaadd667400e811d5783053 Mon Sep 17 00:00:00 2001 From: Sebastian Kuhnert Date: Thu, 14 Aug 2008 14:38:33 +0000 Subject: add utility script to compile all .tex files in a given directory --- texall | 347 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100755 texall (limited to 'texall') 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) -- cgit v1.2.3