Source code for sectiondoc.styles.doc_render
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# file: base_doc.py
# License: LICENSE.TXT
#
# Copyright (c) 2011-14, Enthought, Inc.
# All rights reserved.
# -----------------------------------------------------------------------------
import re
from sectiondoc.items import AnyItem
from sectiondoc.util import is_empty, get_indent
from sectiondoc.sections import rubric
underline_regex = re.compile(r'\s*\S+\s*\Z')
[docs]class DocRender(object):
""" Docstring rendering class.
The class' main purpose is to parse the docstring and find the
sections that need to be refactored. The operation take place in
two stages:
- The class is instanciated with the appropriate section renderers
- The ``parse`` method is called to parse and render the sections
inplace.
Attributes
----------
docstring : list
A list of strings (lines) that holds docstrings. The lines are changed
inplace.
index : int
The zero-based line number of the docstring that is currently
processed.
sections : dict
The sections that will be detected and rendered. The dictionary
maps the section headers for detection to a tuple containing
the section rendering function and optional values for the item
renderer and parser.
"""
def __init__(self, lines, sections=None):
"""
Arguments
---------
lines : list
The docstring as a list of strings where to render the sections
sections : dict
The sections that will be detected and rendered. The dictionary
maps the section headers for detection to a tuple containing
the section rendering function and optional values for the item
renderer and parser. If on section rendering information is
provided the default behaviour of the class is to render
every section using the rubric rendering function.
"""
try:
self._docstring = lines.splitlines()
except AttributeError:
self._docstring = lines
self.sections = {} if sections is None else sections
self.bookmarks = []
self.index = 0
[docs] def parse(self):
""" Parse the docstring for sections.
The docstring is parsed for sections. If a section is found then
the corresponding section rendering method is called.
"""
self.index = 0
self.seek_to_next_non_empty_line()
while not self.eod:
section = self.is_section()
if len(section) > 0:
self._render(section)
else:
self.index += 1
self.seek_to_next_non_empty_line()
def _render(self, section):
""" Call the section rendering function.
The header is removed from the docstring and the appropriate
rendering function is executed.
"""
self.remove_lines(self.index, 2) # Remove header
self.remove_if_empty(self.index) # Remove space after header
method, renderer, item_class = self.sections.get(
section, (rubric, None, None))
lines = method(self, section, renderer, item_class)
self.insert_and_move(lines, self.index)
[docs] def get_next_block(self):
""" Get the next item block from the docstring.
The method reads the next item block in the docstring. The first line
is assumed to be the Item header and the following lines to
belong to the definition body::
<header line>
<definition>
The end of the field is designated by a line with the same indent
as the field header or two empty lines in sequence.
"""
item_header = self.pop()
sub_indent = get_indent(item_header) + ' '
block = [item_header]
while not self.eod:
current = self.peek()
next = self.peek(1)
if is_empty(current) and is_empty(next):
self.seek_to_next_non_empty_line()
break
elif is_empty(current) and not next.startswith(sub_indent):
self.pop()
break
elif not is_empty(current) and not current.startswith(sub_indent):
break
else:
line = self.pop()
block += [line.rstrip()]
return block
[docs] def is_section(self):
""" Check if the current line defines a section.
.. todo:: split and cleanup this method.
"""
if self.eod:
return False
header = self.peek()
line2 = self.peek(1)
# check for underline type format
underline = underline_regex.match(line2)
if underline is None:
return ''
# is the next line an rst section underline?
striped_header = header.rstrip()
expected_underline1 = re.sub(r'[A-Za-z\\]|\b\s', '-', striped_header)
expected_underline2 = re.sub(r'[A-Za-z\\]|\b\s', '=', striped_header)
if (
(underline.group().rstrip() == expected_underline1) or
(underline.group().rstrip() == expected_underline2)):
return header.strip()
else:
return ''
[docs] def insert_lines(self, lines, index):
""" Insert lines in the docstring.
Arguments
---------
lines : list
The list of lines to insert
index : int
Index to start the insertion
"""
docstring = self.docstring
if len(docstring) < index:
raise IndexError('index out of bounds')
for line in reversed(lines):
docstring.insert(index, line)
[docs] def insert_and_move(self, lines, index):
""" Insert lines and move the current index to the end.
"""
self.insert_lines(lines, index)
self.index += len(lines)
[docs] def seek_to_next_non_empty_line(self):
""" Goto the next non_empty line.
"""
docstring = self.docstring
for line in docstring[self.index:]:
if not is_empty(line):
break
self.index += 1
[docs] def get_next_paragraph(self):
""" Get the next paragraph designated by an empty line.
"""
lines = []
while (not self.eod) and (not is_empty(self.peek())):
line = self.pop()
lines.append(line)
return lines
[docs] def read(self):
""" Return the next line and advance the index.
"""
index = self.index
line = self._docstring[index]
self.index += 1
return line
[docs] def remove_lines(self, index, count=1):
""" Removes the lines from the docstring
"""
docstring = self.docstring
del docstring[index:(index + count)]
[docs] def remove_if_empty(self, index=None):
""" Remove the line from the docstring if it is empty.
"""
index = self.index if index is None else index
if is_empty(self.docstring[index]):
self.remove_lines(index)
[docs] def bookmark(self):
""" append the current index to the end of the list of bookmarks.
"""
self.bookmarks.append(self.index)
[docs] def goto_bookmark(self, bookmark_index=-1):
""" Move to bookmark.
Move the current index to the docstring line given by the
``self.bookmarks[bookmark_index]`` and remove it from the
bookmark list. Default value will pop the last entry.
Returns
-------
bookmark : int
"""
self.index = self.bookmarks[bookmark_index]
return self.bookmarks.pop(bookmark_index)
[docs] def peek(self, ahead=0):
""" Peek ahead a number of lines
The function retrieves the line that is ahead of the current
index. If the index is at the end of the list then it returns an
empty string.
Arguments
---------
ahead : int
The number of lines to look ahead.
"""
position = self.index + ahead
try:
line = self.docstring[position]
except IndexError:
line = ''
return line
[docs] def pop(self, index=None):
""" Pop a line from the dostrings.
"""
index = self.index if index is None else index
return self._docstring.pop(index)
@property
def eod(self):
""" End of docstring.
"""
return self.index >= len(self.docstring)
@property
def docstring(self):
""" Get the docstring lines.
"""
return self._docstring