Source code for utilipy.utils.doc_parse_tools.rest_parse_tools
# -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------
#
# TITLE : ReST Parse tools
# PROJECT : utilipy
#
# ----------------------------------------------------------------------------
# Docstring
"""ReST parse tools.
routines to parse docstrings according to the Napoleon format specifications.
code sourced from [1]_, but modified to merge, not replace, docstring sections.
Routine Listings
----------------
merge_rest_docs
Merge two ReST-style docstrings into a single docstring
References
----------
The code source [1]_.
.. [1] https://github.com/rsokl/custom_inherit
"""
__all__ = ["merge_rest_docs"]
###############################################################################
# IMPORTS
# GENERAL
from collections import OrderedDict
from inspect import cleandoc
from string import punctuation
from typing import Optional, Union, Dict
from typing_extensions import Literal
###############################################################################
# CODE
###############################################################################
def is_delimiter(line: str) -> bool:
"""True if a line consists only of a single punctuation character."""
return (
bool(line)
and (line[0] in punctuation)
and (line[0] * len(line) == line)
)
# /def
class Section(object):
def __init__(self, header: str = None, body: str = None):
self.header: str = header
self.body: str = body
# /class
def parse_rest_doc(doc: Union[str, None]) -> Dict[str, Section]:
"""Extract the headers, delimiters, and text from reST-formatted docstrings.
Parameters
----------
doc: Union[str, None]
Returns
-------
Dict[str, Section]
"""
doc_sections = OrderedDict([("", Section(header=""))])
if not doc:
return doc_sections
doc = cleandoc(doc)
lines = iter(doc.splitlines())
header = ""
body = []
section = Section(header=header)
line = ""
while True:
try:
prev_line = line
line = next(lines)
# section header encountered
if is_delimiter(line) and 0 < len(prev_line) <= len(line):
# prev-prev-line is overline
if (
len(body) >= 2
and len(body[-2]) == len(line)
and body[-2][0] == line[0]
and is_delimiter(body[-2])
):
lim = -2
else:
lim = -1
section.body = "\n".join(body[:lim]).rstrip()
doc_sections.update([(header.strip(), section)])
section = Section(header="\n".join(body[lim:] + [line]))
header = prev_line
body = []
line = ""
else:
body.append(line)
except StopIteration:
section.body = "\n".join(body).rstrip()
doc_sections.update([(header.strip(), section)])
break
return doc_sections
# /def
[docs]def merge_rest_docs(
prnt_doc: Optional[str] = None,
child_doc: Optional[str] = None,
method: Union[Literal["merge"], Literal["replace"]] = "replace",
) -> str:
"""See custom_inherit.style_store.reST for details."""
prnt_sections = parse_rest_doc(prnt_doc)
child_sections = parse_rest_doc(child_doc)
header = prnt_sections[""]
prnt_sections.update(child_sections)
if not child_sections[""].body:
prnt_sections[""] = header
if not header.body:
prnt_sections.popitem(last=False)
return "\n\n".join(
("\n".join((x.header, x.body)) for x in prnt_sections.values())
).lstrip()
# /def
###############################################################################
# END