From 131971986c56d92b963e374d1fc416f9771e0274 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Tue, 24 Sep 2019 14:30:28 +0800 Subject: [PATCH] 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 --- litex/soc/integration/doc.py | 150 +++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 litex/soc/integration/doc.py diff --git a/litex/soc/integration/doc.py b/litex/soc/integration/doc.py new file mode 100644 index 00000000..6477cc1b --- /dev/null +++ b/litex/soc/integration/doc.py @@ -0,0 +1,150 @@ +# This file is Copyright (c) 2019 Sean Cross + +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) -- 2.30.2