"""Convert epydoc markup into content renderable by Nevow.""" from pydoctor import model, zopeinterface from nevow import tags import inspect, urllib def link(o): return urllib.quote(o.system.urlprefix+o.fullName()+'.html') def get_parser(formatname): try: mod = __import__('epydoc.markup.' + formatname, globals(), locals(), ['parse_docstring']) except ImportError, e: return None, e else: return mod.parse_docstring, None def boringDocstring(doc, summary=False): """Generate an HTML representation of a docstring in a really boring way. """ # inspect.getdoc requires an object with a __doc__ attribute, not # just a string :-( if doc is None or not doc.strip(): return '
Undocumented' def crappit(): pass crappit.__doc__ = doc return [tags.pre, tags.tt][bool(summary)][inspect.getdoc(crappit)] class _EpydocLinker(object): def __init__(self, obj): self.obj = obj def translate_indexterm(self, something): # X{foobar} is meant to put foobar in an index page (like, a # proper end-of-the-book index). Should we support that? There # are like 2 uses in Twisted. return de_p(something.to_html(self)) def translate_identifier_xref(self, fullID, prettyID): obj = self.obj.resolveDottedName(fullID) if obj is None: return '
%s'%(prettyID,)
else:
if isinstance(obj, model.Function):
linktext = link(obj.parent) + '#' + urllib.quote(obj.name)
else:
linktext = link(obj)
return '%s'%(linktext, prettyID)
class FieldDesc(object):
def __init__(self):
self.kind = None
self.name = None
self.type = None
self.body = None
def format(self):
if self.body is None:
body = ''
else:
body = self.body
if self.type is not None:
body = body, ' (type: ', self.type, ')'
return body
def __repr__(self):
contents = []
for k, v in self.__dict__.iteritems():
contents.append("%s=%r"%(k, v))
return "<%s(%s)>"%(self.__class__.__name__, ', '.join(contents))
def format_desc_list(singular, descs, plural=None):
if plural is None:
plural = singular + 's'
if not descs:
return ''
if len(descs) > 1:
label = plural
else:
label = singular
r = []
first = True
for d in descs:
if first:
row = tags.tr(class_="fieldStart")
row[tags.td(class_="fieldName")[label]]
first = False
else:
row = tags.tr()
row[tags.td()]
if d.name is None:
row[tags.td(colspan=2)[d.format()]]
else:
row[tags.td(class_="fieldArg")[d.name], tags.td[d.format()]]
r.append(row)
return r
def format_field_list(obj, singular, fields, plural=None):
if plural is None:
plural = singular + 's'
if not fields:
return ''
if len(fields) > 1:
label = plural
else:
label = singular
rows = []
first = True
for field in fields:
if first:
row = tags.tr(class_="fieldStart")
row[tags.td(class_="fieldName")[label]]
first=False
else:
row = tags.tr()
row[tags.td()]
row[tags.td(colspan=2)[field.body]]
rows.append(row)
return rows
class Field(object):
"""Like epydoc.markup.Field, but without the gross accessor
methods and with a formatted body."""
def __init__(self, field, obj):
self.tag = field.tag()
self.arg = field.arg()
self.body = tags.raw(de_p(field.body().to_html(_EpydocLinker(obj))))
def __repr__(self):
r = repr(self.body)
if len(r) > 25:
r = r[:20] + '...' + r[-2:]
return "<%s %r %r %s>"%(self.__class__.__name__,
self.tag, self.arg, r)
class FieldHandler(object):
def __init__(self, obj):
self.obj = obj
self.parameter_descs = []
self.ivar_descs = []
self.cvar_descs = []
self.var_descs = []
self.return_desc = None
self.raise_descs = []
self.seealsos = []
self.notes = []
self.authors = []
self.sinces = []
self.unknowns = []
self.unattached_types = {}
def redef(self, field):
self.obj.system.msg(
"epytext",
"on %r: redefinition of @type %s"%(self.obj.fullName(), field.arg),
thresh=-1)
def handle_return(self, field):
if not self.return_desc:
self.return_desc = FieldDesc()
if self.return_desc.body:
self.obj.system.msg('epydoc2stan', 'XXX')
self.return_desc.body = field.body
handle_returns = handle_return
def handle_returntype(self, field):
if not self.return_desc:
self.return_desc = FieldDesc()
if self.return_desc.type:
self.obj.system.msg('epydoc2stan', 'XXX')
self.return_desc.type = field.body
handle_rtype = handle_returntype
def add_type_info(self, desc_list, field):
#print desc_list, field
if desc_list and desc_list[-1].name == field.arg:
if desc_list[-1].type is not None:
self.redef(field)
desc_list[-1].type = field.body
else:
d = FieldDesc()
d.kind = field.tag
d.name = field.arg
d.type = field.body
desc_list.append(d)
def add_info(self, desc_list, field):
if desc_list and desc_list[-1].name == field.arg and desc_list[-1].body is None:
desc_list[-1].body = field.body
else:
d = FieldDesc()
d.kind = field.tag
d.name = field.arg
d.body = field.body
desc_list.append(d)
def handle_type(self, field):
obj = self.obj
if isinstance(obj, model.Function):
self.add_type_info(self.parameter_descs, field)
elif isinstance(obj, model.Class):
ivars = self.ivar_descs
cvars = self.cvar_descs
if ivars and ivars[-1].name == field.arg:
if ivars[-1].type is not None:
self.redef(field)
ivars[-1].type = field.body
elif cvars and cvars[-1].name == field.arg:
if cvars[-1].type is not None:
self.redef(field)
cvars[-1].type = field.body
else:
self.unattached_types[field.arg] = field.body
else:
self.add_type_info(self.var_descs, field)
def handle_param(self, field):
self.add_info(self.parameter_descs, field)
handle_arg = handle_param
handle_keyword = handle_param
def handle_ivar(self, field):
self.add_info(self.ivar_descs, field)
if field.arg in self.unattached_types:
self.ivar_descs[-1].type = self.unattached_types[field.arg]
del self.unattached_types[field.arg]
def handle_cvar(self, field):
self.add_info(self.cvar_descs, field)
if field.arg in self.unattached_types:
self.cvar_descs[-1].type = self.unattached_types[field.arg]
del self.unattached_types[field.arg]
def handle_var(self, field):
self.add_info(self.var_descs, field)
def handle_raises(self, field):
self.add_info(self.raise_descs, field)
handle_raise = handle_raises
def handle_seealso(self, field):
self.seealsos.append(field)
handle_see = handle_seealso
def handle_note(self, field):
self.notes.append(field)
def handle_author(self, field):
self.authors.append(field)
def handle_since(self, field):
self.sinces.append(field)
def handleUnknownField(self, field):
self.obj.system.msg(
'epydoc2stan',
'found unknown field on %r: %r'%(self.obj.fullName(), field),
thresh=-1)
self.add_info(self.unknowns, field)
def handle(self, field):
m = getattr(self, 'handle_' + field.tag, self.handleUnknownField)
m(field)
def format(self):
r = []
ivs = []
for iv in self.ivar_descs:
attr = zopeinterface.Attribute(
self.obj.system, iv.name, iv.body, self.obj)
if iv.name is None or attr.isVisible:
ivs.append(iv)
self.ivar_descs = ivs
cvs = []
for cv in self.cvar_descs:
attr = zopeinterface.Attribute(
self.obj.system, cv.name, cv.body, self.obj)
if attr.isVisible:
cvs.append(cv)
self.cvar_descs = cvs
for d, l in (('Parameters', self.parameter_descs),
('Instance Variables', self.ivar_descs),
('Class Variables', self.cvar_descs),
('Variables', self.var_descs)):
r.append(format_desc_list(d, l, d))
if self.return_desc:
r.append(tags.tr(class_="fieldStart")[tags.td(class_="fieldName")['Returns'],
tags.td(colspan="2")[self.return_desc.format()]])
r.append(format_desc_list("Raises", self.raise_descs, "Raises"))
for s, p, l in (('Author', 'Authors', self.authors),
('See Also', 'See Also', self.seealsos),
('Present Since', 'Present Since', self.sinces),
('Note', 'Notes', self.notes)):
r.append(format_field_list(self.obj, s, l, p))
unknowns = {}
unknownsinorder = []
for fieldinfo in self.unknowns:
tag = fieldinfo.kind
if tag in unknowns:
unknowns[tag].append(fieldinfo)
else:
unknowns[tag] = [fieldinfo]
unknownsinorder.append(unknowns[tag])
for fieldlist in unknownsinorder:
label = "Unknown Field: " + fieldlist[0].kind
r.append(format_desc_list(label, fieldlist, label))
return tags.table(class_='fieldTable')[r]
def de_p(s):
if s.startswith('') and s.endswith('
\n'): return s[3:-5] # argh reST else: return s def reportErrors(obj, errs): for err in errs: if isinstance(err, str): linenumber = '??' descr = err else: linenumber = obj.linenumber + err.linenum() descr = err._descr obj.system.msg( 'epydoc2stan2', '%s:%s epytext error %r' % (obj.fullName(), linenumber, descr)) if errs and obj.fullName() not in obj.system.epytextproblems: obj.system.epytextproblems.append(obj.fullName()) obj.system.msg('epydoc2stan', 'epytext error in %s'%(obj,), thresh=1) p = lambda m:obj.system.msg('epydoc2stan', m, thresh=2) for i, l in enumerate(obj.docstring.splitlines()): p("%4s"%(i+1)+' '+l) for err in errs: p(err) def doc2html(obj, summary=False, docstring=None): """Generate an HTML representation of a docstring""" origobj = obj if isinstance(obj, model.Package): obj = obj.contents['__init__'] if docstring is None: doc = None for source in obj.docsources(): if source.docstring is not None: doc = source.docstring break else: source = obj doc = docstring if doc is None or not doc.strip(): text = "Undocumented" subdocstrings = {} subcounts = {} for subob in origobj.contents.itervalues(): k = subob.kind.lower() subcounts[k] = subcounts.get(k, 0) + 1 if subob.docstring is not None: subdocstrings[k] = subdocstrings.get(k, 0) + 1 if isinstance(origobj, model.Package): subcounts["module"] -= 1 if subdocstrings: plurals = {'class':'classes'} text = "No %s docstring"%origobj.kind.lower() if summary: u = [] for k in sorted(subcounts): u.append("%s/%s %s"%(subdocstrings.get(k, 0), subcounts[k], plurals.get(k, k+'s'))) text += '; ' + ', '.join(u) + " documented" if summary: return tags.span(class_="undocumented")[text], [] else: return tags.div(class_="undocumented")[text], [] if summary: for line in doc.split('\n'): if line.strip(): doc = line break parse_docstring, e = get_parser(obj.system.options.docformat) if not parse_docstring: msg = 'Error trying to import %r parser:\n\n %s: %s\n\nUsing plain text formatting only.'%( obj.system.options.docformat, e.__class__.__name__, e) obj.system.msg('epydoc2stan', msg, thresh=-1, once=True) return boringDocstring(doc, summary), [] errs = [] def crappit(): pass crappit.__doc__ = doc doc = inspect.getdoc(crappit) try: pdoc = parse_docstring(doc, errs) except Exception, e: errs = [e.__class__.__name__ +': ' + str(e)] if errs: reportErrors(source, errs) return boringDocstring(doc, summary), errs pdoc, fields = pdoc.split_fields() if pdoc is not None: try: crap = de_p(pdoc.to_html(_EpydocLinker(getattr(obj, 'docsource', obj)))) except Exception, e: reportErrors(source, [e.__class__.__name__ +': ' + str(e)]) return (boringDocstring(doc, summary), [e.__class__.__name__ +': ' + str(e)]) else: crap = '' if isinstance(crap, unicode): crap = crap.encode('utf-8') if summary: if not crap: return (), [] s = tags.span()[tags.raw(crap)] else: if not crap and not fields: return (), [] s = tags.div()[tags.raw(crap)] fh = FieldHandler(obj) for field in fields: fh.handle(Field(field, obj)) s[fh.format()] return s, []