2021-08-22 15:00:50 +02:00

137 lines
3.7 KiB
Python

"""
Utility to extract extra OAS description from docstring.
"""
import re
import textwrap
from typing import Dict, List
class LinesIterator:
def __init__(self, lines: str):
self._lines = lines.splitlines()
self._i = -1
def next_line(self) -> str:
if self._i == len(self._lines) - 1:
raise StopIteration from None
self._i += 1
return self._lines[self._i]
def rewind(self) -> str:
if self._i == -1:
raise StopIteration from None
self._i -= 1
return self._lines[self._i]
def __iter__(self):
return self
def __next__(self):
return self.next_line()
def _i_extract_block(lines: LinesIterator):
"""
Iter the line within an indented block and dedent them.
"""
# Go to the first not empty or not white space line.
try:
line = next(lines)
except StopIteration:
return # No block to extract.
while line.strip() == "":
try:
line = next(lines)
except StopIteration:
return
indent = re.fullmatch("( *).*", line).groups()[0]
indentation = len(indent)
start_of_other_block = re.compile(f" {{0,{indentation}}}[^ ].*")
yield line[indentation:]
# Yield lines until the indentation is the same or is greater than
# the first block line.
try:
line = next(lines)
except StopIteration:
return
while not start_of_other_block.fullmatch(line):
yield line[indentation:]
try:
line = next(lines)
except StopIteration:
return
lines.rewind()
def _dedent_under_first_line(text: str) -> str:
"""
Apply textwrap.dedent ignoring the first line.
"""
lines = text.splitlines()
other_lines = "\n".join(lines[1:])
if other_lines:
return f"{lines[0]}\n{textwrap.dedent(other_lines)}"
return text
def status_code(docstring: str) -> Dict[int, str]:
"""
Extract the "Status Code:" block of the docstring.
"""
iterator = LinesIterator(docstring)
for line in iterator:
if re.fullmatch("status\\s+codes?\\s*:", line, re.IGNORECASE):
iterator.rewind()
blocks = []
lines = []
i_block = _i_extract_block(iterator)
next(i_block)
for line_of_block in i_block:
if re.search("^\\s*\\d{3}\\s*:", line_of_block):
if lines:
blocks.append("\n".join(lines))
lines = []
lines.append(line_of_block)
if lines:
blocks.append("\n".join(lines))
return {
int(status.strip()): _dedent_under_first_line(desc.strip())
for status, desc in (block.split(":", 1) for block in blocks)
}
return {}
def tags(docstring: str) -> List[str]:
"""
Extract the "Tags:" block of the docstring.
"""
iterator = LinesIterator(docstring)
for line in iterator:
if re.fullmatch("tags\\s*:.*", line, re.IGNORECASE):
iterator.rewind()
lines = " ".join(_i_extract_block(iterator))
return [" ".join(e.split()) for e in re.split("[,;]", lines.split(":")[1])]
return []
def operation(docstring: str) -> str:
"""
Extract all docstring except the "Status Code:" block.
"""
lines = LinesIterator(docstring)
ret = []
for line in lines:
if re.fullmatch("status\\s+codes?\\s*:|tags\\s*:.*", line, re.IGNORECASE):
lines.rewind()
for _ in _i_extract_block(lines):
pass
else:
ret.append(line)
return ("\n".join(ret)).strip()