Source code for xicsrt.tools.xicsrt_doc

# -*- coding: utf-8 -*-
"""
.. Authors
    Novimir pablant <npablant@pppl.gov>

A set of tools to help with automatic API documentation of XICSRT.

Description
-----------
XICSRT uses sphinx for documentation, and API docs are based on the idea of
code self documentation though `python` doc strings. This module contains
a set of decorators and helper function to aid in self documentation.

The most important part of this module is the `@dochelper` decorator which
should be used for all element classes.

.. Note:
    Philosophy: Python help() should be just as readable as the Sphinx webpage.

Todo:
    - The config docstrings should all be indented follow the help() standard.
    - Would it be helpful to show which inherited class the options came from?
"""

import inspect
import re


[docs] def dochelper(cls): """ A functional wrapper for the DocHelper class. Intended to be used as a decorator. This decorator does the following: 1. Adds a 'Configuration Options' section to the class docstring that contains all options defined in default_config and any class ancestors. """ return DocHelper(cls)()
[docs] class DocHelper: """ A class to help generate docstrings for XICSRT. This is expected to be used through the `@dochelper` class decorator. """ def __init__(self, cls): self.cls = cls self.update_class_docstring(cls) def __call__(self): return self.cls
[docs] def update_class_docstring(self, cls): """ Update the class docstring. This method does the following: 1. Creates a new section 'Configuration Options' which contains combined documentation for all config options defined in any ancestor. """ flag_sort_alpha = False if cls.__doc__ == None: cls.__doc__ = '' sections = {} mro = list(inspect.getmro(cls)) # The order here is important. As written, this will always keep # the section from the outermost class, while also adding sections # from outermost to innermost. for ancestor in mro: if hasattr(ancestor, 'default_config'): doc = ancestor.default_config.__doc__ if doc is not None: doc = inspect.getdoc(ancestor.default_config) new = self._parse_config_doc(doc) # I can't use update() because I don't want to overwrite values. for key in new: if key not in sections: sections[key] = new[key] # Build up the new config docs from the section dictionary new_doc = '' key_list = list(sections.keys()) if flag_sort_alpha: key_list.sort() for key in key_list: new_doc += sections[key]['name'] if sections[key]['sig']: new_doc += ' : ' + sections[key]['sig'] new_doc += '\n' new_doc += sections[key]['body'] new_doc += '\n' cls.__doc__ += '\n' cls.__doc__ += 'Configuration Options:\n\n' cls.__doc__ += new_doc
def _parse_config_doc(self, doc): """ Parse configuration documentation into sections. This assumes that the config docs are well formatted. """ section_dict = {} name = None lines = doc.splitlines() for line in lines: m = re.match(r'^([\w]+)\s*:?\s*(.*)', line) if m: name = m.group(1) section_dict[name] = {} section_dict[name]['name'] = m.group(1) section_dict[name]['sig'] = m.group(2) section_dict[name]['body'] = '' else: if name: if line.strip(): section_dict[name]['body'] += line + '\n' return section_dict