Hello Sphinx users,
I want to create a Sphinx directive that documents custom objects for a domain-specific language. I want to define an object like this...
My Custom Item
==============
.. dmn:component:: myitemcls
This is some extra text!
and have Sphinx document it like it were a Python class, but not quite - something like this:
(That's a mock-up of what I'd like - so far I've not managed it. Note also I'd like the "This is some extra text!" text to appear somewhere there too...)
The idea is that the custom Sphinx directive would look up myitemcls in my library and introspect its parameters and then document them here.
I figure this is the same as the Python autodoc module, but I'm having trouble understanding how it works - I find it's horrendously complicated and makes calls to other modules all over the place, not all of which are documented or require calls to non-public APIs. I tried to get it to work instead using a custom directive class, but I had to do something hacky - in the run method I grab the output from the parent (ObjectDescription) and inject some parameters into the middle of the node list - the problem is that the ObjectDescription directive appears to want the :param A: notation to have already been injected in before instantiation, as I believe autodoc would do. In contrast, with my method I have to inject these parameter strings into the object after instantiation (in run()), and that seems to be my problem. I guess the solution is to subclass some part of autodoc, but as I said I find it hard to figure out how.
Here's what I've got so far:
import docutils
from docutils import nodes
import sphinx
from docutils.parsers import rst
from docutils.parsers.rst import directives
from docutils.statemachine import StringList
from sphinx.domains import Domain, Index
from sphinx.domains.std import StandardDomain
from sphinx.roles import XRefRole
from sphinx.directives import ObjectDescription
from sphinx.util.nodes import make_refnode
from sphinx.util.docfields import DocFieldTransformer, GroupedField
from sphinx import addnodes
class ComponentNode(ObjectDescription):
"""A custom node that describes a component."""
required_arguments = 1
option_spec = {}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._component_name = self.arguments[0]
# Argument node list, filled later.
self._arglist = None
self._paramlist = None
# This is my own function, not shown, that populates self._paramlist with a list of strings containing ":param name:" etc. It also builds self._arglist with addnodes.desc_parameter nodes.
self._parse_parameters()
def run(self, *args, **kwargs):
nodes = super().run(*args, **kwargs)
# The following is a bit of a hack. I first overwrite the field type map to set up the
# "param" parser. I then create a list of strings containing ":param [x]: [description]"
# entries and parse the contents into a new node. Finally I stitch this node into the
# middle of the node list returned by the parent run() method.
self._doc_field_type_map = {
"param": (
GroupedField("parameters", label="Parameters", names=('param',), can_collapse=True),
False # is typed
),
}
# Add parameters to content.
extra = StringList(
self._paramlist,
source=((self.content.parent, 0)*len(self._paramlist)),
parent=self.content.parent,
parent_offset=self.content.parent_offset
)
contentnode = addnodes.desc_content()
self.state.nested_parse(extra, self.content_offset, contentnode)
DocFieldTransformer(self).transform_all(contentnode)
return nodes[0:2] + [contentnode] + nodes[2:]
I'm not happy with this, it's quite hacky and I figure I'm not doing it properly. It also generates documentation that looks different to that of class documentation - the "parameter" labels are blue not grey:
Can anyone help me figure out how to implement this custom directive, ideally using autodoc?