]+)?>', rhs)
if m:
lhs = m.group() + lhs
rhs = rhs[m.end():]
return '
%s\n' % vstr
# Do line-wrapping.
return self._linewrap_html(vstr, self._variable_linelen,
self._variable_maxlines)
def _linewrap_html(self, s, linelen, maxlines):
"""
Add line-wrapping to the HTML string C{s}. Line length is
determined by C{linelen}; and the maximum number of
lines to display is determined by C{maxlines}. This
function treats HTML entities (e.g., C{&}) as single
characters; and ignores HTML tags (e.g., C{}).
"""
LINEWRAP_MARKER = r'\'
ELLIPSIS_MARKER = r'...'
open_elements = [] # tag stack
lines = []
start = end = cnum = 0
while len(lines) <= maxlines and end < len(s):
# Skip over HTML tags.
if s[end] == '<':
newend = s.find('>', end)
tag = s[end+1:newend]
if tag[-1]!="/":
# non-empty tag
tagname = tag.split(None,1)[0]
if tagname[0] == "/":
open_elements.pop()
else:
open_elements.append(tagname)
end = newend
cnum -= 1
# HTML entities just count as 1 char.
elif s[end] == '&':
end = s.find(';', end)
# Go on to the next character.
cnum += 1
end += 1
# Check for end-of-line.
if s[end-1] == '\n':
lines.append(s[start:end-1])
cnum = 0
start = end
# Check for line-wrap
if cnum == linelen and end" % (tag,)
return s[start:end]+closing_tags+ELLIPSIS_MARKER
lines.append(s[start:end]+LINEWRAP_MARKER)
cnum = 0
start = end
# Add on anything that's left.
if end == len(s):
lines.append(s[start:end])
# Use the ellipsis marker if the string is too long.
if len(lines) > maxlines:
closing_tags = ""
for tag in open_elements:
closing_tags += "%s>" % (tag,)
lines[-1] = closing_tags+ELLIPSIS_MARKER
cnum = 3
# Pad the last line to linelen.
lines[-1] += ' '*(linelen-cnum+1)
return ('\n').join(lines)
#////////////////////////////////////////////////////////////
#{ 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
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) + ''+
self.contextual_label(doc, context)+'\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)+s)
else:
s = (self.base_tree(base, width-4, ' '+postfix)+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 L{doc} to be shown in C{context}.
"""
context = context.canonical_name
if context is not UNKNOWN:
context = context.container()
return str(doc.canonical_name.contextualize(context))
#////////////////////////////////////////////////////////////
#{ Function Signatures
#////////////////////////////////////////////////////////////
def function_signature(self, api_doc, css_class='sig',
link_name=False):
# [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 (('%s'+
'(...)') %
(css_class, css_class, api_doc.name))
# Get the function's name.
if link_name:
name = self.href(api_doc, css_class=css_class+'-name')
else:
name = ('%s' %
(css_class, api_doc.name))
else:
func_doc = api_doc
name = self.href(api_doc, css_class=css_class+'-name')
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:
if func_doc.vararg == '...':
args.append('...' % css_class)
else:
args.append('*%s' %
(css_class, func_doc.vararg))
if func_doc.kwarg:
args.append('**%s' %
(css_class, func_doc.kwarg))
return ('%s(%s)' %
(css_class, name, ',\n '.join(args)))
# [xx] tuple args???
def func_arg(self, name, default, css_class):
name = self._arg_name(name)
s = '%s' % (css_class, name)
if default is not None:
if default.parse_repr is not UNKNOWN:
s += ('=%s' %
(css_class, plaintext_to_html(default.parse_repr)))
else:
pyval_repr = default.pyval_repr()
if pyval_repr is not UNKNOWN:
s += ('=%s' %
(css_class, plaintext_to_html(pyval_repr)))
else:
s += '=??' % css_class
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('')
out('\n ')
out(',\n '.join([self._import(v, doc) for v in imports]))
out('\n
\n')
def _import(self, var_doc, context):
if var_doc.imported_from not in (None, UNKNOWN):
return self.href(var_doc.imported_from, context=context)
elif (var_doc.value not in (None, UNKNOWN) and not
isinstance(var_doc.value, GenericValueDoc)):
return self.href(var_doc.value, context=context)
else:
return plaintext_to_html(var_doc.name)
#////////////////////////////////////////////////////////////
#{ Function Attributes
#////////////////////////////////////////////////////////////
#////////////////////////////////////////////////////////////
#{ Module Trees
#////////////////////////////////////////////////////////////
def write_module_tree(self, out):
if not self.module_list: return
# Write entries for all top-level modules/packages.
out('\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('
\n')
def write_module_list(self, out, doc):
if len(doc.submodules) == 0: return
self.write_table_header(out, "details", "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(' \n')
for submodule in doc.submodule_groups[group_name]:
self.write_module_tree_item(out, submodule, package=doc)
out(' |
\n')
out(self.TABLE_FOOTER+'\n
\n')
def write_module_tree_item(self, out, doc, package=None):
# If it's a private variable, then mark its .
var = package and package.variables.get(doc.canonical_name[-1])
if var is not None:
priv = var.is_public is False
else:
priv = doc.canonical_name[-1].startswith('_')
out(' %s'
% (priv and ' class="private"' or '', self.href(doc)))
if doc.summary not in (None, UNKNOWN):
out(': '+
self.description(doc.summary, doc, 8)+'')
out('\n')
if doc.submodules != UNKNOWN and doc.submodules:
out(' \n' % (priv and ' class="private"' or ''))
for submodule in doc.submodules:
self.write_module_tree_item(out, submodule, package=doc)
out('
\n \n')
#////////////////////////////////////////////////////////////
#{ Class trees
#////////////////////////////////////////////////////////////
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. This is used by L{write_trees} to write
the class hierarchy.
@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?
if not self.class_list: return
# 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('\n')
for doc in sorted(class_set):
if doc.bases != UNKNOWN and len(doc.bases)==0:
self.write_class_tree_item(out, doc, class_set)
out('
\n')
write_class_tree_item = compile_template(
'''
write_class_tree_item(self, out, doc, class_set)
''',
# /------------------------- Template -------------------------\
'''
>>> if doc.summary in (None, UNKNOWN):
$self.href(doc)$
>>> else:
$self.href(doc)$:
$self.description(doc.summary, doc, 8)$
>>> # endif
>>> if doc.subclasses:
>>> for subclass in set(doc.subclasses):
>>> if subclass in class_set:
>>> self.write_class_tree_item(out, subclass, class_set)
>>> #endif
>>> #endfor
>>> #endif
''')
# \------------------------------------------------------------/
#////////////////////////////////////////////////////////////
#{ 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 = {}
#if _sort_fields: fields = STANDARD_FIELD_NAMES [XX]
for (field, arg, descr) in doc.metadata:
if field not in field_values:
fields.append(field)
if field.takes_arg:
subfields = field_values.setdefault(field,{})
subfields.setdefault(arg,[]).append(descr)
else:
field_values.setdefault(field,[]).append(descr)
for field in fields:
if field.takes_arg:
for arg, descrs in field_values[field].items():
self.write_standard_field(out, doc, field, descrs, arg)
else:
self.write_standard_field(out, doc, field, field_values[field])
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:
$field.singular+arglabel$:
$self.description(descrs[0], doc, 8)$
>>> elif field.short:
- $field.plural+arglabel$:
-
>>> for descr in descrs[:-1]:
$self.description(descr, doc, 10)$,
>>> # end for
$self.description(descrs[-1], doc, 10)$
>>> else:
$field.plural+arglabel$:
>>> for descr in descrs:
-
$self.description(descr, doc, 8)$
>>> # end for
>>> # end else
>>> # end for
""")
# \------------------------------------------------------------/
#////////////////////////////////////////////////////////////
#{ Term index generation
#////////////////////////////////////////////////////////////
def _get_index_terms(self, parsed_docstring, link, terms, links):
"""
A helper function for L{_extract_term_index}.
For each index term M{t} with key M{k} in C{parsed_docstring},
modify C{terms} and C{links} as follows:
- Set C{terms[M{k}] = t} (if C{terms[M{k}]} doesn't exist).
- Append C{link} to C{links[M{k}]}.
"""
if parsed_docstring in (None, UNKNOWN): return
for term in parsed_docstring.index_terms():
key = self._term_index_to_anchor(term)
if not terms.has_key(key):
terms[key] = term
links[key] = []
links[key].append(link)
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)
def _extract_term_index(self):
"""
Extract the set of terms that should be indexed from all
documented docstrings. Return the extracted set as a
list of tuples of the form C{(key, term, [links])}.
This list is used by L{write_indices()} to construct the
term index.
@rtype: C{list} of C{(string, ParsedDocstring, list of ValueDoc)}
"""
terms = {}
links = {}
for doc in self.valdocs:
self._get_index_terms(doc.descr, doc, terms, links)
if doc.metadata not in (None, UNKNOWN):
for (field, arg, descr) in doc.metadata:
self._get_index_terms(descr, doc, terms, links)
# [xx] summary? type_descr? others?
if isinstance(doc, NamespaceDoc):
for var in doc.variables.values():
self._get_index_terms(var.descr, var, terms, links)
for (field, arg, descr) in var.metadata:
self._get_index_terms(descr, var, terms, links)
elif isinstance(doc, RoutineDoc):
self._get_index_terms(doc.return_descr, doc, terms, links)
self._get_index_terms(doc.return_type, doc, terms, links)
if doc.arg_descrs not in (None, UNKNOWN):
for arg, descr in doc.arg_descrs:
self._get_index_terms(descr, doc, terms, links)
if doc.arg_types not in (None, UNKNOWN):
for arg, descr in doc.arg_types.items():
self._get_index_terms(descr, doc, terms, links)
if doc.exception_descrs not in (None, UNKNOWN):
for excname, descr in doc.exception_descrs:
self._get_index_terms(descr, doc, terms, links)
elif isinstance(doc, PropertyDoc):
self._get_index_terms(doc.type_descr, doc, terms, links)
# Combine terms & links into one list
keys = terms.keys()
keys.sort()
return [(k, terms[k], links[k]) for k in keys]
#////////////////////////////////////////////////////////////
#{ Helper functions
#////////////////////////////////////////////////////////////
# [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)
''',
# /------------------------- Template -------------------------\
'''
>>> if heading is not None:
>>> anchor = "section-%s" % re.sub("\W", "", heading)
>>> #endif
>>> if heading is not None:
>>> if private_link:
|
>>> else:
$heading$ |
>>> #endif
>>> #endif
''')
# \------------------------------------------------------------/
TABLE_FOOTER = '
\n'
PRIVATE_LINK = '''
[hide private]
'''.strip()
write_group_header = compile_template(
'''
write_group_header(self, out, group, tr_class='')
''',
# /------------------------- Template -------------------------\
'''
$group$ |
''')
# \------------------------------------------------------------/
def url(self, obj):
"""
Return the URL for the given object, which can be a
C{VariableDoc}, a C{ValueDoc}, or a C{DottedName}.
"""
# Module: -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: -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 'indices.html'
elif obj == 'help':
return 'help.html'
elif obj == 'trees':
return 'trees.html'
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 ('source '
'code' % url)
else:
return ''
def pysrc_url(self, api_doc):
if isinstance(api_doc, VariableDoc):
if api_doc.value not in (None, UNKNOWN):
return pysrc_link(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 wrapping or the like?
def href(self, target, label=None, css_class=None, context=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
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 = 'unreachable' + label[2:]
label = re.sub(r'-\d+$', '', label)
# Get the url for the target.
url = self.url(target)
if url is None: return label
# Construct a string for the class attribute.
if css_class is None:
css = ''
else:
css = ' class="%s"' % css_class
return '%s' % (url, css, label)
def summary(self, api_doc, indent=0):
assert isinstance(api_doc, APIDoc)
if (isinstance(api_doc, VariableDoc) and
api_doc.summary in (None, UNKNOWN)):
if api_doc.value in (None, UNKNOWN): return ''
api_doc = api_doc.value
if api_doc.summary in (None, UNKNOWN): return ''
return self.docstring_to_html(api_doc.summary, api_doc, indent)
def descr(self, api_doc, indent=0):
assert isinstance(api_doc, APIDoc)
if (isinstance(api_doc, VariableDoc) and
api_doc.descr in (None, UNKNOWN)):
if api_doc.value in (None, UNKNOWN): return ''
api_doc = api_doc.value
if api_doc.descr in (None, UNKNOWN): return ''
return self.docstring_to_html(api_doc.descr, api_doc, indent)
def type_descr(self, api_doc, indent=0):
assert isinstance(api_doc, APIDoc)
if (isinstance(api_doc, VariableDoc) and
api_doc.type_descr in (None, UNKNOWN)):
if api_doc.value in (None, UNKNOWN): return ''
api_doc = api_doc.value
if api_doc.type_descr in (None, UNKNOWN): return ''
return self.docstring_to_html(api_doc.type_descr, api_doc, indent)
def rtype(self, api_doc, indent=0):
if isinstance(api_doc, VariableDoc):
if api_doc.value in (None, UNKNOWN): return ''
api_doc = api_doc.value
assert isinstance(api_doc, RoutineDoc)
if api_doc.return_type in (None, UNKNOWN): return ''
return self.docstring_to_html(api_doc.return_type, api_doc, indent)
def return_descr(self, api_doc, indent=0):
if isinstance(api_doc, VariableDoc):
if api_doc.value in (None, UNKNOWN): return ''
api_doc = api_doc.value
assert isinstance(api_doc, RoutineDoc)
if api_doc.return_descr in (None, UNKNOWN): return ''
return self.docstring_to_html(api_doc.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 = '%s' % s
return s
# [XX] Just use docstring_to_html???
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):
var_doc = self.docindex.get_vardoc(name[:i])
if var_doc is not None and var_doc.is_public == False:
return True
return False
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 ('%s' %
(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)
# Translate it into HTML.
if doc is None:
return '%s
' % label
else:
return self.htmlwriter.href(doc, label, 'link')
# [xx] Should this be added to the DocstringLinker interface???
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:
# [xx] ignore if it's inside an import??
# Record that we failed to find it.
failed_xrefs = self.htmlwriter._failed_xrefs
context = self.container.canonical_name
failed_xrefs.setdefault(identifier,{})[context] = 1
elif isinstance(identifier, APIDoc):
return self.htmlwriter.url(identifier)
doc = identifier
else:
raise TypeError('Expected string or APIDoc')