mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-04 08:51:02 +07:00
3492 lines
141 KiB
Python
3492 lines
141 KiB
Python
#
|
|
# epydoc -- HTML output generator
|
|
# Edward Loper
|
|
#
|
|
# Created [01/30/01 05:18 PM]
|
|
# $Id: html.py 1674 2008-01-29 06:03:36Z edloper $
|
|
#
|
|
|
|
"""
|
|
The HTML output generator for epydoc. The main interface provided by
|
|
this module is the L{HTMLWriter} class.
|
|
|
|
@todo: Add a cache to L{HTMLWriter.url()}?
|
|
"""
|
|
__docformat__ = 'epytext en'
|
|
|
|
import re, os, sys, codecs, sre_constants, pprint, base64
|
|
import urllib
|
|
import __builtin__
|
|
from epydoc.apidoc import *
|
|
import epydoc.docstringparser
|
|
import time, epydoc, epydoc.markup, epydoc.markup.epytext
|
|
from epydoc.docwriter.html_colorize import PythonSourceColorizer
|
|
from epydoc.docwriter import html_colorize
|
|
from epydoc.docwriter.html_css import STYLESHEETS
|
|
from epydoc.docwriter.html_help import HTML_HELP
|
|
from epydoc.docwriter.dotgraph import *
|
|
from epydoc import log
|
|
from epydoc.util import plaintext_to_html, is_src_filename
|
|
from epydoc.compat import * # Backwards compatibility
|
|
|
|
######################################################################
|
|
## Template Compiler
|
|
######################################################################
|
|
# The compile_template() method defined in this section is used to
|
|
# define several of HTMLWriter's methods.
|
|
|
|
def compile_template(docstring, template_string,
|
|
output_function='out', debug=epydoc.DEBUG):
|
|
"""
|
|
Given a template string containing inline python source code,
|
|
return a python function that will fill in the template, and
|
|
output the result. The signature for this function is taken from
|
|
the first line of C{docstring}. Output is generated by making
|
|
repeated calls to the output function with the given name (which
|
|
is typically one of the function's parameters).
|
|
|
|
The templating language used by this function passes through all
|
|
text as-is, with three exceptions:
|
|
|
|
- If every line in the template string is indented by at least
|
|
M{x} spaces, then the first M{x} spaces are stripped from each
|
|
line.
|
|
|
|
- Any line that begins with '>>>' (with no indentation)
|
|
should contain python code, and will be inserted as-is into
|
|
the template-filling function. If the line begins a control
|
|
block (such as 'if' or 'for'), then the control block will
|
|
be closed by the first '>>>'-marked line whose indentation is
|
|
less than or equal to the line's own indentation (including
|
|
lines that only contain comments.)
|
|
|
|
- In any other line, any expression between two '$' signs will
|
|
be evaluated and inserted into the line (using C{str()} to
|
|
convert the result to a string).
|
|
|
|
Here is a simple example:
|
|
|
|
>>> TEMPLATE = '''
|
|
... <book>
|
|
... <title>$book.title$</title>
|
|
... <pages>$book.count_pages()$</pages>
|
|
... >>> for chapter in book.chapters:
|
|
... <chaptername>$chapter.name$</chaptername>
|
|
... >>> #endfor
|
|
... </book>
|
|
>>> write_book = compile_template('write_book(out, book)', TEMPLATE)
|
|
|
|
@newfield acknowledgements: Acknowledgements
|
|
@acknowledgements: The syntax used by C{compile_template} is
|
|
loosely based on Cheetah.
|
|
"""
|
|
# Extract signature from the docstring:
|
|
signature = docstring.lstrip().split('\n',1)[0].strip()
|
|
func_name = signature.split('(',1)[0].strip()
|
|
|
|
# Regexp to search for inline substitutions:
|
|
INLINE = re.compile(r'\$([^\$]+)\$')
|
|
# Regexp to search for python statements in the template:
|
|
COMMAND = re.compile(r'(^>>>.*)\n?', re.MULTILINE)
|
|
|
|
# Strip indentation from the template.
|
|
template_string = strip_indent(template_string)
|
|
|
|
# If we're debugging, then we'll store the generated function,
|
|
# so we can print it along with any tracebacks that depend on it.
|
|
if debug:
|
|
signature = re.sub(r'\)\s*$', ', __debug=__debug)', signature)
|
|
|
|
# Funciton declaration line
|
|
pysrc_lines = ['def %s:' % signature]
|
|
indents = [-1]
|
|
|
|
if debug:
|
|
pysrc_lines.append(' try:')
|
|
indents.append(-1)
|
|
|
|
commands = COMMAND.split(template_string.strip()+'\n')
|
|
for i, command in enumerate(commands):
|
|
if command == '': continue
|
|
|
|
# String literal segment:
|
|
if i%2 == 0:
|
|
pieces = INLINE.split(command)
|
|
for j, piece in enumerate(pieces):
|
|
if j%2 == 0:
|
|
# String piece
|
|
pysrc_lines.append(' '*len(indents)+
|
|
'%s(%r)' % (output_function, piece))
|
|
else:
|
|
# Variable piece
|
|
pysrc_lines.append(' '*len(indents)+
|
|
'%s(unicode(%s))' % (output_function, piece))
|
|
|
|
# Python command:
|
|
else:
|
|
srcline = command[3:].lstrip()
|
|
# Update indentation
|
|
indent = len(command)-len(srcline)
|
|
while indent <= indents[-1]: indents.pop()
|
|
# Add on the line.
|
|
srcline = srcline.rstrip()
|
|
pysrc_lines.append(' '*len(indents)+srcline)
|
|
if srcline.endswith(':'):
|
|
indents.append(indent)
|
|
|
|
if debug:
|
|
pysrc_lines.append(' except Exception,e:')
|
|
pysrc_lines.append(' pysrc, func_name = __debug ')
|
|
pysrc_lines.append(' lineno = sys.exc_info()[2].tb_lineno')
|
|
pysrc_lines.append(' print ("Exception in template %s() on "')
|
|
pysrc_lines.append(' "line %d:" % (func_name, lineno))')
|
|
pysrc_lines.append(' print pysrc[lineno-1]')
|
|
pysrc_lines.append(' raise')
|
|
|
|
pysrc = '\n'.join(pysrc_lines)+'\n'
|
|
#log.debug(pysrc)
|
|
if debug: localdict = {'__debug': (pysrc_lines, func_name)}
|
|
else: localdict = {}
|
|
try: exec pysrc in globals(), localdict
|
|
except SyntaxError:
|
|
log.error('Error in script:\n' + pysrc + '\n')
|
|
raise
|
|
template_func = localdict[func_name]
|
|
template_func.__doc__ = docstring
|
|
return template_func
|
|
|
|
def strip_indent(s):
|
|
"""
|
|
Given a multiline string C{s}, find the minimum indentation for
|
|
all non-blank lines, and return a new string formed by stripping
|
|
that amount of indentation from all lines in C{s}.
|
|
"""
|
|
# Strip indentation from the template.
|
|
minindent = sys.maxint
|
|
lines = s.split('\n')
|
|
for line in lines:
|
|
stripline = line.lstrip()
|
|
if stripline:
|
|
minindent = min(minindent, len(line)-len(stripline))
|
|
return '\n'.join([l[minindent:] for l in lines])
|
|
|
|
######################################################################
|
|
## HTML Writer
|
|
######################################################################
|
|
|
|
class HTMLWriter:
|
|
#////////////////////////////////////////////////////////////
|
|
# Table of Contents
|
|
#////////////////////////////////////////////////////////////
|
|
#
|
|
# 1. Interface Methods
|
|
#
|
|
# 2. Page Generation -- write complete web page files
|
|
# 2.1. Module Pages
|
|
# 2.2. Class Pages
|
|
# 2.3. Trees Page
|
|
# 2.4. Indices Page
|
|
# 2.5. Help Page
|
|
# 2.6. Frames-based table of contents pages
|
|
# 2.7. Homepage (index.html)
|
|
# 2.8. CSS Stylesheet
|
|
# 2.9. Javascript file
|
|
# 2.10. Graphs
|
|
# 2.11. Images
|
|
#
|
|
# 3. Page Element Generation -- write pieces of a web page file
|
|
# 3.1. Page Header
|
|
# 3.2. Page Footer
|
|
# 3.3. Navigation Bar
|
|
# 3.4. Breadcrumbs
|
|
# 3.5. Summary Tables
|
|
#
|
|
# 4. Helper functions
|
|
|
|
def __init__(self, docindex, **kwargs):
|
|
"""
|
|
Construct a new HTML writer, using the given documentation
|
|
index.
|
|
|
|
@param docindex: The documentation index.
|
|
|
|
@type prj_name: C{string}
|
|
@keyword prj_name: The name of the project. Defaults to
|
|
none.
|
|
@type prj_url: C{string}
|
|
@keyword prj_url: The target for the project hopeage link on
|
|
the navigation bar. If C{prj_url} is not specified,
|
|
then no hyperlink is created.
|
|
@type prj_link: C{string}
|
|
@keyword prj_link: The label for the project link on the
|
|
navigation bar. This link can contain arbitrary HTML
|
|
code (e.g. images). By default, a label is constructed
|
|
from C{prj_name}.
|
|
@type top_page: C{string}
|
|
@keyword top_page: The top page for the documentation. This
|
|
is the default page shown main frame, when frames are
|
|
enabled. C{top} can be a URL, the name of a
|
|
module, the name of a class, or one of the special
|
|
strings C{"trees.html"}, C{"indices.html"}, or
|
|
C{"help.html"}. By default, the top-level package or
|
|
module is used, if there is one; otherwise, C{"trees"}
|
|
is used.
|
|
@type css: C{string}
|
|
@keyword css: The CSS stylesheet file. If C{css} is a file
|
|
name, then the specified file's conents will be used.
|
|
Otherwise, if C{css} is the name of a CSS stylesheet in
|
|
L{epydoc.docwriter.html_css}, then that stylesheet will
|
|
be used. Otherwise, an error is reported. If no stylesheet
|
|
is specified, then the default stylesheet is used.
|
|
@type help_file: C{string}
|
|
@keyword help_file: The name of the help file. If no help file is
|
|
specified, then the default help file will be used.
|
|
@type show_private: C{boolean}
|
|
@keyword show_private: Whether to create documentation for
|
|
private objects. By default, private objects are documented.
|
|
@type show_frames: C{boolean})
|
|
@keyword show_frames: Whether to create a frames-based table of
|
|
contents. By default, it is produced.
|
|
@type show_imports: C{boolean}
|
|
@keyword show_imports: Whether or not to display lists of
|
|
imported functions and classes. By default, they are
|
|
not shown.
|
|
@type variable_maxlines: C{int}
|
|
@keyword variable_maxlines: The maximum number of lines that
|
|
should be displayed for the value of a variable in the
|
|
variable details section. By default, 8 lines are
|
|
displayed.
|
|
@type variable_linelength: C{int}
|
|
@keyword variable_linelength: The maximum line length used for
|
|
displaying the values of variables in the variable
|
|
details sections. If a line is longer than this length,
|
|
then it will be wrapped to the next line. The default
|
|
line length is 70 characters.
|
|
@type variable_summary_linelength: C{int}
|
|
@keyword variable_summary_linelength: The maximum line length
|
|
used for displaying the values of variables in the summary
|
|
section. If a line is longer than this length, then it
|
|
will be truncated. The default is 40 characters.
|
|
@type variable_tooltip_linelength: C{int}
|
|
@keyword variable_tooltip_linelength: The maximum line length
|
|
used for tooltips for the values of variables. If a
|
|
line is longer than this length, then it will be
|
|
truncated. The default is 600 characters.
|
|
@type property_function_linelength: C{int}
|
|
@keyword property_function_linelength: The maximum line length
|
|
used to dispaly property functions (C{fget}, C{fset}, and
|
|
C{fdel}) that contain something other than a function
|
|
object. The default length is 40 characters.
|
|
@type inheritance: C{string}
|
|
@keyword inheritance: How inherited objects should be displayed.
|
|
If C{inheritance='grouped'}, then inherited objects are
|
|
gathered into groups; if C{inheritance='listed'}, then
|
|
inherited objects are listed in a short list at the
|
|
end of their group; if C{inheritance='included'}, then
|
|
inherited objects are mixed in with non-inherited
|
|
objects. The default is 'grouped'.
|
|
@type include_source_code: C{boolean}
|
|
@keyword include_source_code: If true, then generate colorized
|
|
source code files for each python module.
|
|
@type include_log: C{boolean}
|
|
@keyword include_log: If true, the the footer will include an
|
|
href to the page 'epydoc-log.html'.
|
|
@type src_code_tab_width: C{int}
|
|
@keyword src_code_tab_width: Number of spaces to replace each tab
|
|
with in source code listings.
|
|
"""
|
|
self.docindex = docindex
|
|
|
|
# Process keyword arguments.
|
|
self._show_private = kwargs.get('show_private', 1)
|
|
"""Should private docs be included?"""
|
|
|
|
self._prj_name = kwargs.get('prj_name', None)
|
|
"""The project's name (for the project link in the navbar)"""
|
|
|
|
self._prj_url = kwargs.get('prj_url', None)
|
|
"""URL for the project link in the navbar"""
|
|
|
|
self._prj_link = kwargs.get('prj_link', None)
|
|
"""HTML code for the project link in the navbar"""
|
|
|
|
self._top_page = kwargs.get('top_page', None)
|
|
"""The 'main' page"""
|
|
|
|
self._css = kwargs.get('css')
|
|
"""CSS stylesheet to use"""
|
|
|
|
self._helpfile = kwargs.get('help_file', None)
|
|
"""Filename of file to extract help contents from"""
|
|
|
|
self._frames_index = kwargs.get('show_frames', 1)
|
|
"""Should a frames index be created?"""
|
|
|
|
self._show_imports = kwargs.get('show_imports', False)
|
|
"""Should imports be listed?"""
|
|
|
|
self._propfunc_linelen = kwargs.get('property_function_linelength', 40)
|
|
"""[XXX] Not used!"""
|
|
|
|
self._variable_maxlines = kwargs.get('variable_maxlines', 8)
|
|
"""Max lines for variable values"""
|
|
|
|
self._variable_linelen = kwargs.get('variable_linelength', 70)
|
|
"""Max line length for variable values"""
|
|
|
|
self._variable_summary_linelen = \
|
|
kwargs.get('variable_summary_linelength', 65)
|
|
"""Max length for variable value summaries"""
|
|
|
|
self._variable_tooltip_linelen = \
|
|
kwargs.get('variable_tooltip_linelength', 600)
|
|
"""Max length for variable tooltips"""
|
|
|
|
self._inheritance = kwargs.get('inheritance', 'listed')
|
|
"""How should inheritance be displayed? 'listed', 'included',
|
|
or 'grouped'"""
|
|
|
|
self._incl_sourcecode = kwargs.get('include_source_code', True)
|
|
"""Should pages be generated for source code of modules?"""
|
|
|
|
self._mark_docstrings = kwargs.get('mark_docstrings', False)
|
|
"""Wrap <span class='docstring'>...</span> around docstrings?"""
|
|
|
|
self._graph_types = kwargs.get('graphs', ()) or ()
|
|
"""Graphs that we should include in our output."""
|
|
|
|
self._include_log = kwargs.get('include_log', False)
|
|
"""Are we generating an HTML log page?"""
|
|
|
|
self._src_code_tab_width = kwargs.get('src_code_tab_width', 8)
|
|
"""Number of spaces to replace each tab with in source code
|
|
listings."""
|
|
|
|
self._callgraph_cache = {}
|
|
"""Map the callgraph L{uid<DotGraph.uid>} to their HTML
|
|
representation."""
|
|
|
|
self._redundant_details = kwargs.get('redundant_details', False)
|
|
"""If true, then include objects in the details list even if all
|
|
info about them is already provided by the summary table."""
|
|
|
|
# For use with select_variables():
|
|
if self._show_private:
|
|
self._public_filter = None
|
|
else:
|
|
self._public_filter = True
|
|
|
|
# Make sure inheritance has a sane value.
|
|
if self._inheritance not in ('listed', 'included', 'grouped'):
|
|
raise ValueError, 'Bad value for inheritance'
|
|
|
|
# Create the project homepage link, if it was not specified.
|
|
if (self._prj_name or self._prj_url) and not self._prj_link:
|
|
self._prj_link = plaintext_to_html(self._prj_name or
|
|
'Project Homepage')
|
|
|
|
# Add a hyperlink to _prj_url, if _prj_link doesn't already
|
|
# contain any hyperlinks.
|
|
if (self._prj_link and self._prj_url and
|
|
not re.search(r'<a[^>]*\shref', self._prj_link)):
|
|
self._prj_link = ('<a class="navbar" target="_top" href="'+
|
|
self._prj_url+'">'+self._prj_link+'</a>')
|
|
|
|
# Precompute lists & sets of APIDoc objects that we're
|
|
# interested in.
|
|
self.valdocs = valdocs = sorted(docindex.reachable_valdocs(
|
|
imports=False, packages=False, bases=False, submodules=False,
|
|
subclasses=False, private=self._show_private))
|
|
self.module_list = [d for d in valdocs if isinstance(d, ModuleDoc)]
|
|
"""The list of L{ModuleDoc}s for the documented modules."""
|
|
self.module_set = set(self.module_list)
|
|
"""The set of L{ModuleDoc}s for the documented modules."""
|
|
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."""
|
|
self.routine_list = [d for d in valdocs if isinstance(d, RoutineDoc)]
|
|
"""The list of L{RoutineDoc}s for the documented routines."""
|
|
self.indexed_docs = []
|
|
"""The list of L{APIDoc}s for variables and values that should
|
|
be included in the index."""
|
|
|
|
# URL for 'trees' page
|
|
if self.module_list: self._trees_url = 'module-tree.html'
|
|
else: self._trees_url = 'class-tree.html'
|
|
|
|
# Construct the value for self.indexed_docs.
|
|
self.indexed_docs += [d for d in valdocs
|
|
if not isinstance(d, GenericValueDoc)]
|
|
for doc in valdocs:
|
|
if isinstance(doc, NamespaceDoc):
|
|
# add any vars with generic values; but don't include
|
|
# inherited vars.
|
|
self.indexed_docs += [d for d in doc.variables.values() if
|
|
isinstance(d.value, GenericValueDoc)
|
|
and d.container == doc]
|
|
self.indexed_docs.sort()
|
|
|
|
# Figure out the url for the top page.
|
|
self._top_page_url = self._find_top_page(self._top_page)
|
|
|
|
# Decide whether or not to split the identifier index.
|
|
self._split_ident_index = (len(self.indexed_docs) >=
|
|
self.SPLIT_IDENT_INDEX_SIZE)
|
|
|
|
# Figure out how many output files there will be (for progress
|
|
# reporting).
|
|
self.modules_with_sourcecode = set()
|
|
for doc in self.module_list:
|
|
if isinstance(doc, ModuleDoc) and is_src_filename(doc.filename):
|
|
self.modules_with_sourcecode.add(doc)
|
|
self._num_files = (len(self.class_list) + len(self.module_list) +
|
|
10 + len(self.METADATA_INDICES))
|
|
if self._frames_index:
|
|
self._num_files += len(self.module_list) + 3
|
|
|
|
if self._incl_sourcecode:
|
|
self._num_files += len(self.modules_with_sourcecode)
|
|
if self._split_ident_index:
|
|
self._num_files += len(self.LETTERS)
|
|
|
|
def _find_top_page(self, pagename):
|
|
"""
|
|
Find the top page for the API documentation. This page is
|
|
used as the default page shown in the main frame, when frames
|
|
are used. When frames are not used, this page is copied to
|
|
C{index.html}.
|
|
|
|
@param pagename: The name of the page, as specified by the
|
|
keyword argument C{top} to the constructor.
|
|
@type pagename: C{string}
|
|
@return: The URL of the top page.
|
|
@rtype: C{string}
|
|
"""
|
|
# If a page name was specified, then we need to figure out
|
|
# what it points to.
|
|
if pagename:
|
|
# If it's a URL, then use it directly.
|
|
if pagename.lower().startswith('http:'):
|
|
return pagename
|
|
|
|
# If it's an object, then use that object's page.
|
|
try:
|
|
doc = self.docindex.get_valdoc(pagename)
|
|
return self.url(doc)
|
|
except:
|
|
pass
|
|
|
|
# Otherwise, give up.
|
|
log.warning('Could not find top page %r; using %s '
|
|
'instead' % (pagename, self._trees_url))
|
|
return self._trees_url
|
|
|
|
# If no page name was specified, then try to choose one
|
|
# automatically.
|
|
else:
|
|
root = [val_doc for val_doc in self.docindex.root
|
|
if isinstance(val_doc, (ClassDoc, ModuleDoc))]
|
|
if len(root) == 0:
|
|
# No docs?? Try the trees page.
|
|
return self._trees_url
|
|
elif len(root) == 1:
|
|
# One item in the root; use that.
|
|
return self.url(root[0])
|
|
else:
|
|
# Multiple root items; if they're all in one package,
|
|
# then use that. Otherwise, use self._trees_url
|
|
root = sorted(root, key=lambda v:len(v.canonical_name))
|
|
top = root[0]
|
|
for doc in root[1:]:
|
|
if not top.canonical_name.dominates(doc.canonical_name):
|
|
return self._trees_url
|
|
else:
|
|
return self.url(top)
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 1. Interface Methods
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write(self, directory=None):
|
|
"""
|
|
Write the documentation 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 = self._variable_summary_linelen
|
|
ValueDoc.REPR_LINELEN = self._variable_linelen
|
|
ValueDoc.REPR_MAXLINES = self._variable_maxlines
|
|
|
|
# Use an image for the crarr symbol.
|
|
from epydoc.markup.epytext import ParsedEpytextDocstring
|
|
orig_crarr_html = ParsedEpytextDocstring.SYMBOL_TO_HTML['crarr']
|
|
ParsedEpytextDocstring.SYMBOL_TO_HTML['crarr'] = (
|
|
r'<span class="variable-linewrap">'
|
|
r'<img src="crarr.png" alt="\" /></span>')
|
|
|
|
# Keep track of failed xrefs, and report them at the end.
|
|
self._failed_xrefs = {}
|
|
|
|
# Create destination directories, if necessary
|
|
if not directory: directory = os.curdir
|
|
self._mkdir(directory)
|
|
self._directory = directory
|
|
|
|
# Write the CSS file.
|
|
self._files_written += 1
|
|
log.progress(self._files_written/self._num_files, 'epydoc.css')
|
|
self.write_css(directory, self._css)
|
|
|
|
# Write the Javascript file.
|
|
self._files_written += 1
|
|
log.progress(self._files_written/self._num_files, 'epydoc.js')
|
|
self.write_javascript(directory)
|
|
|
|
# Write images
|
|
self.write_images(directory)
|
|
|
|
# Build the indices.
|
|
indices = {'ident': self.build_identifier_index(),
|
|
'term': self.build_term_index()}
|
|
for (name, label, label2) in self.METADATA_INDICES:
|
|
indices[name] = self.build_metadata_index(name)
|
|
|
|
# Write the identifier index. If requested, split it into
|
|
# separate pages for each letter.
|
|
ident_by_letter = self._group_by_letter(indices['ident'])
|
|
if not self._split_ident_index:
|
|
self._write(self.write_link_index, directory,
|
|
'identifier-index.html', indices,
|
|
'Identifier Index', 'identifier-index.html',
|
|
ident_by_letter)
|
|
else:
|
|
# Write a page for each section.
|
|
for letter in self.LETTERS:
|
|
filename = 'identifier-index-%s.html' % letter
|
|
self._write(self.write_link_index, directory, filename,
|
|
indices, 'Identifier Index', filename,
|
|
ident_by_letter, [letter],
|
|
'identifier-index-%s.html')
|
|
# Use the first non-empty section as the main index page.
|
|
for letter in self.LETTERS:
|
|
if letter in ident_by_letter:
|
|
filename = 'identifier-index.html'
|
|
self._write(self.write_link_index, directory, filename,
|
|
indices, 'Identifier Index', filename,
|
|
ident_by_letter, [letter],
|
|
'identifier-index-%s.html')
|
|
break
|
|
|
|
# Write the term index.
|
|
if indices['term']:
|
|
term_by_letter = self._group_by_letter(indices['term'])
|
|
self._write(self.write_link_index, directory, 'term-index.html',
|
|
indices, 'Term Definition Index',
|
|
'term-index.html', term_by_letter)
|
|
else:
|
|
self._files_written += 1 # (skipped)
|
|
|
|
# Write the metadata indices.
|
|
for (name, label, label2) in self.METADATA_INDICES:
|
|
if indices[name]:
|
|
self._write(self.write_metadata_index, directory,
|
|
'%s-index.html' % name, indices, name,
|
|
label, label2)
|
|
else:
|
|
self._files_written += 1 # (skipped)
|
|
|
|
# Write the trees file (package & class hierarchies)
|
|
if self.module_list:
|
|
self._write(self.write_module_tree, directory, 'module-tree.html')
|
|
else:
|
|
self._files_written += 1 # (skipped)
|
|
if self.class_list:
|
|
self._write(self.write_class_tree, directory, 'class-tree.html')
|
|
else:
|
|
self._files_written += 1 # (skipped)
|
|
|
|
# Write the help file.
|
|
self._write(self.write_help, directory,'help.html')
|
|
|
|
# Write the frames-based table of contents.
|
|
if self._frames_index:
|
|
self._write(self.write_frames_index, directory, 'frames.html')
|
|
self._write(self.write_toc, directory, 'toc.html')
|
|
self._write(self.write_project_toc, directory, 'toc-everything.html')
|
|
for doc in self.module_list:
|
|
filename = 'toc-%s' % urllib.unquote(self.url(doc))
|
|
self._write(self.write_module_toc, directory, filename, doc)
|
|
|
|
# Write the object documentation.
|
|
for doc in self.module_list:
|
|
filename = urllib.unquote(self.url(doc))
|
|
self._write(self.write_module, directory, filename, doc)
|
|
for doc in self.class_list:
|
|
filename = urllib.unquote(self.url(doc))
|
|
self._write(self.write_class, directory, filename, doc)
|
|
|
|
# Write source code files.
|
|
if self._incl_sourcecode:
|
|
# Build a map from short names to APIDocs, used when
|
|
# linking names in the source code.
|
|
name_to_docs = {}
|
|
for api_doc in self.indexed_docs:
|
|
if (api_doc.canonical_name is not None and
|
|
self.url(api_doc) is not None):
|
|
name = api_doc.canonical_name[-1]
|
|
name_to_docs.setdefault(name, []).append(api_doc)
|
|
# Sort each entry of the name_to_docs list.
|
|
for doc_list in name_to_docs.values():
|
|
doc_list.sort()
|
|
# Write the source code for each module.
|
|
for doc in self.modules_with_sourcecode:
|
|
filename = urllib.unquote(self.pysrc_url(doc))
|
|
self._write(self.write_sourcecode, directory, filename, doc,
|
|
name_to_docs)
|
|
|
|
# Write the auto-redirect page.
|
|
self._write(self.write_redirect_page, directory, 'redirect.html')
|
|
|
|
# Write the mapping object name -> URL
|
|
self._write(self.write_api_list, directory, 'api-objects.txt')
|
|
|
|
# Write the index.html files.
|
|
# (this must be done last, since it might copy another file)
|
|
self._files_written += 1
|
|
log.progress(self._files_written/self._num_files, 'index.html')
|
|
self.write_homepage(directory)
|
|
|
|
# Don't report references to builtins as missing
|
|
for k in self._failed_xrefs.keys(): # have a copy of keys
|
|
if hasattr(__builtin__, k):
|
|
del self._failed_xrefs[k]
|
|
|
|
# Report any failed crossreferences
|
|
if self._failed_xrefs:
|
|
estr = 'Failed identifier crossreference targets:\n'
|
|
failed_identifiers = self._failed_xrefs.keys()
|
|
failed_identifiers.sort()
|
|
for identifier in failed_identifiers:
|
|
names = self._failed_xrefs[identifier].keys()
|
|
names.sort()
|
|
estr += '- %s' % identifier
|
|
estr += '\n'
|
|
for name in names:
|
|
estr += ' (from %s)\n' % name
|
|
log.docstring_warning(estr)
|
|
|
|
# [xx] testing:
|
|
if self._num_files != int(self._files_written):
|
|
log.debug("Expected to write %d files, but actually "
|
|
"wrote %d files" %
|
|
(self._num_files, int(self._files_written)))
|
|
|
|
# Restore defaults that we changed.
|
|
(ValueDoc.SUMMARY_REPR_LINELEN, ValueDoc.REPR_LINELEN,
|
|
ValueDoc.REPR_MAXLINES) = orig_valdoc_defaults
|
|
ParsedEpytextDocstring.SYMBOL_TO_HTML['crarr'] = orig_crarr_html
|
|
|
|
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)
|
|
f = codecs.open(path, 'w', 'ascii', errors='xmlcharrefreplace')
|
|
write_func(f.write, *args)
|
|
f.close()
|
|
|
|
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)
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 2.1. Module Pages
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_module(self, out, doc):
|
|
"""
|
|
Write an HTML page containing the API documentation for the
|
|
given module to C{out}.
|
|
|
|
@param doc: A L{ModuleDoc} containing the API documentation
|
|
for the module that should be described.
|
|
"""
|
|
longname = doc.canonical_name
|
|
shortname = doc.canonical_name[-1]
|
|
|
|
# Write the page header (incl. navigation bar & breadcrumbs)
|
|
self.write_header(out, str(longname))
|
|
self.write_navbar(out, doc)
|
|
self.write_breadcrumbs(out, doc, self.url(doc))
|
|
|
|
# Write the name of the module we're describing.
|
|
if doc.is_package is True: typ = 'Package'
|
|
else: typ = 'Module'
|
|
if longname[0].startswith('script-'):
|
|
shortname = str(longname)[7:]
|
|
typ = 'Script'
|
|
out('<!-- ==================== %s ' % typ.upper() +
|
|
'DESCRIPTION ==================== -->\n')
|
|
out('<h1 class="epydoc">%s %s</h1>' % (typ, shortname))
|
|
out('<p class="nomargin-top">%s</p>\n' % self.pysrc_link(doc))
|
|
|
|
# If the module has a description, then list it.
|
|
if doc.descr not in (None, UNKNOWN):
|
|
out(self.descr(doc, 2)+'\n\n')
|
|
|
|
# Write any standarad metadata (todo, author, etc.)
|
|
if doc.metadata is not UNKNOWN and doc.metadata:
|
|
out('<hr />\n')
|
|
self.write_standard_fields(out, doc)
|
|
|
|
# If it's a package, then list the modules it contains.
|
|
if doc.is_package is True:
|
|
self.write_module_list(out, doc)
|
|
|
|
# Write summary tables describing the variables that the
|
|
# module defines.
|
|
self.write_summary_table(out, "Classes", doc, "class")
|
|
self.write_summary_table(out, "Functions", doc, "function")
|
|
self.write_summary_table(out, "Variables", doc, "other")
|
|
|
|
# Write a list of all imported objects.
|
|
if self._show_imports:
|
|
self.write_imports(out, doc)
|
|
|
|
# Write detailed descriptions of functions & variables defined
|
|
# in this module.
|
|
self.write_details_list(out, "Function Details", doc, "function")
|
|
self.write_details_list(out, "Variables Details", doc, "other")
|
|
|
|
# Write the page footer (including navigation bar)
|
|
self.write_navbar(out, doc)
|
|
self.write_footer(out)
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 2.??. Source Code Pages
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_sourcecode(self, out, doc, name_to_docs):
|
|
#t0 = time.time()
|
|
|
|
filename = doc.filename
|
|
name = str(doc.canonical_name)
|
|
|
|
# Header
|
|
self.write_header(out, name)
|
|
self.write_navbar(out, doc)
|
|
self.write_breadcrumbs(out, doc, self.pysrc_url(doc))
|
|
|
|
# Source code listing
|
|
out('<h1 class="epydoc">Source Code for %s</h1>\n' %
|
|
self.href(doc, label='%s %s' % (self.doc_kind(doc), name)))
|
|
out('<pre class="py-src">\n')
|
|
out(PythonSourceColorizer(filename, name, self.docindex,
|
|
self.url, name_to_docs,
|
|
self._src_code_tab_width).colorize())
|
|
out('</pre>\n<br />\n')
|
|
|
|
# Footer
|
|
self.write_navbar(out, doc)
|
|
self.write_footer(out)
|
|
|
|
#log.debug('[%6.2f sec] Wrote pysrc for %s' %
|
|
# (time.time()-t0, name))
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 2.2. Class Pages
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_class(self, out, doc):
|
|
"""
|
|
Write an HTML page containing the API documentation for the
|
|
given class to C{out}.
|
|
|
|
@param doc: A L{ClassDoc} containing the API documentation
|
|
for the class that should be described.
|
|
"""
|
|
longname = doc.canonical_name
|
|
shortname = doc.canonical_name[-1]
|
|
|
|
# Write the page header (incl. navigation bar & breadcrumbs)
|
|
self.write_header(out, str(longname))
|
|
self.write_navbar(out, doc)
|
|
self.write_breadcrumbs(out, doc, self.url(doc))
|
|
|
|
# Write the name of the class we're describing.
|
|
if doc.is_type(): typ = 'Type'
|
|
elif doc.is_exception(): typ = 'Exception'
|
|
else: typ = 'Class'
|
|
out('<!-- ==================== %s ' % typ.upper() +
|
|
'DESCRIPTION ==================== -->\n')
|
|
out('<h1 class="epydoc">%s %s</h1>' % (typ, shortname))
|
|
out('<p class="nomargin-top">%s</p>\n' % self.pysrc_link(doc))
|
|
|
|
if ((doc.bases not in (UNKNOWN, None) and len(doc.bases) > 0) or
|
|
(doc.subclasses not in (UNKNOWN,None) and len(doc.subclasses)>0)):
|
|
# Display bases graphically, if requested.
|
|
if 'umlclasstree' in self._graph_types:
|
|
self.write_class_tree_graph(out, doc, uml_class_tree_graph)
|
|
elif 'classtree' in self._graph_types:
|
|
self.write_class_tree_graph(out, doc, class_tree_graph)
|
|
|
|
# Otherwise, use ascii-art.
|
|
else:
|
|
# Write the base class tree.
|
|
if doc.bases not in (UNKNOWN, None) and len(doc.bases) > 0:
|
|
out('<pre class="base-tree">\n%s</pre>\n\n' %
|
|
self.base_tree(doc))
|
|
|
|
# Write the known subclasses
|
|
if (doc.subclasses not in (UNKNOWN, None) and
|
|
len(doc.subclasses) > 0):
|
|
out('<dl><dt>Known Subclasses:</dt>\n<dd>\n ')
|
|
out(' <ul class="subclass-list">\n')
|
|
for i, subclass in enumerate(doc.subclasses):
|
|
href = self.href(subclass, context=doc)
|
|
if self._val_is_public(subclass): css = ''
|
|
else: css = ' class="private"'
|
|
if i > 0: href = ', '+href
|
|
out('<li%s>%s</li>' % (css, href))
|
|
out(' </ul>\n')
|
|
out('</dd></dl>\n\n')
|
|
|
|
out('<hr />\n')
|
|
|
|
# If the class has a description, then list it.
|
|
if doc.descr not in (None, UNKNOWN):
|
|
out(self.descr(doc, 2)+'\n\n')
|
|
|
|
# Write any standarad metadata (todo, author, etc.)
|
|
if doc.metadata is not UNKNOWN and doc.metadata:
|
|
out('<hr />\n')
|
|
self.write_standard_fields(out, doc)
|
|
|
|
# Write summary tables describing the variables that the
|
|
# class defines.
|
|
self.write_summary_table(out, "Nested Classes", doc, "class")
|
|
self.write_summary_table(out, "Instance Methods", doc,
|
|
"instancemethod")
|
|
self.write_summary_table(out, "Class Methods", doc, "classmethod")
|
|
self.write_summary_table(out, "Static Methods", doc, "staticmethod")
|
|
self.write_summary_table(out, "Class Variables", doc,
|
|
"classvariable")
|
|
self.write_summary_table(out, "Instance Variables", doc,
|
|
"instancevariable")
|
|
self.write_summary_table(out, "Properties", doc, "property")
|
|
|
|
# Write a list of all imported objects.
|
|
if self._show_imports:
|
|
self.write_imports(out, doc)
|
|
|
|
# Write detailed descriptions of functions & variables defined
|
|
# in this class.
|
|
# [xx] why group methods into one section but split vars into two?
|
|
# seems like we should either group in both cases or split in both
|
|
# cases.
|
|
self.write_details_list(out, "Method Details", doc, "method")
|
|
self.write_details_list(out, "Class Variable Details", doc,
|
|
"classvariable")
|
|
self.write_details_list(out, "Instance Variable Details", doc,
|
|
"instancevariable")
|
|
self.write_details_list(out, "Property Details", doc, "property")
|
|
|
|
# Write the page footer (including navigation bar)
|
|
self.write_navbar(out, doc)
|
|
self.write_footer(out)
|
|
|
|
def write_class_tree_graph(self, out, doc, graphmaker):
|
|
"""
|
|
Write HTML code for a class tree graph of C{doc} (a classdoc),
|
|
using C{graphmaker} to draw the actual graph. C{graphmaker}
|
|
should be L{class_tree_graph()}, or L{uml_class_tree_graph()},
|
|
or any other function with a compatible signature.
|
|
|
|
If the given class has any private sublcasses (including
|
|
recursive subclasses), then two graph images will be generated
|
|
-- one to display when private values are shown, and the other
|
|
to display when private values are hidden.
|
|
"""
|
|
linker = _HTMLDocstringLinker(self, doc)
|
|
private_subcls = self._private_subclasses(doc)
|
|
if private_subcls:
|
|
out('<center>\n'
|
|
' <div class="private">%s</div>\n'
|
|
' <div class="public" style="display:none">%s</div>\n'
|
|
'</center>\n' %
|
|
(self.render_graph(graphmaker(doc, linker, doc)),
|
|
self.render_graph(graphmaker(doc, linker, doc,
|
|
exclude=private_subcls))))
|
|
else:
|
|
out('<center>\n%s\n</center>\n' %
|
|
self.render_graph(graphmaker(doc, linker, doc)))
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 2.3. Trees pages
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_module_tree(self, out):
|
|
# Header material
|
|
self.write_treepage_header(out, 'Module Hierarchy', 'module-tree.html')
|
|
out('<h1 class="epydoc">Module Hierarchy</h1>\n')
|
|
|
|
# Write entries for all top-level modules/packages.
|
|
out('<ul class="nomargin-top">\n')
|
|
for doc in self.module_list:
|
|
if (doc.package in (None, UNKNOWN) or
|
|
doc.package not in self.module_set):
|
|
self.write_module_tree_item(out, doc)
|
|
out('</ul>\n')
|
|
|
|
# Footer material
|
|
self.write_navbar(out, 'trees')
|
|
self.write_footer(out)
|
|
|
|
def write_class_tree(self, out):
|
|
"""
|
|
Write HTML code for a nested list showing the base/subclass
|
|
relationships between all documented classes. Each element of
|
|
the top-level list is a class with no (documented) bases; and
|
|
under each class is listed all of its subclasses. Note that
|
|
in the case of multiple inheritance, a class may appear
|
|
multiple times.
|
|
|
|
@todo: For multiple inheritance, don't repeat subclasses the
|
|
second time a class is mentioned; instead, link to the
|
|
first mention.
|
|
"""
|
|
# [XX] backref for multiple inheritance?
|
|
# Header material
|
|
self.write_treepage_header(out, 'Class Hierarchy', 'class-tree.html')
|
|
out('<h1 class="epydoc">Class Hierarchy</h1>\n')
|
|
|
|
# Build a set containing all classes that we should list.
|
|
# This includes everything in class_list, plus any of those
|
|
# class' bases, but not undocumented subclasses.
|
|
class_set = self.class_set.copy()
|
|
for doc in self.class_list:
|
|
if doc.bases != UNKNOWN:
|
|
for base in doc.bases:
|
|
if base not in class_set:
|
|
if isinstance(base, ClassDoc):
|
|
class_set.update(base.mro())
|
|
else:
|
|
# [XX] need to deal with this -- how?
|
|
pass
|
|
#class_set.add(base)
|
|
|
|
out('<ul class="nomargin-top">\n')
|
|
for doc in sorted(class_set, key=lambda c:c.canonical_name[-1]):
|
|
if doc.bases != UNKNOWN and len(doc.bases)==0:
|
|
self.write_class_tree_item(out, doc, class_set)
|
|
out('</ul>\n')
|
|
|
|
# Footer material
|
|
self.write_navbar(out, 'trees')
|
|
self.write_footer(out)
|
|
|
|
def write_treepage_header(self, out, title, url):
|
|
# Header material.
|
|
self.write_header(out, title)
|
|
self.write_navbar(out, 'trees')
|
|
self.write_breadcrumbs(out, 'trees', url)
|
|
if self.class_list and self.module_list:
|
|
out('<center><b>\n')
|
|
out(' [ <a href="module-tree.html">Module Hierarchy</a>\n')
|
|
out(' | <a href="class-tree.html">Class Hierarchy</a> ]\n')
|
|
out('</b></center><br />\n')
|
|
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 2.4. Index pages
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
SPLIT_IDENT_INDEX_SIZE = 3000
|
|
"""If the identifier index has more than this number of entries,
|
|
then it will be split into separate pages, one for each
|
|
alphabetical section."""
|
|
|
|
LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
|
|
"""The alphabetical sections that are used for link index pages."""
|
|
|
|
def write_link_index(self, out, indices, title, url, index_by_section,
|
|
sections=LETTERS, section_url='#%s'):
|
|
|
|
# Header
|
|
self.write_indexpage_header(out, indices, title, url)
|
|
|
|
# Index title & links to alphabetical sections.
|
|
out('<table border="0" width="100%">\n'
|
|
'<tr valign="bottom"><td>\n')
|
|
out('<h1 class="epydoc">%s</h1>\n</td><td>\n[\n' % title)
|
|
for sec in self.LETTERS:
|
|
if sec in index_by_section:
|
|
out(' <a href="%s">%s</a>\n' % (section_url % sec, sec))
|
|
else:
|
|
out(' %s\n' % sec)
|
|
out(']\n')
|
|
out('</td></table>\n')
|
|
|
|
# Alphabetical sections.
|
|
sections = [s for s in sections if s in index_by_section]
|
|
if sections:
|
|
out('<table border="0" width="100%">\n')
|
|
for section in sorted(sections):
|
|
out('<tr valign="top"><td valign="top" width="1%">')
|
|
out('<h2 class="epydoc"><a name="%s">%s</a></h2></td>\n' %
|
|
(section, section))
|
|
out('<td valign="top">\n')
|
|
self.write_index_section(out, index_by_section[section], True)
|
|
out('</td></tr>\n')
|
|
out('</table>\n<br />')
|
|
|
|
# Footer material.
|
|
out('<br />')
|
|
self.write_navbar(out, 'indices')
|
|
self.write_footer(out)
|
|
|
|
|
|
def write_metadata_index(self, out, indices, field, title, typ):
|
|
"""
|
|
Write an HTML page containing a metadata index.
|
|
"""
|
|
index = indices[field]
|
|
|
|
# Header material.
|
|
self.write_indexpage_header(out, indices, title,
|
|
'%s-index.html' % field)
|
|
|
|
# Page title.
|
|
out('<h1 class="epydoc"><a name="%s">%s</a></h1>\n<br />\n' %
|
|
(field, title))
|
|
|
|
# Index (one section per arg)
|
|
for arg in sorted(index):
|
|
# Write a section title.
|
|
if arg is not None:
|
|
if len([1 for (doc, descrs) in index[arg] if
|
|
not self._doc_or_ancestor_is_private(doc)]) == 0:
|
|
out('<div class="private">')
|
|
else:
|
|
out('<div>')
|
|
self.write_table_header(out, 'metadata-index', arg)
|
|
out('</table>')
|
|
# List every descr for this arg.
|
|
for (doc, descrs) in index[arg]:
|
|
if self._doc_or_ancestor_is_private(doc):
|
|
out('<div class="private">\n')
|
|
else:
|
|
out('<div>\n')
|
|
out('<table width="100%" class="metadata-index" '
|
|
'bgcolor="#e0e0e0"><tr><td class="metadata-index">')
|
|
out('<b>%s in %s</b>' %
|
|
(typ, self.href(doc, label=doc.canonical_name)))
|
|
out(' <ul class="nomargin">\n')
|
|
for descr in descrs:
|
|
out(' <li>%s</li>\n' %
|
|
self.docstring_to_html(descr,doc,4))
|
|
out(' </ul>\n')
|
|
out('</table></div>\n')
|
|
|
|
# Footer material.
|
|
out('<br />')
|
|
self.write_navbar(out, 'indices')
|
|
self.write_footer(out)
|
|
|
|
def write_indexpage_header(self, out, indices, title, url):
|
|
"""
|
|
A helper for the index page generation functions, which
|
|
generates a header that can be used to navigate between the
|
|
different indices.
|
|
"""
|
|
self.write_header(out, title)
|
|
self.write_navbar(out, 'indices')
|
|
self.write_breadcrumbs(out, 'indices', url)
|
|
|
|
if (indices['term'] or
|
|
[1 for (name,l,l2) in self.METADATA_INDICES if indices[name]]):
|
|
out('<center><b>[\n')
|
|
out(' <a href="identifier-index.html">Identifiers</a>\n')
|
|
if indices['term']:
|
|
out('| <a href="term-index.html">Term Definitions</a>\n')
|
|
for (name, label, label2) in self.METADATA_INDICES:
|
|
if indices[name]:
|
|
out('| <a href="%s-index.html">%s</a>\n' %
|
|
(name, label2))
|
|
out(']</b></center><br />\n')
|
|
|
|
def write_index_section(self, out, items, add_blankline=False):
|
|
out('<table class="link-index" width="100%" border="1">\n')
|
|
num_rows = (len(items)+2)/3
|
|
for row in range(num_rows):
|
|
out('<tr>\n')
|
|
for col in range(3):
|
|
out('<td width="33%" class="link-index">')
|
|
i = col*num_rows+row
|
|
if i < len(items):
|
|
name, url, container = items[col*num_rows+row]
|
|
out('<a href="%s">%s</a>' % (url, name))
|
|
if container is not None:
|
|
out('<br />\n')
|
|
if isinstance(container, ModuleDoc):
|
|
label = container.canonical_name
|
|
else:
|
|
label = container.canonical_name[-1]
|
|
out('<span class="index-where">(in %s)'
|
|
'</span>' % self.href(container, label))
|
|
else:
|
|
out(' ')
|
|
out('</td>\n')
|
|
out('</tr>\n')
|
|
if add_blankline and num_rows == 1:
|
|
blank_cell = '<td class="link-index"> </td>'
|
|
out('<tr>'+3*blank_cell+'</tr>\n')
|
|
out('</table>\n')
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 2.5. Help Page
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_help(self, out):
|
|
"""
|
|
Write an HTML help file to the given stream. If
|
|
C{self._helpfile} contains a help file, then use it;
|
|
otherwise, use the default helpfile from
|
|
L{epydoc.docwriter.html_help}.
|
|
"""
|
|
# todo: optionally parse .rst etc help files?
|
|
|
|
# Get the contents of the help file.
|
|
if self._helpfile:
|
|
if os.path.exists(self._helpfile):
|
|
try: help = open(self._helpfile).read()
|
|
except: raise IOError("Can't open help file: %r" %
|
|
self._helpfile)
|
|
else:
|
|
raise IOError("Can't find help file: %r" % self._helpfile)
|
|
else:
|
|
if self._prj_name: thisprj = self._prj_name
|
|
else: thisprj = 'this project'
|
|
help = HTML_HELP % {'this_project':thisprj}
|
|
|
|
# Insert the help contents into a webpage.
|
|
self.write_header(out, 'Help')
|
|
self.write_navbar(out, 'help')
|
|
self.write_breadcrumbs(out, 'help', 'help.html')
|
|
out(help)
|
|
self.write_navbar(out, 'help')
|
|
self.write_footer(out)
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 2.6. Frames-based Table of Contents
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
write_frames_index = compile_template(
|
|
"""
|
|
write_frames_index(self, out)
|
|
|
|
Write the frames index file for the frames-based table of
|
|
contents to the given streams.
|
|
""",
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
|
|
"DTD/xhtml1-frameset.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
|
<head>
|
|
<title> $self._prj_name or "API Documentation"$ </title>
|
|
</head>
|
|
<frameset cols="20%,80%">
|
|
<frameset rows="30%,70%">
|
|
<frame src="toc.html" name="moduleListFrame"
|
|
id="moduleListFrame" />
|
|
<frame src="toc-everything.html" name="moduleFrame"
|
|
id="moduleFrame" />
|
|
</frameset>
|
|
<frame src="$self._top_page_url$" name="mainFrame" id="mainFrame" />
|
|
</frameset>
|
|
</html>
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
write_toc = compile_template(
|
|
"""
|
|
write_toc(self, out)
|
|
""",
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
>>> self.write_header(out, "Table of Contents")
|
|
<h1 class="toc">Table of Contents</h1>
|
|
<hr />
|
|
<a target="moduleFrame" href="toc-everything.html">Everything</a>
|
|
<br />
|
|
>>> self.write_toc_section(out, "Modules", self.module_list)
|
|
<hr />
|
|
>>> if self._show_private:
|
|
$self.PRIVATE_LINK$
|
|
>>> #endif
|
|
>>> self.write_footer(out, short=True)
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
def write_toc_section(self, out, name, docs, fullname=True):
|
|
if not docs: return
|
|
|
|
# Assign names to each item, and sort by name.
|
|
if fullname:
|
|
docs = [(str(d.canonical_name), d) for d in docs]
|
|
else:
|
|
docs = [(str(d.canonical_name[-1]), d) for d in docs]
|
|
docs.sort()
|
|
|
|
out(' <h2 class="toc">%s</h2>\n' % name)
|
|
for label, doc in docs:
|
|
doc_url = self.url(doc)
|
|
toc_url = 'toc-%s' % doc_url
|
|
is_private = self._doc_or_ancestor_is_private(doc)
|
|
if is_private:
|
|
if not self._show_private: continue
|
|
out(' <div class="private">\n')
|
|
|
|
if isinstance(doc, ModuleDoc):
|
|
out(' <a target="moduleFrame" href="%s"\n'
|
|
' onclick="setFrame(\'%s\',\'%s\');"'
|
|
' >%s</a><br />' % (toc_url, toc_url, doc_url, label))
|
|
else:
|
|
out(' <a target="mainFrame" href="%s"\n'
|
|
' >%s</a><br />' % (doc_url, label))
|
|
if is_private:
|
|
out(' </div>\n')
|
|
|
|
def write_project_toc(self, out):
|
|
self.write_header(out, "Everything")
|
|
out('<h1 class="toc">Everything</h1>\n')
|
|
out('<hr />\n')
|
|
|
|
# List the classes.
|
|
self.write_toc_section(out, "All Classes", self.class_list)
|
|
|
|
# List the functions.
|
|
funcs = [d for d in self.routine_list
|
|
if not isinstance(self.docindex.container(d),
|
|
(ClassDoc, types.NoneType))]
|
|
self.write_toc_section(out, "All Functions", funcs)
|
|
|
|
# List the variables.
|
|
vars = []
|
|
for doc in self.module_list:
|
|
vars += doc.select_variables(value_type='other',
|
|
imported=False,
|
|
public=self._public_filter)
|
|
self.write_toc_section(out, "All Variables", vars)
|
|
|
|
# Footer material.
|
|
out('<hr />\n')
|
|
if self._show_private:
|
|
out(self.PRIVATE_LINK+'\n')
|
|
self.write_footer(out, short=True)
|
|
|
|
def write_module_toc(self, out, doc):
|
|
"""
|
|
Write an HTML page containing the table of contents page for
|
|
the given module to the given streams. This page lists the
|
|
modules, classes, exceptions, functions, and variables defined
|
|
by the module.
|
|
"""
|
|
name = doc.canonical_name[-1]
|
|
self.write_header(out, name)
|
|
out('<h1 class="toc">Module %s</h1>\n' % name)
|
|
out('<hr />\n')
|
|
|
|
|
|
# List the classes.
|
|
classes = doc.select_variables(value_type='class', imported=False,
|
|
public=self._public_filter)
|
|
self.write_toc_section(out, "Classes", classes, fullname=False)
|
|
|
|
# List the functions.
|
|
funcs = doc.select_variables(value_type='function', imported=False,
|
|
public=self._public_filter)
|
|
self.write_toc_section(out, "Functions", funcs, fullname=False)
|
|
|
|
# List the variables.
|
|
variables = doc.select_variables(value_type='other', imported=False,
|
|
public=self._public_filter)
|
|
self.write_toc_section(out, "Variables", variables, fullname=False)
|
|
|
|
# Footer material.
|
|
out('<hr />\n')
|
|
if self._show_private:
|
|
out(self.PRIVATE_LINK+'\n')
|
|
self.write_footer(out, short=True)
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 2.7. Project homepage (index.html)
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_homepage(self, directory):
|
|
"""
|
|
Write an C{index.html} file in the given directory. The
|
|
contents of this file are copied or linked from an existing
|
|
page, so this method must be called after all pages have been
|
|
written. The page used is determined by L{_frames_index} and
|
|
L{_top_page}:
|
|
- If L{_frames_index} is true, then C{frames.html} is
|
|
copied.
|
|
- Otherwise, the page specified by L{_top_page} is
|
|
copied.
|
|
"""
|
|
filename = os.path.join(directory, 'index.html')
|
|
if self._frames_index: top = 'frames.html'
|
|
else: top = self._top_page_url
|
|
|
|
# Copy the non-frames index file from top, if it's internal.
|
|
if top[:5] != 'http:' and '/' not in top:
|
|
try:
|
|
# Read top into `s`.
|
|
topfile = os.path.join(directory, top)
|
|
s = open(topfile, 'r').read()
|
|
|
|
# Write the output file.
|
|
open(filename, 'w').write(s)
|
|
return
|
|
except:
|
|
log.error('Warning: error copying index; '
|
|
'using a redirect page')
|
|
|
|
# Use a redirect if top is external, or if we faild to copy.
|
|
name = self._prj_name or 'this project'
|
|
f = open(filename, 'w')
|
|
self.write_redirect_index(f.write, top, name)
|
|
f.close()
|
|
|
|
write_redirect_index = compile_template(
|
|
"""
|
|
write_redirect_index(self, out, top, name)
|
|
""",
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
|
"DTD/xhtml1-strict.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
|
<head>
|
|
<title> Redirect </title>
|
|
<meta http-equiv="refresh" content="1;url=$top$" />
|
|
<link rel="stylesheet" href="epydoc.css" type="text/css"></link>
|
|
</head>
|
|
<body>
|
|
<p>Redirecting to the API documentation for
|
|
<a href="$top$">$self._prj_name or "this project"$</a>...</p>
|
|
</body>
|
|
</html>
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 2.8. Stylesheet (epydoc.css)
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_css(self, directory, cssname):
|
|
"""
|
|
Write the CSS stylesheet in the given directory. If
|
|
C{cssname} contains a stylesheet file or name (from
|
|
L{epydoc.docwriter.html_css}), then use that stylesheet;
|
|
otherwise, use the default stylesheet.
|
|
|
|
@rtype: C{None}
|
|
"""
|
|
filename = os.path.join(directory, 'epydoc.css')
|
|
|
|
# Get the contents for the stylesheet file.
|
|
if cssname is None:
|
|
css = STYLESHEETS['default'][0]
|
|
else:
|
|
if os.path.exists(cssname):
|
|
try: css = open(cssname).read()
|
|
except: raise IOError("Can't open CSS file: %r" % cssname)
|
|
elif cssname in STYLESHEETS:
|
|
css = STYLESHEETS[cssname][0]
|
|
else:
|
|
raise IOError("Can't find CSS file: %r" % cssname)
|
|
|
|
# Write the stylesheet.
|
|
cssfile = open(filename, 'w')
|
|
cssfile.write(css)
|
|
cssfile.close()
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 2.9. Javascript (epydoc.js)
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_javascript(self, directory):
|
|
jsfile = open(os.path.join(directory, 'epydoc.js'), 'w')
|
|
print >> jsfile, self.TOGGLE_PRIVATE_JS
|
|
print >> jsfile, self.SHOW_PRIVATE_JS
|
|
print >> jsfile, self.GET_COOKIE_JS
|
|
print >> jsfile, self.SET_FRAME_JS
|
|
print >> jsfile, self.HIDE_PRIVATE_JS
|
|
print >> jsfile, self.TOGGLE_CALLGRAPH_JS
|
|
print >> jsfile, html_colorize.PYSRC_JAVASCRIPTS
|
|
print >> jsfile, self.GET_ANCHOR_JS
|
|
print >> jsfile, self.REDIRECT_URL_JS
|
|
jsfile.close()
|
|
|
|
#: A javascript that is used to show or hide the API documentation
|
|
#: for private objects. In order for this to work correctly, all
|
|
#: documentation for private objects should be enclosed in
|
|
#: C{<div class="private">...</div>} elements.
|
|
TOGGLE_PRIVATE_JS = '''
|
|
function toggle_private() {
|
|
// Search for any private/public links on this page. Store
|
|
// their old text in "cmd," so we will know what action to
|
|
// take; and change their text to the opposite action.
|
|
var cmd = "?";
|
|
var elts = document.getElementsByTagName("a");
|
|
for(var i=0; i<elts.length; i++) {
|
|
if (elts[i].className == "privatelink") {
|
|
cmd = elts[i].innerHTML;
|
|
elts[i].innerHTML = ((cmd && cmd.substr(0,4)=="show")?
|
|
"hide private":"show private");
|
|
}
|
|
}
|
|
// Update all DIVs containing private objects.
|
|
var elts = document.getElementsByTagName("div");
|
|
for(var i=0; i<elts.length; i++) {
|
|
if (elts[i].className == "private") {
|
|
elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":"block");
|
|
}
|
|
else if (elts[i].className == "public") {
|
|
elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"block":"none");
|
|
}
|
|
}
|
|
// Update all table rows containing private objects. Note, we
|
|
// use "" instead of "block" becaue IE & firefox disagree on what
|
|
// this should be (block vs table-row), and "" just gives the
|
|
// default for both browsers.
|
|
var elts = document.getElementsByTagName("tr");
|
|
for(var i=0; i<elts.length; i++) {
|
|
if (elts[i].className == "private") {
|
|
elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":"");
|
|
}
|
|
}
|
|
// Update all list items containing private objects.
|
|
var elts = document.getElementsByTagName("li");
|
|
for(var i=0; i<elts.length; i++) {
|
|
if (elts[i].className == "private") {
|
|
elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?
|
|
"none":"");
|
|
}
|
|
}
|
|
// Update all list items containing private objects.
|
|
var elts = document.getElementsByTagName("ul");
|
|
for(var i=0; i<elts.length; i++) {
|
|
if (elts[i].className == "private") {
|
|
elts[i].style.display = ((cmd && cmd.substr(0,4)=="hide")?"none":"block");
|
|
}
|
|
}
|
|
// Set a cookie to remember the current option.
|
|
document.cookie = "EpydocPrivate="+cmd;
|
|
}
|
|
'''.strip()
|
|
|
|
#: A javascript that is used to read the value of a cookie. This
|
|
#: is used to remember whether private variables should be shown or
|
|
#: hidden.
|
|
GET_COOKIE_JS = '''
|
|
function getCookie(name) {
|
|
var dc = document.cookie;
|
|
var prefix = name + "=";
|
|
var begin = dc.indexOf("; " + prefix);
|
|
if (begin == -1) {
|
|
begin = dc.indexOf(prefix);
|
|
if (begin != 0) return null;
|
|
} else
|
|
{ begin += 2; }
|
|
var end = document.cookie.indexOf(";", begin);
|
|
if (end == -1)
|
|
{ end = dc.length; }
|
|
return unescape(dc.substring(begin + prefix.length, end));
|
|
}
|
|
'''.strip()
|
|
|
|
#: A javascript that is used to set the contents of two frames at
|
|
#: once. This is used by the project table-of-contents frame to
|
|
#: set both the module table-of-contents frame and the main frame
|
|
#: when the user clicks on a module.
|
|
SET_FRAME_JS = '''
|
|
function setFrame(url1, url2) {
|
|
parent.frames[1].location.href = url1;
|
|
parent.frames[2].location.href = url2;
|
|
}
|
|
'''.strip()
|
|
|
|
#: A javascript that is used to hide private variables, unless
|
|
#: either: (a) the cookie says not to; or (b) we appear to be
|
|
#: linking to a private variable.
|
|
HIDE_PRIVATE_JS = '''
|
|
function checkCookie() {
|
|
var cmd=getCookie("EpydocPrivate");
|
|
if (cmd && cmd.substr(0,4)!="show" && location.href.indexOf("#_") < 0)
|
|
toggle_private();
|
|
}
|
|
'''.strip()
|
|
|
|
TOGGLE_CALLGRAPH_JS = '''
|
|
function toggleCallGraph(id) {
|
|
var elt = document.getElementById(id);
|
|
if (elt.style.display == "none")
|
|
elt.style.display = "block";
|
|
else
|
|
elt.style.display = "none";
|
|
}
|
|
'''.strip()
|
|
|
|
SHOW_PRIVATE_JS = '''
|
|
function show_private() {
|
|
var elts = document.getElementsByTagName("a");
|
|
for(var i=0; i<elts.length; i++) {
|
|
if (elts[i].className == "privatelink") {
|
|
cmd = elts[i].innerHTML;
|
|
if (cmd && cmd.substr(0,4)=="show")
|
|
toggle_private();
|
|
}
|
|
}
|
|
}
|
|
'''.strip()
|
|
|
|
GET_ANCHOR_JS = '''
|
|
function get_anchor() {
|
|
var href = location.href;
|
|
var start = href.indexOf("#")+1;
|
|
if ((start != 0) && (start != href.length))
|
|
return href.substring(start, href.length);
|
|
}
|
|
'''.strip()
|
|
|
|
#: A javascript that is used to implement the auto-redirect page.
|
|
#: When the user visits <redirect.html#dotted.name>, they will
|
|
#: automatically get redirected to the page for the object with
|
|
#: the given fully-qualified dotted name. E.g., for epydoc,
|
|
#: <redirect.html#epydoc.apidoc.UNKNOWN> redirects the user to
|
|
#: <epydoc.apidoc-module.html#UNKNOWN>.
|
|
REDIRECT_URL_JS = '''
|
|
function redirect_url(dottedName) {
|
|
// Scan through each element of the "pages" list, and check
|
|
// if "name" matches with any of them.
|
|
for (var i=0; i<pages.length; i++) {
|
|
|
|
// Each page has the form "<pagename>-m" or "<pagename>-c";
|
|
// extract the <pagename> portion & compare it to dottedName.
|
|
var pagename = pages[i].substring(0, pages[i].length-2);
|
|
if (pagename == dottedName.substring(0,pagename.length)) {
|
|
|
|
// We\'ve found a page that matches `dottedName`;
|
|
// construct its URL, using leftover `dottedName`
|
|
// content to form an anchor.
|
|
var pagetype = pages[i].charAt(pages[i].length-1);
|
|
var url = pagename + ((pagetype=="m")?"-module.html":
|
|
"-class.html");
|
|
if (dottedName.length > pagename.length)
|
|
url += "#" + dottedName.substring(pagename.length+1,
|
|
dottedName.length);
|
|
return url;
|
|
}
|
|
}
|
|
}
|
|
'''.strip()
|
|
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 2.10. Graphs
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def render_graph(self, graph):
|
|
if graph is None: return ''
|
|
graph.caption = graph.title = None
|
|
image_url = '%s.gif' % graph.uid
|
|
image_file = os.path.join(self._directory, image_url)
|
|
return graph.to_html(image_file, image_url)
|
|
|
|
RE_CALLGRAPH_ID = re.compile(r"""["'](.+-div)['"]""")
|
|
|
|
def render_callgraph(self, callgraph, token=""):
|
|
"""Render the HTML chunk of a callgraph.
|
|
|
|
If C{callgraph} is a string, use the L{_callgraph_cache} to return
|
|
a pre-rendered HTML chunk. This mostly avoids to run C{dot} twice for
|
|
the same callgraph. Else, run the graph and store its HTML output in
|
|
the cache.
|
|
|
|
@param callgraph: The graph to render or its L{uid<DotGraph.uid>}.
|
|
@type callgraph: L{DotGraph} or C{str}
|
|
@param token: A string that can be used to make the C{<div>} id
|
|
unambiguous, if the callgraph is used more than once in a page.
|
|
@type token: C{str}
|
|
@return: The HTML representation of the graph.
|
|
@rtype: C{str}
|
|
"""
|
|
if callgraph is None: return ""
|
|
|
|
if isinstance(callgraph, basestring):
|
|
uid = callgraph
|
|
rv = self._callgraph_cache.get(callgraph, "")
|
|
|
|
else:
|
|
uid = callgraph.uid
|
|
graph_html = self.render_graph(callgraph)
|
|
if graph_html == '':
|
|
rv = ""
|
|
else:
|
|
rv = ('<div style="display:none" id="%%s-div"><center>\n'
|
|
'<table border="0" cellpadding="0" cellspacing="0">\n'
|
|
' <tr><td>%s</td></tr>\n'
|
|
' <tr><th>Call Graph</th></tr>\n'
|
|
'</table><br />\n</center></div>\n' % graph_html)
|
|
|
|
# Store in the cache the complete HTML chunk without the
|
|
# div id, which may be made unambiguous by the token
|
|
self._callgraph_cache[uid] = rv
|
|
|
|
# Mangle with the graph
|
|
if rv: rv = rv % (uid + token)
|
|
return rv
|
|
|
|
def callgraph_link(self, callgraph, token=""):
|
|
"""Render the HTML chunk of a callgraph link.
|
|
|
|
The link can toggles the visibility of the callgraph rendered using
|
|
L{render_callgraph} with matching parameters.
|
|
|
|
@param callgraph: The graph to render or its L{uid<DotGraph.uid>}.
|
|
@type callgraph: L{DotGraph} or C{str}
|
|
@param token: A string that can be used to make the C{<div>} id
|
|
unambiguous, if the callgraph is used more than once in a page.
|
|
@type token: C{str}
|
|
@return: The HTML representation of the graph link.
|
|
@rtype: C{str}
|
|
"""
|
|
# Use class=codelink, to match style w/ the source code link.
|
|
if callgraph is None: return ''
|
|
|
|
if isinstance(callgraph, basestring):
|
|
uid = callgraph
|
|
else:
|
|
uid = callgraph.uid
|
|
|
|
return ('<br /><span class="codelink"><a href="javascript:void(0);" '
|
|
'onclick="toggleCallGraph(\'%s-div\');return false;">'
|
|
'call graph</a></span> ' % (uid + token))
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 2.11. Images
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
IMAGES = {'crarr.png': # Carriage-return arrow, used for LINEWRAP.
|
|
'iVBORw0KGgoAAAANSUhEUgAAABEAAAAKCAMAAABlokWQAAAALHRFWHRD'
|
|
'cmVhdGlvbiBUaW1lAFR1\nZSAyMiBBdWcgMjAwNiAwMDo0MzoxMCAtMD'
|
|
'UwMGAMEFgAAAAHdElNRQfWCBYFASkQ033WAAAACXBI\nWXMAAB7CAAAe'
|
|
'wgFu0HU+AAAABGdBTUEAALGPC/xhBQAAAEVQTFRF////zcOw18/AgGY0'
|
|
'c1cg4dvQ\ninJEYEAAYkME3NXI6eTcloFYe2Asr5+AbE4Uh29A9fPwqp'
|
|
'l4ZEUI8O3onopk0Ma0lH5U1nfFdgAA\nAAF0Uk5TAEDm2GYAAABNSURB'
|
|
'VHjaY2BAAbzsvDAmK5oIlxgfioiwCAe7KJKIgKAQOzsLLwTwA0VY\n+d'
|
|
'iRAT8T0AxuIIMHqoaXCWIPGzsHJ6orGJiYWRjQASOcBQAocgMSPKMTIg'
|
|
'AAAABJRU5ErkJggg==\n',
|
|
}
|
|
|
|
def write_images(self, directory):
|
|
for (name, data) in self.IMAGES.items():
|
|
f = open(os.path.join(directory, name), 'wb')
|
|
f.write(base64.decodestring(data))
|
|
f.close()
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 3.1. Page Header
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
write_header = compile_template(
|
|
"""
|
|
write_header(self, out, title)
|
|
|
|
Generate HTML code for the standard page header, and write it
|
|
to C{out}. C{title} is a string containing the page title.
|
|
It should be appropriately escaped/encoded.
|
|
""",
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
<?xml version="1.0" encoding="ascii"?>
|
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
|
"DTD/xhtml1-transitional.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
|
<head>
|
|
<title>$title$</title>
|
|
<link rel="stylesheet" href="epydoc.css" type="text/css" />
|
|
<script type="text/javascript" src="epydoc.js"></script>
|
|
</head>
|
|
|
|
<body bgcolor="white" text="black" link="blue" vlink="#204080"
|
|
alink="#204080">
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 3.2. Page Footer
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
write_footer = compile_template(
|
|
"""
|
|
write_footer(self, out, short=False)
|
|
|
|
Generate HTML code for the standard page footer, and write it
|
|
to C{out}.
|
|
""",
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
>>> if not short:
|
|
<table border="0" cellpadding="0" cellspacing="0" width="100%%">
|
|
<tr>
|
|
<td align="left" class="footer">
|
|
>>> if self._include_log:
|
|
<a href="epydoc-log.html">Generated by Epydoc
|
|
$epydoc.__version__$ on $time.asctime()$</a>
|
|
>>> else:
|
|
Generated by Epydoc $epydoc.__version__$ on $time.asctime()$
|
|
>>> #endif
|
|
</td>
|
|
<td align="right" class="footer">
|
|
<a target="mainFrame" href="http://epydoc.sourceforge.net"
|
|
>http://epydoc.sourceforge.net</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
>>> #endif
|
|
|
|
<script type="text/javascript">
|
|
<!--
|
|
// Private objects are initially displayed (because if
|
|
// javascript is turned off then we want them to be
|
|
// visible); but by default, we want to hide them. So hide
|
|
// them unless we have a cookie that says to show them.
|
|
checkCookie();
|
|
// -->
|
|
</script>
|
|
</body>
|
|
</html>
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 3.3. Navigation Bar
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
write_navbar = compile_template(
|
|
"""
|
|
write_navbar(self, out, context)
|
|
|
|
Generate HTML code for the navigation bar, and write it to
|
|
C{out}. The navigation bar typically looks like::
|
|
|
|
[ Home Trees Index Help Project ]
|
|
|
|
@param context: A value indicating what page we're generating
|
|
a navigation bar for. If we're generating an API
|
|
documentation page for an object, then C{context} is a
|
|
L{ValueDoc} containing the documentation for that object;
|
|
otherwise, C{context} is a string name for the page. The
|
|
following string names are recognized: C{'tree'}, C{'index'},
|
|
and C{'help'}.
|
|
""",
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
<!-- ==================== NAVIGATION BAR ==================== -->
|
|
<table class="navbar" border="0" width="100%" cellpadding="0"
|
|
bgcolor="#a0c0ff" cellspacing="0">
|
|
<tr valign="middle">
|
|
>>> if self._top_page_url not in (self._trees_url, "identifier-index.html", "help.html"):
|
|
<!-- Home link -->
|
|
>>> if (isinstance(context, ValueDoc) and
|
|
>>> self._top_page_url == self.url(context.canonical_name)):
|
|
<th bgcolor="#70b0f0" class="navbar-select"
|
|
> Home </th>
|
|
>>> else:
|
|
<th> <a
|
|
href="$self._top_page_url$">Home</a> </th>
|
|
>>> #endif
|
|
|
|
<!-- Tree link -->
|
|
>>> if context == "trees":
|
|
<th bgcolor="#70b0f0" class="navbar-select"
|
|
> Trees </th>
|
|
>>> else:
|
|
<th> <a
|
|
href="$self._trees_url$">Trees</a> </th>
|
|
>>> #endif
|
|
|
|
<!-- Index link -->
|
|
>>> if context == "indices":
|
|
<th bgcolor="#70b0f0" class="navbar-select"
|
|
> Indices </th>
|
|
>>> else:
|
|
<th> <a
|
|
href="identifier-index.html">Indices</a> </th>
|
|
>>> #endif
|
|
|
|
<!-- Help link -->
|
|
>>> if context == "help":
|
|
<th bgcolor="#70b0f0" class="navbar-select"
|
|
> Help </th>
|
|
>>> else:
|
|
<th> <a
|
|
href="help.html">Help</a> </th>
|
|
>>> #endif
|
|
|
|
>>> if self._prj_link:
|
|
<!-- Project homepage -->
|
|
<th class="navbar" align="right" width="100%">
|
|
<table border="0" cellpadding="0" cellspacing="0">
|
|
<tr><th class="navbar" align="center"
|
|
>$self._prj_link.strip()$</th>
|
|
</tr></table></th>
|
|
>>> else:
|
|
<th class="navbar" width="100%"></th>
|
|
>>> #endif
|
|
</tr>
|
|
</table>
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 3.4. Breadcrumbs
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
write_breadcrumbs = compile_template(
|
|
"""
|
|
write_breadcrumbs(self, out, context, context_url)
|
|
|
|
Generate HTML for the breadcrumbs line, and write it to
|
|
C{out}. The breadcrumbs line is an invisible table with a
|
|
list of pointers to the current object's ancestors on the
|
|
left; and the show/hide private selector and the
|
|
frames/noframes selector on the right.
|
|
|
|
@param context: The API documentation for the object whose
|
|
breadcrumbs we should generate.
|
|
@type context: L{ValueDoc}
|
|
""",
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
<table width="100%" cellpadding="0" cellspacing="0">
|
|
<tr valign="top">
|
|
>>> if isinstance(context, APIDoc):
|
|
<td width="100%">
|
|
<span class="breadcrumbs">
|
|
>>> crumbs = self.breadcrumbs(context)
|
|
>>> for crumb in crumbs[:-1]:
|
|
$crumb$ ::
|
|
>>> #endfor
|
|
$crumbs[-1]$
|
|
</span>
|
|
</td>
|
|
>>> else:
|
|
<td width="100%"> </td>
|
|
>>> #endif
|
|
<td>
|
|
<table cellpadding="0" cellspacing="0">
|
|
<!-- hide/show private -->
|
|
>>> if self._show_private:
|
|
<tr><td align="right">$self.PRIVATE_LINK$</td></tr>
|
|
>>> #endif
|
|
>>> if self._frames_index:
|
|
<tr><td align="right"><span class="options"
|
|
>[<a href="frames.html" target="_top">frames</a
|
|
>] | <a href="$context_url$"
|
|
target="_top">no frames</a>]</span></td></tr>
|
|
>>> #endif
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
def breadcrumbs(self, doc):
|
|
crumbs = [self._crumb(doc)]
|
|
|
|
# Generate the crumbs for uid's ancestors.
|
|
while True:
|
|
container = self.docindex.container(doc)
|
|
assert doc != container, 'object is its own container?'
|
|
if container is None:
|
|
if doc.canonical_name is UNKNOWN:
|
|
return ['??']+crumbs
|
|
elif isinstance(doc, ModuleDoc):
|
|
return ['Package %s' % ident
|
|
for ident in doc.canonical_name[:-1]]+crumbs
|
|
else:
|
|
return list(doc.canonical_name)+crumbs
|
|
else:
|
|
label = self._crumb(container)
|
|
name = container.canonical_name
|
|
crumbs.insert(0, self.href(container, label)) # [xx] code=0??
|
|
doc = container
|
|
|
|
def _crumb(self, doc):
|
|
if (len(doc.canonical_name)==1 and
|
|
doc.canonical_name[0].startswith('script-')):
|
|
return 'Script %s' % doc.canonical_name[0][7:]
|
|
return '%s %s' % (self.doc_kind(doc), doc.canonical_name[-1])
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 3.5. Summary Tables
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_summary_table(self, out, heading, doc, value_type):
|
|
"""
|
|
Generate HTML code for a summary table, and write it to
|
|
C{out}. A summary table is a table that includes a one-row
|
|
description for each variable (of a given type) in a module
|
|
or class.
|
|
|
|
@param heading: The heading for the summary table; typically,
|
|
this indicates what kind of value the table describes
|
|
(e.g., functions or classes).
|
|
@param doc: A L{ValueDoc} object containing the API
|
|
documentation for the module or class whose variables
|
|
we should summarize.
|
|
@param value_type: A string indicating what type of value
|
|
should be listed in this summary table. This value
|
|
is passed on to C{doc}'s C{select_variables()} method.
|
|
"""
|
|
# inh_var_groups is a dictionary used to hold "inheritance
|
|
# pseudo-groups", which are created when inheritance is
|
|
# 'grouped'. It maps each base to a list of vars inherited
|
|
# from that base.
|
|
grouped_inh_vars = {}
|
|
|
|
# Divide all public variables of the given type into groups.
|
|
groups = [(plaintext_to_html(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_table_header(out, "summary", heading)
|
|
|
|
# Write a section for each group.
|
|
for name, var_docs in groups:
|
|
self.write_summary_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' % self.href(base, context=doc)
|
|
tr_class = ''
|
|
if len([v for v in grouped_inh_vars[base]
|
|
if v.is_public]) == 0:
|
|
tr_class = ' class="private"'
|
|
self.write_group_header(out, hdr, tr_class)
|
|
for var_doc in grouped_inh_vars[base]:
|
|
self.write_summary_line(out, var_doc, doc)
|
|
|
|
# Write a footer for the table.
|
|
out(self.TABLE_FOOTER)
|
|
|
|
def write_summary_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 not isinstance(base, ClassDoc):
|
|
# This *should* never happen:
|
|
log.warning("%s's container is not a class!" % var_doc)
|
|
normal_vars.append(var_doc)
|
|
elif (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 != '':
|
|
tr_class = ''
|
|
if len([v for v in var_docs if v.is_public]) == 0:
|
|
tr_class = ' class="private"'
|
|
self.write_group_header(out, name, tr_class)
|
|
|
|
# Write a line for each normal var:
|
|
for var_doc in normal_vars:
|
|
self.write_summary_line(out, var_doc, doc)
|
|
# Write a subsection for inherited vars:
|
|
if listed_inh_vars:
|
|
self.write_inheritance_list(out, doc, listed_inh_vars)
|
|
|
|
def write_inheritance_list(self, out, doc, listed_inh_vars):
|
|
out(' <tr>\n <td colspan="2" class="summary">\n')
|
|
for base in doc.mro():
|
|
if base not in listed_inh_vars: continue
|
|
public_vars = [v for v in listed_inh_vars[base]
|
|
if v.is_public]
|
|
private_vars = [v for v in listed_inh_vars[base]
|
|
if not v.is_public]
|
|
if public_vars:
|
|
out(' <p class="indent-wrapped-lines">'
|
|
'<b>Inherited from <code>%s</code></b>:\n' %
|
|
self.href(base, context=doc))
|
|
self.write_var_list(out, public_vars)
|
|
out(' </p>\n')
|
|
if private_vars and self._show_private:
|
|
out(' <div class="private">')
|
|
out(' <p class="indent-wrapped-lines">'
|
|
'<b>Inherited from <code>%s</code></b> (private):\n' %
|
|
self.href(base, context=doc))
|
|
self.write_var_list(out, private_vars)
|
|
out(' </p></div>\n')
|
|
out(' </td>\n </tr>\n')
|
|
|
|
def write_var_list(self, out, vardocs):
|
|
out(' ')
|
|
out(',\n '.join(['<code>%s</code>' % self.href(v,v.name)
|
|
for v in vardocs])+'\n')
|
|
|
|
def write_summary_line(self, out, var_doc, container):
|
|
"""
|
|
Generate HTML code for a single line of a summary table, and
|
|
write it to C{out}. See L{write_summary_table} for more
|
|
information.
|
|
|
|
@param var_doc: The API documentation for the variable that
|
|
should be described by this line of the summary table.
|
|
@param container: The API documentation for the class or
|
|
module whose summary table we're writing.
|
|
"""
|
|
pysrc_link = None
|
|
callgraph = None
|
|
|
|
# If it's a private variable, then mark its <tr>.
|
|
if var_doc.is_public: tr_class = ''
|
|
else: tr_class = ' class="private"'
|
|
|
|
# Decide an anchor or a link is to be generated.
|
|
link_name = self._redundant_details or var_doc.is_detailed()
|
|
anchor = not link_name
|
|
|
|
# Construct the HTML code for the type (cell 1) & description
|
|
# (cell 2).
|
|
if isinstance(var_doc.value, RoutineDoc):
|
|
typ = self.return_type(var_doc, indent=6)
|
|
description = self.function_signature(var_doc, is_summary=True,
|
|
link_name=link_name, anchor=anchor)
|
|
pysrc_link = self.pysrc_link(var_doc.value)
|
|
|
|
# Perpare the call-graph, if requested
|
|
if 'callgraph' in self._graph_types:
|
|
linker = _HTMLDocstringLinker(self, var_doc.value)
|
|
callgraph = call_graph([var_doc.value], self.docindex,
|
|
linker, var_doc, add_callers=True,
|
|
add_callees=True)
|
|
if callgraph and callgraph.nodes:
|
|
var_doc.value.callgraph_uid = callgraph.uid
|
|
else:
|
|
callgraph = None
|
|
else:
|
|
typ = self.type_descr(var_doc, indent=6)
|
|
description = self.summary_name(var_doc,
|
|
link_name=link_name, anchor=anchor)
|
|
if isinstance(var_doc.value, GenericValueDoc):
|
|
# The summary max length has been chosen setting
|
|
# L{ValueDoc.SUMMARY_REPR_LINELEN} in the constructor
|
|
max_len=self._variable_summary_linelen-3-len(var_doc.name)
|
|
val_repr = var_doc.value.summary_pyval_repr(max_len)
|
|
tooltip = self.variable_tooltip(var_doc)
|
|
description += (' = <code%s>%s</code>' %
|
|
(tooltip, val_repr.to_html(None)))
|
|
|
|
# Add the summary to the description (if there is one).
|
|
summary = self.summary(var_doc, indent=6)
|
|
if summary: description += '<br />\n %s' % summary
|
|
|
|
# If it's inherited, then add a note to the description.
|
|
if var_doc.container != container and self._inheritance=="included":
|
|
description += ("\n <em>(Inherited from " +
|
|
self.href(var_doc.container) + ")</em>")
|
|
|
|
# Write the summary line.
|
|
self._write_summary_line(out, typ, description, tr_class, pysrc_link,
|
|
callgraph)
|
|
|
|
_write_summary_line = compile_template(
|
|
"_write_summary_line(self, out, typ, description, tr_class, "
|
|
"pysrc_link, callgraph)",
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
<tr$tr_class$>
|
|
<td width="15%" align="right" valign="top" class="summary">
|
|
<span class="summary-type">$typ or " "$</span>
|
|
</td><td class="summary">
|
|
>>> if pysrc_link is not None or callgraph is not None:
|
|
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
<tr>
|
|
<td>$description$</td>
|
|
<td align="right" valign="top">
|
|
$pysrc_link$
|
|
$self.callgraph_link(callgraph, token='-summary')$
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
$self.render_callgraph(callgraph, token='-summary')$
|
|
>>> #endif
|
|
>>> if pysrc_link is None and callgraph is None:
|
|
$description$
|
|
>>> #endif
|
|
</td>
|
|
</tr>
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ 3.6. Details Lists
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_details_list(self, out, heading, doc, value_type):
|
|
# Get a list of the VarDocs we should describe.
|
|
if self._redundant_details:
|
|
detailed = None
|
|
else:
|
|
detailed = True
|
|
if isinstance(doc, ClassDoc):
|
|
var_docs = doc.select_variables(value_type=value_type,
|
|
imported=False, inherited=False,
|
|
public=self._public_filter,
|
|
detailed=detailed)
|
|
else:
|
|
var_docs = doc.select_variables(value_type=value_type,
|
|
imported=False,
|
|
public=self._public_filter,
|
|
detailed=detailed)
|
|
if not var_docs: return
|
|
|
|
# Write a header
|
|
self.write_table_header(out, "details", heading)
|
|
out(self.TABLE_FOOTER)
|
|
|
|
for var_doc in var_docs:
|
|
self.write_details_entry(out, var_doc)
|
|
|
|
out('<br />\n')
|
|
|
|
def write_details_entry(self, out, var_doc):
|
|
descr = self.descr(var_doc, indent=2) or ''
|
|
if var_doc.is_public: div_class = ''
|
|
else: div_class = ' class="private"'
|
|
|
|
# Functions
|
|
if isinstance(var_doc.value, RoutineDoc):
|
|
rtype = self.return_type(var_doc, indent=10)
|
|
rdescr = self.return_descr(var_doc, indent=10)
|
|
arg_descrs = []
|
|
args = set()
|
|
# Find the description for each arg. (Leave them in the
|
|
# same order that they're listed in the docstring.)
|
|
for (arg_names, arg_descr) in var_doc.value.arg_descrs:
|
|
args.update(arg_names)
|
|
lhs = ', '.join([self.arg_name_to_html(var_doc.value, n)
|
|
for n in arg_names])
|
|
rhs = self.docstring_to_html(arg_descr, var_doc.value, 10)
|
|
arg_descrs.append( (lhs, rhs) )
|
|
# Check for arguments for which we have @type but not @param;
|
|
# and add them to the arg_descrs list.
|
|
for arg in var_doc.value.arg_types:
|
|
if arg not in args:
|
|
argname = self.arg_name_to_html(var_doc.value, arg)
|
|
arg_descrs.append( (argname,'') )
|
|
|
|
self.write_function_details_entry(out, var_doc, descr,
|
|
var_doc.value.callgraph_uid,
|
|
rtype, rdescr, arg_descrs,
|
|
div_class)
|
|
|
|
# Properties
|
|
elif isinstance(var_doc.value, PropertyDoc):
|
|
prop_doc = var_doc.value
|
|
accessors = [ (name,
|
|
self.property_accessor_to_html(val_doc, prop_doc),
|
|
self.summary(val_doc))
|
|
for (name, val_doc) in
|
|
[('Get', prop_doc.fget), ('Set', prop_doc.fset),
|
|
('Delete', prop_doc.fdel)]
|
|
if val_doc not in (None, UNKNOWN)
|
|
and val_doc.pyval is not None ]
|
|
|
|
self.write_property_details_entry(out, var_doc, descr,
|
|
accessors, div_class)
|
|
|
|
# Variables
|
|
else:
|
|
self.write_variable_details_entry(out, var_doc, descr, div_class)
|
|
|
|
def labelled_list_item(self, lhs, rhs):
|
|
# If the RHS starts with a paragraph, then move the
|
|
# paragraph-start tag to the beginning of the lhs instead (so
|
|
# there won't be a line break after the '-').
|
|
m = re.match(r'^<p( [^>]+)?>', rhs)
|
|
if m:
|
|
lhs = m.group() + lhs
|
|
rhs = rhs[m.end():]
|
|
|
|
if rhs:
|
|
return '<li>%s - %s</li>' % (lhs, rhs)
|
|
else:
|
|
return '<li>%s</li>' % (lhs,)
|
|
|
|
def property_accessor_to_html(self, val_doc, context=None):
|
|
if val_doc not in (None, UNKNOWN):
|
|
if isinstance(val_doc, RoutineDoc):
|
|
return self.function_signature(val_doc, is_summary=True,
|
|
link_name=True, context=context)
|
|
elif isinstance(val_doc, GenericValueDoc):
|
|
return self.pprint_value(val_doc)
|
|
else:
|
|
return self.href(val_doc, context=context)
|
|
else:
|
|
return '??'
|
|
|
|
def arg_name_to_html(self, func_doc, arg_name):
|
|
"""
|
|
A helper function used to format an argument name, for use in
|
|
the argument description list under a routine's details entry.
|
|
This just wraps strong & code tags around the arg name; and if
|
|
the arg name is associated with a type, then adds it
|
|
parenthetically after the name.
|
|
"""
|
|
s = '<strong class="pname"><code>%s</code></strong>' % arg_name
|
|
if arg_name in func_doc.arg_types:
|
|
typ = func_doc.arg_types[arg_name]
|
|
typ_html = self.docstring_to_html(typ, func_doc, 10)
|
|
s += " (%s)" % typ_html
|
|
return s
|
|
|
|
write_function_details_entry = compile_template(
|
|
'''
|
|
write_function_details_entry(self, out, var_doc, descr, callgraph, \
|
|
rtype, rdescr, arg_descrs, div_class)
|
|
''',
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
>>> func_doc = var_doc.value
|
|
<a name="$var_doc.name$"></a>
|
|
<div$div_class$>
|
|
>>> self.write_table_header(out, "details")
|
|
<tr><td>
|
|
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
<tr valign="top"><td>
|
|
<h3 class="epydoc">$self.function_signature(var_doc)$
|
|
>>> if var_doc.name in self.SPECIAL_METHODS:
|
|
<br /><em class="fname">($self.SPECIAL_METHODS[var_doc.name]$)</em>
|
|
>>> #endif
|
|
>>> if isinstance(func_doc, ClassMethodDoc):
|
|
<br /><em class="fname">Class Method</em>
|
|
>>> #endif
|
|
>>> if isinstance(func_doc, StaticMethodDoc):
|
|
<br /><em class="fname">Static Method</em>
|
|
>>> #endif
|
|
</h3>
|
|
</td><td align="right" valign="top"
|
|
>$self.pysrc_link(func_doc)$
|
|
$self.callgraph_link(callgraph)$</td>
|
|
</tr></table>
|
|
$self.render_callgraph(callgraph)$
|
|
$descr$
|
|
<dl class="fields">
|
|
>>> # === parameters ===
|
|
>>> if arg_descrs:
|
|
<dt>Parameters:</dt>
|
|
<dd><ul class="nomargin-top">
|
|
>>> for lhs, rhs in arg_descrs:
|
|
$self.labelled_list_item(lhs, rhs)$
|
|
>>> #endfor
|
|
</ul></dd>
|
|
>>> #endif
|
|
>>> # === return type ===
|
|
>>> if rdescr and rtype:
|
|
<dt>Returns: $rtype$</dt>
|
|
<dd>$rdescr$</dd>
|
|
>>> elif rdescr:
|
|
<dt>Returns:</dt>
|
|
<dd>$rdescr$</dd>
|
|
>>> elif rtype:
|
|
<dt>Returns: $rtype$</dt>
|
|
>>> #endif
|
|
>>> # === decorators ===
|
|
>>> if func_doc.decorators not in (None, UNKNOWN):
|
|
>>> # (staticmethod & classmethod are already shown, above)
|
|
>>> decos = filter(lambda deco:
|
|
>>> not ((deco=="staticmethod" and
|
|
>>> isinstance(func_doc, StaticMethodDoc)) or
|
|
>>> (deco=="classmethod" and
|
|
>>> isinstance(func_doc, ClassMethodDoc))),
|
|
>>> func_doc.decorators)
|
|
>>> else:
|
|
>>> decos = None
|
|
>>> #endif
|
|
>>> if decos:
|
|
<dt>Decorators:</dt>
|
|
<dd><ul class="nomargin-top">
|
|
>>> for deco in decos:
|
|
<li><code>@$deco$</code></li>
|
|
>>> #endfor
|
|
</ul></dd>
|
|
>>> #endif
|
|
>>> # === exceptions ===
|
|
>>> if func_doc.exception_descrs not in (None, UNKNOWN, (), []):
|
|
<dt>Raises:</dt>
|
|
<dd><ul class="nomargin-top">
|
|
>>> for name, descr in func_doc.exception_descrs:
|
|
>>> exc_name = self.docindex.find(name, func_doc)
|
|
>>> if exc_name is not None:
|
|
>>> name = self.href(exc_name, label=str(name))
|
|
>>> #endif
|
|
$self.labelled_list_item(
|
|
"<code><strong class=\'fraise\'>" +
|
|
str(name) + "</strong></code>",
|
|
self.docstring_to_html(descr, func_doc, 8))$
|
|
>>> #endfor
|
|
</ul></dd>
|
|
>>> #endif
|
|
>>> # === overrides ===
|
|
>>> if var_doc.overrides not in (None, UNKNOWN):
|
|
<dt>Overrides:
|
|
>>> # Avoid passing GenericValueDoc to href()
|
|
>>> if isinstance(var_doc.overrides.value, RoutineDoc):
|
|
$self.href(var_doc.overrides.value, context=var_doc)$
|
|
>>> else:
|
|
>>> # In this case, a less interesting label is generated.
|
|
$self.href(var_doc.overrides, context=var_doc)$
|
|
>>> #endif
|
|
>>> if (func_doc.docstring in (None, UNKNOWN) and
|
|
>>> var_doc.overrides.value.docstring not in (None, UNKNOWN)):
|
|
<dd><em class="note">(inherited documentation)</em></dd>
|
|
>>> #endif
|
|
</dt>
|
|
>>> #endif
|
|
</dl>
|
|
>>> # === metadata ===
|
|
>>> self.write_standard_fields(out, func_doc)
|
|
</td></tr></table>
|
|
</div>
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
# Names for the __special__ methods.
|
|
SPECIAL_METHODS ={
|
|
'__init__': 'Constructor',
|
|
'__del__': 'Destructor',
|
|
'__add__': 'Addition operator',
|
|
'__sub__': 'Subtraction operator',
|
|
'__and__': 'And operator',
|
|
'__or__': 'Or operator',
|
|
'__xor__': 'Exclusive-Or operator',
|
|
'__repr__': 'Representation operator',
|
|
'__call__': 'Call operator',
|
|
'__getattr__': 'Qualification operator',
|
|
'__getitem__': 'Indexing operator',
|
|
'__setitem__': 'Index assignment operator',
|
|
'__delitem__': 'Index deletion operator',
|
|
'__delslice__': 'Slice deletion operator',
|
|
'__setslice__': 'Slice assignment operator',
|
|
'__getslice__': 'Slicling operator',
|
|
'__len__': 'Length operator',
|
|
'__cmp__': 'Comparison operator',
|
|
'__eq__': 'Equality operator',
|
|
'__in__': 'Containership operator',
|
|
'__gt__': 'Greater-than operator',
|
|
'__lt__': 'Less-than operator',
|
|
'__ge__': 'Greater-than-or-equals operator',
|
|
'__le__': 'Less-than-or-equals operator',
|
|
'__radd__': 'Right-side addition operator',
|
|
'__hash__': 'Hashing function',
|
|
'__contains__': 'In operator',
|
|
'__nonzero__': 'Boolean test operator',
|
|
'__str__': 'Informal representation operator',
|
|
}
|
|
|
|
write_property_details_entry = compile_template(
|
|
'''
|
|
write_property_details_entry(self, out, var_doc, descr, \
|
|
accessors, div_class)
|
|
''',
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
>>> prop_doc = var_doc.value
|
|
<a name="$var_doc.name$"></a>
|
|
<div$div_class$>
|
|
>>> self.write_table_header(out, "details")
|
|
<tr><td>
|
|
<h3 class="epydoc">$var_doc.name$</h3>
|
|
$descr$
|
|
<dl class="fields">
|
|
>>> for (name, val, summary) in accessors:
|
|
<dt>$name$ Method:</dt>
|
|
<dd class="value">$val$
|
|
>>> if summary:
|
|
- $summary$
|
|
>>> #endif
|
|
</dd>
|
|
>>> #endfor
|
|
>>> if prop_doc.type_descr not in (None, UNKNOWN):
|
|
<dt>Type:</dt>
|
|
<dd>$self.type_descr(var_doc, indent=6)$</dd>
|
|
>>> #endif
|
|
</dl>
|
|
>>> self.write_standard_fields(out, prop_doc)
|
|
</td></tr></table>
|
|
</div>
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
write_variable_details_entry = compile_template(
|
|
'''
|
|
write_variable_details_entry(self, out, var_doc, descr, div_class)
|
|
''',
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
<a name="$var_doc.name$"></a>
|
|
<div$div_class$>
|
|
>>> self.write_table_header(out, "details")
|
|
<tr><td>
|
|
<h3 class="epydoc">$var_doc.name$</h3>
|
|
$descr$
|
|
<dl class="fields">
|
|
>>> if var_doc.type_descr not in (None, UNKNOWN):
|
|
<dt>Type:</dt>
|
|
<dd>$self.type_descr(var_doc, indent=6)$</dd>
|
|
>>> #endif
|
|
</dl>
|
|
>>> self.write_standard_fields(out, var_doc)
|
|
>>> if var_doc.value is not UNKNOWN:
|
|
<dl class="fields">
|
|
<dt>Value:</dt>
|
|
<dd>$self.pprint_value(var_doc.value)$</dd>
|
|
</dl>
|
|
>>> #endif
|
|
</td></tr></table>
|
|
</div>
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
def variable_tooltip(self, var_doc):
|
|
if var_doc.value in (None, UNKNOWN):
|
|
return ''
|
|
s = var_doc.value.pyval_repr().to_plaintext(None)
|
|
if len(s) > self._variable_tooltip_linelen:
|
|
s = s[:self._variable_tooltip_linelen-3]+'...'
|
|
return ' title="%s"' % plaintext_to_html(s)
|
|
|
|
def pprint_value(self, val_doc):
|
|
if val_doc is UNKNOWN:
|
|
return '??'
|
|
elif isinstance(val_doc, GenericValueDoc):
|
|
return ('<table><tr><td><pre class="variable">\n' +
|
|
val_doc.pyval_repr().to_html(None) +
|
|
'\n</pre></td></tr></table>\n')
|
|
else:
|
|
return self.href(val_doc)
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ Base Tree
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def base_tree(self, doc, width=None, postfix='', context=None):
|
|
"""
|
|
@return: The HTML code for a class's base tree. The tree is
|
|
drawn 'upside-down' and right justified, to allow for
|
|
multiple inheritance.
|
|
@rtype: C{string}
|
|
"""
|
|
if context is None:
|
|
context = doc.defining_module
|
|
if width == None: width = self.find_tree_width(doc, context)
|
|
if isinstance(doc, ClassDoc) and doc.bases != UNKNOWN:
|
|
bases = doc.bases
|
|
else:
|
|
bases = []
|
|
|
|
if postfix == '':
|
|
# [XX] use var name instead of canonical name?
|
|
s = (' '*(width-2) + '<strong class="uidshort">'+
|
|
self.contextual_label(doc, context)+'</strong>\n')
|
|
else: s = ''
|
|
for i in range(len(bases)-1, -1, -1):
|
|
base = bases[i]
|
|
label = self.contextual_label(base, context)
|
|
s = (' '*(width-4-len(label)) + self.href(base, label)
|
|
+' --+'+postfix+'\n' +
|
|
' '*(width-4) +
|
|
' |'+postfix+'\n' +
|
|
s)
|
|
if i != 0:
|
|
s = (self.base_tree(base, width-4, ' |'+postfix, context)+s)
|
|
else:
|
|
s = (self.base_tree(base, width-4, ' '+postfix, context)+s)
|
|
return s
|
|
|
|
def find_tree_width(self, doc, context):
|
|
"""
|
|
Helper function for L{base_tree}.
|
|
@return: The width of a base tree, when drawn
|
|
right-justified. This is used by L{base_tree} to
|
|
determine how far to indent lines of the base tree.
|
|
@rtype: C{int}
|
|
"""
|
|
if not isinstance(doc, ClassDoc): return 2
|
|
if doc.bases == UNKNOWN: return 2
|
|
width = 2
|
|
for base in doc.bases:
|
|
width = max(width, len(self.contextual_label(base, context))+4,
|
|
self.find_tree_width(base, context)+4)
|
|
return width
|
|
|
|
def contextual_label(self, doc, context):
|
|
"""
|
|
Return the label for C{doc} to be shown in C{context}.
|
|
"""
|
|
if doc.canonical_name is None:
|
|
if doc.parse_repr is not None:
|
|
return doc.parse_repr
|
|
else:
|
|
return '??'
|
|
else:
|
|
if context is UNKNOWN:
|
|
return str(doc.canonical_name)
|
|
else:
|
|
context_name = context.canonical_name
|
|
return str(doc.canonical_name.contextualize(context_name))
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ Function Signatures
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def function_signature(self, api_doc, is_summary=False,
|
|
link_name=False, anchor=False, context=None):
|
|
"""Render a function signature in HTML.
|
|
|
|
@param api_doc: The object whose name is to be rendered. If a
|
|
C{VariableDoc}, its C{value} should be a C{RoutineDoc}
|
|
@type api_doc: L{VariableDoc} or L{RoutineDoc}
|
|
@param is_summary: True if the fuction is to be rendered in the summary.
|
|
type css_class: C{bool}
|
|
@param link_name: If True, the name is a link to the object anchor.
|
|
@type link_name: C{bool}
|
|
@param anchor: If True, the name is the object anchor.
|
|
@type anchor: C{bool}
|
|
@param context: If set, represent the function name from this context.
|
|
Only useful when C{api_doc} is a L{RoutineDoc}.
|
|
@type context: L{DottedName}
|
|
|
|
@return: The HTML code for the object.
|
|
@rtype: C{str}
|
|
"""
|
|
if is_summary: css_class = 'summary-sig'
|
|
else: css_class = 'sig'
|
|
|
|
# [XX] clean this up!
|
|
if isinstance(api_doc, VariableDoc):
|
|
func_doc = api_doc.value
|
|
# This should never happen, but just in case:
|
|
if api_doc.value in (None, UNKNOWN):
|
|
return (('<span class="%s"><span class="%s-name">%s'+
|
|
'</span>(...)</span>') %
|
|
(css_class, css_class, api_doc.name))
|
|
# Get the function's name.
|
|
name = self.summary_name(api_doc, css_class=css_class+'-name',
|
|
link_name=link_name, anchor=anchor)
|
|
else:
|
|
func_doc = api_doc
|
|
name = self.href(api_doc, css_class=css_class+'-name',
|
|
context=context)
|
|
|
|
if func_doc.posargs == UNKNOWN:
|
|
args = ['...']
|
|
else:
|
|
args = [self.func_arg(n, d, css_class) for (n, d)
|
|
in zip(func_doc.posargs, func_doc.posarg_defaults)]
|
|
if func_doc.vararg not in (None, UNKNOWN):
|
|
if func_doc.vararg == '...':
|
|
args.append('<span class="%s-arg">...</span>' % css_class)
|
|
else:
|
|
args.append('<span class="%s-arg">*%s</span>' %
|
|
(css_class, func_doc.vararg))
|
|
if func_doc.kwarg not in (None, UNKNOWN):
|
|
args.append('<span class="%s-arg">**%s</span>' %
|
|
(css_class, func_doc.kwarg))
|
|
|
|
return ('<span class="%s">%s(%s)</span>' %
|
|
(css_class, name, ',\n '.join(args)))
|
|
|
|
def summary_name(self, api_doc, css_class='summary-name',
|
|
link_name=False, anchor=False):
|
|
"""Render an object name in HTML.
|
|
|
|
@param api_doc: The object whose name is to be rendered
|
|
@type api_doc: L{APIDoc}
|
|
@param css_class: The CSS class to assign to the rendered name
|
|
type css_class: C{str}
|
|
@param link_name: If True, the name is a link to the object anchor.
|
|
@type link_name: C{bool}
|
|
@param anchor: If True, the name is the object anchor.
|
|
@type anchor: C{bool}
|
|
|
|
@return: The HTML code for the object.
|
|
@rtype: C{str}
|
|
"""
|
|
if anchor:
|
|
rv = '<a name="%s"></a>' % api_doc.name
|
|
else:
|
|
rv = ''
|
|
|
|
if link_name:
|
|
rv += self.href(api_doc, css_class=css_class)
|
|
else:
|
|
rv += '<span class="%s">%s</span>' % (css_class, api_doc.name)
|
|
|
|
return rv
|
|
|
|
# [xx] tuple args???
|
|
def func_arg(self, name, default, css_class):
|
|
name = self._arg_name(name)
|
|
s = '<span class="%s-arg">%s</span>' % (css_class, name)
|
|
if default is not None:
|
|
s += ('=<span class="%s-default">%s</span>' %
|
|
(css_class, default.summary_pyval_repr().to_html(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]))
|
|
|
|
|
|
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ Import Lists
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_imports(self, out, doc):
|
|
assert isinstance(doc, NamespaceDoc)
|
|
imports = doc.select_variables(imported=True,
|
|
public=self._public_filter)
|
|
if not imports: return
|
|
|
|
out('<p class="indent-wrapped-lines">')
|
|
out('<b>Imports:</b>\n ')
|
|
out(',\n '.join([self._import(v, doc) for v in imports]))
|
|
out('\n</p><br />\n')
|
|
|
|
def _import(self, var_doc, context):
|
|
if var_doc.imported_from not in (None, UNKNOWN):
|
|
return self.href(var_doc.imported_from,
|
|
var_doc.name, context=context,
|
|
tooltip='%s' % var_doc.imported_from)
|
|
elif (var_doc.value not in (None, UNKNOWN) and not
|
|
isinstance(var_doc.value, GenericValueDoc)):
|
|
return self.href(var_doc.value,
|
|
var_doc.name, context=context,
|
|
tooltip='%s' % var_doc.value.canonical_name)
|
|
else:
|
|
return plaintext_to_html(var_doc.name)
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ Function Attributes
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ Module Trees
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_module_list(self, out, doc):
|
|
if len(doc.submodules) == 0: return
|
|
self.write_table_header(out, "summary", "Submodules")
|
|
|
|
for group_name in doc.group_names():
|
|
if not doc.submodule_groups[group_name]: continue
|
|
if group_name:
|
|
self.write_group_header(out, group_name)
|
|
out(' <tr><td class="summary">\n'
|
|
' <ul class="nomargin">\n')
|
|
for submodule in doc.submodule_groups[group_name]:
|
|
self.write_module_tree_item(out, submodule, package=doc)
|
|
out(' </ul></td></tr>\n')
|
|
|
|
out(self.TABLE_FOOTER+'\n<br />\n')
|
|
|
|
def write_module_tree_item(self, out, doc, package=None):
|
|
# If it's a private variable, then mark its <li>.
|
|
var = package and package.variables.get(doc.canonical_name[-1])
|
|
priv = ((var is not None and var.is_public is False) or
|
|
(var is None and doc.canonical_name[-1].startswith('_')))
|
|
out(' <li%s> <strong class="uidlink">%s</strong>'
|
|
% (priv and ' class="private"' or '', self.href(doc)))
|
|
if doc.summary not in (None, UNKNOWN):
|
|
out(': <em class="summary">'+
|
|
self.description(doc.summary, doc, 8)+'</em>')
|
|
if doc.submodules != UNKNOWN and doc.submodules:
|
|
if priv: out('\n <ul class="private">\n')
|
|
else: out('\n <ul>\n')
|
|
for submodule in doc.submodules:
|
|
self.write_module_tree_item(out, submodule, package=doc)
|
|
out(' </ul>\n')
|
|
out(' </li>\n')
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ Class trees
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
write_class_tree_item = compile_template(
|
|
'''
|
|
write_class_tree_item(self, out, doc, class_set)
|
|
''',
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
>>> if doc.summary in (None, UNKNOWN):
|
|
<li> <strong class="uidlink">$self.href(doc)$</strong>
|
|
>>> else:
|
|
<li> <strong class="uidlink">$self.href(doc)$</strong>:
|
|
<em class="summary">$self.description(doc.summary, doc, 8)$</em>
|
|
>>> # endif
|
|
>>> if doc.subclasses:
|
|
<ul>
|
|
>>> for subclass in sorted(set(doc.subclasses), key=lambda c:c.canonical_name[-1]):
|
|
>>> if subclass in class_set:
|
|
>>> self.write_class_tree_item(out, subclass, class_set)
|
|
>>> #endif
|
|
>>> #endfor
|
|
</ul>
|
|
>>> #endif
|
|
</li>
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ Standard Fields
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_standard_fields(self, out, doc):
|
|
"""
|
|
Write HTML code containing descriptions of any standard markup
|
|
fields that are defined by the given L{APIDoc} object (such as
|
|
C{@author} and C{@todo} fields).
|
|
|
|
@param doc: The L{APIDoc} object containing the API documentation
|
|
for the object whose standard markup fields should be
|
|
described.
|
|
"""
|
|
fields = []
|
|
field_values = {}
|
|
|
|
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)
|
|
|
|
if not fields: return
|
|
|
|
out('<div class="fields">')
|
|
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])
|
|
|
|
out('</div>')
|
|
|
|
write_standard_field = compile_template(
|
|
"""
|
|
write_standard_field(self, out, doc, field, descrs, arg='')
|
|
|
|
""",
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
>>> if arg: arglabel = " (%s)" % arg
|
|
>>> else: arglabel = ""
|
|
>>> if len(descrs) == 1:
|
|
<p><strong>$field.singular+arglabel$:</strong>
|
|
$self.description(descrs[0], doc, 8)$
|
|
</p>
|
|
>>> elif field.short:
|
|
<dl><dt>$field.plural+arglabel$:</dt>
|
|
<dd>
|
|
>>> for descr in descrs[:-1]:
|
|
$self.description(descr, doc, 10)$,
|
|
>>> # end for
|
|
$self.description(descrs[-1], doc, 10)$
|
|
</dd>
|
|
</dl>
|
|
>>> else:
|
|
<strong>$field.plural+arglabel$:</strong>
|
|
<ul class="nomargin-top">
|
|
>>> for descr in descrs:
|
|
<li>
|
|
$self.description(descr, doc, 8)$
|
|
</li>
|
|
>>> # end for
|
|
</ul>
|
|
>>> # end else
|
|
>>> # end for
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ Index generation
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
#: A list of metadata indices that should be generated. Each
|
|
#: entry in this list is a tuple C{(tag, label, short_label)},
|
|
#: where C{tag} is the cannonical tag of a metadata field;
|
|
#: C{label} is a label for the index page; and C{short_label}
|
|
#: is a shorter label, used in the index selector.
|
|
METADATA_INDICES = [('bug', 'Bug List', 'Bugs'),
|
|
('todo', 'To Do List', 'To Do'),
|
|
('change', 'Change Log', 'Changes'),
|
|
('deprecated', 'Deprecation List', 'Deprecations'),
|
|
('since', 'Introductions List', 'Introductions'),
|
|
]
|
|
|
|
def build_identifier_index(self):
|
|
items = []
|
|
for doc in self.indexed_docs:
|
|
name = plaintext_to_html(doc.canonical_name[-1])
|
|
if isinstance(doc, RoutineDoc): name += '()'
|
|
url = self.url(doc)
|
|
if not url: continue
|
|
container = self.docindex.container(doc)
|
|
items.append( (name, url, container) )
|
|
return sorted(items, key=lambda v:v[0].lower())
|
|
|
|
def _group_by_letter(self, items):
|
|
"""Preserves sort order of the input."""
|
|
index = {}
|
|
for item in items:
|
|
first_letter = item[0][0].upper()
|
|
if not ("A" <= first_letter <= "Z"):
|
|
first_letter = '_'
|
|
index.setdefault(first_letter, []).append(item)
|
|
return index
|
|
|
|
def build_term_index(self):
|
|
items = []
|
|
for doc in self.indexed_docs:
|
|
url = self.url(doc)
|
|
items += self._terms_from_docstring(url, doc, doc.descr)
|
|
for (field, arg, descr) in doc.metadata:
|
|
items += self._terms_from_docstring(url, doc, descr)
|
|
if hasattr(doc, 'type_descr'):
|
|
items += self._terms_from_docstring(url, doc,
|
|
doc.type_descr)
|
|
if hasattr(doc, 'return_descr'):
|
|
items += self._terms_from_docstring(url, doc,
|
|
doc.return_descr)
|
|
if hasattr(doc, 'return_type'):
|
|
items += self._terms_from_docstring(url, doc,
|
|
doc.return_type)
|
|
return sorted(items, key=lambda v:v[0].lower())
|
|
|
|
def _terms_from_docstring(self, base_url, container, parsed_docstring):
|
|
if parsed_docstring in (None, UNKNOWN): return []
|
|
terms = []
|
|
# Strip any existing anchor off:
|
|
base_url = re.sub('#.*', '', '%s' % (base_url,))
|
|
for term in parsed_docstring.index_terms():
|
|
anchor = self._term_index_to_anchor(term)
|
|
url = '%s#%s' % (base_url, anchor)
|
|
terms.append( (term.to_plaintext(None), url, container) )
|
|
return terms
|
|
|
|
def build_metadata_index(self, field_name):
|
|
# Build the index.
|
|
index = {}
|
|
for doc in self.indexed_docs:
|
|
if (not self._show_private and
|
|
self._doc_or_ancestor_is_private(doc)):
|
|
continue
|
|
descrs = {}
|
|
if doc.metadata is not UNKNOWN:
|
|
for (field, arg, descr) in doc.metadata:
|
|
if field.tags[0] == field_name:
|
|
descrs.setdefault(arg, []).append(descr)
|
|
for (arg, descr_list) in descrs.iteritems():
|
|
index.setdefault(arg, []).append( (doc, descr_list) )
|
|
return index
|
|
|
|
def _term_index_to_anchor(self, term):
|
|
"""
|
|
Given the name of an inline index item, construct a URI anchor.
|
|
These anchors are used to create links from the index page to each
|
|
index item.
|
|
"""
|
|
# Include "-" so we don't accidentally collide with the name
|
|
# of a python identifier.
|
|
s = re.sub(r'\s\s+', '-', term.to_plaintext(None))
|
|
return "index-"+re.sub("[^a-zA-Z0-9]", "_", s)
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ Redirect page
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_redirect_page(self, out):
|
|
"""
|
|
Build the auto-redirect page, which translates dotted names to
|
|
URLs using javascript. When the user visits
|
|
<redirect.html#dotted.name>, they will automatically get
|
|
redirected to the page for the object with the given
|
|
fully-qualified dotted name. E.g., for epydoc,
|
|
<redirect.html#epydoc.apidoc.UNKNOWN> redirects the user to
|
|
<epydoc.apidoc-module.html#UNKNOWN>.
|
|
"""
|
|
# Construct a list of all the module & class pages that we're
|
|
# documenting. The redirect_url javascript will scan through
|
|
# this list, looking for a page name that matches the
|
|
# requested dotted name.
|
|
pages = (['%s-m' % val_doc.canonical_name
|
|
for val_doc in self.module_list] +
|
|
['%s-c' % val_doc.canonical_name
|
|
for val_doc in self.class_list])
|
|
# Sort the pages from longest to shortest. This ensures that
|
|
# we find e.g. "x.y.z" in the list before "x.y".
|
|
pages = sorted(pages, key=lambda p:-len(p))
|
|
|
|
# Write the redirect page.
|
|
self._write_redirect_page(out, pages)
|
|
|
|
_write_redirect_page = compile_template(
|
|
'''
|
|
_write_redirect_page(self, out, pages)
|
|
''',
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
<html><head><title>Epydoc Redirect Page</title>
|
|
<meta http-equiv="cache-control" content="no-cache" />
|
|
<meta http-equiv="expires" content="0" />
|
|
<meta http-equiv="pragma" content="no-cache" />
|
|
<script type="text/javascript" src="epydoc.js"></script>
|
|
</head>
|
|
<body>
|
|
<script type="text/javascript">
|
|
<!--
|
|
var pages = $"[%s]" % ", ".join(['"%s"' % v for v in pages])$;
|
|
var dottedName = get_anchor();
|
|
if (dottedName) {
|
|
var target = redirect_url(dottedName);
|
|
if (target) window.location.replace(target);
|
|
}
|
|
// -->
|
|
</script>
|
|
|
|
<h3>Epydoc Auto-redirect page</h3>
|
|
|
|
<p>When javascript is enabled, this page will redirect URLs of
|
|
the form <tt>redirect.html#<i>dotted.name</i></tt> to the
|
|
documentation for the object with the given fully-qualified
|
|
dotted name.</p>
|
|
<p><a id="message"> </a></p>
|
|
|
|
<script type="text/javascript">
|
|
<!--
|
|
if (dottedName) {
|
|
var msg = document.getElementById("message");
|
|
msg.innerHTML = "No documentation found for <tt>"+
|
|
dottedName+"</tt>";
|
|
}
|
|
// -->
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ URLs list
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def write_api_list(self, out):
|
|
"""
|
|
Write a list of mapping name->url for all the documented objects.
|
|
"""
|
|
# Construct a list of all the module & class pages that we're
|
|
# documenting. The redirect_url javascript will scan through
|
|
# this list, looking for a page name that matches the
|
|
# requested dotted name.
|
|
skip = (ModuleDoc, ClassDoc, type(UNKNOWN))
|
|
for val_doc in self.module_list:
|
|
self.write_url_record(out, val_doc)
|
|
for var in val_doc.variables.itervalues():
|
|
if not isinstance(var.value, skip):
|
|
self.write_url_record(out, var)
|
|
|
|
for val_doc in self.class_list:
|
|
self.write_url_record(out, val_doc)
|
|
for var in val_doc.variables.itervalues():
|
|
self.write_url_record(out, var)
|
|
|
|
def write_url_record(self, out, obj):
|
|
url = self.url(obj)
|
|
if url is not None:
|
|
out("%s\t%s\n" % (obj.canonical_name, url))
|
|
|
|
#////////////////////////////////////////////////////////////
|
|
#{ Helper functions
|
|
#////////////////////////////////////////////////////////////
|
|
|
|
def _val_is_public(self, valdoc):
|
|
"""Make a best-guess as to whether the given class is public."""
|
|
container = self.docindex.container(valdoc)
|
|
if isinstance(container, NamespaceDoc):
|
|
for vardoc in container.variables.values():
|
|
if vardoc in (UNKNOWN, None): continue
|
|
if vardoc.value is valdoc:
|
|
return vardoc.is_public
|
|
return True
|
|
|
|
# [XX] Is it worth-while to pull the anchor tricks that I do here?
|
|
# Or should I just live with the fact that show/hide private moves
|
|
# stuff around?
|
|
write_table_header = compile_template(
|
|
'''
|
|
write_table_header(self, out, css_class, heading=None, \
|
|
private_link=True, colspan=2)
|
|
''',
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
>>> if heading is not None:
|
|
>>> anchor = "section-%s" % re.sub("\W", "", heading)
|
|
<!-- ==================== $heading.upper()$ ==================== -->
|
|
<a name="$anchor$"></a>
|
|
>>> #endif
|
|
<table class="$css_class$" border="1" cellpadding="3"
|
|
cellspacing="0" width="100%" bgcolor="white">
|
|
>>> if heading is not None:
|
|
<tr bgcolor="#70b0f0" class="table-header">
|
|
>>> if private_link and self._show_private:
|
|
<td colspan="$colspan$" class="table-header">
|
|
<table border="0" cellpadding="0" cellspacing="0" width="100%">
|
|
<tr valign="top">
|
|
<td align="left"><span class="table-header">$heading$</span></td>
|
|
<td align="right" valign="top"
|
|
><span class="options">[<a href="#$anchor$"
|
|
class="privatelink" onclick="toggle_private();"
|
|
>hide private</a>]</span></td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
>>> else:
|
|
<td align="left" colspan="2" class="table-header">
|
|
<span class="table-header">$heading$</span></td>
|
|
>>> #endif
|
|
</tr>
|
|
>>> #endif
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
TABLE_FOOTER = '</table>\n'
|
|
|
|
PRIVATE_LINK = '''
|
|
<span class="options">[<a href="javascript:void(0);" class="privatelink"
|
|
onclick="toggle_private();">hide private</a>]</span>
|
|
'''.strip()
|
|
|
|
write_group_header = compile_template(
|
|
'''
|
|
write_group_header(self, out, group, tr_class='')
|
|
''',
|
|
# /------------------------- Template -------------------------\
|
|
'''
|
|
<tr bgcolor="#e8f0f8" $tr_class$>
|
|
<th colspan="2" class="group-header"
|
|
> $group$</th></tr>
|
|
''')
|
|
# \------------------------------------------------------------/
|
|
|
|
_url_cache = {}
|
|
def url(self, obj):
|
|
"""
|
|
Return the URL for the given object, which can be a
|
|
C{VariableDoc}, a C{ValueDoc}, or a C{DottedName}.
|
|
"""
|
|
cached_url = self._url_cache.get(id(obj))
|
|
if cached_url is not None:
|
|
return cached_url
|
|
else:
|
|
url = self._url_cache[id(obj)] = self._url(obj)
|
|
return url
|
|
|
|
def _url(self, obj):
|
|
"""
|
|
Internal helper for L{url}.
|
|
"""
|
|
# Module: <canonical_name>-module.html
|
|
if isinstance(obj, ModuleDoc):
|
|
if obj not in self.module_set: return None
|
|
return urllib.quote('%s'%obj.canonical_name) + '-module.html'
|
|
# Class: <canonical_name>-class.html
|
|
elif isinstance(obj, ClassDoc):
|
|
if obj not in self.class_set: return None
|
|
return urllib.quote('%s'%obj.canonical_name) + '-class.html'
|
|
# Variable
|
|
elif isinstance(obj, VariableDoc):
|
|
val_doc = obj.value
|
|
if isinstance(val_doc, (ModuleDoc, ClassDoc)):
|
|
return self.url(val_doc)
|
|
elif obj.container in (None, UNKNOWN):
|
|
if val_doc in (None, UNKNOWN): return None
|
|
return self.url(val_doc)
|
|
elif obj.is_imported == True:
|
|
if obj.imported_from is not UNKNOWN:
|
|
return self.url(obj.imported_from)
|
|
else:
|
|
return None
|
|
else:
|
|
container_url = self.url(obj.container)
|
|
if container_url is None: return None
|
|
return '%s#%s' % (container_url, urllib.quote('%s'%obj.name))
|
|
# Value (other than module or class)
|
|
elif isinstance(obj, ValueDoc):
|
|
container = self.docindex.container(obj)
|
|
if container is None:
|
|
return None # We couldn't find it!
|
|
else:
|
|
container_url = self.url(container)
|
|
if container_url is None: return None
|
|
anchor = urllib.quote('%s'%obj.canonical_name[-1])
|
|
return '%s#%s' % (container_url, anchor)
|
|
# Dotted name: look up the corresponding APIDoc
|
|
elif isinstance(obj, DottedName):
|
|
val_doc = self.docindex.get_valdoc(obj)
|
|
if val_doc is None: return None
|
|
return self.url(val_doc)
|
|
# Special pages:
|
|
elif obj == 'indices':
|
|
return 'identifier-index.html'
|
|
elif obj == 'help':
|
|
return 'help.html'
|
|
elif obj == 'trees':
|
|
return self._trees_url
|
|
else:
|
|
raise ValueError, "Don't know what to do with %r" % obj
|
|
|
|
def pysrc_link(self, api_doc):
|
|
if not self._incl_sourcecode:
|
|
return ''
|
|
url = self.pysrc_url(api_doc)
|
|
if url is not None:
|
|
return ('<span class="codelink"><a href="%s">source '
|
|
'code</a></span>' % url)
|
|
else:
|
|
return ''
|
|
|
|
def pysrc_url(self, api_doc):
|
|
if isinstance(api_doc, VariableDoc):
|
|
if api_doc.value not in (None, UNKNOWN):
|
|
return pysrc_url(api_doc.value)
|
|
else:
|
|
return None
|
|
elif isinstance(api_doc, ModuleDoc):
|
|
if api_doc in self.modules_with_sourcecode:
|
|
return ('%s-pysrc.html' %
|
|
urllib.quote('%s' % api_doc.canonical_name))
|
|
else:
|
|
return None
|
|
else:
|
|
module = api_doc.defining_module
|
|
if module == UNKNOWN: return None
|
|
module_pysrc_url = self.pysrc_url(module)
|
|
if module_pysrc_url is None: return None
|
|
module_name = module.canonical_name
|
|
if not module_name.dominates(api_doc.canonical_name, True):
|
|
log.debug('%r is in %r but name does not dominate' %
|
|
(api_doc, module))
|
|
return module_pysrc_url
|
|
mname_len = len(module.canonical_name)
|
|
anchor = '%s' % api_doc.canonical_name[mname_len:]
|
|
return '%s#%s' % (module_pysrc_url, urllib.quote(anchor))
|
|
|
|
# We didn't find it:
|
|
return None
|
|
|
|
# [xx] add code to automatically do <code> wrapping or the like?
|
|
def href(self, target, label=None, css_class=None, context=None,
|
|
tooltip=None):
|
|
"""
|
|
Return the HTML code for an HREF link to the given target
|
|
(which can be a C{VariableDoc}, a C{ValueDoc}, or a
|
|
C{DottedName}.
|
|
If a C{NamespaceDoc} C{context} is specified, the target label is
|
|
contextualized to it.
|
|
"""
|
|
assert isinstance(target, (APIDoc, DottedName))
|
|
|
|
# Pick a label, if none was given.
|
|
if label is None:
|
|
if isinstance(target, VariableDoc):
|
|
label = target.name
|
|
elif (isinstance(target, ValueDoc) and
|
|
target.canonical_name is not UNKNOWN):
|
|
label = target.canonical_name
|
|
elif isinstance(target, DottedName):
|
|
label = target
|
|
elif isinstance(target, GenericValueDoc):
|
|
raise ValueError("href() should not be called with "
|
|
"GenericValueDoc objects (perhaps you "
|
|
"meant to use the containing variable?)")
|
|
else:
|
|
raise ValueError("Unable to find a label for %r" % target)
|
|
|
|
if context is not None and isinstance(label, DottedName):
|
|
label = label.contextualize(context.canonical_name.container())
|
|
|
|
label = plaintext_to_html(str(label))
|
|
|
|
# Munge names for scripts & unreachable values
|
|
if label.startswith('script-'):
|
|
label = label[7:] + ' (script)'
|
|
if label.startswith('??'):
|
|
label = '<i>unreachable</i>' + label[2:]
|
|
label = re.sub(r'-\d+$', '', label)
|
|
|
|
# Get the url for the target.
|
|
url = self.url(target)
|
|
if url is None:
|
|
if tooltip: return '<span title="%s">%s</span>' % (tooltip, label)
|
|
else: return label
|
|
|
|
# Construct a string for the class attribute.
|
|
if css_class is None:
|
|
css = ''
|
|
else:
|
|
css = ' class="%s"' % css_class
|
|
|
|
onclick = ''
|
|
if ((isinstance(target, VariableDoc) and not target.is_public) or
|
|
(isinstance(target, ValueDoc) and
|
|
not isinstance(target, GenericValueDoc) and
|
|
not self._val_is_public(target))):
|
|
onclick = ' onclick="show_private();"'
|
|
|
|
if tooltip:
|
|
tooltip = ' title="%s"' % tooltip
|
|
else:
|
|
tooltip = ''
|
|
|
|
return '<a href="%s"%s%s%s>%s</a>' % (url, css, onclick, tooltip, label)
|
|
|
|
def _attr_to_html(self, attr, api_doc, indent):
|
|
if api_doc in (None, UNKNOWN):
|
|
return ''
|
|
pds = getattr(api_doc, attr, None) # pds = ParsedDocstring.
|
|
if pds not in (None, UNKNOWN):
|
|
return self.docstring_to_html(pds, api_doc, indent)
|
|
elif isinstance(api_doc, VariableDoc):
|
|
return self._attr_to_html(attr, api_doc.value, indent)
|
|
|
|
def summary(self, api_doc, indent=0):
|
|
return self._attr_to_html('summary', api_doc, indent)
|
|
|
|
def descr(self, api_doc, indent=0):
|
|
return self._attr_to_html('descr', api_doc, indent)
|
|
|
|
def type_descr(self, api_doc, indent=0):
|
|
return self._attr_to_html('type_descr', api_doc, indent)
|
|
|
|
def return_type(self, api_doc, indent=0):
|
|
return self._attr_to_html('return_type', api_doc, indent)
|
|
|
|
def return_descr(self, api_doc, indent=0):
|
|
return self._attr_to_html('return_descr', api_doc, indent)
|
|
|
|
def docstring_to_html(self, parsed_docstring, where=None, indent=0):
|
|
if parsed_docstring in (None, UNKNOWN): return ''
|
|
linker = _HTMLDocstringLinker(self, where)
|
|
s = parsed_docstring.to_html(linker, indent=indent,
|
|
directory=self._directory,
|
|
docindex=self.docindex,
|
|
context=where).strip()
|
|
if self._mark_docstrings:
|
|
s = '<span class="docstring">%s</span><!--end docstring-->' % s
|
|
return s
|
|
|
|
def description(self, parsed_docstring, where=None, indent=0):
|
|
assert isinstance(where, (APIDoc, type(None)))
|
|
if parsed_docstring in (None, UNKNOWN): return ''
|
|
linker = _HTMLDocstringLinker(self, where)
|
|
descr = parsed_docstring.to_html(linker, indent=indent,
|
|
directory=self._directory,
|
|
docindex=self.docindex,
|
|
context=where).strip()
|
|
if descr == '': return ' '
|
|
return descr
|
|
|
|
# [xx] Should this be defined by the APIDoc classes themselves??
|
|
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 _doc_or_ancestor_is_private(self, api_doc):
|
|
name = api_doc.canonical_name
|
|
for i in range(len(name), 0, -1):
|
|
# Is it (or an ancestor) a private var?
|
|
var_doc = self.docindex.get_vardoc(name[:i])
|
|
if var_doc is not None and var_doc.is_public == False:
|
|
return True
|
|
# Is it (or an ancestor) a private module?
|
|
val_doc = self.docindex.get_valdoc(name[:i])
|
|
if (val_doc is not None and isinstance(val_doc, ModuleDoc) and
|
|
val_doc.canonical_name[-1].startswith('_')):
|
|
return True
|
|
return False
|
|
|
|
def _private_subclasses(self, class_doc):
|
|
"""Return a list of all subclasses of the given class that are
|
|
private, as determined by L{_val_is_private}. Recursive
|
|
subclasses are included in this list."""
|
|
queue = [class_doc]
|
|
private = set()
|
|
for cls in queue:
|
|
if (isinstance(cls, ClassDoc) and
|
|
cls.subclasses not in (None, UNKNOWN)):
|
|
queue.extend(cls.subclasses)
|
|
private.update([c for c in cls.subclasses if
|
|
not self._val_is_public(c)])
|
|
return private
|
|
|
|
class _HTMLDocstringLinker(epydoc.markup.DocstringLinker):
|
|
def __init__(self, htmlwriter, container):
|
|
self.htmlwriter = htmlwriter
|
|
self.docindex = htmlwriter.docindex
|
|
self.container = container
|
|
|
|
def translate_indexterm(self, indexterm):
|
|
key = self.htmlwriter._term_index_to_anchor(indexterm)
|
|
return ('<a name="%s"></a><i class="indexterm">%s</i>' %
|
|
(key, indexterm.to_html(self)))
|
|
|
|
def translate_identifier_xref(self, identifier, label=None):
|
|
# Pick a label for this xref.
|
|
if label is None: label = plaintext_to_html(identifier)
|
|
|
|
# Find the APIDoc for it (if it's available).
|
|
doc = self.docindex.find(identifier, self.container)
|
|
|
|
# If we didn't find a target, then try checking in the contexts
|
|
# of the ancestor classes.
|
|
if doc is None and isinstance(self.container, RoutineDoc):
|
|
container = self.docindex.get_vardoc(
|
|
self.container.canonical_name)
|
|
while (doc is None and container not in (None, UNKNOWN)
|
|
and container.overrides not in (None, UNKNOWN)):
|
|
container = container.overrides
|
|
doc = self.docindex.find(identifier, container)
|
|
|
|
# Translate it into HTML.
|
|
if doc is None:
|
|
self._failed_xref(identifier)
|
|
return '<code class="link">%s</code>' % label
|
|
else:
|
|
return self.htmlwriter.href(doc, label, 'link')
|
|
|
|
# [xx] Should this be added to the DocstringLinker interface???
|
|
# Currently, this is *only* used by dotgraph.
|
|
def url_for(self, identifier):
|
|
if isinstance(identifier, (basestring, DottedName)):
|
|
doc = self.docindex.find(identifier, self.container)
|
|
if doc:
|
|
return self.htmlwriter.url(doc)
|
|
else:
|
|
return None
|
|
|
|
elif isinstance(identifier, APIDoc):
|
|
return self.htmlwriter.url(identifier)
|
|
doc = identifier
|
|
|
|
else:
|
|
raise TypeError('Expected string or APIDoc')
|
|
|
|
def _failed_xref(self, identifier):
|
|
"""Add an identifier to the htmlwriter's failed crossreference
|
|
list."""
|
|
# Don't count it as a failed xref if it's a parameter of the
|
|
# current function.
|
|
if (isinstance(self.container, RoutineDoc) and
|
|
identifier in self.container.all_args()):
|
|
return
|
|
|
|
failed_xrefs = self.htmlwriter._failed_xrefs
|
|
context = self.container.canonical_name
|
|
failed_xrefs.setdefault(identifier,{})[context] = 1
|