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