Created
June 24, 2016 00:30
-
-
Save crmackay/0b513022708c3dbad8b699105b9db8e1 to your computer and use it in GitHub Desktop.
latex build script (work in progress)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
markdown --> pandoc --> latex --> pdf builder script | |
features: | |
- checkes for changes in the current working directory | |
- stores change data between runs in this file itself (at the bottom) | |
- will only rebuild and rerun those parts that need to be rebuilt | |
- will run `pdflatex` only as many times as necessary | |
- for every `.md` file in the cwd it will run pandoc and produce a so-named `.tex` latex file | |
- if any `.tex` file changes then pdflatex is run | |
- pdflatex will then write to .aux, .log, and .out files, and changes to these files | |
trigger pdflatex to run again | |
- pdflatex is run until no more changes to these files are seen (ie it's done) | |
todo: | |
- add bibtex support | |
""" | |
import os, hashlib, subprocess, sys | |
def storedHashes(): | |
""" returns a dictionary of stored paths and stored hashes | |
""" | |
with open("build.py", "r") as dataFile: | |
data = {} | |
withinData = False | |
for line in dataFile: | |
if line.startswith("# END DATA"): | |
withinData = False | |
if withinData == True: | |
filename, hash = line.lstrip("# ").rstrip("\n").split("\t") | |
data[filename] = hash | |
if line.startswith("# BEGIN DATA"): | |
withinData = True | |
#data will be empty if no data or data fields are present | |
return data | |
def saveHashes(data): | |
""" saves the current hashes to the `build.py` file found in the cwd | |
""" | |
previouslySaved = False | |
file = "" | |
toWrite = "" | |
with open("build.py","rw") as dataFile: | |
file = dataFile.read() | |
fileLines = file.split("\n") | |
for i, line in enumerate(fileLines): | |
if line.startswith("# BEGIN DATA"): | |
previouslySaved = True | |
if previouslySaved == True: | |
toWrite = replaceData(fileLines, data) | |
else: | |
toWrite = saveFresh(fileLines, data) | |
with open("build.py","w") as dataFile: | |
dataFile.write("\n".join(toWrite)) | |
return | |
def replaceData(listOfLines, data): | |
""" used to find and replace hash data stored in `build.py` with new data | |
""" | |
start = 0 | |
end = 0 | |
for i, line in enumerate(listOfLines): | |
if line.startswith("# BEGIN DATA"): | |
start = i | |
elif line.startswith("# END DATA"): | |
end = i | |
forInsertion = [] | |
for k,v in data.iteritems(): | |
forInsertion.append("# "+"\t".join([k,v])) | |
return listOfLines[0:start+1]+forInsertion+listOfLines[end:] | |
def saveFresh(listOfLines, data): | |
""" used if no previous data is present in the `build.py` file, to store said data | |
""" | |
forInsertion = [] | |
forInsertion.append("") | |
forInsertion.append("# BEGIN DATA") | |
for k,v in data.iteritems(): | |
forInsertion.append("# "+"\t".join([k,v])) | |
forInsertion.append("# END DATA") | |
return listOfLines + forInsertion | |
def findChanges(old, new): | |
""" given two dictionaries of paths and hashes, this function crossreferences each one | |
with the other looking for differences. Files found in old, but not in new are ignored. | |
Files found in new, but not in new are deemed "changed". All other files are examined by | |
old and new hash to determine is any changes have occured | |
""" | |
changedFiles = [] | |
for file, old_hash in old.iteritems(): | |
if file in new: | |
new_hash = new[file] | |
if old_hash != new_hash: | |
changedFiles.append(file) | |
for file, new_hash in new.iteritems(): | |
if file not in old: | |
changedFiles.append(file) | |
print changedFiles | |
return changedFiles | |
def changeHandler(extension): | |
""" returns the handler function for a given file extenion | |
""" | |
if extension == ".md": | |
return handleMarkdown | |
elif extension == ".tex" or extension ==".log" or extension == ".aux" or extension == ".out": | |
return handleLatex | |
else: | |
return handleDefault | |
def handleMarkdown(file, verbose): | |
""" handles markdown files, converting them to latex | |
""" | |
mdown = '' | |
with open(file, "r") as inFile: | |
mdown = inFile.read() | |
inFile = '' | |
inStart = 0 | |
inEnd = 0 | |
if "\input{" in mdown: | |
# print "found input" | |
inStart = mdown.find("\input{") | |
done = False | |
inFile = '' | |
i = 0 | |
while done == False: | |
# print "parsing name" | |
next = mdown[inStart+7+i] | |
if next == "}": | |
done = True | |
inEnd = inStart+7+i | |
else: | |
i+=1 | |
inFile += next | |
# print("filename: ", inFile) | |
if inFile.endswith(".md"): | |
# print "detected as markdown" | |
forInput = '' | |
with open(inFile, "r") as inFile: | |
forInput = inFile.read() | |
mdown = mdown[:inStart] + forInput + mdown[inEnd:] | |
command = ["pandoc", " -o" + file.rstrip(".md") + ".tex", " -f markdown+yaml_metadata_block "] | |
# command = ["pandoc", " -s -o main.tex", " -f markdown+yaml_metadata_block "] | |
if file == "main.md": | |
command = command[0:1]+["-s"]+command[1:] | |
p = subprocess.Popen(" ".join(command), shell=True, stdin = subprocess.PIPE, stdout=subprocess.PIPE) | |
stdout_data = p.communicate(input=mdown)[0] | |
print "handling markdown" | |
if verbose: | |
print stdout_data | |
def handleLatex(file, verbose): | |
output = subprocess.Popen("pdflatex -interaction=nonstopmode main.tex", shell=True, stdout=subprocess.PIPE).stdout.read() | |
print "handling latex" | |
if verbose: | |
print output | |
def handleDefault(file, verbose): | |
print "handling default" | |
def getChanges(dir): | |
old = storedHashes() | |
new = currentHashes(dir) | |
changes = findChanges(old, new) | |
saveHashes(new) | |
return changes | |
def currentHashes(dir): | |
filesToWatch = [] | |
for path in os.listdir(dir): | |
if path != "build.py" and os.path.isfile(path): | |
filesToWatch.append(path) | |
results = {} | |
for file in filesToWatch: | |
results[file] = hashlib.sha256(open(file, 'rb').read()).hexdigest() | |
return results | |
if __name__ == "__main__": | |
verbose = False | |
if len(sys.argv) > 1: | |
if sys.argv[1] == "-v": | |
verbose = True | |
else: | |
print ("to print verbose error messages, use '-v'") | |
cwd = os.getcwd() | |
# TODO: handle each file seperatley and check for changes in the handler | |
# - this allows for markdown handlers to check for \input{file.md} changes | |
changes = getChanges(cwd) | |
while len(changes) > 0: | |
print changes | |
for file in changes: | |
if file == "build.py": | |
continue | |
_, extension=os.path.splitext(file) | |
print extension | |
changeHandler(extension)(file, verbose) | |
changes = getChanges(cwd) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment