mklatex.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. # The script builds the LaTeX documentation.
  2. # Testing:
  3. # python mklatex.py latex .. 1.0
  4. from docstructure import SITE_STRUCTURE, BASENAME_MAP
  5. import os, shutil, re, sys, datetime
  6. try:
  7. set
  8. except NameError:
  9. # Python 2.3
  10. from sets import Set as set
  11. TARGET_FILE = "lxmldoc.tex"
  12. RST2LATEX_OPTIONS = " ".join([
  13. # "--no-toc-backlinks",
  14. "--strip-comments",
  15. "--language en",
  16. # "--date",
  17. "--use-latex-footnotes",
  18. "--use-latex-citations",
  19. "--use-latex-toc",
  20. "--font-encoding=T1",
  21. "--output-encoding=utf-8",
  22. "--input-encoding=utf-8",
  23. "--graphicx-option=pdftex",
  24. ])
  25. htmlnsmap = {"h" : "http://www.w3.org/1999/xhtml"}
  26. replace_invalid = re.compile(r'[-_/.\s\\]').sub
  27. replace_content = re.compile("\{[^\}]*\}").sub
  28. replace_epydoc_macros = re.compile(r'(,\s*amssymb|dvips\s*,\s*)').sub
  29. replace_rst_macros = re.compile(r'(\\usepackage\{color}|\\usepackage\[[^]]*]\{hyperref})').sub
  30. BASENAME_MAP = BASENAME_MAP.copy()
  31. BASENAME_MAP.update({'api' : 'lxmlapi'})
  32. # LaTeX snippets
  33. DOCUMENT_CLASS = r"""
  34. \documentclass[10pt,english]{report}
  35. \usepackage[a4paper]{geometry}
  36. \usepackage{tabularx}
  37. \usepackage{ifthen}
  38. \usepackage[pdftex]{graphicx}
  39. \parindent0pt
  40. \parskip1ex
  41. %%% Fallback definitions for Docutils-specific commands
  42. % providelength (provide a length variable and set default, if it is new)
  43. \providecommand*{\DUprovidelength}[2]{
  44. \ifthenelse{\isundefined{#1}}{\newlength{#1}\setlength{#1}{#2}}{}
  45. }
  46. % docinfo (width of docinfo table)
  47. \DUprovidelength{\DUdocinfowidth}{0.9\textwidth}
  48. % titlereference role
  49. \providecommand*{\DUroletitlereference}[1]{\textsl{#1}}
  50. """
  51. PYGMENTS_IMPORT = r"""
  52. \usepackage{fancyvrb}
  53. \input{_part_pygments.tex}
  54. """
  55. EPYDOC_IMPORT = r"""
  56. \input{_part_epydoc.tex}
  57. """
  58. def write_chapter(master, title, filename):
  59. filename = os.path.join(os.path.dirname(filename),
  60. "_part_%s" % os.path.basename(filename))
  61. master.write(r"""
  62. \chapter{%s}
  63. \label{%s}
  64. \input{%s}
  65. """ % (title, filename, filename))
  66. # the program ----
  67. def rest2latex(script, source_path, dest_path):
  68. command = ('%s %s %s %s > %s' %
  69. (sys.executable, script, RST2LATEX_OPTIONS,
  70. source_path, dest_path))
  71. os.system(command)
  72. def build_pygments_macros(filename):
  73. from pygments.formatters import LatexFormatter
  74. text = LatexFormatter().get_style_defs()
  75. f = file(filename, "w")
  76. f.write(text)
  77. f.write('\n')
  78. f.close()
  79. def copy_epydoc_macros(src, dest, existing_header_lines):
  80. doc = file(src, 'r')
  81. out = file(dest, "w")
  82. for line in doc:
  83. if line.startswith('%% generator') \
  84. or line.startswith('% generated by ') \
  85. or '\\begin{document}' in line \
  86. or '\\makeindex' in line:
  87. break
  88. if line.startswith('%') or \
  89. r'\documentclass' in line or \
  90. r'\makeindex' in line or \
  91. r'{inputenc}' in line:
  92. continue
  93. if line.startswith(r'\usepackage'):
  94. if line in existing_header_lines:
  95. continue
  96. if '{hyperref}' in line:
  97. line = line.replace('black', 'blue')
  98. out.write( replace_epydoc_macros('', line) )
  99. out.close()
  100. doc.close()
  101. def noop(input):
  102. return input
  103. counter_no = 0
  104. def tex_postprocess(src_path, dest_path, want_header=False, process_line=noop):
  105. """
  106. Postprocessing of the LaTeX file generated from ReST.
  107. Reads file src_path and saves to dest_path only the true content
  108. (without the document header and final) - so it is suitable
  109. to be used as part of the longer document.
  110. Returns the title of document
  111. If want_header is set, returns also the document header (as
  112. the list of lines).
  113. """
  114. title = ''
  115. header = []
  116. add_header_line = header.append
  117. global counter_no
  118. counter_no = counter_no + 1
  119. counter_text = "listcnt%d" % counter_no
  120. search_title = re.compile(r'\\title{([^{}]*(?:{[^}]*})*)}').search
  121. skipping = re.compile(r'(\\end{document}|\\tableofcontents|^%)').search
  122. src = file(src_path)
  123. dest = file(dest_path, "w")
  124. src_text = src.read()
  125. src.close()
  126. title = search_title(src_text)
  127. if title:
  128. # remove any commands from the title
  129. title = re.sub(r'\\\w+({[^}]*})?', '', title.group(1))
  130. iter_lines = iter(src_text.splitlines())
  131. for l in iter_lines:
  132. l = process_line(l)
  133. if not l:
  134. continue
  135. if want_header:
  136. add_header_line(replace_rst_macros('', l))
  137. if l.startswith("\\maketitle"):
  138. break
  139. for l in iter_lines:
  140. l = process_line(l)
  141. if skipping(l):
  142. # To-Do minitoc instead of tableofcontents
  143. continue
  144. elif "\hypertarget{old-versions}" in l:
  145. break
  146. elif "listcnt0" in l:
  147. l = l.replace("listcnt0", counter_text)
  148. dest.write(l + '\n')
  149. if not title:
  150. raise Exception("Bueee, no title in %s" % src_path)
  151. return title, header
  152. def publish(dirname, lxml_path, release):
  153. if not os.path.exists(dirname):
  154. os.mkdir(dirname)
  155. book_title = "lxml %s" % release
  156. doc_dir = os.path.join(lxml_path, 'doc')
  157. script = os.path.join(doc_dir, 'rest2latex.py')
  158. pubkey = os.path.join(doc_dir, 'pubkey.asc')
  159. shutil.copy(pubkey, dirname)
  160. # build pygments macros
  161. build_pygments_macros(os.path.join(dirname, '_part_pygments.tex'))
  162. # Used in postprocessing of generated LaTeX files
  163. header = []
  164. titles = {}
  165. replace_interdoc_hyperrefs = re.compile(
  166. r'\\href\{([^/}]+)[.]([^./}]+)\}').sub
  167. replace_docinternal_hyperrefs = re.compile(
  168. r'\\href\{\\#([^}]+)\}').sub
  169. replace_image_paths = re.compile(
  170. r'^(\\includegraphics{)').sub
  171. def build_hyperref(match):
  172. basename, extension = match.groups()
  173. outname = BASENAME_MAP.get(basename, basename)
  174. if '#' in extension:
  175. anchor = extension.split('#')[-1]
  176. return r"\hyperref[%s]" % anchor
  177. elif extension != 'html':
  178. return r'\href{http://codespeak.net/lxml/%s.%s}' % (
  179. outname, extension)
  180. else:
  181. return r"\hyperref[_part_%s.tex]" % outname
  182. def fix_relative_hyperrefs(line):
  183. line = replace_image_paths(r'\1../html/', line)
  184. if r'\href' not in line:
  185. return line
  186. line = replace_interdoc_hyperrefs(build_hyperref, line)
  187. return replace_docinternal_hyperrefs(r'\hyperref[\1]', line)
  188. # Building pages
  189. for section, text_files in SITE_STRUCTURE:
  190. for filename in text_files:
  191. if filename.startswith('@'):
  192. continue
  193. #page_title = filename[1:]
  194. #url = href_map[page_title]
  195. #build_menu_entry(page_title, url, section_head)
  196. basename = os.path.splitext(os.path.basename(filename))[0]
  197. basename = BASENAME_MAP.get(basename, basename)
  198. outname = basename + '.tex'
  199. outpath = os.path.join(dirname, outname)
  200. path = os.path.join(doc_dir, filename)
  201. print "Creating %s" % outname
  202. rest2latex(script, path, outpath)
  203. final_name = os.path.join(dirname, os.path.dirname(outname),
  204. "_part_%s" % os.path.basename(outname))
  205. title, hd = tex_postprocess(outpath, final_name,
  206. want_header = not header,
  207. process_line=fix_relative_hyperrefs)
  208. if not header:
  209. header = hd
  210. titles[outname] = title
  211. # integrate generated API docs
  212. print "Integrating API docs"
  213. apidocsname = 'api.tex'
  214. apipath = os.path.join(dirname, apidocsname)
  215. tex_postprocess(apipath, os.path.join(dirname, "_part_%s" % apidocsname),
  216. process_line=fix_relative_hyperrefs)
  217. copy_epydoc_macros(apipath, os.path.join(dirname, '_part_epydoc.tex'),
  218. set(header))
  219. # convert CHANGES.txt
  220. print "Integrating ChangeLog"
  221. find_version_title = re.compile(
  222. r'(.*\\section\{)([0-9][^\} ]*)\s+\(([^)]+)\)(\}.*)').search
  223. def fix_changelog(line):
  224. m = find_version_title(line)
  225. if m:
  226. line = "%sChanges in version %s, released %s%s" % m.groups()
  227. else:
  228. line = line.replace(r'\subsection{', r'\subsection*{')
  229. return line
  230. chgname = 'changes-%s.tex' % release
  231. chgpath = os.path.join(dirname, chgname)
  232. rest2latex(script,
  233. os.path.join(lxml_path, 'CHANGES.txt'),
  234. chgpath)
  235. tex_postprocess(chgpath, os.path.join(dirname, "_part_%s" % chgname),
  236. process_line=fix_changelog)
  237. # Writing a master file
  238. print "Building %s\n" % TARGET_FILE
  239. master = file( os.path.join(dirname, TARGET_FILE), "w")
  240. for hln in header:
  241. if hln.startswith(r"\documentclass"):
  242. #hln = hln.replace('article', 'book')
  243. hln = DOCUMENT_CLASS + EPYDOC_IMPORT
  244. elif hln.startswith(r"\begin{document}"):
  245. # pygments and epydoc support
  246. master.write(PYGMENTS_IMPORT)
  247. elif hln.startswith(r"\title{"):
  248. hln = replace_content(
  249. r'{%s\\\\\\vspace{1cm}\\includegraphics[width=2.5cm]{../html/tagpython-big.png}}' % book_title, hln)
  250. elif hln.startswith(r"\date{"):
  251. hln = replace_content(
  252. r'{%s}' % datetime.date.today().isoformat(), hln)
  253. elif hln.startswith("pdftitle"):
  254. hln = replace_content(
  255. r'{%s}' % book_title, hln)
  256. master.write(hln + '\n')
  257. master.write("\\setcounter{page}{2}\n")
  258. master.write("\\tableofcontents\n")
  259. for section, text_files in SITE_STRUCTURE:
  260. master.write("\n\n\\part{%s}\n" % section)
  261. for filename in text_files:
  262. if filename.startswith('@'):
  263. continue
  264. #print "Not yet implemented: %s" % filename[1:]
  265. #page_title = filename[1:]
  266. #url = href_map[page_title]
  267. #build_menu_entry(page_title, url, section_head)
  268. else:
  269. basename = os.path.splitext(os.path.basename(filename))[0]
  270. basename = BASENAME_MAP.get(basename, basename)
  271. outname = basename + '.tex'
  272. write_chapter(master, titles[outname], outname)
  273. master.write("\\appendix\n")
  274. master.write("\\begin{appendix}\n")
  275. write_chapter(master, "Changes", chgname)
  276. write_chapter(master, "Generated API documentation", apidocsname)
  277. master.write("\\end{appendix}\n")
  278. master.write("\\end{document}\n")
  279. if __name__ == '__main__':
  280. publish(sys.argv[1], sys.argv[2], sys.argv[3])