Skip to content

Instantly share code, notes, and snippets.

@shyuep
Last active June 11, 2017 05:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shyuep/6c4665dd77a5eb6b8bb8 to your computer and use it in GitHub Desktop.
Save shyuep/6c4665dd77a5eb6b8bb8 to your computer and use it in GitHub Desktop.
pyltx is a script with some useful tools to working with LaTeX. Download or clone it, give it executable permissions, and then run "pyltx -h" to see what it can do.
#!/usr/bin/env python
"""
pyltx is a script with some useful tools to working with LaTeX.
Run "pyltx -h" to see what it can do.
Some basic functionality include:
1. Compiling latex with multiple repetitions to ensure correct references,
with options to clean previous output files and build a file with an
integrated bibliography.
2. Consolidate all active files in a separate directory. Useful for generating
a clean working directory for final upload for publication.
Note that this script works with relative links in the tex file. Absolute
links are not yet supported.
"""
import argparse
import subprocess
import os
import sys
import re
import os
import shutil
import glob
import logging
log = logging.getLogger(__name__)
def create_tex_with_bbl(name):
log.info("Building full bbl file...")
with open("%s.bbl" % name) as f:
bbl = f.read()
with open("%s.tex" % name) as f:
lines = []
for l in f:
if l.startswith("\\bibliography"):
lines.append(bbl.strip())
else:
lines.append(l.strip())
return "\n".join(lines)
def compile(filename, clean):
name = filename.rsplit(".")[0]
if not os.path.exists("%s.tex" % name):
log.info("%s.tex does not exist!" % name)
sys.exit(-1)
if clean:
to_rm = [name + "." + ext for ext in ["aux", "blg", "log", "bbl"]]
log.info("Removing %s." % ", ".join(to_rm))
try:
subprocess.check_output(["rm"] + to_rm)
except subprocess.CalledProcessError:
log.info("No files to delete! Continuing...")
tex = "%s.tex" % name
with open(tex) as f:
contents = f.read()
for m in re.finditer(r"\\externaldocument(\[[^\]]+\])*\{([^\}]+)\}",
contents, re.MULTILINE):
fname = m.group(2)
log.info("Processing external document %s..." % fname)
cwd = os.getcwd()
os.chdir(os.path.dirname(fname))
compile(os.path.basename(fname), clean)
os.chdir(cwd)
count = 0
output = "Rerun"
while ("Rerun" in output or "natbib Warning" in output) and count < 5:
# This repetitive compilation process is necessary to ensure that
# bibliography and references are properly updated.
count += 1
log.info("Compiling attempt %d" % count)
for cmd in (["pdflatex", "-halt-on-error"], ["bibtex"]):
to_run = cmd + [name]
log.info("Running \"%s\"..." % " ".join(to_run))
try:
o = subprocess.check_output(to_run)
o = o.decode("utf-8")
if cmd[0] == "pdflatex":
output = o
except subprocess.CalledProcessError as ex:
log.error("Error has occurred... Output is:")
log.error(ex.output)
if cmd[0] == "bibtex":
log.warning("Ignoring...")
else:
log.critical("Terminating...")
sys.exit(-1)
for l in o.split("\n"):
if "Rerun" in l or "natbib Warning" in l:
print(l)
log.debug(o)
def finalize_file(texfile, destdir):
name = texfile.rsplit(".")[0]
if not os.path.exists("%s.tex" % name):
log.info("%s.tex does not exist!" % name)
sys.exit(-1)
try:
os.mkdir(destdir)
except Exception as ex:
print(ex)
tex = "%s.tex" % name
srcdir = os.path.dirname(os.path.abspath(tex))
with open(tex) as f:
contents = f.read()
def process_pattern(p):
for m in re.finditer(p, contents, re.MULTILINE):
fname = m.group(2)
fullpath = None
for ext in ["", ".pdf", ".eps"]:
if os.path.exists(os.path.join(srcdir, fname + ext)):
fullpath = os.path.join(srcdir, fname + ext)
dest = os.path.join(destdir, fname + ext)
break
if fullpath is None:
fnames = glob.glob(os.path.join(srcdir, fname + "*"))
if not fnames:
log.info("%s not found!" % str(fnames))
sys.exit(-1)
fullpath = fnames[0]
dest = os.path.join(destdir, os.path.basename(fullpath))
try:
os.makedirs(os.path.dirname(dest))
except Exception as ex:
print(ex)
log.info("Copying %s..." % fname)
shutil.copy(fullpath, dest)
process_pattern(r"\\includegraphics(\[[^\]]+\])*\{([^\}]+)\}")
finaltex = create_tex_with_bbl(name)
with open(os.path.join(destdir, os.path.basename("%s.tex" % name)),
"wt") as f:
f.write(finaltex)
log.info("%s.tex written!" % name)
def run_compile(args):
compile(args.filename, args.clean)
if args.ref_file:
log.info("Doing diff...")
reff = os.path.abspath(args.ref_file)
reff = reff.rsplit(".")[0] + ".tex"
cmpf = os.path.abspath(args.filename).rsplit(".")[0] + ".tex"
diff = cmpf.rsplit(".")[0] + "_diff.tex"
with open(diff, "wt") as f:
o = subprocess.check_output(["latexdiff", reff, cmpf])
o = o.decode("utf-8")
f.write(o)
compile(diff, False)
log.info("Diff file compiled at %s!" % diff)
def run_finalize(args):
name = args.filename.rsplit(".")[0]
if not os.path.exists("%s.tex" % name):
log.info("%s.tex does not exist!" % name)
sys.exit(-1)
finalize_file(args.filename, args.output_dir)
tex = "%s.tex" % name
with open(tex) as f:
contents = f.read()
for m in re.finditer(r"\\externaldocument(\[[^\]]+\])*\{([^\}]+)\}",
contents, re.MULTILINE):
fname = m.group(2)
destdir = os.path.dirname(os.path.join(args.output_dir, fname))
log.info("Processing %s to %s..." % (fname, destdir))
finalize_file(fname, destdir)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="""
pyltx is a convenient script that provides useful tools for LaTeX. "
"Type \"pyltx -h\" for help.""",
epilog="Author: Shyue Ping Ong")
parser.add_argument(
"-v", "--verbose", dest="verbose",
action="store_true",
help="Verbose mode. Output from all commands are printed.")
subparsers = parser.add_subparsers()
parser_compile = subparsers.add_parser("compile", help="Compile a file")
parser_compile.add_argument("filename", metavar="filename", default="main",
type=str, nargs="?",
help="Filename to compile.")
parser_compile.add_argument(
"-c", "--clean", dest="clean",
action="store_true",
help="Clean the output files before compilation. Latex usually has "
"the usual aux, blg, bbl files. This will delete all those files "
"to force a clean compile. A clean compile will also attempt to "
"rebuild all external documents.")
parser_compile.add_argument(
"-d", "--diff", dest="ref_file", type=str, nargs="?",
help="Create a diff between the reference file and the compiled file.")
parser_compile.set_defaults(func=run_compile)
parser_finalize = subparsers.add_parser(
"finalize",
help="Consolidate all actually used files in a new directory. Useful "
"for preparing final submission. Files which do not appear in "
"the actual .tex file will not be included in the new dir. Also, "
"all bibtex is consolidated into the final tex file itself. "
"Some journals require this. ")
parser_finalize.add_argument(
"filename", metavar="filename", default="main",
type=str, nargs="?", help="Filename to compile.")
parser_finalize.add_argument("-o", "--output_dir", dest="output_dir",
type=str, default="final",
help="Directory to output files to.")
parser_finalize.set_defaults(func=run_finalize)
args = parser.parse_args()
if args.verbose:
logging.basicConfig(level=logging.DEBUG, format="%(message)s")
else:
logging.basicConfig(level=logging.INFO, format="%(message)s")
try:
a = getattr(args, "func")
except AttributeError:
parser.log.info_help()
sys.exit(0)
args.func(args)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment