diff --git a/litex/soc/integration/doc.py b/litex/soc/integration/doc.py new file mode 100644 index 000000000..6477cc1bc --- /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)