| # |
| # epydoc.py: epydoc LaTeX output generator |
| # Edward Loper |
| # |
| # Created [01/30/01 05:18 PM] |
| # $Id: latex.py 1621 2007-09-23 18:54:23Z edloper $ |
| # |
| |
| """ |
| The LaTeX output generator for epydoc. The main interface provided by |
| this module is the L{LatexWriter} class. |
| |
| @todo: Inheritance=listed |
| """ |
| __docformat__ = 'epytext en' |
| |
| import os.path, sys, time, re, textwrap, codecs |
| |
| from epydoc.apidoc import * |
| from epydoc.compat import * |
| import epydoc |
| from epydoc import log |
| from epydoc import markup |
| from epydoc.util import plaintext_to_latex |
| import epydoc.markup |
| |
| class LatexWriter: |
| PREAMBLE = [ |
| "\\documentclass{article}", |
| "\\usepackage{alltt, parskip, fancyhdr, boxedminipage}", |
| "\\usepackage{makeidx, multirow, longtable, tocbibind, amssymb}", |
| "\\usepackage{fullpage}", |
| "\\usepackage[usenames]{color}", |
| # Fix the heading position -- without this, the headings generated |
| # by the fancyheadings package sometimes overlap the text. |
| "\\setlength{\\headheight}{16pt}", |
| "\\setlength{\\headsep}{24pt}", |
| "\\setlength{\\topmargin}{-\\headsep}", |
| # By default, do not indent paragraphs. |
| "\\setlength{\\parindent}{0ex}", |
| "\\setlength{\\parskip}{2ex}", |
| # Double the standard size boxedminipage outlines. |
| "\\setlength{\\fboxrule}{2\\fboxrule}", |
| # Create a 'base class' length named BCL for use in base trees. |
| "\\newlength{\\BCL} % base class length, for base trees.", |
| # Display the section & subsection names in a header. |
| "\\pagestyle{fancy}", |
| "\\renewcommand{\\sectionmark}[1]{\\markboth{#1}{}}", |
| "\\renewcommand{\\subsectionmark}[1]{\\markright{#1}}", |
| # Colorization for python source code |
| "\\definecolor{py@keywordcolour}{rgb}{1,0.45882,0}", |
| "\\definecolor{py@stringcolour}{rgb}{0,0.666666,0}", |
| "\\definecolor{py@commentcolour}{rgb}{1,0,0}", |
| "\\definecolor{py@ps1colour}{rgb}{0.60784,0,0}", |
| "\\definecolor{py@ps2colour}{rgb}{0.60784,0,1}", |
| "\\definecolor{py@inputcolour}{rgb}{0,0,0}", |
| "\\definecolor{py@outputcolour}{rgb}{0,0,1}", |
| "\\definecolor{py@exceptcolour}{rgb}{1,0,0}", |
| "\\definecolor{py@defnamecolour}{rgb}{1,0.5,0.5}", |
| "\\definecolor{py@builtincolour}{rgb}{0.58039,0,0.58039}", |
| "\\definecolor{py@identifiercolour}{rgb}{0,0,0}", |
| "\\definecolor{py@linenumcolour}{rgb}{0.4,0.4,0.4}", |
| "\\definecolor{py@inputcolour}{rgb}{0,0,0}", |
| "% Prompt", |
| "\\newcommand{\\pysrcprompt}[1]{\\textcolor{py@ps1colour}" |
| "{\\small\\textbf{#1}}}", |
| "\\newcommand{\\pysrcmore}[1]{\\textcolor{py@ps2colour}" |
| "{\\small\\textbf{#1}}}", |
| "% Source code", |
| "\\newcommand{\\pysrckeyword}[1]{\\textcolor{py@keywordcolour}" |
| "{\\small\\textbf{#1}}}", |
| "\\newcommand{\\pysrcbuiltin}[1]{\\textcolor{py@builtincolour}" |
| "{\\small\\textbf{#1}}}", |
| "\\newcommand{\\pysrcstring}[1]{\\textcolor{py@stringcolour}" |
| "{\\small\\textbf{#1}}}", |
| "\\newcommand{\\pysrcdefname}[1]{\\textcolor{py@defnamecolour}" |
| "{\\small\\textbf{#1}}}", |
| "\\newcommand{\\pysrcother}[1]{\\small\\textbf{#1}}", |
| "% Comments", |
| "\\newcommand{\\pysrccomment}[1]{\\textcolor{py@commentcolour}" |
| "{\\small\\textbf{#1}}}", |
| "% Output", |
| "\\newcommand{\\pysrcoutput}[1]{\\textcolor{py@outputcolour}" |
| "{\\small\\textbf{#1}}}", |
| "% Exceptions", |
| "\\newcommand{\\pysrcexcept}[1]{\\textcolor{py@exceptcolour}" |
| "{\\small\\textbf{#1}}}", |
| # Size of the function description boxes. |
| "\\newlength{\\funcindent}", |
| "\\newlength{\\funcwidth}", |
| "\\setlength{\\funcindent}{1cm}", |
| "\\setlength{\\funcwidth}{\\textwidth}", |
| "\\addtolength{\\funcwidth}{-2\\funcindent}", |
| # Size of the var description tables. |
| "\\newlength{\\varindent}", |
| "\\newlength{\\varnamewidth}", |
| "\\newlength{\\vardescrwidth}", |
| "\\newlength{\\varwidth}", |
| "\\setlength{\\varindent}{1cm}", |
| "\\setlength{\\varnamewidth}{.3\\textwidth}", |
| "\\setlength{\\varwidth}{\\textwidth}", |
| "\\addtolength{\\varwidth}{-4\\tabcolsep}", |
| "\\addtolength{\\varwidth}{-3\\arrayrulewidth}", |
| "\\addtolength{\\varwidth}{-2\\varindent}", |
| "\\setlength{\\vardescrwidth}{\\varwidth}", |
| "\\addtolength{\\vardescrwidth}{-\\varnamewidth}", |
| # Define new environment for displaying parameter lists. |
| textwrap.dedent("""\ |
| \\newenvironment{Ventry}[1]% |
| {\\begin{list}{}{% |
| \\renewcommand{\\makelabel}[1]{\\texttt{##1:}\\hfil}% |
| \\settowidth{\\labelwidth}{\\texttt{#1:}}% |
| \\setlength{\\leftmargin}{\\labelsep}% |
| \\addtolength{\\leftmargin}{\\labelwidth}}}% |
| {\\end{list}}"""), |
| ] |
| |
| HRULE = '\\rule{\\textwidth}{0.5\\fboxrule}\n\n' |
| |
| SECTIONS = ['\\part{%s}', '\\chapter{%s}', '\\section{%s}', |
| '\\subsection{%s}', '\\subsubsection{%s}', |
| '\\textbf{%s}'] |
| |
| STAR_SECTIONS = ['\\part*{%s}', '\\chapter*{%s}', '\\section*{%s}', |
| '\\subsection*{%s}', '\\subsubsection*{%s}', |
| '\\textbf{%s}'] |
| |
| def __init__(self, docindex, **kwargs): |
| self.docindex = docindex |
| # Process keyword arguments |
| self._show_private = kwargs.get('private', 0) |
| self._prj_name = kwargs.get('prj_name', None) or 'API Documentation' |
| self._crossref = kwargs.get('crossref', 1) |
| self._index = kwargs.get('index', 1) |
| self._list_classes_separately=kwargs.get('list_classes_separately',0) |
| self._inheritance = kwargs.get('inheritance', 'listed') |
| self._exclude = kwargs.get('exclude', 1) |
| self._top_section = 2 |
| self._index_functions = 1 |
| self._hyperref = 1 |
| |
| #: The Python representation of the encoding. |
| #: Update L{latex_encodings} in case of mismatch between it and |
| #: the C{inputenc} LaTeX package. |
| self._encoding = kwargs.get('encoding', 'utf-8') |
| |
| self.valdocs = valdocs = sorted(docindex.reachable_valdocs( |
| imports=False, packages=False, bases=False, submodules=False, |
| subclasses=False, private=self._show_private)) |
| self._num_files = self.num_files() |
| # For use with select_variables(): |
| if self._show_private: self._public_filter = None |
| else: self._public_filter = True |
| |
| self.class_list = [d for d in valdocs if isinstance(d, ClassDoc)] |
| """The list of L{ClassDoc}s for the documented classes.""" |
| self.class_set = set(self.class_list) |
| """The set of L{ClassDoc}s for the documented classes.""" |
| |
| def write(self, directory=None): |
| """ |
| Write the API documentation for the entire project to the |
| given directory. |
| |
| @type directory: C{string} |
| @param directory: The directory to which output should be |
| written. If no directory is specified, output will be |
| written to the current directory. If the directory does |
| not exist, it will be created. |
| @rtype: C{None} |
| @raise OSError: If C{directory} cannot be created, |
| @raise OSError: If any file cannot be created or written to. |
| """ |
| # For progress reporting: |
| self._files_written = 0. |
| |
| # Set the default values for ValueDoc formatted representations. |
| orig_valdoc_defaults = (ValueDoc.SUMMARY_REPR_LINELEN, |
| ValueDoc.REPR_LINELEN, |
| ValueDoc.REPR_MAXLINES) |
| ValueDoc.SUMMARY_REPR_LINELEN = 60 |
| ValueDoc.REPR_LINELEN = 52 |
| ValueDoc.REPR_MAXLINES = 5 |
| |
| # Create destination directories, if necessary |
| if not directory: directory = os.curdir |
| self._mkdir(directory) |
| self._directory = directory |
| |
| # Write the top-level file. |
| self._write(self.write_topfile, directory, 'api.tex') |
| |
| # Write the module & class files. |
| for val_doc in self.valdocs: |
| if isinstance(val_doc, ModuleDoc): |
| filename = '%s-module.tex' % val_doc.canonical_name |
| self._write(self.write_module, directory, filename, val_doc) |
| elif (isinstance(val_doc, ClassDoc) and |
| self._list_classes_separately): |
| filename = '%s-class.tex' % val_doc.canonical_name |
| self._write(self.write_class, directory, filename, val_doc) |
| |
| # Restore defaults that we changed. |
| (ValueDoc.SUMMARY_REPR_LINELEN, ValueDoc.REPR_LINELEN, |
| ValueDoc.REPR_MAXLINES) = orig_valdoc_defaults |
| |
| def _write(self, write_func, directory, filename, *args): |
| # Display our progress. |
| self._files_written += 1 |
| log.progress(self._files_written/self._num_files, filename) |
| |
| path = os.path.join(directory, filename) |
| if self._encoding == 'utf-8': |
| f = codecs.open(path, 'w', 'utf-8') |
| write_func(f.write, *args) |
| f.close() |
| else: |
| result = [] |
| write_func(result.append, *args) |
| s = u''.join(result) |
| try: |
| s = s.encode(self._encoding) |
| except UnicodeError: |
| log.error("Output could not be represented with the " |
| "given encoding (%r). Unencodable characters " |
| "will be displayed as '?'. It is recommended " |
| "that you use a different output encoding (utf-8, " |
| "if it's supported by latex on your system)." |
| % self._encoding) |
| s = s.encode(self._encoding, 'replace') |
| f = open(path, 'w') |
| f.write(s) |
| f.close() |
| |
| def num_files(self): |
| """ |
| @return: The number of files that this C{LatexFormatter} will |
| generate. |
| @rtype: C{int} |
| """ |
| n = 1 |
| for doc in self.valdocs: |
| if isinstance(doc, ModuleDoc): n += 1 |
| if isinstance(doc, ClassDoc) and self._list_classes_separately: |
| n += 1 |
| return n |
| |
| def _mkdir(self, directory): |
| """ |
| If the given directory does not exist, then attempt to create it. |
| @rtype: C{None} |
| """ |
| if not os.path.isdir(directory): |
| if os.path.exists(directory): |
| raise OSError('%r is not a directory' % directory) |
| os.mkdir(directory) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Main Doc File |
| #//////////////////////////////////////////////////////////// |
| |
| def write_topfile(self, out): |
| self.write_header(out, 'Include File') |
| self.write_preamble(out) |
| out('\n\\begin{document}\n\n') |
| self.write_start_of(out, 'Header') |
| |
| # Write the title. |
| self.write_start_of(out, 'Title') |
| out('\\title{%s}\n' % plaintext_to_latex(self._prj_name, 1)) |
| out('\\author{API Documentation}\n') |
| out('\\maketitle\n') |
| |
| # Add a table of contents. |
| self.write_start_of(out, 'Table of Contents') |
| out('\\addtolength{\\parskip}{-2ex}\n') |
| out('\\tableofcontents\n') |
| out('\\addtolength{\\parskip}{2ex}\n') |
| |
| # Include documentation files. |
| self.write_start_of(out, 'Includes') |
| for val_doc in self.valdocs: |
| if isinstance(val_doc, ModuleDoc): |
| out('\\include{%s-module}\n' % val_doc.canonical_name) |
| |
| # If we're listing classes separately, put them after all the |
| # modules. |
| if self._list_classes_separately: |
| for val_doc in self.valdocs: |
| if isinstance(val_doc, ClassDoc): |
| out('\\include{%s-class}\n' % val_doc.canonical_name) |
| |
| # Add the index, if requested. |
| if self._index: |
| self.write_start_of(out, 'Index') |
| out('\\printindex\n\n') |
| |
| # Add the footer. |
| self.write_start_of(out, 'Footer') |
| out('\\end{document}\n\n') |
| |
| def write_preamble(self, out): |
| out('\n'.join(self.PREAMBLE)) |
| out('\n') |
| |
| # Set the encoding. |
| out('\\usepackage[%s]{inputenc}\n' % self.get_latex_encoding()) |
| |
| # If we're generating hyperrefs, add the appropriate packages. |
| if self._hyperref: |
| out('\\definecolor{UrlColor}{rgb}{0,0.08,0.45}\n') |
| out('\\usepackage[dvips, pagebackref, pdftitle={%s}, ' |
| 'pdfcreator={epydoc %s}, bookmarks=true, ' |
| 'bookmarksopen=false, pdfpagemode=UseOutlines, ' |
| 'colorlinks=true, linkcolor=black, anchorcolor=black, ' |
| 'citecolor=black, filecolor=black, menucolor=black, ' |
| 'pagecolor=black, urlcolor=UrlColor]{hyperref}\n' % |
| (self._prj_name or '', epydoc.__version__)) |
| |
| # If we're generating an index, add it to the preamble. |
| if self._index: |
| out("\\makeindex\n") |
| |
| # If restructuredtext was used, then we need to extend |
| # the prefix to include LatexTranslator.head_prefix. |
| if 'restructuredtext' in epydoc.markup.MARKUP_LANGUAGES_USED: |
| from epydoc.markup import restructuredtext |
| rst_head = restructuredtext.latex_head_prefix() |
| rst_head = ''.join(rst_head).split('\n') |
| for line in rst_head[1:]: |
| m = re.match(r'\\usepackage(\[.*?\])?{(.*?)}', line) |
| if m and m.group(2) in ( |
| 'babel', 'hyperref', 'color', 'alltt', 'parskip', |
| 'fancyhdr', 'boxedminipage', 'makeidx', |
| 'multirow', 'longtable', 'tocbind', 'assymb', |
| 'fullpage', 'inputenc'): |
| pass |
| else: |
| out(line+'\n') |
| |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Chapters |
| #//////////////////////////////////////////////////////////// |
| |
| def write_module(self, out, doc): |
| self.write_header(out, doc) |
| self.write_start_of(out, 'Module Description') |
| |
| # Add this module to the index. |
| out(' ' + self.indexterm(doc, 'start')) |
| |
| # Add a section marker. |
| out(self.section('%s %s' % (self.doc_kind(doc), |
| doc.canonical_name))) |
| |
| # Label our current location. |
| out(' \\label{%s}\n' % self.label(doc)) |
| |
| # Add the module's description. |
| if doc.descr not in (None, UNKNOWN): |
| out(self.docstring_to_latex(doc.descr)) |
| |
| # Add version, author, warnings, requirements, notes, etc. |
| self.write_standard_fields(out, doc) |
| |
| # If it's a package, list the sub-modules. |
| if doc.submodules != UNKNOWN and doc.submodules: |
| self.write_module_list(out, doc) |
| |
| # Contents. |
| if self._list_classes_separately: |
| self.write_class_list(out, doc) |
| self.write_func_list(out, 'Functions', doc, 'function') |
| self.write_var_list(out, 'Variables', doc, 'other') |
| |
| # Class list. |
| if not self._list_classes_separately: |
| classes = doc.select_variables(imported=False, value_type='class', |
| public=self._public_filter) |
| for var_doc in classes: |
| self.write_class(out, var_doc.value) |
| |
| # Mark the end of the module (for the index) |
| out(' ' + self.indexterm(doc, 'end')) |
| |
| def write_class(self, out, doc): |
| if self._list_classes_separately: |
| self.write_header(out, doc) |
| self.write_start_of(out, 'Class Description') |
| |
| # Add this class to the index. |
| out(' ' + self.indexterm(doc, 'start')) |
| |
| # Add a section marker. |
| if self._list_classes_separately: |
| seclevel = 0 |
| out(self.section('%s %s' % (self.doc_kind(doc), |
| doc.canonical_name), seclevel)) |
| else: |
| seclevel = 1 |
| out(self.section('%s %s' % (self.doc_kind(doc), |
| doc.canonical_name[-1]), seclevel)) |
| |
| # Label our current location. |
| out(' \\label{%s}\n' % self.label(doc)) |
| |
| # Add our base list. |
| if doc.bases not in (UNKNOWN, None) and len(doc.bases) > 0: |
| out(self.base_tree(doc)) |
| |
| # The class's known subclasses |
| if doc.subclasses not in (UNKNOWN, None) and len(doc.subclasses) > 0: |
| sc_items = [plaintext_to_latex('%s' % sc.canonical_name) |
| for sc in doc.subclasses] |
| out(self._descrlist(sc_items, 'Known Subclasses', short=1)) |
| |
| # The class's description. |
| if doc.descr not in (None, UNKNOWN): |
| out(self.docstring_to_latex(doc.descr)) |
| |
| # Version, author, warnings, requirements, notes, etc. |
| self.write_standard_fields(out, doc) |
| |
| # Contents. |
| self.write_func_list(out, 'Methods', doc, 'method', |
| seclevel+1) |
| self.write_var_list(out, 'Properties', doc, |
| 'property', seclevel+1) |
| self.write_var_list(out, 'Class Variables', doc, |
| 'classvariable', seclevel+1) |
| self.write_var_list(out, 'Instance Variables', doc, |
| 'instancevariable', seclevel+1) |
| |
| # Mark the end of the class (for the index) |
| out(' ' + self.indexterm(doc, 'end')) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Module hierarchy trees |
| #//////////////////////////////////////////////////////////// |
| |
| def write_module_tree(self, out): |
| modules = [doc for doc in self.valdocs |
| if isinstance(doc, ModuleDoc)] |
| if not modules: return |
| |
| # Write entries for all top-level modules/packages. |
| out('\\begin{itemize}\n') |
| out('\\setlength{\\parskip}{0ex}\n') |
| for doc in modules: |
| if (doc.package in (None, UNKNOWN) or |
| doc.package not in self.valdocs): |
| self.write_module_tree_item(out, doc) |
| return s +'\\end{itemize}\n' |
| |
| def write_module_list(self, out, doc): |
| if len(doc.submodules) == 0: return |
| self.write_start_of(out, 'Modules') |
| |
| out(self.section('Modules', 1)) |
| out('\\begin{itemize}\n') |
| out('\\setlength{\\parskip}{0ex}\n') |
| |
| for group_name in doc.group_names(): |
| if not doc.submodule_groups[group_name]: continue |
| if group_name: |
| out(' \\item \\textbf{%s}\n' % group_name) |
| out(' \\begin{itemize}\n') |
| for submodule in doc.submodule_groups[group_name]: |
| self.write_module_tree_item(out, submodule) |
| if group_name: |
| out(' \end{itemize}\n') |
| |
| out('\\end{itemize}\n\n') |
| |
| def write_module_tree_item(self, out, doc, depth=0): |
| """ |
| Helper function for L{write_module_tree} and L{write_module_list}. |
| |
| @rtype: C{string} |
| """ |
| out(' '*depth + '\\item \\textbf{') |
| out(plaintext_to_latex(doc.canonical_name[-1]) +'}') |
| if doc.summary not in (None, UNKNOWN): |
| out(': %s\n' % self.docstring_to_latex(doc.summary)) |
| if self._crossref: |
| out('\n \\textit{(Section \\ref{%s}' % self.label(doc)) |
| out(', p.~\\pageref{%s})}\n\n' % self.label(doc)) |
| if doc.submodules != UNKNOWN and doc.submodules: |
| out(' '*depth + ' \\begin{itemize}\n') |
| out(' '*depth + '\\setlength{\\parskip}{0ex}\n') |
| for submodule in doc.submodules: |
| self.write_module_tree_item(out, submodule, depth+4) |
| out(' '*depth + ' \\end{itemize}\n') |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Base class trees |
| #//////////////////////////////////////////////////////////// |
| |
| def base_tree(self, doc, width=None, linespec=None): |
| if width is None: |
| width = self._find_tree_width(doc)+2 |
| linespec = [] |
| s = ('&'*(width-4)+'\\multicolumn{2}{l}{\\textbf{%s}}\n' % |
| plaintext_to_latex('%s'%self._base_name(doc))) |
| s += '\\end{tabular}\n\n' |
| top = 1 |
| else: |
| s = self._base_tree_line(doc, width, linespec) |
| top = 0 |
| |
| if isinstance(doc, ClassDoc): |
| for i in range(len(doc.bases)-1, -1, -1): |
| base = doc.bases[i] |
| spec = (i > 0) |
| s = self.base_tree(base, width, [spec]+linespec) + s |
| |
| if top: |
| s = '\\begin{tabular}{%s}\n' % (width*'c') + s |
| |
| return s |
| |
| def _base_name(self, doc): |
| if doc.canonical_name is None: |
| if doc.parse_repr is not None: |
| return doc.parse_repr |
| else: |
| return '??' |
| else: |
| return '%s' % doc.canonical_name |
| |
| def _find_tree_width(self, doc): |
| if not isinstance(doc, ClassDoc): return 2 |
| width = 2 |
| for base in doc.bases: |
| width = max(width, self._find_tree_width(base)+2) |
| return width |
| |
| def _base_tree_line(self, doc, width, linespec): |
| base_name = plaintext_to_latex(self._base_name(doc)) |
| |
| # linespec is a list of booleans. |
| s = '%% Line for %s, linespec=%s\n' % (base_name, linespec) |
| |
| labelwidth = width-2*len(linespec)-2 |
| |
| # The base class name. |
| s += ('\\multicolumn{%s}{r}{' % labelwidth) |
| s += '\\settowidth{\\BCL}{%s}' % base_name |
| s += '\\multirow{2}{\\BCL}{%s}}\n' % base_name |
| |
| # The vertical bars for other base classes (top half) |
| for vbar in linespec: |
| if vbar: s += '&&\\multicolumn{1}{|c}{}\n' |
| else: s += '&&\n' |
| |
| # The horizontal line. |
| s += ' \\\\\\cline{%s-%s}\n' % (labelwidth+1, labelwidth+1) |
| |
| # The vertical bar for this base class. |
| s += ' ' + '&'*labelwidth |
| s += '\\multicolumn{1}{c|}{}\n' |
| |
| # The vertical bars for other base classes (bottom half) |
| for vbar in linespec: |
| if vbar: s += '&\\multicolumn{1}{|c}{}&\n' |
| else: s += '&&\n' |
| s += ' \\\\\n' |
| |
| return s |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Class List |
| #//////////////////////////////////////////////////////////// |
| |
| def write_class_list(self, out, doc): |
| groups = [(plaintext_to_latex(group_name), |
| doc.select_variables(group=group_name, imported=False, |
| value_type='class', |
| public=self._public_filter)) |
| for group_name in doc.group_names()] |
| |
| # Discard any empty groups; and return if they're all empty. |
| groups = [(g,vars) for (g,vars) in groups if vars] |
| if not groups: return |
| |
| # Write a header. |
| self.write_start_of(out, 'Classes') |
| out(self.section('Classes', 1)) |
| out('\\begin{itemize}') |
| out(' \\setlength{\\parskip}{0ex}\n') |
| |
| for name, var_docs in groups: |
| if name: |
| out(' \\item \\textbf{%s}\n' % name) |
| out(' \\begin{itemize}\n') |
| # Add the lines for each class |
| for var_doc in var_docs: |
| self.write_class_list_line(out, var_doc) |
| if name: |
| out(' \\end{itemize}\n') |
| |
| out('\\end{itemize}\n') |
| |
| def write_class_list_line(self, out, var_doc): |
| if var_doc.value in (None, UNKNOWN): return # shouldn't happen |
| doc = var_doc.value |
| out(' ' + '\\item \\textbf{') |
| out(plaintext_to_latex(var_doc.name) + '}') |
| if doc.summary not in (None, UNKNOWN): |
| out(': %s\n' % self.docstring_to_latex(doc.summary)) |
| if self._crossref: |
| out(('\n \\textit{(Section \\ref{%s}' % self.label(doc))) |
| out((', p.~\\pageref{%s})}\n\n' % self.label(doc))) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Function List |
| #//////////////////////////////////////////////////////////// |
| _FUNC_GROUP_HEADER = '\n\\large{\\textbf{\\textit{%s}}}\n\n' |
| |
| def write_func_list(self, out, heading, doc, value_type, seclevel=1): |
| # Divide all public variables of the given type into groups. |
| groups = [(plaintext_to_latex(group_name), |
| doc.select_variables(group=group_name, imported=False, |
| value_type=value_type, |
| public=self._public_filter)) |
| for group_name in doc.group_names()] |
| |
| # Discard any empty groups; and return if they're all empty. |
| groups = [(g,vars) for (g,vars) in groups if vars] |
| if not groups: return |
| |
| # Write a header. |
| self.write_start_of(out, heading) |
| out(' '+self.section(heading, seclevel)) |
| |
| # Write a section for each group. |
| grouped_inh_vars = {} |
| for name, var_docs in groups: |
| self.write_func_group(out, doc, name, var_docs, grouped_inh_vars) |
| |
| # Write a section for each inheritance pseudo-group (used if |
| # inheritance=='grouped') |
| if grouped_inh_vars: |
| for base in doc.mro(): |
| if base in grouped_inh_vars: |
| hdr = ('Inherited from %s' % |
| plaintext_to_latex('%s' % base.canonical_name)) |
| if self._crossref and base in self.class_set: |
| hdr += ('\\textit{(Section \\ref{%s})}' % |
| self.label(base)) |
| out(self._FUNC_GROUP_HEADER % (hdr)) |
| for var_doc in grouped_inh_vars[base]: |
| self.write_func_list_box(out, var_doc) |
| |
| def write_func_group(self, out, doc, name, var_docs, grouped_inh_vars): |
| # Split up the var_docs list, according to the way each var |
| # should be displayed: |
| # - listed_inh_vars -- for listed inherited variables. |
| # - grouped_inh_vars -- for grouped inherited variables. |
| # - normal_vars -- for all other variables. |
| listed_inh_vars = {} |
| normal_vars = [] |
| for var_doc in var_docs: |
| if var_doc.container != doc: |
| base = var_doc.container |
| if (base not in self.class_set or |
| self._inheritance == 'listed'): |
| listed_inh_vars.setdefault(base,[]).append(var_doc) |
| elif self._inheritance == 'grouped': |
| grouped_inh_vars.setdefault(base,[]).append(var_doc) |
| else: |
| normal_vars.append(var_doc) |
| else: |
| normal_vars.append(var_doc) |
| |
| # Write a header for the group. |
| if name: |
| out(self._FUNC_GROUP_HEADER % name) |
| # Write an entry for each normal var: |
| for var_doc in normal_vars: |
| self.write_func_list_box(out, var_doc) |
| # Write a subsection for inherited vars: |
| if listed_inh_vars: |
| self.write_func_inheritance_list(out, doc, listed_inh_vars) |
| |
| def write_func_inheritance_list(self, out, doc, listed_inh_vars): |
| for base in doc.mro(): |
| if base not in listed_inh_vars: continue |
| #if str(base.canonical_name) == 'object': continue |
| var_docs = listed_inh_vars[base] |
| if self._public_filter: |
| var_docs = [v for v in var_docs if v.is_public] |
| if var_docs: |
| hdr = ('Inherited from %s' % |
| plaintext_to_latex('%s' % base.canonical_name)) |
| if self._crossref and base in self.class_set: |
| hdr += ('\\textit{(Section \\ref{%s})}' % |
| self.label(base)) |
| out(self._FUNC_GROUP_HEADER % hdr) |
| out('\\begin{quote}\n') |
| out('%s\n' % ', '.join( |
| ['%s()' % plaintext_to_latex(var_doc.name) |
| for var_doc in var_docs])) |
| out('\\end{quote}\n') |
| |
| def write_func_list_box(self, out, var_doc): |
| func_doc = var_doc.value |
| is_inherited = (var_doc.overrides not in (None, UNKNOWN)) |
| |
| # nb: this gives the containing section, not a reference |
| # directly to the function. |
| if not is_inherited: |
| out(' \\label{%s}\n' % self.label(func_doc)) |
| out(' %s\n' % self.indexterm(func_doc)) |
| |
| # Start box for this function. |
| out(' \\vspace{0.5ex}\n\n') |
| out('\\hspace{.8\\funcindent}') |
| out('\\begin{boxedminipage}{\\funcwidth}\n\n') |
| |
| # Function signature. |
| out(' %s\n\n' % self.function_signature(var_doc)) |
| |
| if (func_doc.docstring not in (None, UNKNOWN) and |
| func_doc.docstring.strip() != ''): |
| out(' \\vspace{-1.5ex}\n\n') |
| out(' \\rule{\\textwidth}{0.5\\fboxrule}\n') |
| |
| # Description |
| out("\\setlength{\\parskip}{2ex}\n") |
| if func_doc.descr not in (None, UNKNOWN): |
| out(self.docstring_to_latex(func_doc.descr, 4)) |
| |
| # Parameters |
| out("\\setlength{\\parskip}{1ex}\n") |
| if func_doc.arg_descrs or func_doc.arg_types: |
| # Find the longest name. |
| longest = max([0]+[len(n) for n in func_doc.arg_types]) |
| for names, descrs in func_doc.arg_descrs: |
| longest = max([longest]+[len(n) for n in names]) |
| # Table header. |
| out(' '*6+'\\textbf{Parameters}\n') |
| out(' \\vspace{-1ex}\n\n') |
| out(' '*6+'\\begin{quote}\n') |
| out(' \\begin{Ventry}{%s}\n\n' % (longest*'x')) |
| # Add params that have @type but not @param info: |
| arg_descrs = list(func_doc.arg_descrs) |
| args = set() |
| for arg_names, arg_descr in arg_descrs: |
| args.update(arg_names) |
| for arg in var_doc.value.arg_types: |
| if arg not in args: |
| arg_descrs.append( ([arg],None) ) |
| # Display params |
| for (arg_names, arg_descr) in arg_descrs: |
| arg_name = plaintext_to_latex(', '.join(arg_names)) |
| out('%s\\item[%s]\n\n' % (' '*10, arg_name)) |
| if arg_descr: |
| out(self.docstring_to_latex(arg_descr, 10)) |
| for arg_name in arg_names: |
| arg_typ = func_doc.arg_types.get(arg_name) |
| if arg_typ is not None: |
| if len(arg_names) == 1: |
| lhs = 'type' |
| else: |
| lhs = 'type of %s' % arg_name |
| rhs = self.docstring_to_latex(arg_typ).strip() |
| out('%s{\\it (%s=%s)}\n\n' % (' '*12, lhs, rhs)) |
| out(' \\end{Ventry}\n\n') |
| out(' '*6+'\\end{quote}\n\n') |
| |
| # Returns |
| rdescr = func_doc.return_descr |
| rtype = func_doc.return_type |
| if rdescr not in (None, UNKNOWN) or rtype not in (None, UNKNOWN): |
| out(' '*6+'\\textbf{Return Value}\n') |
| out(' \\vspace{-1ex}\n\n') |
| out(' '*6+'\\begin{quote}\n') |
| if rdescr not in (None, UNKNOWN): |
| out(self.docstring_to_latex(rdescr, 6)) |
| if rtype not in (None, UNKNOWN): |
| out(' '*6+'{\\it (type=%s)}\n\n' % |
| self.docstring_to_latex(rtype, 6).strip()) |
| elif rtype not in (None, UNKNOWN): |
| out(self.docstring_to_latex(rtype, 6)) |
| out(' '*6+'\\end{quote}\n\n') |
| |
| # Raises |
| if func_doc.exception_descrs not in (None, UNKNOWN, [], ()): |
| out(' '*6+'\\textbf{Raises}\n') |
| out(' \\vspace{-1ex}\n\n') |
| out(' '*6+'\\begin{quote}\n') |
| out(' \\begin{description}\n\n') |
| for name, descr in func_doc.exception_descrs: |
| out(' '*10+'\\item[\\texttt{%s}]\n\n' % |
| plaintext_to_latex('%s' % name)) |
| out(self.docstring_to_latex(descr, 10)) |
| out(' \\end{description}\n\n') |
| out(' '*6+'\\end{quote}\n\n') |
| |
| ## Overrides |
| if var_doc.overrides not in (None, UNKNOWN): |
| out(' Overrides: ' + |
| plaintext_to_latex('%s'%var_doc.overrides.canonical_name)) |
| if (func_doc.docstring in (None, UNKNOWN) and |
| var_doc.overrides.value.docstring not in (None, UNKNOWN)): |
| out(' \textit{(inherited documentation)}') |
| out('\n\n') |
| |
| # Add version, author, warnings, requirements, notes, etc. |
| self.write_standard_fields(out, func_doc) |
| |
| out(' \\end{boxedminipage}\n\n') |
| |
| def function_signature(self, var_doc): |
| func_doc = var_doc.value |
| func_name = var_doc.name |
| |
| # This should never happen, but just in case: |
| if func_doc in (None, UNKNOWN): |
| return ('\\raggedright \\textbf{%s}(...)' % |
| plaintext_to_latex(func_name)) |
| |
| if func_doc.posargs == UNKNOWN: |
| args = ['...'] |
| else: |
| args = [self.func_arg(name, default) for (name, default) |
| in zip(func_doc.posargs, func_doc.posarg_defaults)] |
| if func_doc.vararg: |
| if func_doc.vararg == '...': |
| args.append('\\textit{...}') |
| else: |
| args.append('*\\textit{%s}' % |
| plaintext_to_latex(func_doc.vararg)) |
| if func_doc.kwarg: |
| args.append('**\\textit{%s}' % |
| plaintext_to_latex(func_doc.kwarg)) |
| return ('\\raggedright \\textbf{%s}(%s)' % |
| (plaintext_to_latex(func_name), ', '.join(args))) |
| |
| def func_arg(self, name, default): |
| s = '\\textit{%s}' % plaintext_to_latex(self._arg_name(name)) |
| if default is not None: |
| s += '={\\tt %s}' % default.summary_pyval_repr().to_latex(None) |
| return s |
| |
| def _arg_name(self, arg): |
| if isinstance(arg, basestring): |
| return arg |
| elif len(arg) == 1: |
| return '(%s,)' % self._arg_name(arg[0]) |
| else: |
| return '(%s)' % (', '.join([self._arg_name(a) for a in arg])) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Variable List |
| #//////////////////////////////////////////////////////////// |
| _VAR_GROUP_HEADER = '\\multicolumn{2}{|l|}{\\textit{%s}}\\\\\n' |
| |
| # Also used for the property list. |
| def write_var_list(self, out, heading, doc, value_type, seclevel=1): |
| groups = [(plaintext_to_latex(group_name), |
| doc.select_variables(group=group_name, imported=False, |
| value_type=value_type, |
| public=self._public_filter)) |
| for group_name in doc.group_names()] |
| |
| # Discard any empty groups; and return if they're all empty. |
| groups = [(g,vars) for (g,vars) in groups if vars] |
| if not groups: return |
| |
| # Write a header. |
| self.write_start_of(out, heading) |
| out(' '+self.section(heading, seclevel)) |
| |
| # [xx] without this, there's a huge gap before the table -- why?? |
| out(' \\vspace{-1cm}\n') |
| |
| out('\\hspace{\\varindent}') |
| out('\\begin{longtable}') |
| out('{|p{\\varnamewidth}|') |
| out('p{\\vardescrwidth}|l}\n') |
| out('\\cline{1-2}\n') |
| |
| # Set up the headers & footer (this makes the table span |
| # multiple pages in a happy way). |
| out('\\cline{1-2} ') |
| out('\\centering \\textbf{Name} & ') |
| out('\\centering \\textbf{Description}& \\\\\n') |
| out('\\cline{1-2}\n') |
| out('\\endhead') |
| out('\\cline{1-2}') |
| out('\\multicolumn{3}{r}{\\small\\textit{') |
| out('continued on next page}}\\\\') |
| out('\\endfoot') |
| out('\\cline{1-2}\n') |
| out('\\endlastfoot') |
| |
| # Write a section for each group. |
| grouped_inh_vars = {} |
| for name, var_docs in groups: |
| self.write_var_group(out, doc, name, var_docs, grouped_inh_vars) |
| |
| # Write a section for each inheritance pseudo-group (used if |
| # inheritance=='grouped') |
| if grouped_inh_vars: |
| for base in doc.mro(): |
| if base in grouped_inh_vars: |
| hdr = ('Inherited from %s' % |
| plaintext_to_latex('%s' % base.canonical_name)) |
| if self._crossref and base in self.class_set: |
| hdr += (' \\textit{(Section \\ref{%s})}' % |
| self.label(base)) |
| out(self._VAR_GROUP_HEADER % (hdr)) |
| out('\\cline{1-2}\n') |
| for var_doc in grouped_inh_vars[base]: |
| if isinstance(var_doc.value3, PropertyDoc): |
| self.write_property_list_line(out, var_doc) |
| else: |
| self.write_var_list_line(out, var_doc) |
| |
| out('\\end{longtable}\n\n') |
| |
| def write_var_group(self, out, doc, name, var_docs, grouped_inh_vars): |
| # Split up the var_docs list, according to the way each var |
| # should be displayed: |
| # - listed_inh_vars -- for listed inherited variables. |
| # - grouped_inh_vars -- for grouped inherited variables. |
| # - normal_vars -- for all other variables. |
| listed_inh_vars = {} |
| normal_vars = [] |
| for var_doc in var_docs: |
| if var_doc.container != doc: |
| base = var_doc.container |
| if (base not in self.class_set or |
| self._inheritance == 'listed'): |
| listed_inh_vars.setdefault(base,[]).append(var_doc) |
| elif self._inheritance == 'grouped': |
| grouped_inh_vars.setdefault(base,[]).append(var_doc) |
| else: |
| normal_vars.append(var_doc) |
| else: |
| normal_vars.append(var_doc) |
| |
| # Write a header for the group. |
| if name: |
| out(self._VAR_GROUP_HEADER % name) |
| out('\\cline{1-2}\n') |
| # Write an entry for each normal var: |
| for var_doc in normal_vars: |
| if isinstance(var_doc.value, PropertyDoc): |
| self.write_property_list_line(out, var_doc) |
| else: |
| self.write_var_list_line(out, var_doc) |
| # Write a subsection for inherited vars: |
| if listed_inh_vars: |
| self.write_var_inheritance_list(out, doc, listed_inh_vars) |
| |
| def write_var_inheritance_list(self, out, doc, listed_inh_vars): |
| for base in doc.mro(): |
| if base not in listed_inh_vars: continue |
| #if str(base.canonical_name) == 'object': continue |
| var_docs = listed_inh_vars[base] |
| if self._public_filter: |
| var_docs = [v for v in var_docs if v.is_public] |
| if var_docs: |
| hdr = ('Inherited from %s' % |
| plaintext_to_latex('%s' % base.canonical_name)) |
| if self._crossref and base in self.class_set: |
| hdr += (' \\textit{(Section \\ref{%s})}' % |
| self.label(base)) |
| out(self._VAR_GROUP_HEADER % hdr) |
| out('\\multicolumn{2}{|p{\\varwidth}|}{' |
| '\\raggedright %s}\\\\\n' % |
| ', '.join(['%s' % plaintext_to_latex(var_doc.name) |
| for var_doc in var_docs])) |
| out('\\cline{1-2}\n') |
| |
| |
| def write_var_list_line(self, out, var_doc): |
| out('\\raggedright ') |
| out(plaintext_to_latex(var_doc.name, nbsp=True, breakany=True)) |
| out(' & ') |
| has_descr = var_doc.descr not in (None, UNKNOWN) |
| has_type = var_doc.type_descr not in (None, UNKNOWN) |
| has_value = var_doc.value is not UNKNOWN |
| if has_type or has_value: |
| out('\\raggedright ') |
| if has_descr: |
| out(self.docstring_to_latex(var_doc.descr, 10).strip()) |
| if has_type or has_value: out('\n\n') |
| if has_value: |
| out('\\textbf{Value:} \n{\\tt %s}' % |
| var_doc.value.summary_pyval_repr().to_latex(None)) |
| if has_type: |
| ptype = self.docstring_to_latex(var_doc.type_descr, 12).strip() |
| out('%s{\\it (type=%s)}' % (' '*12, ptype)) |
| out('&\\\\\n') |
| out('\\cline{1-2}\n') |
| |
| def write_property_list_line(self, out, var_doc): |
| prop_doc = var_doc.value |
| out('\\raggedright ') |
| out(plaintext_to_latex(var_doc.name, nbsp=True, breakany=True)) |
| out(' & ') |
| has_descr = prop_doc.descr not in (None, UNKNOWN) |
| has_type = prop_doc.type_descr not in (None, UNKNOWN) |
| if has_descr or has_type: |
| out('\\raggedright ') |
| if has_descr: |
| out(self.docstring_to_latex(prop_doc.descr, 10).strip()) |
| if has_type: out('\n\n') |
| if has_type: |
| ptype = self.docstring_to_latex(prop_doc.type_descr, 12).strip() |
| out('%s{\\it (type=%s)}' % (' '*12, ptype)) |
| # [xx] List the fget/fset/fdel functions? |
| out('&\\\\\n') |
| out('\\cline{1-2}\n') |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Standard Fields |
| #//////////////////////////////////////////////////////////// |
| |
| # Copied from HTMLWriter: |
| def write_standard_fields(self, out, doc): |
| fields = [] |
| field_values = {} |
| |
| #if _sort_fields: fields = STANDARD_FIELD_NAMES [XX] |
| |
| for (field, arg, descr) in doc.metadata: |
| if field not in field_values: |
| fields.append(field) |
| if field.takes_arg: |
| subfields = field_values.setdefault(field,{}) |
| subfields.setdefault(arg,[]).append(descr) |
| else: |
| field_values.setdefault(field,[]).append(descr) |
| |
| for field in fields: |
| if field.takes_arg: |
| for arg, descrs in field_values[field].items(): |
| self.write_standard_field(out, doc, field, descrs, arg) |
| |
| else: |
| self.write_standard_field(out, doc, field, field_values[field]) |
| |
| def write_standard_field(self, out, doc, field, descrs, arg=''): |
| singular = field.singular |
| plural = field.plural |
| if arg: |
| singular += ' (%s)' % arg |
| plural += ' (%s)' % arg |
| out(self._descrlist([self.docstring_to_latex(d) for d in descrs], |
| field.singular, field.plural, field.short)) |
| |
| def _descrlist(self, items, singular, plural=None, short=0): |
| if plural is None: plural = singular |
| if len(items) == 0: return '' |
| if len(items) == 1 and singular is not None: |
| return '\\textbf{%s:} %s\n\n' % (singular, items[0]) |
| if short: |
| s = '\\textbf{%s:}\n' % plural |
| items = [item.strip() for item in items] |
| return s + ',\n '.join(items) + '\n\n' |
| else: |
| s = '\\textbf{%s:}\n' % plural |
| s += '\\begin{quote}\n' |
| s += ' \\begin{itemize}\n\n \item\n' |
| s += ' \\setlength{\\parskip}{0.6ex}\n' |
| s += '\n\n \item '.join(items) |
| return s + '\n\n\\end{itemize}\n\n\\end{quote}\n\n' |
| |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Docstring -> LaTeX Conversion |
| #//////////////////////////////////////////////////////////// |
| |
| # We only need one linker, since we don't use context: |
| class _LatexDocstringLinker(markup.DocstringLinker): |
| def translate_indexterm(self, indexterm): |
| indexstr = re.sub(r'["!|@]', r'"\1', indexterm.to_latex(self)) |
| return ('\\index{%s}\\textit{%s}' % (indexstr, indexstr)) |
| def translate_identifier_xref(self, identifier, label=None): |
| if label is None: label = markup.plaintext_to_latex(identifier) |
| return '\\texttt{%s}' % label |
| _docstring_linker = _LatexDocstringLinker() |
| |
| def docstring_to_latex(self, docstring, indent=0, breakany=0): |
| if docstring is None: return '' |
| return docstring.to_latex(self._docstring_linker, indent=indent, |
| hyperref=self._hyperref) |
| |
| #//////////////////////////////////////////////////////////// |
| #{ Helpers |
| #//////////////////////////////////////////////////////////// |
| |
| def write_header(self, out, where): |
| out('%\n% API Documentation') |
| if self._prj_name: out(' for %s' % self._prj_name) |
| if isinstance(where, APIDoc): |
| out('\n%% %s %s' % (self.doc_kind(where), where.canonical_name)) |
| else: |
| out('\n%% %s' % where) |
| out('\n%%\n%% Generated by epydoc %s\n' % epydoc.__version__) |
| out('%% [%s]\n%%\n' % time.asctime(time.localtime(time.time()))) |
| |
| def write_start_of(self, out, section_name): |
| out('\n' + 75*'%' + '\n') |
| out('%%' + ((71-len(section_name))/2)*' ') |
| out(section_name) |
| out(((72-len(section_name))/2)*' ' + '%%\n') |
| out(75*'%' + '\n\n') |
| |
| def section(self, title, depth=0): |
| sec = self.SECTIONS[depth+self._top_section] |
| return (('%s\n\n' % sec) % plaintext_to_latex(title)) |
| |
| def sectionstar(self, title, depth): |
| sec = self.STARSECTIONS[depth+self._top_section] |
| return (('%s\n\n' % sec) % plaintext_to_latex(title)) |
| |
| def doc_kind(self, doc): |
| if isinstance(doc, ModuleDoc) and doc.is_package == True: |
| return 'Package' |
| elif (isinstance(doc, ModuleDoc) and |
| doc.canonical_name[0].startswith('script')): |
| return 'Script' |
| elif isinstance(doc, ModuleDoc): |
| return 'Module' |
| elif isinstance(doc, ClassDoc): |
| return 'Class' |
| elif isinstance(doc, ClassMethodDoc): |
| return 'Class Method' |
| elif isinstance(doc, StaticMethodDoc): |
| return 'Static Method' |
| elif isinstance(doc, RoutineDoc): |
| if isinstance(self.docindex.container(doc), ClassDoc): |
| return 'Method' |
| else: |
| return 'Function' |
| else: |
| return 'Variable' |
| |
| def indexterm(self, doc, pos='only'): |
| """Mark a term or section for inclusion in the index.""" |
| if not self._index: return '' |
| if isinstance(doc, RoutineDoc) and not self._index_functions: |
| return '' |
| |
| pieces = [] |
| while doc is not None: |
| if doc.canonical_name == UNKNOWN: |
| return '' # Give up. |
| pieces.append('%s \\textit{(%s)}' % |
| (plaintext_to_latex('%s'%doc.canonical_name), |
| self.doc_kind(doc).lower())) |
| doc = self.docindex.container(doc) |
| if doc == UNKNOWN: |
| return '' # Give up. |
| |
| pieces.reverse() |
| if pos == 'only': |
| return '\\index{%s}\n' % '!'.join(pieces) |
| elif pos == 'start': |
| return '\\index{%s|(}\n' % '!'.join(pieces) |
| elif pos == 'end': |
| return '\\index{%s|)}\n' % '!'.join(pieces) |
| else: |
| raise AssertionError('Bad index position %s' % pos) |
| |
| def label(self, doc): |
| return ':'.join(doc.canonical_name) |
| |
| #: Map the Python encoding representation into mismatching LaTeX ones. |
| latex_encodings = { |
| 'utf-8': 'utf8', |
| } |
| |
| def get_latex_encoding(self): |
| """ |
| @return: The LaTeX representation of the selected encoding. |
| @rtype: C{str} |
| """ |
| enc = self._encoding.lower() |
| return self.latex_encodings.get(enc, enc) |