First step toward better handling of doc fields.

This commit is contained in:
Georg Brandl 2010-01-15 00:00:37 +01:00
parent 5705adc9d5
commit f5c08cbb60
2 changed files with 200 additions and 3 deletions

View File

@ -292,3 +292,23 @@ The special files are located in the root output directory. They are:
Unlike the other pickle files this pickle file requires that the sphinx
module is available on unpickling.
.. class:: Blah
:param app: the Sphinx application object
:type app: sphinx.builders.Builder
:param what: the type of the object which the docstring belongs to (one of
``"module"``, ``"class"``, ``"exception"``, ``"function"``, ``"method"``,
``"attribute"``)
:param name: the fully qualified name of the object
:param obj: the object itself
:param options: the options given to the directive: an object with attributes
``inherited_members``, ``undoc_members``, ``show_inheritance`` and
``noindex`` that are true if the flag option of same name was given to the
auto directive
:param lines: the lines of the docstring, see above
:raises foo: why?
:raises sphinx.builders.Builder: why not...
:returns: me
:rtype: sphinx.builders.Builder

View File

@ -32,6 +32,10 @@ except AttributeError:
directives.length_or_percentage_or_unitless
# RE to strip backslash escapes
strip_backslash_re = re.compile(r'\\(?=[^\\])')
def _is_only_paragraph(node):
"""True if the node only contains one paragraph (and system messages)."""
if len(node) == 0:
@ -45,8 +49,164 @@ def _is_only_paragraph(node):
return False
# RE to strip backslash escapes
strip_backslash_re = re.compile(r'\\(?=[^\\])')
class FieldType(object):
def __init__(self, name, names=(), label=None, grouplabel=None,
objtype=None, has_arg=True):
self.name = name
self.names = names
self.label = label
self.grouplabel = grouplabel
self.objtype = objtype
self.has_arg = has_arg
class TypedFieldType(FieldType):
def __init__(self, name, names=(), typefields=(), label=None,
grouplabel=None, objtype=None, has_arg=True):
FieldType.__init__(self, name, names, label,
grouplabel, objtype, has_arg)
self.typefields = typefields
class DocFieldTransformer(object):
"""
Transforms field lists in "doc field" syntax into better-looking
equivalents, using the field type definitions given on a domain.
"""
def __init__(self, directive):
self.domain = directive.domain
if not hasattr(directive, '_doc_field_type_map'):
directive._doc_field_type_map = \
self.preprocess_fieldtypes(directive.doc_field_types)
self.typemap = directive._doc_field_type_map
def preprocess_fieldtypes(self, types):
typemap = {}
for fieldtype in types:
for name in fieldtype.names:
typemap[name] = fieldtype, 0
if isinstance(fieldtype, TypedFieldType):
for name in fieldtype.typefields:
typemap[name] = fieldtype, 1
return typemap
def transform_all(self, node):
"""Transform all field list children of a node."""
# don't traverse, only handle field lists that are immediate children
for child in node:
if isinstance(child, nodes.field_list):
self.transform(child)
def transform(self, node):
"""Transform a single field list *node*."""
typemap = self.typemap
entries = []
groupindices = {}
types = {}
for field in node:
fieldname, fieldbody = field
try:
fieldtype, arg = fieldname.astext().split(None, 1)
except ValueError:
# argument-less field type?
fieldtype, arg = fieldname.astext(), ''
typedesc, is_typefield = typemap.get(fieldtype, (None, None))
if typedesc is None or \
typedesc.has_arg != bool(arg):
# capitalize field name and be done with it
new_fieldname = fieldtype.capitalize() + ' ' + arg
fieldname[0] = nodes.Text(new_fieldname)
entries.append(field)
continue
typename = typedesc.name
if _is_only_paragraph(fieldbody):
content = fieldbody.children[0].children
else:
content = fieldbody.children
if is_typefield:
types.setdefault(typename, {})[arg] = content
continue
# support syntax like ``:param type name:``
try:
argtype, argname = arg.split(None, 1)
except ValueError:
pass
else:
types.setdefault(typename, {})[arg] = nodes.Text(argtype)
arg = argname
if typedesc.grouplabel:
if typename in groupindices:
group = entries[groupindices[typename]]
else:
group = [typedesc]
groupindices[typename] = len(entries)
entries.append(group)
group.append((arg, content))
else:
entries.append((typedesc, arg, content))
# now that all entries are collected, construct the new field list
new_list = nodes.field_list()
for entry in entries:
if isinstance(entry, nodes.field):
# pass-through old field
new_list += entry
elif isinstance(entry, tuple):
# single entry
entrytype, arg, content = entry
fieldname = nodes.field_name()
para = nodes.paragraph('', entrytype.label)
if arg:
para += nodes.Text(' ')
if entrytype.objtype:
para += addnodes.pending_xref(
'', arg, refdomain=self.domain,
reftype=entrytype.objtype, refexplicit=False,
reftarget=arg)
else:
para += nodes.Text(arg)
fieldname += para
fieldbody = nodes.field_body('', nodes.paragraph('', *content))
new_list += nodes.field('', fieldname, fieldbody)
else:
# group entry
grouptype = entry[0]
groupitems = entry[1:]
grouptypes = types.get(grouptype.name, {})
fieldname = nodes.field_name()
fieldname += nodes.paragraph('', grouptype.grouplabel)
bullets = nodes.bullet_list()
for name, content in groupitems:
par = nodes.paragraph()
par += nodes.emphasis('', name)
if name in grouptypes:
typenodes = grouptypes[name]
par += nodes.Text(' (')
if grouptype.objtype and len(typenodes) == 1:
typename = typenodes[0].astext()
par += addnodes.pending_xref(
'', refdomain=self.domain,
reftype=grouptype.objtype, refexplicit=False,
reftarget=typename)
par[-1] += nodes.emphasis('', typename)
else:
par += typenodes
par += nodes.Text(')')
par += nodes.Text(' -- ')
par += content
bullets += nodes.list_item('', par)
fieldbody = nodes.field_body('', bullets)
new_list += nodes.field('', fieldname, fieldbody)
node.replace_self(new_list)
class ObjectDescription(Directive):
@ -64,6 +224,22 @@ class ObjectDescription(Directive):
'module': directives.unchanged,
}
doc_field_types = [
TypedFieldType('parameter',
names=('param', 'parameter', 'arg', 'argument',
'keyword', 'kwarg', 'kwparam', 'type'),
label=l_('Parameter'), grouplabel=l_('Parameters'),
objtype='obj', typefields=('type',)),
TypedFieldType('variable', names=('var', 'ivar', 'cvar'), objtype='obj',
label=l_('Variable'), grouplabel=l_('Variables')),
FieldType('exceptions', names=('raises', 'raise', 'exception', 'except'),
label=l_('Raises'), grouplabel=l_('Raises'), objtype='exc'),
FieldType('returnvalue', names=('returns', 'return'),
label=l_('Returns'), has_arg=False),
FieldType('returntype', names=('rtype',),
label=l_('Return type'), has_arg=False),
]
# XXX make this more domain specific
doc_fields_with_arg = {
@ -264,7 +440,8 @@ class ObjectDescription(Directive):
self.env.doc_read_data['object'] = self.names[0]
self.before_content()
self.state.nested_parse(self.content, self.content_offset, contentnode)
self.handle_doc_fields(contentnode)
#self.handle_doc_fields(contentnode)
DocFieldTransformer(self).transform_all(contentnode)
self.env.doc_read_data['object'] = None
self.after_content()
return [self.indexnode, node]