integration: add ModuleDoc and AutoDoc

It is important to be able to document modules other than CSRs.
This patch adds ModuleDoc and AutoDoc, both of which can be used
together to document modules.

ModuleDoc can be used to transform the __doc__ string of a class into a
reference-manual section.  Alternately, it can be used to add additional
sections to a module.

AutoDoc is used to gather all submodule ModuleDoc objects in order to
traverse the tree of documentation.

Signed-off-by: Sean Cross <sean@xobs.io>
This commit is contained in:
Sean Cross 2019-09-24 14:30:28 +08:00
parent 8b7d8217a0
commit 131971986c
1 changed files with 150 additions and 0 deletions

View File

@ -0,0 +1,150 @@
# This file is Copyright (c) 2019 Sean Cross <sean@xobs.io>
from migen.fhdl.module import DUID
from migen.util.misc import xdir
from litex.soc.interconnect.csr_eventmanager import EventManager
import textwrap
import inspect
class ModuleDoc(DUID):
"""Module Documentation Support
ModuleDoc enables you to add documentation to your Module. This documentation is in addition to
any CSR-level documentation you may add to your module, if applicable.
There are two ways to use :obj:`ModuleDoc`:
1. Inherit :obj:`ModuleDoc` as part of your class. The docstring of your class will become the
first section of your class' module documentation
2. Add a :obj:`ModuleDoc` object to your class and inherit from :obj:`AutoDoc`.
If you inherit from :obj:`ModuleDoc`, then there is no need to call ``__init__()``
Synopsis
--------
::
class SomeClass(Module, ModuleDoc, AutoDoc):
\"\"\"Some Special Hardware Module
This is a hardware module that implements something really cool.
\"\"\"
def __init__(self):
self.other_section = ModuleDoc(title="Protocol Details", body="This section details more
information about the protocol")
"""
def __init__(self, body=None, title=None, file=None, format="rst"):
"""Construct a :obj:`ModuleDoc` object for use with :obj:`AutoDoc`
Arguments
---------
body (:obj:`str`): Main body of the document. If ``title`` is omitted, then the
title is taken as the first line of ``body``.
title (:obj:`str` Optional): Title of this particular section.
file (:obj:`str` Optional): It is possible to load the documentation from an external
file instead of specifying it inline. This allows for the use of an external text
editor. If a ``file`` is specified, then it will override the ``body`` argument.
format (:obj:`str` Optional): The text format. Python prefers reStructured Text, so this
defaults to `rst`. If specifying a `file`, then the suffix will be used instead of
`format`. If you specify a format other than `rst`, you may need to install a converter.
"""
import os
DUID.__init__(self)
self._title = title
self._format = format
if file == None and body == None and self.__doc__ is None:
raise ValueError("Must specify `file` or `body` when constructing a ModuleDoc()")
if file is not None:
if not os.path.isabs(file):
relative_path = inspect.stack()[1][1]
file = os.path.dirname(relative_path) + os.path.sep + file
(_, self._format) = os.path.splitext(file)
self._format = self._format[1:] # Strip off "." from extension
# If it's a reStructured Text file, read the whole thing in.
if self._format == "rst":
with open(file, "r") as f:
self.__doc__ = f.read()
# Otherwise, we'll simply make a link to it and let sphinx take care of it
else:
self._path = file
elif body is not None:
self.__doc__ = body
def title(self):
# This object might not have _title as an attribute, because
# the ModuleDoc constructor may not have been called. If this
# is the case, manipulate the __doc__ string directly.
if hasattr(self, "_title") and self._title is not None:
return self._title
_lines = self.__doc__.splitlines()
return textwrap.dedent(_lines[0])
def body(self):
if hasattr(self, "_title") and self._title is not None:
return self.__doc__
_lines = self.__doc__.splitlines()
_lines.pop(0)
return textwrap.dedent("\n".join(_lines))
def format(self):
if hasattr(self, "_format") and self._format is not None:
return self._format
return "rst"
def path(self):
if hasattr(self, "_path"):
return self._path
return None
def documentationprefix(prefix, documents, done):
for doc in documents:
if doc.duid not in done:
# doc.name = prefix + doc.name
done.add(doc.duid)
def _make_gatherer(method, cls, prefix_cb):
def gatherer(self):
try:
exclude = self.autodoc_exclude
except AttributeError:
exclude = {}
try:
prefixed = self.__prefixed
except AttributeError:
prefixed = self.__prefixed = set()
r = []
for k, v in xdir(self, True):
if k not in exclude:
if isinstance(v, cls):
r.append(v)
elif hasattr(v, method) and callable(getattr(v, method)):
items = getattr(v, method)()
prefix_cb(k + "_", items, prefixed)
r += items
return sorted(r, key=lambda x: x.duid)
return gatherer
class AutoDoc:
"""MixIn to provide documentation support.
A module can inherit from the ``AutoDoc`` class, which provides ``get_module_documentation``.
This will iterate through all objects looking for ones that inherit from ModuleDoc.
If the module has child objects that implement ``get_module_documentation``,
they will be called by the``AutoCSR`` methods and their documentation added to the lists returned,
with the child objects' names as prefixes.
"""
get_module_documentation = _make_gatherer("get_module_documentation", ModuleDoc, documentationprefix)