# The script builds the LaTeX documentation. # Testing: # python mklatex.py latex .. 1.0 from docstructure import SITE_STRUCTURE, BASENAME_MAP import os, shutil, re, sys, datetime try: set except NameError: # Python 2.3 from sets import Set as set TARGET_FILE = "lxmldoc.tex" RST2LATEX_OPTIONS = " ".join([ # "--no-toc-backlinks", "--strip-comments", "--language en", # "--date", "--use-latex-footnotes", "--use-latex-citations", "--use-latex-toc", "--font-encoding=T1", "--output-encoding=utf-8", "--input-encoding=utf-8", "--graphicx-option=pdftex", ]) htmlnsmap = {"h" : "http://www.w3.org/1999/xhtml"} replace_invalid = re.compile(r'[-_/.\s\\]').sub replace_content = re.compile("\{[^\}]*\}").sub replace_epydoc_macros = re.compile(r'(,\s*amssymb|dvips\s*,\s*)').sub replace_rst_macros = re.compile(r'(\\usepackage\{color}|\\usepackage\[[^]]*]\{hyperref})').sub BASENAME_MAP = BASENAME_MAP.copy() BASENAME_MAP.update({'api' : 'lxmlapi'}) # LaTeX snippets DOCUMENT_CLASS = r""" \documentclass[10pt,english]{report} \usepackage[a4paper]{geometry} \usepackage{tabularx} \usepackage{ifthen} \usepackage[pdftex]{graphicx} \parindent0pt \parskip1ex %%% Fallback definitions for Docutils-specific commands % providelength (provide a length variable and set default, if it is new) \providecommand*{\DUprovidelength}[2]{ \ifthenelse{\isundefined{#1}}{\newlength{#1}\setlength{#1}{#2}}{} } % docinfo (width of docinfo table) \DUprovidelength{\DUdocinfowidth}{0.9\textwidth} % titlereference role \providecommand*{\DUroletitlereference}[1]{\textsl{#1}} """ PYGMENTS_IMPORT = r""" \usepackage{fancyvrb} \input{_part_pygments.tex} """ EPYDOC_IMPORT = r""" \input{_part_epydoc.tex} """ def write_chapter(master, title, filename): filename = os.path.join(os.path.dirname(filename), "_part_%s" % os.path.basename(filename)) master.write(r""" \chapter{%s} \label{%s} \input{%s} """ % (title, filename, filename)) # the program ---- def rest2latex(script, source_path, dest_path): command = ('%s %s %s %s > %s' % (sys.executable, script, RST2LATEX_OPTIONS, source_path, dest_path)) os.system(command) def build_pygments_macros(filename): from pygments.formatters import LatexFormatter text = LatexFormatter().get_style_defs() f = file(filename, "w") f.write(text) f.write('\n') f.close() def copy_epydoc_macros(src, dest, existing_header_lines): doc = file(src, 'r') out = file(dest, "w") for line in doc: if line.startswith('%% generator') \ or line.startswith('% generated by ') \ or '\\begin{document}' in line \ or '\\makeindex' in line: break if line.startswith('%') or \ r'\documentclass' in line or \ r'\makeindex' in line or \ r'{inputenc}' in line: continue if line.startswith(r'\usepackage'): if line in existing_header_lines: continue if '{hyperref}' in line: line = line.replace('black', 'blue') out.write( replace_epydoc_macros('', line) ) out.close() doc.close() def noop(input): return input counter_no = 0 def tex_postprocess(src_path, dest_path, want_header=False, process_line=noop): """ Postprocessing of the LaTeX file generated from ReST. Reads file src_path and saves to dest_path only the true content (without the document header and final) - so it is suitable to be used as part of the longer document. Returns the title of document If want_header is set, returns also the document header (as the list of lines). """ title = '' header = [] add_header_line = header.append global counter_no counter_no = counter_no + 1 counter_text = "listcnt%d" % counter_no search_title = re.compile(r'\\title{([^{}]*(?:{[^}]*})*)}').search skipping = re.compile(r'(\\end{document}|\\tableofcontents|^%)').search src = file(src_path) dest = file(dest_path, "w") src_text = src.read() src.close() title = search_title(src_text) if title: # remove any commands from the title title = re.sub(r'\\\w+({[^}]*})?', '', title.group(1)) iter_lines = iter(src_text.splitlines()) for l in iter_lines: l = process_line(l) if not l: continue if want_header: add_header_line(replace_rst_macros('', l)) if l.startswith("\\maketitle"): break for l in iter_lines: l = process_line(l) if skipping(l): # To-Do minitoc instead of tableofcontents continue elif "\hypertarget{old-versions}" in l: break elif "listcnt0" in l: l = l.replace("listcnt0", counter_text) dest.write(l + '\n') if not title: raise Exception("Bueee, no title in %s" % src_path) return title, header def publish(dirname, lxml_path, release): if not os.path.exists(dirname): os.mkdir(dirname) book_title = "lxml %s" % release doc_dir = os.path.join(lxml_path, 'doc') script = os.path.join(doc_dir, 'rest2latex.py') pubkey = os.path.join(doc_dir, 'pubkey.asc') shutil.copy(pubkey, dirname) # build pygments macros build_pygments_macros(os.path.join(dirname, '_part_pygments.tex')) # Used in postprocessing of generated LaTeX files header = [] titles = {} replace_interdoc_hyperrefs = re.compile( r'\\href\{([^/}]+)[.]([^./}]+)\}').sub replace_docinternal_hyperrefs = re.compile( r'\\href\{\\#([^}]+)\}').sub replace_image_paths = re.compile( r'^(\\includegraphics{)').sub def build_hyperref(match): basename, extension = match.groups() outname = BASENAME_MAP.get(basename, basename) if '#' in extension: anchor = extension.split('#')[-1] return r"\hyperref[%s]" % anchor elif extension != 'html': return r'\href{http://codespeak.net/lxml/%s.%s}' % ( outname, extension) else: return r"\hyperref[_part_%s.tex]" % outname def fix_relative_hyperrefs(line): line = replace_image_paths(r'\1../html/', line) if r'\href' not in line: return line line = replace_interdoc_hyperrefs(build_hyperref, line) return replace_docinternal_hyperrefs(r'\hyperref[\1]', line) # Building pages for section, text_files in SITE_STRUCTURE: for filename in text_files: if filename.startswith('@'): continue #page_title = filename[1:] #url = href_map[page_title] #build_menu_entry(page_title, url, section_head) basename = os.path.splitext(os.path.basename(filename))[0] basename = BASENAME_MAP.get(basename, basename) outname = basename + '.tex' outpath = os.path.join(dirname, outname) path = os.path.join(doc_dir, filename) print "Creating %s" % outname rest2latex(script, path, outpath) final_name = os.path.join(dirname, os.path.dirname(outname), "_part_%s" % os.path.basename(outname)) title, hd = tex_postprocess(outpath, final_name, want_header = not header, process_line=fix_relative_hyperrefs) if not header: header = hd titles[outname] = title # integrate generated API docs print "Integrating API docs" apidocsname = 'api.tex' apipath = os.path.join(dirname, apidocsname) tex_postprocess(apipath, os.path.join(dirname, "_part_%s" % apidocsname), process_line=fix_relative_hyperrefs) copy_epydoc_macros(apipath, os.path.join(dirname, '_part_epydoc.tex'), set(header)) # convert CHANGES.txt print "Integrating ChangeLog" find_version_title = re.compile( r'(.*\\section\{)([0-9][^\} ]*)\s+\(([^)]+)\)(\}.*)').search def fix_changelog(line): m = find_version_title(line) if m: line = "%sChanges in version %s, released %s%s" % m.groups() else: line = line.replace(r'\subsection{', r'\subsection*{') return line chgname = 'changes-%s.tex' % release chgpath = os.path.join(dirname, chgname) rest2latex(script, os.path.join(lxml_path, 'CHANGES.txt'), chgpath) tex_postprocess(chgpath, os.path.join(dirname, "_part_%s" % chgname), process_line=fix_changelog) # Writing a master file print "Building %s\n" % TARGET_FILE master = file( os.path.join(dirname, TARGET_FILE), "w") for hln in header: if hln.startswith(r"\documentclass"): #hln = hln.replace('article', 'book') hln = DOCUMENT_CLASS + EPYDOC_IMPORT elif hln.startswith(r"\begin{document}"): # pygments and epydoc support master.write(PYGMENTS_IMPORT) elif hln.startswith(r"\title{"): hln = replace_content( r'{%s\\\\\\vspace{1cm}\\includegraphics[width=2.5cm]{../html/tagpython-big.png}}' % book_title, hln) elif hln.startswith(r"\date{"): hln = replace_content( r'{%s}' % datetime.date.today().isoformat(), hln) elif hln.startswith("pdftitle"): hln = replace_content( r'{%s}' % book_title, hln) master.write(hln + '\n') master.write("\\setcounter{page}{2}\n") master.write("\\tableofcontents\n") for section, text_files in SITE_STRUCTURE: master.write("\n\n\\part{%s}\n" % section) for filename in text_files: if filename.startswith('@'): continue #print "Not yet implemented: %s" % filename[1:] #page_title = filename[1:] #url = href_map[page_title] #build_menu_entry(page_title, url, section_head) else: basename = os.path.splitext(os.path.basename(filename))[0] basename = BASENAME_MAP.get(basename, basename) outname = basename + '.tex' write_chapter(master, titles[outname], outname) master.write("\\appendix\n") master.write("\\begin{appendix}\n") write_chapter(master, "Changes", chgname) write_chapter(master, "Generated API documentation", apidocsname) master.write("\\end{appendix}\n") master.write("\\end{document}\n") if __name__ == '__main__': publish(sys.argv[1], sys.argv[2], sys.argv[3])