Created
July 26, 2022 18:07
-
-
Save wesen/aab11dfcb16cc7380cdc3d89019a3b31 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| #! /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