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.
|
* 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 the ``viewcode`` extension.
|
||||||
|
|
||||||
* Added ``html-collect-pages`` event.
|
* 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
|
Unlike the other pickle files this pickle file requires that the sphinx
|
||||||
module is available on unpickling.
|
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
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
# coming with Sphinx (named 'sphinx.addons.*') or your custom ones.
|
# coming with Sphinx (named 'sphinx.addons.*') or your custom ones.
|
||||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo',
|
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.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
@ -11,12 +11,11 @@
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from docutils import nodes
|
|
||||||
from docutils.parsers.rst import Directive, directives
|
from docutils.parsers.rst import Directive, directives
|
||||||
from docutils.parsers.rst.directives import images
|
from docutils.parsers.rst.directives import images
|
||||||
|
|
||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
from sphinx.locale import l_
|
from sphinx.util.docfields import DocFieldTransformer
|
||||||
|
|
||||||
# import and register directives
|
# import and register directives
|
||||||
from sphinx.directives.code import *
|
from sphinx.directives.code import *
|
||||||
@ -36,179 +35,6 @@ except AttributeError:
|
|||||||
strip_backslash_re = re.compile(r'\\(?=[^\\])')
|
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):
|
class ObjectDescription(Directive):
|
||||||
"""
|
"""
|
||||||
Directive to describe a class, function or similar object. Not used
|
Directive to describe a class, function or similar object. Not used
|
||||||
@ -224,139 +50,8 @@ class ObjectDescription(Directive):
|
|||||||
'module': directives.unchanged,
|
'module': directives.unchanged,
|
||||||
}
|
}
|
||||||
|
|
||||||
doc_field_types = [
|
# types of doc fields that this directive handles, see sphinx.util.docfields
|
||||||
TypedFieldType('parameter',
|
doc_field_types = []
|
||||||
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)
|
|
||||||
|
|
||||||
def get_signatures(self):
|
def get_signatures(self):
|
||||||
"""
|
"""
|
||||||
@ -422,7 +117,7 @@ class ObjectDescription(Directive):
|
|||||||
# this is strictly domain-specific (i.e. no assumptions may
|
# this is strictly domain-specific (i.e. no assumptions may
|
||||||
# be made in this base class)
|
# be made in this base class)
|
||||||
name = self.handle_signature(sig, signode)
|
name = self.handle_signature(sig, signode)
|
||||||
except ValueError, err:
|
except ValueError:
|
||||||
# signature parsing failed
|
# signature parsing failed
|
||||||
signode.clear()
|
signode.clear()
|
||||||
signode += addnodes.desc_name(sig, sig)
|
signode += addnodes.desc_name(sig, sig)
|
||||||
@ -463,7 +158,7 @@ class DefaultDomain(Directive):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
env = self.state.document.settings.env
|
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)
|
env.doc_read_data['default_domain'] = env.domains.get(domain_name)
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ import re
|
|||||||
import string
|
import string
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
from docutils.parsers.rst import directives
|
|
||||||
|
|
||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
from sphinx.roles import XRefRole
|
from sphinx.roles import XRefRole
|
||||||
@ -21,7 +20,7 @@ from sphinx.locale import l_
|
|||||||
from sphinx.domains import Domain, ObjType
|
from sphinx.domains import Domain, ObjType
|
||||||
from sphinx.directives import ObjectDescription
|
from sphinx.directives import ObjectDescription
|
||||||
from sphinx.util import make_refnode
|
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
|
# RE to split at word boundaries
|
||||||
@ -48,6 +47,16 @@ class CObject(ObjectDescription):
|
|||||||
Description of a C language object.
|
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
|
# These C types aren't described anywhere, so don't try to create
|
||||||
# a cross-reference to them
|
# a cross-reference to them
|
||||||
stopwords = set(('const', 'void', 'char', 'int', 'long', 'FILE', 'struct'))
|
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.directives import ObjectDescription
|
||||||
from sphinx.util import make_refnode
|
from sphinx.util import make_refnode
|
||||||
from sphinx.util.compat import Directive
|
from sphinx.util.compat import Directive
|
||||||
|
from sphinx.util.docfields import Field, GroupedField, TypedField
|
||||||
|
|
||||||
|
|
||||||
# REs for Python signatures
|
# REs for Python signatures
|
||||||
@ -40,6 +41,22 @@ class PyObject(ObjectDescription):
|
|||||||
Description of a general Python object.
|
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):
|
def get_signature_prefix(self, sig):
|
||||||
"""
|
"""
|
||||||
May return a prefix to put before the object name in the signature.
|
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