css-inline compiles with cmucl
[clinton/parenscript.git] / pbook.py
CommitLineData
dc06e063
MB
1#! /usr/bin/env python\r
2\r
3import re\r
4import tempfile\r
5import os, getopt, sys, shutil, random\r
6from os.path import splitext, basename\r
7\r
8class 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
56class 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
162class 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
166class 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
192class 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
295class 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
303class 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
312def 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
316def 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
324def 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
391if __name__ == "__main__":\r
392 sys.exit(main())\r