Commit | Line | Data |
---|---|---|
c8f25870 LC |
1 | #! /usr/bin/env python |
2 | ||
3 | import re | |
4 | import tempfile | |
5 | import os, getopt, sys, shutil, random | |
6 | from os.path import splitext, basename | |
7 | ||
8 | class TxtFile: | |
9 | def __init__(self, title, author = None, style = None, includeToc = None): | |
10 | self.title = title | |
11 | self.author = author | |
12 | self.buffer = "" | |
13 | ||
14 | def reset(self): | |
15 | self.buffer = "" | |
16 | ||
17 | def __add__(self, aStr): | |
18 | self.buffer += aStr | |
19 | return self | |
20 | ||
21 | def tempFile(self): | |
22 | return tempfile.NamedTemporaryFile('w+', -1, '.txt', 'pbook') | |
23 | ||
24 | def writeFile(self, fileName = None): | |
25 | if not fileName: | |
26 | file = self.tempFile() | |
27 | elif fileName == "-": | |
28 | file = sys.stdout | |
29 | else: | |
30 | name,ext = splitext(fileName) | |
31 | if ext == "": | |
32 | ext = ".txt" | |
33 | elif ext != ".txt": | |
34 | raise "Can not write format " + ext | |
35 | file = open(name + ext, "w+") | |
36 | file.write(self.buffer) | |
37 | file.flush() | |
38 | return file | |
39 | ||
40 | def addEscape(self, escape, fileName, line): | |
41 | return | |
42 | def addHeading(self, level, heading, fileName, line): | |
43 | heading = re.sub("\s+", " ", heading) | |
44 | self += "++ " + heading + "\n" | |
45 | def addComment(self, comment, fileName, startLine, endLine): | |
46 | self += comment | |
47 | def addFigure(self, figureFile, figureScale, figureName, fileName, endLine): | |
48 | self += " " + figureName + " (" + figureFile + ")" + "\n" | |
49 | def addCode(self, code, fileName, startLine, endLine): | |
50 | code = code.rstrip() | |
51 | code = re.sub("\t", " ", code) | |
52 | self += "\n== %s (%s:%s) ================\n" % (basename(fileName), startLine, endLine) | |
53 | self += code | |
54 | self += "\n=========================================================\n\n" | |
55 | ||
56 | class TexFile(TxtFile): | |
57 | def __init__(self, title, author = None, style = "article", includeToc = True): | |
58 | TxtFile.__init__(self, title, author, style, includeToc) | |
59 | self.style = style | |
60 | self.includeToc = includeToc | |
61 | self.bookSectioningCommands = ("chapter", "section", \ | |
62 | "subsection", "subsubsection") | |
63 | self.articleSectioningCommands = ("section", "subsection", \ | |
64 | "subsubsection") | |
65 | ||
66 | def beginning(self): | |
67 | return '\n\\documentclass[notitlepage,a4paper,makeidx]{' + self.style + '}\n' + \ | |
68 | '\\usepackage{fancyvrb,color,palatino,makeidx}\n' + \ | |
9d5ab933 | 69 | "\\usepackage{graphicx}\n" + \ |
c8f25870 LC |
70 | '\\definecolor{gray}{gray}{0.6}\n' + \ |
71 | '\\title{' + TexFile.escapeString(self.title) + '}\n' + \ | |
72 | (self.author and ('\\author{' + self.author + '}\n') or '') + \ | |
73 | '\\makeindex' + \ | |
74 | '\\begin{document}\n\\maketitle\n' + \ | |
75 | (self.includeToc and '\\tableofcontents\n' or '') | |
76 | def ending(self): | |
77 | return '\\printindex\n\\end{document}\n' | |
78 | def sectioningCommand(self, level): | |
79 | if self.style == "article": | |
80 | return self.articleSectioningCommands[min(level, len(self.articleSectioningCommands))] | |
81 | elif self.style == "book": | |
82 | return self.bookSectioningCommands[min(level, len(self.bookSectioningCommands))] | |
83 | ||
84 | def escapeString(aStr): | |
85 | aStr = re.sub("\\\\", "$\\\\backslash$", aStr) | |
86 | def escapeRepl(match): | |
87 | if match.group(1) != '$' or \ | |
88 | not (aStr[match.start():].startswith("$\\backslash$") or \ | |
89 | aStr[:match.start()].endswith("$\\backslash")): | |
90 | return '\\' + match.group(1) | |
91 | else: | |
92 | return match.group(0) | |
93 | return re.sub("([#%&~$_^{}])", escapeRepl, aStr) | |
94 | escapeString = staticmethod(escapeString) | |
95 | ||
96 | def tempFile(self): | |
97 | return tempfile.NamedTemporaryFile('w+', -1, '.tex', 'pbook') | |
98 | def writeFile(self, fileName = None): | |
99 | if not fileName: | |
100 | file = self.tempFile() | |
101 | elif fileName == "-": | |
102 | return self.writeTex(sys.stdout) | |
103 | else: | |
104 | name,ext = splitext(fileName) | |
105 | if ext == "": | |
106 | ext = ".pdf" | |
107 | file = open(name + ext, "w+") | |
108 | name,ext = splitext(file.name) | |
109 | if ext == ".tex": | |
110 | return self.writeTex(file) | |
111 | elif ext == ".pdf": | |
112 | return self.writePdf(file) | |
113 | else: | |
114 | raise "Can not write format " + ext | |
115 | ||
116 | def writeTex(self, output): | |
117 | output.write(self.beginning()) | |
118 | output.write(self.buffer) | |
119 | output.write(self.ending()) | |
120 | output.flush() | |
121 | return output | |
122 | ||
123 | def writePdf(self, output): | |
124 | tmpfile = self.tempFile() | |
125 | self.writeTex(tmpfile) | |
126 | (dir, name) = os.path.split(tmpfile.name) | |
127 | print "cd " + dir + "; latex " + name + " && pdflatex " + name | |
128 | os.system("cd " + dir + "; latex " + name + " && pdflatex " + name) | |
129 | tmpfile.close() | |
130 | pdfname = splitext(tmpfile.name)[0] + ".pdf" | |
131 | shutil.copyfile(pdfname, output.name) | |
132 | os.remove(pdfname) | |
133 | return output | |
134 | ||
135 | def addEscape(self, escape, fileName, line): | |
136 | self += escape + "\n" | |
137 | def addFigure(self, figureFile, figureScale, figureName, fileName, endLine): | |
138 | self += "\\begin{figure}[htbp]\n \\centering\n" | |
139 | self += " \\fbox{\\includegraphics[scale=" + figureScale + "]{" + figureFile + "}}\n" | |
140 | self += " \\caption{" + figureName + "}\n" | |
141 | self += "\\end{figure}\n" | |
142 | def addHeading(self, level, heading, fileName, line): | |
143 | heading = re.sub("\s+", " ", heading) | |
144 | self += "\n\n\\" + self.sectioningCommand(level) + "{" + \ | |
145 | TexFile.escapeString(heading) + "}\n" | |
146 | def addComment(self, comment, fileName, startLine, endLine): | |
147 | comment = TexFile.escapeString(comment) | |
148 | comment = re.sub("`([^`']*)'", "{\\\\tt \\1}", comment) | |
149 | self += re.sub("\"([^\"]*)\"", "``\\1''", comment) | |
150 | def addCode(self, code, fileName, startLine, endLine): | |
151 | code = code.rstrip() | |
152 | code = re.sub("\\\\end{Verbatim}", "\\\\_end{Verbatim}", code) | |
153 | code = re.sub("\t", " ", code) | |
154 | self += "\n\\begin{Verbatim}[fontsize=\\small,frame=leftline,framerule=0.9mm," + \ | |
155 | "rulecolor=\\color{gray},framesep=5.1mm,xleftmargin=5mm,fontfamily=cmtt]\n" | |
156 | self += code | |
157 | self += "\n\\end{Verbatim}\n" | |
158 | ||
159 | class IdqTexFile(TexFile): | |
160 | def __init__(self, title, author = "id Quantique", style = "article", includeToc = True): | |
161 | TexFile.__init__(self, title, author, style, includeToc) | |
162 | ||
163 | class BknrTexFile(TexFile): | |
164 | def __init__(self, title, author, style, includeToc): | |
165 | TexFile.__init__(self, title, author, style, includeToc) | |
166 | self.firstSection = True | |
167 | def beginning(self): | |
168 | return '\\chapter{' + TexFile.escapeString(self.title) + '}\n' | |
169 | def ending(self): | |
170 | return '' | |
171 | def addComment(self, comment, fileName, startLine, endLine): | |
172 | comment = TexFile.escapeString(comment) | |
173 | self += re.sub("\"([^\"]*)\"", "``\\1''", comment) | |
174 | def sectioningCommand(self, level): | |
175 | string = "" | |
176 | if level == 0: | |
177 | if self.firstSection == False: | |
178 | string = "" | |
179 | # string = "vbox{\n\\vspace{1cm}\n\\centering\n" + \ | |
180 | # "\\includegraphics[scale=0.6]{peecol" + \ | |
181 | # str(random.randint(1, 10) ) + "}}\n\n\\" | |
182 | else: | |
183 | self.firstSection = False | |
184 | if self.style == "article": | |
185 | return string + self.articleSectioningCommands[min(level, len(self.articleSectioningCommands))] | |
186 | elif self.style == "book": | |
187 | return string + self.bookSectioningCommands[min(level, len(self.bookSectioningCommands))] | |
188 | ||
189 | class Pbook: | |
190 | def __init__(self, files, outFile): | |
191 | self.files = files | |
192 | self.commentRe = None | |
193 | self.headingRe = None | |
194 | self.removeRe = None | |
195 | self.outFile = outFile | |
196 | self.lineCounter = 0 | |
197 | if not self.outFile.title: self.outFile.title = basename(file) | |
198 | ||
199 | def formatBuffer(self): | |
200 | self.outFile.reset() | |
201 | self.lineCounter = 0 | |
202 | for file in self.files: | |
203 | data = open(file, "r").read() | |
204 | if self.removeRe: | |
205 | data = self.removeRe.sub("", data) | |
206 | # search the first heading | |
207 | startMatch = self.headingRe.search(data) | |
208 | if not startMatch: | |
209 | raise "File must have at least one heading" | |
210 | self.lineCounter += len(data[:startMatch.start()].split('\n')) | |
211 | data = data[startMatch.start():] | |
212 | self.fileName = file | |
213 | ||
214 | lines = data.split('\n') | |
215 | while len(lines) > 0: | |
216 | line = lines[0] | |
217 | if re.match("^\s*$", line): | |
218 | lines.pop(0) | |
219 | self.lineCounter += 1 | |
220 | continue | |
221 | elif self.figureRe.match(line): | |
222 | line = lines.pop(0) | |
223 | self.doFigure(line) | |
224 | self.lineCounter += 1 | |
225 | elif self.escapeRe.match(line): | |
226 | line = lines.pop(0) | |
227 | self.doEscape(line) | |
228 | self.lineCounter += 1 | |
229 | elif self.headingRe.match(line): | |
230 | line = lines.pop(0) | |
231 | self.doHeading(line) | |
232 | self.lineCounter += 1 | |
233 | elif self.commentRe.match(line): | |
234 | self.doComment(lines) | |
235 | else: | |
236 | self.doCode(lines) | |
237 | ||
238 | def doHeading(self, line): | |
239 | match = self.headingRe.match(line) | |
240 | assert(match != None) | |
241 | level = len(match.group(1)) - 1 | |
242 | headingName = line[match.end():] | |
243 | self.outFile.addHeading(level, headingName, self.fileName, self.lineCounter) | |
244 | ||
245 | def doFigure(self, line): | |
246 | match = self.figureRe.match(line) | |
247 | assert(match != None) | |
248 | figureFile = match.group(1) | |
249 | figureName = match.group(3) | |
250 | figureScale = match.group(2) | |
251 | self.outFile.addFigure(figureFile, figureScale, figureName, self.fileName, self.lineCounter) | |
252 | ||
253 | def doEscape(self, line): | |
254 | match = self.escapeRe.match(line) | |
255 | assert(match != None) | |
256 | escape = match.group(1) | |
257 | self.outFile.addEscape(escape, self.fileName, self.lineCounter) | |
258 | ||
259 | def doComment(self, lines): | |
260 | comment = "" | |
261 | lineCount = 0 | |
262 | while len(lines) > 0: | |
263 | line = lines[0] | |
264 | match = self.commentRe.match(line) | |
265 | if not match: break | |
266 | line = lines.pop(0) | |
267 | lineCount += 1 | |
268 | comment += line[:match.start()] + line[match.end():] + "\n" | |
269 | self.outFile.addComment(comment, self.fileName, self.lineCounter, self.lineCounter + lineCount) | |
270 | self.lineCounter += lineCount | |
271 | ||
272 | def doCode(self, lines): | |
273 | lineCount = 0 | |
274 | code = "" | |
275 | while len(lines) > 0: | |
276 | line = lines[0] | |
277 | if (self.headingRe.match(line) or self.escapeRe.match(line) \ | |
278 | or self.figureRe.match(line) \ | |
279 | or self.commentRe.match(line)): | |
280 | break | |
281 | line = lines.pop(0) | |
282 | lineCount += 1 | |
283 | code += line + "\n" | |
284 | self.outFile.addCode(code, self.fileName, self.lineCounter, self.lineCounter + lineCount) | |
285 | self.lineCounter += lineCount | |
286 | ||
287 | def makeFile(self, fileName): | |
288 | self.outFile.reset() | |
289 | self.formatBuffer() | |
290 | return self.outFile.writeFile(fileName) | |
291 | ||
292 | class LispPbook(Pbook): | |
293 | def __init__(self, files, outFile): | |
294 | Pbook.__init__(self, files, outFile) | |
295 | self.commentRe = re.compile('^;;;($|[^#f])', re.M) | |
296 | self.headingRe = re.compile('^;;;(#+)', re.M) | |
297 | self.figureRe = re.compile('^;;;f\s+\"(.+)\"\s+([^\s]+)\s+(.*)', re.M) | |
298 | self.escapeRe = re.compile('^;;;t\s+(.+)', re.M) | |
299 | ||
300 | class CPbook(Pbook): | |
301 | def __init__(self, files, outFile): | |
302 | Pbook.__init__(self, files, outFile) | |
303 | self.commentRe = re.compile('^(\s|/)*\*\*($|[^f#/])', re.M); | |
304 | self.headingRe = re.compile("^/\*\*(#+)", re.M) | |
305 | self.removeRe = re.compile('\*\*+/', re.M) | |
306 | self.figureRe = re.compile('^/\*\*f \"(.+)\"\s+([^\s]+)\s(.*)', re.M) | |
307 | ||
308 | ||
309 | def usage(): | |
310 | print "Usage: ", sys.argv[0], " [-h] [-c TexFile|BknrTexFile|IdqTexFile|TxtFile] ", \ | |
311 | "[-T C|Lisp] [-t title] [-a author] [-O] [-o output] [-s style] file ..." | |
312 | ||
313 | def extToType(ext): | |
314 | fileExtToType = ( ((".c", ".cpp", ".C", ".h"), CPbook), | |
315 | ((".lisp", ".el", ".l", ".cl"), LispPbook) ) | |
316 | for types, typeClass in fileExtToType: | |
317 | if (ext in types): | |
318 | return typeClass | |
319 | return None | |
320 | ||
321 | def main(): | |
322 | texClass = TexFile | |
323 | type = None | |
324 | output = None | |
325 | (author, title, toc, style) = (None, None, True, "article") | |
326 | try: | |
327 | opts, args = getopt.getopt(sys.argv[1:], "hc:T:t:a:Oo:s:") | |
328 | if not args: | |
329 | raise getopt.error, "At least one file argument required" | |
330 | for optname, optvalue in opts: | |
331 | if optname == "-h": | |
332 | usage() | |
333 | return | |
334 | elif optname == "-c": | |
335 | if optvalue == "TexFile": | |
336 | texClass = TexFile | |
337 | elif optvalue == "IdqTexFile": | |
338 | texClass = IdqTexFile | |
339 | elif optvalue == "BknrTexFile": | |
340 | texClass = BknrTexFile | |
341 | elif optvalue == "TxtFile": | |
342 | texClass = TxtFile | |
343 | else: | |
344 | raise getopt.error, "Unknown TexFile class ", optvalue | |
345 | elif optname == "-t": | |
346 | title = optvalue | |
347 | elif optname == "-a": | |
348 | author = optvalue | |
349 | elif optname == "-O": | |
350 | toc = False | |
351 | elif optname == "-s": | |
352 | style = optvalue | |
353 | elif optname == "-T": | |
354 | if optvalue == "C": | |
355 | type = CPbook | |
356 | elif optvalue == "Lisp": | |
357 | type = LispPbook | |
358 | else: | |
359 | raise getopt.error, "Unknown pbook file type ", optvalue | |
360 | elif optname == "-o": | |
361 | output = optvalue | |
362 | except getopt.error, msg: | |
363 | print msg | |
364 | usage() | |
365 | return 1 | |
366 | file = args[0] | |
367 | name,ext = splitext(file) | |
368 | if not title: | |
369 | title = basename(name) | |
370 | if not type: | |
371 | type = extToType(ext) | |
372 | if not type: | |
373 | print "Could not get type for ", ext | |
374 | return 2 | |
375 | pbook = type(args, texClass(title, author, style, toc)) | |
376 | if not output: | |
377 | output = basename(name) | |
378 | try: | |
379 | file = pbook.makeFile(output) | |
380 | if output != "-": | |
381 | print "Wrote output to ", file.name | |
382 | except IOError, (errno, strerror): | |
383 | print "Caught an error while generating \"%s\": %s" % (output, strerror) | |
384 | except: | |
385 | print "Caught an error while generating \"%s\"" % (output) | |
386 | return 1 | |
387 | ||
388 | if __name__ == "__main__": | |
389 | sys.exit(main()) |