Source code for refactordoc.definition_items

# -*- coding: utf-8 -*-
#-----------------------------------------------------------------------------
#  file: fields.py
#  License: LICENSE.TXT
#  Author: Ioannis Tziakos
#
#  Copyright (c) 2011, Enthought, Inc.
#  All rights reserved.
#-----------------------------------------------------------------------------
import collections
import re

from .line_functions import (add_indent, fix_star, trim_indent, NEW_LINE,
                             fix_trailing_underscore)

header_regex = re.compile(r'\s:\s?')
definition_regex = re.compile(r"""
\*{0,2}            #  no, one or two stars
\w+\s:             #  a word followed by a semicolumn and optionally a space
(
        \s         # just a space
    |              # OR
        \s[\w.]+   # dot separated words
        (\(.*\))?  # with maybe a signature
    |
        \s[\w.]+   # dot separated words
        (\(.*\))?
        \sor       # with an or in between
        \s[\w.]+
        (\(.*\))?
)?
$                  # match at the end of the line
""", re.VERBOSE)
function_regex = re.compile(r'\w+\(.*\)\s*')
signature_regex = re.compile('\((.*)\)')


[docs]class DefinitionItem(collections.namedtuple( 'DefinitionItem', ('term', 'classifier', 'definition'))): """ A docstring definition item Syntax diagram:: +-------------------------------------------------+ | term [ " : " classifier [ " or " classifier] ] | +--+----------------------------------------------+---+ | definition | | (body elements)+ | +--------------------------------------------------+ The Definition class is based on the nametuple class and is responsible to check, parse and refactor a docstring definition item into sphinx friendly rst. Attributes ---------- term : str The term usually reflects the name of a parameter or an attribute. classifier: str The classifier of the definition. Commonly used to reflect the type of an argument or the signature of a function. .. note:: Currently only one classifier is supported. definition : list The list of strings that holds the description the definition item. .. note:: A Definition item is based on the item of a section definition list as it defined in restructured text (_http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#sections). """ @classmethod
[docs] def is_definition(cls, line): """ Check if the line is describing a definition item. The method is used to check that a line is following the expected format for the term and classifier attributes. The expected format is:: +-------------------------------------------------+ | term [ " : " classifier [ " or " classifier] ] | +-------------------------------------------------+ Subclasses can subclass to restrict or expand this format. """ return definition_regex.match(line) is not None
@classmethod
[docs] def parse(cls, lines): """Parse a definition item from a set of lines. The class method parses the definition list item from the list of docstring lines and produces a DefinitionItem with the term, classifier and the definition. .. note:: The global indention in the definition lines is striped The term definition is assumed to be in one of the following formats:: term Definition. :: term Definition, paragraph 1. Definition, paragraph 2. :: term : classifier Definition. Arguments --------- lines docstring lines of the definition without any empty lines before or after. Returns ------- definition : DefinitionItem """ header = lines[0].strip() term, classifier = header_regex.split(header, maxsplit=1) if \ (' :' in header) else (header, '') trimed_lines = trim_indent(lines[1:]) if (len(lines) > 1) else [''] definition = [line.rstrip() for line in trimed_lines] return cls(term.strip(), classifier.strip(), definition)
[docs] def to_rst(self, **kwards): """ Outputs the Definition in sphinx friendly rst. The method renders the definition into a list of lines that follow the rst markup. The default behaviour is to render the definition as an sphinx definition item:: <term> (<classifier>) -- <definition> Subclasses will usually override the method to provide custom made behaviour. However the signature of the method should hold only keyword arguments which have default values. The keyword arguments can be used to pass addition rendering information to subclasses. Returns ------- lines : list A list of string lines rendered in rst. Example ------- :: >>> item = DefinitionItem('lines', 'list', ['A list of string lines rendered in rst.']) >>> item.to_rst() lines *(list)* -- A list of string lines rendered in rst. .. note:: An empty line is added at the end of the list of strings so that the results can be concatenated directly and rendered properly by sphinx. """ postfix = ' --' if (len(self.definition) > 0) else '' lines = [] lines += [self.term] lines += [NEW_LINE] lines += [' *({0})*{1}'.format(self.classifier, postfix)] lines += add_indent(self.definition) # definition is all ready a list lines += [NEW_LINE] return lines
[docs]class AttributeItem(DefinitionItem): """ Definition that renders the rst output using the attribute directive. """ _normal = (".. attribute:: {0}\n" " :annotation: = {1}\n" "\n" "{2}\n\n") _no_definition = (".. attribute:: {0}\n" " :annotation: = {1}\n\n") _no_classifier = (".. attribute:: {0}\n\n" "{2}\n\n") _only_term = ".. attribute:: {0}\n\n"
[docs] def to_rst(self, ): """ Return the attribute info using the attribute sphinx markup. Examples -------- :: >>> item = AttributeItem('indent', 'int', ... ['The indent to use for the description block.']) >>> item.to_rst() .. attribute:: indent :annotation: = int The indent to use for the description block >>> :: >>> item = AttributeItem('indent', '', ... ['The indent to use for the description block.']) >>> item.to_rst() .. attribute:: indent The indent to use for the description block >>> .. note:: An empty line is added at the end of the list of strings so that the results can be concatenated directly and rendered properly by sphinx. """ definition = '\n'.join(add_indent(self.definition)) template = self.template.format(self.term, self.classifier, definition) return template.splitlines()
@property def template(self): if self.classifier == '' and self.definition == ['']: template = self._only_term elif self.classifier == '': template = self._no_classifier elif self.definition == ['']: template = self._no_definition else: template = self._normal return template
[docs]class ArgumentItem(DefinitionItem): """ A definition item for function argument sections. """ _normal = (":param {0}:\n" "{2}\n" ":type {0}: {1}") _no_definition = (":param {0}:\n" ":type {0}: {1}") _no_classifier = (":param {0}:\n" "{2}") _only_term = ":param {0}:"
[docs] def to_rst(self): """ Render ArgumentItem in sphinx friendly rst using the ``:param:`` role. Example ------- :: >>> item = ArgumentItem('indent', 'int', ... ['The indent to use for the description block.', '' 'This is the second paragraph of the argument definition.']) >>> item.to_rst() :param indent: The indent to use for the description block. This is the second paragraph of the argument definition. :type indent: int .. note:: There is no new line added at the last line of the :meth:`to_rst` method. """ argument = fix_star(self.term) argument = fix_trailing_underscore(argument) argument_type = self.classifier definition = '\n'.join(add_indent(self.definition)) template = self.template.format(argument, argument_type, definition) return template.splitlines()
@property def template(self): if self.classifier == '' and self.definition == ['']: template = self._only_term elif self.classifier == '': template = self._no_classifier elif self.definition == ['']: template = self._no_definition else: template = self._normal return template
[docs]class ListItem(DefinitionItem): """ A definition item that is rendered as an ordered/unordered list """ _normal = ("**{0}** (*{1}*) --\n" "{2}\n\n") _only_term = "**{0}**\n\n" _no_definition = "**{0}** (*{1}*)\n\n" _no_classifier = ("**{0}** --\n" "{2}\n\n")
[docs] def to_rst(self, prefix=None): """ Outputs ListItem in rst using as items in an list. Arguments --------- prefix : str The prefix to use. For example if the item is part of a numbered list then ``prefix='-'``. Example ------- >>> item = ListItem('indent', 'int', ... ['The indent to use for the description block.']) >>> item.to_rst(prefix='-') - **indent** (`int`) -- The indent to use for the description block. >>> item = ListItem('indent', 'int', ... ['The indent to use for' 'the description block.']) >>> item.to_rst(prefix='-') - **indent** (`int`) -- The indent to use for the description block. .. note:: An empty line is added at the end of the list of strings so that the results can be concatenated directly and rendered properly by sphinx. """ indent = 0 if (prefix is None) else len(prefix) + 1 definition = '\n'.join(add_indent(self.definition, indent)) template = self.template.format(self.term, self.classifier, definition) if prefix is not None: template = prefix + ' ' + template return template.splitlines()
@property def template(self): if self.classifier == '' and self.definition == ['']: template = self._only_term elif self.classifier == '': template = self._no_classifier elif self.definition == ['']: template = self._no_definition else: template = self._normal return template
[docs]class TableLineItem(DefinitionItem): """ A Definition Item that represents a table line. """
[docs] def to_rst(self, columns=(0, 0, 0)): """ Outputs definition in rst as a line in a table. Arguments --------- columns : tuple The three item tuple of column widths for the term, classifier and definition fields of the TableLineItem. When the column width is 0 then the field .. note:: - The strings attributes are clipped to the column width. Example ------- >>> item = TableLineItem('function(arg1, arg2)', '', ... ['This is the best function ever.']) >>> item.to_rst(columns=(22, 0, 20)) function(arg1, arg2) This is the best fun """ definition = ' '.join([line.strip() for line in self.definition]) term = self.term[:columns[0]] classifier = self.classifier[:columns[1]] definition = definition[:columns[2]] first_column = '' if columns[0] == 0 else '{0:<{first}} ' second_column = '' if columns[1] == 0 else '{1:<{second}} ' third_column = '' if columns[2] == 0 else '{2:<{third}}' table_line = ''.join((first_column, second_column, third_column)) lines = [] lines += [table_line.format(term, classifier, definition, first=columns[0], second=columns[1], third=columns[2])] lines += [''] return lines
[docs]class MethodItem(DefinitionItem): """ A TableLineItem subclass to parse and render class methods. """ @classmethod
[docs] def is_definition(cls, line): """ Check if the definition header is a function signature. """ match = function_regex.match(line) return match
@classmethod
[docs] def parse(cls, lines): """Parse a method definition item from a set of lines. The class method parses the method signature and definition from the list of docstring lines and produces a MethodItem where the term is the method name and the classifier is arguments .. note:: The global indention in the definition lines is striped The method definition item is assumed to be as follows:: +------------------------------+ | term "(" [ classifier ] ")" | +--+---------------------------+---+ | definition | | (body elements)+ | +--------------------- ---------+ Arguments --------- lines : docstring lines of the method definition item without any empty lines before or after. Returns ------- definition : MethodItem """ header = lines[0].strip() term, classifier, _ = signature_regex.split(header) definition = trim_indent(lines[1:]) if (len(lines) > 1) else [''] return cls(term, classifier, definition)
[docs] def to_rst(self, columns=(0, 0)): """ Outputs definition in rst as a line in a table. Arguments --------- columns : tuple The two item tuple of column widths for the :meth: role column and the definition (i.e. summary) of the MethodItem .. note:: The strings attributes are clipped to the column width. Example ------- :: >>> item = MethodItem('function', 'arg1, arg2', ... ['This is the best function ever.']) >>> item.to_rst(columns=(40, 20)) :meth:`function <function(arg1, arg2)>` This is the best fun """ definition = ' '.join([line.strip() for line in self.definition]) method_role = ':meth:`{0}({1}) <{0}>`'.format(self.term, self.classifier) table_line = '{0:<{first}} {1:<{second}}' lines = [] lines += [table_line.format(method_role[:columns[0]], definition[:columns[1]], first=columns[0], second=columns[1])] return lines
@property def signature(self): return '{}({})'.format(self.term, self.classifier)
#------------------------------------------------------------------------------ # Functions to work with Definition Items #------------------------------------------------------------------------------
[docs]def max_attribute_length(items, attr): """ Find the max length of the attribute in a list of DefinitionItems. Arguments --------- items : list The list of the DefinitionItem instances (or subclasses). attr : str Attribute to look at. """ if attr == 'definition': maximum = max([len(' '.join(item.definition)) for item in items]) else: maximum = max([len(getattr(item, attr)) for item in items]) return maximum
[docs]def max_attribute_index(items, attr): """ Find the index of the attribute with the maximum length in a list of DefinitionItems. Arguments --------- items : list The list of the DefinitionItems (or subclasses). attr : str Attribute to look at. """ if attr == 'definition': attributes = [len(' '.join(item.definition)) for item in items] else: attributes = [len(getattr(item, attr)) for item in items] maximum = max(attributes) return attributes.index(maximum)