cerc_persistence_doc/source/_ext/cerc_documenter.py

160 lines
6.1 KiB
Python

from typing import Optional, Any
from docutils.statemachine import StringList
from sphinx.application import Sphinx
from sphinx.ext.autodoc import ClassDocumenter, PropertyDocumenter, MethodDocumenter, bool_option
from sphinx.util import inspect
from sphinx.util.typing import restify
def setup(app: Sphinx) -> None:
app.setup_extension('sphinx.ext.autodoc') # Require autodoc extension
app.add_autodocumenter(CercClassDocumenter)
app.add_autodocumenter(CercPropertyDocumenter)
app.add_autodocumenter(CercMethodDocumenter)
class CercMethodDocumenter(MethodDocumenter):
objtype = 'cercmethod'
directivetype = 'method'
member_order = 50
priority = MethodDocumenter.priority + 10
@staticmethod
def _get_type(sig_return):
types = sig_return.replace('Optional', '').replace('List', '').split('.')
prefix = ''
if types[0][0] in '[(':
prefix = types[0][0]
return f'{prefix}{types[len(types) -1]}'
def format_signature(self, **kwargs: Any) -> str:
sig = super().format_signature(**kwargs)
if '->' in sig:
sig_pars = sig.split(' -> ')
return_type = CercMethodDocumenter._get_type(sig_pars[1])
return_str = f'{return_type}'
if 'Optional' in sig_pars[1]:
return_str = f'Optional{return_str}'
return f'{sig_pars[0]} -> {return_str}'
return sig
def add_content(self, more_content: Optional[StringList], no_docstring: bool = False) -> None:
source_name = self.get_sourcename()
docstrings = self.get_doc()
self.content_indent = ' '
for i, line in enumerate(self.process_doc(docstrings)):
if ':return:' in line:
line = line.replace(':return:', '**Returns**')
elif ':param ' in line:
line = line.replace(':param ', '**Parameter** ')
line = line.replace(':', ' ')
elif ':alert:' in line:
line = line.replace(':alert:', '|alert|')
elif ':cat:' in line:
line = line.replace(':cat:', '|cat|')
self.add_line(f'{line}\n', source_name, i)
class CercPropertyDocumenter(PropertyDocumenter):
objtype = 'cercproperty'
directivetype = 'property'
member_order = 60
priority = PropertyDocumenter.priority + 10
@staticmethod
def _get_type(annotations):
if len(annotations) == 0:
return annotations
return_str = str(annotations['return']).replace('typing.', '')
if 'Union' in return_str:
return_str = return_str.replace('Union', 'Optional').replace(', None', '').replace('None, ', '')
return_str = return_str.replace('NoneType, ','').replace(', NoneType', '')
if 'List' in return_str:
return_str = return_str.replace('typing.List', '').replace('List', '')
if "<class '" in return_str:
return_str = return_str.replace("<class '", '').replace("'>", '')
if '.' in return_str:
for i, c in enumerate(reversed(return_str)):
if c not in '])':
types_str = return_str.split('.')
if 'Optional' in return_str:
return_str = f'{types_str[0][0:8+i]}{types_str[len(types_str)-1]}'
else:
return_str = f'{types_str[0][0:i]}{types_str[len(types_str)-1]}'
break
return_str = return_str.replace('~', '').replace('Type', '')
return return_str
def add_directive_header(self, sig: str) -> None:
name = self.format_name()
source_name = self.get_sourcename()
annotations = self.get_attr(self.object.fget, '__annotations__', None)
self.retann = CercPropertyDocumenter._get_type(annotations)
for i, sig_line in enumerate(sig.split("\n")):
self.add_line(f'.. py:property:: {name}({sig_line}) {(f" -> {self.retann}" if self.retann else "")}', source_name)
if self.objpath:
self.add_line(f' :module: {self.modname}', source_name)
if inspect.isabstractmethod(self.object):
self.add_line(' :abstractmethod:', source_name)
self.add_line(f' :property:', source_name)
def add_content(self, more_content: Optional[StringList], no_docstring: bool = False) -> None:
source_name = self.get_sourcename()
docstrings = self.get_doc()
self.content_indent = ' '
if not no_docstring:
if not docstrings:
docstrings.append([])
for i, line in enumerate(self.process_doc(docstrings)):
if ':return:' in line:
line = line.replace(':return:', '**Returns**')
elif ':alert:' in line:
line = line.replace(':alert:', '|alert|')
self.add_line(f'{line}\n', source_name, i)
class CercClassDocumenter(ClassDocumenter):
objtype = 'cercclass'
directivetype = 'class'
priority = 10 + ClassDocumenter.priority
option_spec = dict(ClassDocumenter.option_spec)
option_spec['hex'] = bool_option
def get_bases(self, source_name):
bases = []
if hasattr(self.object, '__orig_bases__') and len(self.object.__orig_bases__):
bases = [restify(cls) for cls in self.object.__orig_bases__]
elif hasattr(self.object, '__bases__') and len(self.object.__bases__):
# A normal class
bases = [restify(cls) for cls in self.object.__bases__]
if ':class:`object`' in bases:
bases.remove(':class:`object`')
cleaned_bases = []
for base in bases:
base_tmp = base.replace('`', '').split('.')
base_tmp = base_tmp[len(base_tmp)-1]
cleaned_bases.append(base_tmp)
if len(cleaned_bases) != 0:
self.add_line(' Inherit: %s' % ', '.join(cleaned_bases), source_name)
self.add_line(' ', source_name)
def add_directive_header(self, sig: str) -> None:
prefix = f'.. py:class:: '
name = self.format_name()
source_name = self.get_sourcename()
self.add_line('%s%s%s' % (prefix, name, sig), source_name)
if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals:
self.add_line(' :final:', source_name)
self.get_bases(source_name)
def add_content(self, more_content: Optional[StringList], no_docstring: bool = False) -> None:
source_name = self.get_sourcename()
docstrings = self.get_doc()
self.content_indent = ' '
if not no_docstring:
if not docstrings:
docstrings.append([])
for i, line in enumerate(self.process_doc(docstrings)):
self.add_line(line, source_name)