s/js:funcall/js::funcall/
[clinton/parenscript.git] / docs / pbook.py
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' + \
69 "\\usepackage{graphicx}\n" + \
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())