mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Finish new doc field handling implementation.
This commit is contained in:
parent
c00fd08aa9
commit
c98236bc61
3
CHANGES
3
CHANGES
@ -7,6 +7,9 @@ Release 1.0 (in development)
|
||||
|
||||
* Support for docutils 0.4 has been removed.
|
||||
|
||||
* New more compact doc field syntax is now recognized:
|
||||
``:param type name: description``.
|
||||
|
||||
* Added the ``viewcode`` extension.
|
||||
|
||||
* Added ``html-collect-pages`` event.
|
||||
|
@ -292,23 +292,3 @@ 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
|
||||
|
@ -7,7 +7,7 @@ import sys, os, re
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.addons.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo',
|
||||
'sphinx.ext.autosummary', 'sphinx.ext.viewcode']
|
||||
'sphinx.ext.autosummary']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
@ -11,12 +11,11 @@
|
||||
|
||||
import re
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import Directive, directives
|
||||
from docutils.parsers.rst.directives import images
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.locale import l_
|
||||
from sphinx.util.docfields import DocFieldTransformer
|
||||
|
||||
# import and register directives
|
||||
from sphinx.directives.code import *
|
||||
@ -36,179 +35,6 @@ except AttributeError:
|
||||
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:
|
||||
return False
|
||||
elif len(node) > 1:
|
||||
for subnode in node[1:]:
|
||||
if not isinstance(subnode, nodes.system_message):
|
||||
return False
|
||||
if isinstance(node[0], nodes.paragraph):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Directive to describe a class, function or similar object. Not used
|
||||
@ -224,139 +50,8 @@ 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 = {
|
||||
'param': '%param',
|
||||
'parameter': '%param',
|
||||
'arg': '%param',
|
||||
'argument': '%param',
|
||||
'keyword': '%param',
|
||||
'kwarg': '%param',
|
||||
'kwparam': '%param',
|
||||
'type': '%type',
|
||||
'raises': l_('Raises'),
|
||||
'raise': l_('Raises'),
|
||||
'exception': l_('Raises'),
|
||||
'except': l_('Raises'),
|
||||
'var': l_('Variable'),
|
||||
'ivar': l_('Variable'),
|
||||
'cvar': l_('Variable'),
|
||||
'returns': l_('Returns'),
|
||||
'return': l_('Returns'),
|
||||
}
|
||||
|
||||
doc_fields_with_linked_arg = ('raises', 'raise', 'exception', 'except')
|
||||
|
||||
doc_fields_without_arg = {
|
||||
'returns': l_('Returns'),
|
||||
'return': l_('Returns'),
|
||||
'rtype': l_('Return type'),
|
||||
}
|
||||
|
||||
# XXX refactor this
|
||||
|
||||
def handle_doc_fields(self, node):
|
||||
"""
|
||||
Convert field lists with known keys inside the description content into
|
||||
better-looking equivalents.
|
||||
"""
|
||||
# don't traverse, only handle field lists that are immediate children
|
||||
for child in node.children:
|
||||
if not isinstance(child, nodes.field_list):
|
||||
continue
|
||||
params = []
|
||||
pfield = None
|
||||
param_nodes = {}
|
||||
param_types = {}
|
||||
new_list = nodes.field_list()
|
||||
for field in child:
|
||||
fname, fbody = field
|
||||
try:
|
||||
typ, obj = fname.astext().split(None, 1)
|
||||
typdesc = self.doc_fields_with_arg[typ]
|
||||
if _is_only_paragraph(fbody):
|
||||
children = fbody.children[0].children
|
||||
else:
|
||||
children = fbody.children
|
||||
if typdesc == '%param':
|
||||
if not params:
|
||||
# add the field that later gets all the parameters
|
||||
pfield = nodes.field()
|
||||
new_list += pfield
|
||||
dlitem = nodes.list_item()
|
||||
dlpar = nodes.paragraph()
|
||||
dlpar += nodes.emphasis(obj, obj)
|
||||
dlpar += nodes.Text(' -- ', ' -- ')
|
||||
dlpar += children
|
||||
param_nodes[obj] = dlpar
|
||||
dlitem += dlpar
|
||||
params.append(dlitem)
|
||||
elif typdesc == '%type':
|
||||
typenodes = fbody.children
|
||||
if _is_only_paragraph(fbody):
|
||||
typenodes = ([nodes.Text(' (')] +
|
||||
typenodes[0].children +
|
||||
[nodes.Text(')')])
|
||||
param_types[obj] = typenodes
|
||||
else:
|
||||
fieldname = typdesc + ' '
|
||||
nfield = nodes.field()
|
||||
nfieldname = nodes.field_name(fieldname, fieldname)
|
||||
nfield += nfieldname
|
||||
node = nfieldname
|
||||
if typ in self.doc_fields_with_linked_arg:
|
||||
# XXX currmodule/currclass
|
||||
node = addnodes.pending_xref(
|
||||
obj, reftype='obj', refexplicit=False,
|
||||
reftarget=obj)
|
||||
#, modname=self.env.currmodule
|
||||
#, classname=self.env.currclass
|
||||
nfieldname += node
|
||||
node += nodes.Text(obj, obj)
|
||||
nfield += nodes.field_body()
|
||||
nfield[1] += fbody.children
|
||||
new_list += nfield
|
||||
except (KeyError, ValueError):
|
||||
fnametext = fname.astext()
|
||||
try:
|
||||
typ = self.doc_fields_without_arg[fnametext]
|
||||
except KeyError:
|
||||
# at least capitalize the field name
|
||||
typ = fnametext.capitalize()
|
||||
fname[0] = nodes.Text(typ)
|
||||
new_list += field
|
||||
if params:
|
||||
if len(params) == 1:
|
||||
pfield += nodes.field_name('', _('Parameter'))
|
||||
pfield += nodes.field_body()
|
||||
pfield[1] += params[0][0]
|
||||
else:
|
||||
pfield += nodes.field_name('', _('Parameters'))
|
||||
pfield += nodes.field_body()
|
||||
pfield[1] += nodes.bullet_list()
|
||||
pfield[1][0].extend(params)
|
||||
|
||||
for param, type in param_types.iteritems():
|
||||
if param in param_nodes:
|
||||
param_nodes[param][1:1] = type
|
||||
child.replace_self(new_list)
|
||||
# types of doc fields that this directive handles, see sphinx.util.docfields
|
||||
doc_field_types = []
|
||||
|
||||
def get_signatures(self):
|
||||
"""
|
||||
@ -422,7 +117,7 @@ class ObjectDescription(Directive):
|
||||
# this is strictly domain-specific (i.e. no assumptions may
|
||||
# be made in this base class)
|
||||
name = self.handle_signature(sig, signode)
|
||||
except ValueError, err:
|
||||
except ValueError:
|
||||
# signature parsing failed
|
||||
signode.clear()
|
||||
signode += addnodes.desc_name(sig, sig)
|
||||
@ -463,7 +158,7 @@ class DefaultDomain(Directive):
|
||||
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
domain_name = arguments[0]
|
||||
domain_name = self.arguments[0]
|
||||
env.doc_read_data['default_domain'] = env.domains.get(domain_name)
|
||||
|
||||
|
||||
|
@ -13,7 +13,6 @@ import re
|
||||
import string
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst import directives
|
||||
|
||||
from sphinx import addnodes
|
||||
from sphinx.roles import XRefRole
|
||||
@ -21,7 +20,7 @@ from sphinx.locale import l_
|
||||
from sphinx.domains import Domain, ObjType
|
||||
from sphinx.directives import ObjectDescription
|
||||
from sphinx.util import make_refnode
|
||||
from sphinx.util.compat import Directive
|
||||
from sphinx.util.docfields import Field, TypedField
|
||||
|
||||
|
||||
# RE to split at word boundaries
|
||||
@ -48,6 +47,16 @@ class CObject(ObjectDescription):
|
||||
Description of a C language object.
|
||||
"""
|
||||
|
||||
doc_field_types = [
|
||||
TypedField('parameter', label=l_('Parameters'),
|
||||
names=('param', 'parameter', 'arg', 'argument'),
|
||||
typerolename='obj', typenames=('type',)),
|
||||
Field('returnvalue', label=l_('Returns'), has_arg=False,
|
||||
names=('returns', 'return')),
|
||||
Field('returntype', label=l_('Return type'), has_arg=False,
|
||||
names=('rtype',), rolename='obj'),
|
||||
]
|
||||
|
||||
# These C types aren't described anywhere, so don't try to create
|
||||
# a cross-reference to them
|
||||
stopwords = set(('const', 'void', 'char', 'int', 'long', 'FILE', 'struct'))
|
||||
|
@ -21,6 +21,7 @@ from sphinx.domains import Domain, ObjType
|
||||
from sphinx.directives import ObjectDescription
|
||||
from sphinx.util import make_refnode
|
||||
from sphinx.util.compat import Directive
|
||||
from sphinx.util.docfields import Field, GroupedField, TypedField
|
||||
|
||||
|
||||
# REs for Python signatures
|
||||
@ -40,6 +41,22 @@ class PyObject(ObjectDescription):
|
||||
Description of a general Python object.
|
||||
"""
|
||||
|
||||
doc_field_types = [
|
||||
TypedField('parameter', label=l_('Parameters'),
|
||||
names=('param', 'parameter', 'arg', 'argument',
|
||||
'keyword', 'kwarg', 'kwparam'),
|
||||
typerolename='obj', typenames=('type',)),
|
||||
TypedField('variable', label=l_('Variables'), rolename='obj',
|
||||
names=('var', 'ivar', 'cvar')),
|
||||
GroupedField('exceptions', label=l_('Raises'), rolename='exc',
|
||||
names=('raises', 'raise', 'exception', 'except'),
|
||||
can_collapse=True),
|
||||
Field('returnvalue', label=l_('Returns'), has_arg=False,
|
||||
names=('returns', 'return')),
|
||||
Field('returntype', label=l_('Return type'), has_arg=False,
|
||||
names=('rtype',), rolename='obj'),
|
||||
]
|
||||
|
||||
def get_signature_prefix(self, sig):
|
||||
"""
|
||||
May return a prefix to put before the object name in the signature.
|
||||
|
257
sphinx/util/docfields.py
Normal file
257
sphinx/util/docfields.py
Normal file
@ -0,0 +1,257 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sphinx.util.docfields
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
"Doc fields" are reST field lists in object descriptions that will
|
||||
be domain-specifically transformed to a more appealing presentation.
|
||||
|
||||
:copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
from docutils import nodes
|
||||
|
||||
from sphinx import addnodes
|
||||
|
||||
|
||||
def _is_only_paragraph(node):
|
||||
"""True if the node only contains one paragraph (and system messages)."""
|
||||
if len(node) == 0:
|
||||
return False
|
||||
elif len(node) > 1:
|
||||
for subnode in node[1:]:
|
||||
if not isinstance(subnode, nodes.system_message):
|
||||
return False
|
||||
if isinstance(node[0], nodes.paragraph):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Field(object):
|
||||
"""
|
||||
A doc field that is never grouped. It can have an argument or not, the
|
||||
argument can be linked using a specified *rolename*. Field should be used
|
||||
for doc fields that usually don't occur more than once.
|
||||
|
||||
Example::
|
||||
|
||||
:returns: description of the return value
|
||||
:rtype: description of the return type
|
||||
"""
|
||||
is_grouped = False
|
||||
is_typed = False
|
||||
|
||||
def __init__(self, name, names=(), label=None, has_arg=True, rolename=None):
|
||||
self.name = name
|
||||
self.names = names
|
||||
self.label = label
|
||||
self.has_arg = has_arg
|
||||
self.rolename = rolename
|
||||
|
||||
def make_xref(self, rolename, domain, target, innernode=nodes.emphasis):
|
||||
if not rolename:
|
||||
return innernode(target, target)
|
||||
refnode = addnodes.pending_xref('', refdomain=domain, refexplicit=False,
|
||||
reftype=rolename, reftarget=target)
|
||||
refnode += innernode(target, target)
|
||||
return refnode
|
||||
|
||||
def make_entry(self, fieldarg, content):
|
||||
return (fieldarg, content)
|
||||
|
||||
def make_field(self, types, domain, item):
|
||||
fieldarg, content = item
|
||||
fieldname = nodes.field_name('', self.label)
|
||||
if fieldarg:
|
||||
fieldname += nodes.Text(' ')
|
||||
fieldname += self.make_xref(self.rolename, domain,
|
||||
fieldarg, nodes.Text)
|
||||
fieldbody = nodes.field_body('', nodes.paragraph('', *content))
|
||||
return nodes.field('', fieldname, fieldbody)
|
||||
|
||||
|
||||
class GroupedField(Field):
|
||||
"""
|
||||
A doc field that is grouped; i.e., all fields of that type will be
|
||||
transformed into one field with its body being a bulleted list. It always
|
||||
has an argument. The argument can be linked using the given *rolename*.
|
||||
GroupedField should be used for doc fields that can occur more than once.
|
||||
If *can_collapse* is true, this field will revert to a Field if only used
|
||||
once.
|
||||
|
||||
Example::
|
||||
|
||||
:raises ErrorClass: description when it is raised
|
||||
"""
|
||||
is_grouped = True
|
||||
list_type = nodes.bullet_list
|
||||
|
||||
def __init__(self, name, names=(), label=None, rolename=None,
|
||||
can_collapse=False):
|
||||
Field.__init__(self, name, names, label, True, rolename)
|
||||
self.can_collapse = can_collapse
|
||||
|
||||
def make_field(self, types, domain, items):
|
||||
fieldname = nodes.field_name('', self.label)
|
||||
listnode = self.list_type()
|
||||
if len(items) == 1 and self.can_collapse:
|
||||
return Field.make_field(self, types, domain, items[0])
|
||||
for fieldarg, content in items:
|
||||
par = nodes.paragraph()
|
||||
par += self.make_xref(self.rolename, domain, fieldarg, nodes.strong)
|
||||
par += nodes.Text(' -- ')
|
||||
par += content
|
||||
listnode += nodes.list_item('', par)
|
||||
fieldbody = nodes.field_body('', listnode)
|
||||
return nodes.field('', fieldname, fieldbody)
|
||||
|
||||
|
||||
class TypedField(GroupedField):
|
||||
"""
|
||||
A doc field that is grouped and has type information for the arguments. It
|
||||
always has an argument. The argument can be linked using the given
|
||||
*rolename*, the type using the given *typerolename*.
|
||||
|
||||
Two uses are possible: either parameter and type description are given
|
||||
separately, using a field from *names* and one from *typenames*,
|
||||
respectively, or both are given using a field from *names*, see the example.
|
||||
|
||||
Example::
|
||||
|
||||
:param foo: description of parameter foo
|
||||
:type foo: SomeClass
|
||||
|
||||
-- or --
|
||||
|
||||
:param SomeClass foo: description of parameter foo
|
||||
"""
|
||||
is_typed = True
|
||||
|
||||
def __init__(self, name, names=(), typenames=(), label=None,
|
||||
rolename=None, typerolename=None):
|
||||
GroupedField.__init__(self, name, names, label, rolename, False)
|
||||
self.typenames = typenames
|
||||
self.typerolename = typerolename
|
||||
|
||||
def make_field(self, types, domain, items):
|
||||
fieldname = nodes.field_name('', self.label)
|
||||
listnode = self.list_type()
|
||||
for fieldarg, content in items:
|
||||
par = nodes.paragraph()
|
||||
par += self.make_xref(self.rolename, domain, fieldarg, nodes.strong)
|
||||
if fieldarg in types:
|
||||
typename = types[fieldarg]
|
||||
if isinstance(typename, list):
|
||||
typename = u''.join(n.astext() for n in typename)
|
||||
par += nodes.Text(' (')
|
||||
par += self.make_xref(self.typerolename, domain, typename)
|
||||
par += nodes.Text(')')
|
||||
par += nodes.Text(' -- ')
|
||||
par += content
|
||||
listnode += nodes.list_item('', par)
|
||||
fieldbody = nodes.field_body('', listnode)
|
||||
return nodes.field('', fieldname, fieldbody)
|
||||
|
||||
|
||||
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.__class__._doc_field_type_map = \
|
||||
self.preprocess_fieldtypes(directive.__class__.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 fieldtype.is_typed:
|
||||
for name in fieldtype.typenames:
|
||||
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 = {}
|
||||
|
||||
# step 1: traverse all fields and collect field types and content
|
||||
for field in node:
|
||||
fieldname, fieldbody = field
|
||||
try:
|
||||
# split into field type and argument
|
||||
fieldtype, fieldarg = fieldname.astext().split(None, 1)
|
||||
except ValueError:
|
||||
# maybe an argument-less field type?
|
||||
fieldtype, fieldarg = fieldname.astext(), ''
|
||||
typedesc, is_typefield = typemap.get(fieldtype, (None, None))
|
||||
if typedesc is None or \
|
||||
typedesc.has_arg != bool(fieldarg):
|
||||
# either the field name is unknown, or the argument doesn't
|
||||
# match the spec; capitalize field name and be done with it
|
||||
new_fieldname = fieldtype.capitalize() + ' ' + fieldarg
|
||||
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, {})[fieldarg] = content
|
||||
continue
|
||||
|
||||
# also support syntax like ``:param type name:``
|
||||
try:
|
||||
argtype, argname = fieldarg.split(None, 1)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
types.setdefault(typename, {})[argname] = nodes.Text(argtype)
|
||||
fieldarg = argname
|
||||
|
||||
if typedesc.is_grouped:
|
||||
if typename in groupindices:
|
||||
group = entries[groupindices[typename]]
|
||||
else:
|
||||
groupindices[typename] = len(entries)
|
||||
group = [typedesc, []]
|
||||
entries.append(group)
|
||||
group[1].append(typedesc.make_entry(fieldarg, content))
|
||||
else:
|
||||
entries.append([typedesc,
|
||||
typedesc.make_entry(fieldarg, content)])
|
||||
|
||||
# step 2: 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
|
||||
else:
|
||||
fieldtype, content = entry
|
||||
fieldtypes = types.get(fieldtype.name, {})
|
||||
new_list += fieldtype.make_field(fieldtypes, self.domain, content)
|
||||
|
||||
node.replace_self(new_list)
|
Loading…
Reference in New Issue
Block a user