Skip to content

Instantly share code, notes, and snippets.

@wesen
Created July 26, 2022 18:07
Show Gist options
  • Select an option

  • Save wesen/aab11dfcb16cc7380cdc3d89019a3b31 to your computer and use it in GitHub Desktop.

Select an option

Save wesen/aab11dfcb16cc7380cdc3d89019a3b31 to your computer and use it in GitHub Desktop.
#! /usr/bin/env python
import re
import tempfile
import os, getopt, sys, shutil
import traceback
from os.path import splitext, basename
class TxtFile:
def __init__(self, title, author = None, style = None, includeToc = None):
self.title = title
self.author = author
self.buffer = ""
def reset(self):
self.buffer = ""
def __add__(self, aStr):
self.buffer += aStr
return self
def tempFile(self):
return tempfile.NamedTemporaryFile('w+', -1, '.txt', 'pbook')
def writeFile(self, fileName = None):
if not fileName:
file = self.tempFile()
elif fileName == "-":
file = sys.stdout
else:
name,ext = splitext(fileName)
if ext == "":
ext = ".txt"
elif ext != ".txt":
raise "Can not write format " + ext
file = open(name + ext, "w+")
file.write(self.buffer)
file.flush()
return file
def addHeading(self, level, heading, fileName, line):
heading = re.sub("\s+", " ", heading)
self += "++ " + heading + "\n"
def addComment(self, comment, fileName, startLine, endLine):
self += comment
def addCode(self, code, fileName, startLine, endLine):
code = code.rstrip()
code = re.sub("\t", " ", code)
self += "\n== %s (%s:%s) ================\n" % (basename(fileName), startLine, endLine)
self += code
self += "\n=========================================================\n\n"
class TexFile(TxtFile):
def __init__(self, title, author = None, style = "article", includeToc = True):
TxtFile.__init__(self, title, author, style, includeToc)
self.style = style
self.includeToc = includeToc
self.bookSectioningCommands = ("chapter", "section", \
"subsection", "subsubsection")
self.articleSectioningCommands = ("section", "subsection", \
"subsubsection")
def beginning(self):
return '\n\\documentclass[notitlepage,a4paper]{' + self.style + '}\n' + \
'\\usepackage{fancyvrb,color,palatino}\n' + \
'\\definecolor{gray}{gray}{0.6}\n' + \
'\\title{' + TexFile.escapeString(self.title) + '}\n' + \
(self.author and ('\\author{' + self.author + '}\n') or '') + \
'\\begin{document}\n\\maketitle\n' + \
(self.includeToc and '\\tableofcontents\n' or '')
def ending(self):
return '\\end{document}\n'
def sectioningCommand(self, level):
if self.style == "article":
return self.articleSectioningCommands[min(level, len(self.articleSectioningCommands))]
elif self.style == "book":
return self.bookSectioningCommands[min(level, len(self.bookSectioningCommands))]
def escapeString(aStr):
aStr = re.sub("\\\\", "$\\\\backslash$", aStr)
def escapeRepl(match):
if match.group(1) != '$' or \
not (aStr[match.start():].startswith("$\\backslash$") or \
aStr[:match.start()].endswith("$\\backslash")):
return '\\' + match.group(1)
else:
return match.group(0)
return re.sub("([#%&~$_^{}])", escapeRepl, aStr)
escapeString = staticmethod(escapeString)
def tempFile(self):
return tempfile.NamedTemporaryFile('w+', -1, '.tex', 'pbook')
def writeFile(self, fileName = None):
if not fileName:
file = self.tempFile()
elif fileName == "-":
return self.writeTex(sys.stdout)
else:
name,ext = splitext(fileName)
if ext == "":
ext = ".pdf"
file = open(name + ext, "w+")
name,ext = splitext(file.name)
if ext == ".tex":
return self.writeTex(file)
elif ext == ".pdf":
return self.writePdf(file)
else:
raise "Can not write format " + ext
def writeTex(self, output):
output.write(self.beginning())
output.write(self.buffer)
output.write(self.ending())
output.flush()
return output
def writePdf(self, output):
tmpfile = self.tempFile()
self.writeTex(tmpfile)
(dir, name) = os.path.split(tmpfile.name)
print "cd " + dir + "; latex " + name + " && pdflatex " + name
os.system("cd " + dir + "; latex " + name + " && pdflatex " + name)
tmpfile.close()
pdfname = splitext(tmpfile.name)[0] + ".pdf"
shutil.copyfile(pdfname, output.name)
os.remove(pdfname)
return output
def addHeading(self, level, heading, fileName, line):
heading = re.sub("\s+", " ", heading)
self += "\\" + self.sectioningCommand(level) + "{" + \
TexFile.escapeString(heading) + "}\n"
def addComment(self, comment, fileName, startLine, endLine):
comment = TexFile.escapeString(comment)
comment = re.sub("`([^`']*)'", "{\\\\tt \\1}", comment)
self += re.sub("\"([^\"]*)\"", "``\\1''", comment)
def addCode(self, code, fileName, startLine, endLine):
code = code.rstrip()
code = re.sub("\\\\end{Verbatim}", "\\\\_end{Verbatim}", code)
code = re.sub("\t", " ", code)
self += "\n\\begin{Verbatim}[fontsize=\\small,frame=leftline,framerule=0.9mm," + \
"rulecolor=\\color{gray},framesep=5.1mm,xleftmargin=5mm,fontfamily=cmtt]\n"
self += code
self += "\n\\end{Verbatim}\n"
class IdqTexFile(TexFile):
def __init__(self, title, author = "id Quantique", style = "article", includeToc = True):
TexFile.__init__(self, title, author, style, includeToc)
class Pbook:
def __init__(self, files, outFile):
self.files = files
self.commentRe = None
self.headingRe = None
self.removeRe = None
self.outFile = outFile
self.lineCounter = 0
if not self.outFile.title: self.outFile.title = basename(file)
def formatBuffer(self):
self.outFile.reset()
self.lineCounter = 0
for file in self.files:
data = open(file, "r").read()
if self.removeRe:
data = self.removeRe.sub("", data)
# search the first heading
startMatch = self.headingRe.search(data)
if not startMatch:
raise "File must have at least one heading"
self.lineCounter += len(data[:startMatch.start()].split('\n'))
data = data[startMatch.start():]
self.fileName = file
lines = data.split('\n')
while len(lines) > 0:
line = lines[0]
if re.match("^\s*$", line):
lines.pop(0)
self.lineCounter += 1
continue
elif self.headingRe.match(line):
line = lines.pop(0)
self.doHeading(line)
self.lineCounter += 1
elif self.commentRe.match(line):
self.doComment(lines)
else:
self.doCode(lines)
def doHeading(self, line):
match = self.headingRe.match(line)
assert(match != None)
level = len(match.group(1)) - 1
headingName = line[match.end():]
self.outFile.addHeading(level, headingName, self.fileName, self.lineCounter)
def doComment(self, lines):
comment = ""
lineCount = 0
while len(lines) > 0:
line = lines[0]
match = self.commentRe.match(line)
if not match: break
line = lines.pop(0)
lineCount += 1
comment += line[:match.start()] + line[match.end():] + "\n"
self.outFile.addComment(comment, self.fileName, self.lineCounter, self.lineCounter + lineCount)
self.lineCounter += lineCount
def doCode(self, lines):
lineCount = 0
code = ""
while len(lines) > 0:
line = lines[0]
if (self.headingRe.match(line) or self.commentRe.match(line)):
break
line = lines.pop(0)
lineCount += 1
code += line + "\n"
self.outFile.addCode(code, self.fileName, self.lineCounter, self.lineCounter + lineCount)
self.lineCounter += lineCount
def makeFile(self, fileName):
self.outFile.reset()
self.formatBuffer()
return self.outFile.writeFile(fileName)
class LispPbook(Pbook):
def __init__(self, files, outFile):
Pbook.__init__(self, files, outFile)
self.commentRe = re.compile('^;;;($|[^#])', re.M)
self.headingRe = re.compile('^;;;(#+)', re.M)
class CPbook(Pbook):
def __init__(self, files, outFile):
Pbook.__init__(self, files, outFile)
self.commentRe = re.compile('^(\s|/)*\*\*($|[^#/])', re.M);
self.headingRe = re.compile("^\s*/\*\*(#+)", re.M)
self.removeRe = re.compile('\s*/?\*\*+/', re.M)
def usage():
print "Usage: ", sys.argv[0], " [-h] [-c TexFile|IdqTexFile|TxtFile] ", \
"[-T C|Lisp] [-t title] [-a author] [-O] [-o output] [-s style] file ..."
def extToType(ext):
fileExtToType = ( ((".c", ".cpp", ".C", ".h"), CPbook),
((".lisp", ".el", ".l", ".cl"), LispPbook) )
for types, typeClass in fileExtToType:
if (ext in types):
return typeClass
return None
def main():
texClass = TexFile
type = None
output = None
(author, title, toc, style) = (None, None, True, "article")
try:
opts, args = getopt.getopt(sys.argv[1:], "hc:T:t:a:Oo:s:")
if not args:
raise getopt.error, "At least one file argument required"
for optname, optvalue in opts:
if optname == "-h":
usage()
return
elif optname == "-c":
if optvalue == "TexFile":
texClass = TexFile
elif optvalue == "IdqTexFile":
texClass = IdqTexFile
elif optvalue == "TxtFile":
texClass = TxtFile
else:
raise getopt.error, "Unknown TexFile class ", optvalue
elif optname == "-t":
title = optvalue
elif optname == "-a":
author = optvalue
elif optname == "-O":
toc = False
elif optname == "-s":
style = optvalue
elif optname == "-T":
if optvalue == "C":
type = CPbook
elif optvalue == "Lisp":
type = LispPbook
else:
raise getopt.error, "Unknown pbook file type ", optvalue
elif optname == "-o":
output = optvalue
except getopt.error, msg:
print msg
usage()
return 1
file = args[0]
name,ext = splitext(file)
if not title:
title = basename(name)
if not type:
type = extToType(ext)
if not type:
print "Could not get type for ", ext
return 2
pbook = type(args, texClass(title, author, style, toc))
if not output:
output = basename(name)
try:
file = pbook.makeFile(output)
if output != "-":
print "Wrote output to ", file.name
except IOError, (errno, strerror):
print "Caught an error while generating \"%s\": %s" % (output, strerror)
except:
print "Caught an error while generating \"%s\"" % (output)
traceback.print_exc(file=sys.stdout)
return 1
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment