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