/python/helpers/sphinxcontrib/napoleon/docstring.py
Python | 933 lines | 901 code | 6 blank | 26 comment | 0 complexity | fb6c7cc3c209771142572c8c0e9f30dc MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, MPL-2.0-no-copyleft-exception, MIT, EPL-1.0, AGPL-1.0
- # -*- coding: utf-8 -*-
- # Copyright 2014 Rob Ruana
- # Licensed under the BSD License, see LICENSE file for details.
- """Classes for docstring parsing and formatting."""
- import collections
- import inspect
- import re
- import sys
- from six.moves import range
- from pockets import modify_iter
- from six import string_types
- _directive_regex = re.compile(r'\.\. \S+::')
- _google_section_regex = re.compile(r'^(\s|\w)+:\s*$')
- _google_typed_arg_regex = re.compile(r'\s*(.+?)\s*\(\s*(.+?)\s*\)')
- _numpy_section_regex = re.compile(r'^[=\-`:\'"~^_*+#<>]{2,}\s*$')
- _xref_regex = re.compile(r'(:\w+:\S+:`.+?`|:\S+:`.+?`|`.+?`)')
- class GoogleDocstring(object):
- """Convert Google style docstrings to reStructuredText.
- Parameters
- ----------
- docstring : str or List[str]
- The docstring to parse, given either as a string or split into
- individual lines.
- config : Optional[sphinxcontrib.napoleon.Config or sphinx.config.Config]
- The configuration settings to use. If not given, defaults to the
- config object on `app`; or if `app` is not given defaults to the
- a new `sphinxcontrib.napoleon.Config` object.
- See Also
- --------
- :class:`sphinxcontrib.napoleon.Config`
- Other Parameters
- ----------------
- app : Optional[sphinx.application.Sphinx]
- Application object representing the Sphinx process.
- what : Optional[str]
- A string specifying the type of the object to which the docstring
- belongs. Valid values: "module", "class", "exception", "function",
- "method", "attribute".
- name : Optional[str]
- The fully qualified name of the object.
- obj : module, class, exception, function, method, or attribute
- The object to which the docstring belongs.
- options : Optional[sphinx.ext.autodoc.Options]
- The options given to the directive: an object with attributes
- inherited_members, undoc_members, show_inheritance and noindex that
- are True if the flag option of same name was given to the auto
- directive.
- Example
- -------
- >>> from sphinxcontrib.napoleon import Config
- >>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True)
- >>> docstring = '''One line summary.
- ...
- ... Extended description.
- ...
- ... Args:
- ... arg1(int): Description of `arg1`
- ... arg2(str): Description of `arg2`
- ... Returns:
- ... str: Description of return value.
- ... '''
- >>> print(GoogleDocstring(docstring, config))
- One line summary.
- <BLANKLINE>
- Extended description.
- <BLANKLINE>
- :param arg1: Description of `arg1`
- :type arg1: int
- :param arg2: Description of `arg2`
- :type arg2: str
- <BLANKLINE>
- :returns: Description of return value.
- :rtype: str
- <BLANKLINE>
- """
- def __init__(self, docstring, config=None, app=None, what='', name='',
- obj=None, options=None):
- self._config = config
- self._app = app
- if not self._config:
- from sphinxcontrib.napoleon import Config
- self._config = self._app and self._app.config or Config()
- if not what:
- if inspect.isclass(obj):
- what = 'class'
- elif inspect.ismodule(obj):
- what = 'module'
- elif isinstance(obj, collections.Callable):
- what = 'function'
- else:
- what = 'object'
- self._what = what
- self._name = name
- self._obj = obj
- self._opt = options
- if isinstance(docstring, string_types):
- docstring = docstring.splitlines()
- self._lines = docstring
- self._line_iter = modify_iter(docstring, modifier=lambda s: s.rstrip())
- self._parsed_lines = []
- self._is_in_section = False
- self._section_indent = 0
- if not hasattr(self, '_directive_sections'):
- self._directive_sections = []
- if not hasattr(self, '_sections'):
- self._sections = {
- 'args': self._parse_parameters_section,
- 'arguments': self._parse_parameters_section,
- 'attributes': self._parse_attributes_section,
- 'example': self._parse_examples_section,
- 'examples': self._parse_examples_section,
- 'keyword args': self._parse_keyword_arguments_section,
- 'keyword arguments': self._parse_keyword_arguments_section,
- 'methods': self._parse_methods_section,
- 'note': self._parse_note_section,
- 'notes': self._parse_notes_section,
- 'other parameters': self._parse_other_parameters_section,
- 'parameters': self._parse_parameters_section,
- 'return': self._parse_returns_section,
- 'returns': self._parse_returns_section,
- 'raises': self._parse_raises_section,
- 'references': self._parse_references_section,
- 'see also': self._parse_see_also_section,
- 'warning': self._parse_warning_section,
- 'warnings': self._parse_warning_section,
- 'warns': self._parse_warns_section,
- 'yield': self._parse_yields_section,
- 'yields': self._parse_yields_section,
- }
- self._parse()
- def __str__(self):
- """Return the parsed docstring in reStructuredText format.
- Returns
- -------
- str
- UTF-8 encoded version of the docstring.
- """
- if sys.version_info[0] >= 3:
- return self.__unicode__()
- else:
- return self.__unicode__().encode('utf8')
- def __unicode__(self):
- """Return the parsed docstring in reStructuredText format.
- Returns
- -------
- unicode
- Unicode version of the docstring.
- """
- return u'\n'.join(self.lines())
- def lines(self):
- """Return the parsed lines of the docstring in reStructuredText format.
- Returns
- -------
- List[str]
- The lines of the docstring in a list.
- """
- return self._parsed_lines
- def _consume_indented_block(self, indent=1):
- lines = []
- line = self._line_iter.peek()
- while(not self._is_section_break() and
- (not line or self._is_indented(line, indent))):
- lines.append(next(self._line_iter))
- line = self._line_iter.peek()
- return lines
- def _consume_contiguous(self):
- lines = []
- while (self._line_iter.has_next() and
- self._line_iter.peek() and
- not self._is_section_header()):
- lines.append(next(self._line_iter))
- return lines
- def _consume_empty(self):
- lines = []
- line = self._line_iter.peek()
- while self._line_iter.has_next() and not line:
- lines.append(next(self._line_iter))
- line = self._line_iter.peek()
- return lines
- def _consume_field(self, parse_type=True, prefer_type=False):
- line = next(self._line_iter)
- before, colon, after = self._partition_field_on_colon(line)
- _name, _type, _desc = before, '', after
- if parse_type:
- match = _google_typed_arg_regex.match(before)
- if match:
- _name = match.group(1)
- _type = match.group(2)
- if _name[:2] == '**':
- _name = r'\*\*'+_name[2:]
- elif _name[:1] == '*':
- _name = r'\*'+_name[1:]
- if prefer_type and not _type:
- _type, _name = _name, _type
- indent = self._get_indent(line) + 1
- _desc = [_desc] + self._dedent(self._consume_indented_block(indent))
- _desc = self.__class__(_desc, self._config).lines()
- return _name, _type, _desc
- def _consume_fields(self, parse_type=True, prefer_type=False):
- self._consume_empty()
- fields = []
- while not self._is_section_break():
- _name, _type, _desc = self._consume_field(parse_type, prefer_type)
- if _name or _type or _desc:
- fields.append((_name, _type, _desc,))
- return fields
- def _consume_inline_attribute(self):
- line = next(self._line_iter)
- _type, colon, _desc = self._partition_field_on_colon(line)
- if not colon:
- _type, _desc = _desc, _type
- _desc = [_desc] + self._dedent(self._consume_to_end())
- _desc = self.__class__(_desc, self._config).lines()
- return _type, _desc
- def _consume_returns_section(self):
- lines = self._dedent(self._consume_to_next_section())
- if lines:
- before, colon, after = self._partition_field_on_colon(lines[0])
- _name, _type, _desc = '', '', lines
- if colon:
- if after:
- _desc = [after] + lines[1:]
- else:
- _desc = lines[1:]
- match = _google_typed_arg_regex.match(before)
- if match:
- _name = match.group(1)
- _type = match.group(2)
- else:
- _type = before
- _desc = self.__class__(_desc, self._config).lines()
- return [(_name, _type, _desc,)]
- else:
- return []
- def _consume_usage_section(self):
- lines = self._dedent(self._consume_to_next_section())
- return lines
- def _consume_section_header(self):
- section = next(self._line_iter)
- stripped_section = section.strip(':')
- if stripped_section.lower() in self._sections:
- section = stripped_section
- return section
- def _consume_to_end(self):
- lines = []
- while self._line_iter.has_next():
- lines.append(next(self._line_iter))
- return lines
- def _consume_to_next_section(self):
- self._consume_empty()
- lines = []
- while not self._is_section_break():
- lines.append(next(self._line_iter))
- return lines + self._consume_empty()
- def _dedent(self, lines, full=False):
- if full:
- return [line.lstrip() for line in lines]
- else:
- min_indent = self._get_min_indent(lines)
- return [line[min_indent:] for line in lines]
- def _format_admonition(self, admonition, lines):
- lines = self._strip_empty(lines)
- if len(lines) == 1:
- return ['.. %s:: %s' % (admonition, lines[0].strip()), '']
- elif lines:
- lines = self._indent(self._dedent(lines), 3)
- return ['.. %s::' % admonition, ''] + lines + ['']
- else:
- return ['.. %s::' % admonition, '']
- def _format_block(self, prefix, lines, padding=None):
- if lines:
- if padding is None:
- padding = ' ' * len(prefix)
- result_lines = []
- for i, line in enumerate(lines):
- if i == 0:
- result_lines.append((prefix + line).rstrip())
- elif line:
- result_lines.append(padding + line)
- else:
- result_lines.append('')
- return result_lines
- else:
- return [prefix]
- def _format_field(self, _name, _type, _desc):
- _desc = self._strip_empty(_desc)
- has_desc = any(_desc)
- separator = has_desc and ' -- ' or ''
- if _name:
- if _type:
- if '`' in _type:
- field = '**%s** (%s)%s' % (_name, _type, separator)
- else:
- field = '**%s** (*%s*)%s' % (_name, _type, separator)
- else:
- field = '**%s**%s' % (_name, separator)
- elif _type:
- if '`' in _type:
- field = '%s%s' % (_type, separator)
- else:
- field = '*%s*%s' % (_type, separator)
- else:
- field = ''
- if has_desc:
- return [field + _desc[0]] + _desc[1:]
- else:
- return [field]
- def _format_fields(self, field_type, fields):
- field_type = ':%s:' % field_type.strip()
- padding = ' ' * len(field_type)
- multi = len(fields) > 1
- lines = []
- for _name, _type, _desc in fields:
- field = self._format_field(_name, _type, _desc)
- if multi:
- if lines:
- lines.extend(self._format_block(padding + ' * ', field))
- else:
- lines.extend(self._format_block(field_type + ' * ', field))
- else:
- lines.extend(self._format_block(field_type + ' ', field))
- if lines and lines[-1]:
- lines.append('')
- return lines
- def _get_current_indent(self, peek_ahead=0):
- line = self._line_iter.peek(peek_ahead + 1)[peek_ahead]
- while line != self._line_iter.sentinel:
- if line:
- return self._get_indent(line)
- peek_ahead += 1
- line = self._line_iter.peek(peek_ahead + 1)[peek_ahead]
- return 0
- def _get_indent(self, line):
- for i, s in enumerate(line):
- if not s.isspace():
- return i
- return len(line)
- def _get_min_indent(self, lines):
- min_indent = None
- for line in lines:
- if line:
- indent = self._get_indent(line)
- if min_indent is None:
- min_indent = indent
- elif indent < min_indent:
- min_indent = indent
- return min_indent or 0
- def _indent(self, lines, n=4):
- return [(' ' * n) + line for line in lines]
- def _is_indented(self, line, indent=1):
- for i, s in enumerate(line):
- if i >= indent:
- return True
- elif not s.isspace():
- return False
- return False
- def _is_section_header(self):
- section = self._line_iter.peek().lower()
- match = _google_section_regex.match(section)
- if match and section.strip(':') in self._sections:
- header_indent = self._get_indent(section)
- section_indent = self._get_current_indent(peek_ahead=1)
- return section_indent > header_indent
- elif self._directive_sections:
- if _directive_regex.match(section):
- for directive_section in self._directive_sections:
- if section.startswith(directive_section):
- return True
- return False
- def _is_section_break(self):
- line = self._line_iter.peek()
- return (not self._line_iter.has_next() or
- self._is_section_header() or
- (self._is_in_section and
- line and
- not self._is_indented(line, self._section_indent)))
- def _parse(self):
- self._parsed_lines = self._consume_empty()
- if self._name and (self._what == 'attribute' or self._what == 'data'):
- self._parsed_lines.extend(self._parse_attribute_docstring())
- return
- while self._line_iter.has_next():
- if self._is_section_header():
- try:
- section = self._consume_section_header()
- self._is_in_section = True
- self._section_indent = self._get_current_indent()
- if _directive_regex.match(section):
- lines = [section] + self._consume_to_next_section()
- else:
- lines = self._sections[section.lower()](section)
- finally:
- self._is_in_section = False
- self._section_indent = 0
- else:
- if not self._parsed_lines:
- lines = self._consume_contiguous() + self._consume_empty()
- else:
- lines = self._consume_to_next_section()
- self._parsed_lines.extend(lines)
- def _parse_attribute_docstring(self):
- _type, _desc = self._consume_inline_attribute()
- return self._format_field('', _type, _desc)
- def _parse_attributes_section(self, section):
- lines = []
- for _name, _type, _desc in self._consume_fields():
- if self._config.napoleon_use_ivar:
- field = ':ivar %s: ' % _name
- lines.extend(self._format_block(field, _desc))
- if _type:
- lines.append(':vartype %s: %s' % (_name, _type))
- else:
- lines.extend(['.. attribute:: ' + _name, ''])
- field = self._format_field('', _type, _desc)
- lines.extend(self._indent(field, 3))
- lines.append('')
- if self._config.napoleon_use_ivar:
- lines.append('')
- return lines
- def _parse_examples_section(self, section):
- use_admonition = self._config.napoleon_use_admonition_for_examples
- return self._parse_generic_section(section, use_admonition)
- def _parse_usage_section(self, section):
- header = ['.. rubric:: Usage:', '']
- block = ['.. code-block:: python', '']
- lines = self._consume_usage_section()
- lines = self._indent(lines, 3)
- return header + block + lines + ['']
- def _parse_generic_section(self, section, use_admonition):
- lines = self._strip_empty(self._consume_to_next_section())
- lines = self._dedent(lines)
- if use_admonition:
- header = '.. admonition:: %s' % section
- lines = self._indent(lines, 3)
- else:
- header = '.. rubric:: %s' % section
- if lines:
- return [header, ''] + lines + ['']
- else:
- return [header, '']
- def _parse_keyword_arguments_section(self, section):
- return self._format_fields('Keyword Arguments', self._consume_fields())
- def _parse_methods_section(self, section):
- lines = []
- for _name, _, _desc in self._consume_fields(parse_type=False):
- lines.append('.. method:: %s' % _name)
- if _desc:
- lines.extend([''] + self._indent(_desc, 3))
- lines.append('')
- return lines
- def _parse_note_section(self, section):
- lines = self._consume_to_next_section()
- return self._format_admonition('note', lines)
- def _parse_notes_section(self, section):
- use_admonition = self._config.napoleon_use_admonition_for_notes
- return self._parse_generic_section('Notes', use_admonition)
- def _parse_other_parameters_section(self, section):
- return self._format_fields('Other Parameters', self._consume_fields())
- def _parse_parameters_section(self, section):
- fields = self._consume_fields()
- if self._config.napoleon_use_param:
- lines = []
- for _name, _type, _desc in fields:
- field = ':param %s: ' % _name
- lines.extend(self._format_block(field, _desc))
- if _type:
- lines.append(':type %s: %s' % (_name, _type))
- return lines + ['']
- else:
- return self._format_fields('Parameters', fields)
- def _parse_raises_section(self, section):
- fields = self._consume_fields(parse_type=False, prefer_type=True)
- field_type = ':raises:'
- padding = ' ' * len(field_type)
- multi = len(fields) > 1
- lines = []
- for _, _type, _desc in fields:
- _desc = self._strip_empty(_desc)
- has_desc = any(_desc)
- separator = has_desc and ' -- ' or ''
- if _type:
- has_refs = '`' in _type or ':' in _type
- has_space = any(c in ' \t\n\v\f ' for c in _type)
- if not has_refs and not has_space:
- _type = ':exc:`%s`%s' % (_type, separator)
- elif has_desc and has_space:
- _type = '*%s*%s' % (_type, separator)
- else:
- _type = '%s%s' % (_type, separator)
- if has_desc:
- field = [_type + _desc[0]] + _desc[1:]
- else:
- field = [_type]
- else:
- field = _desc
- if multi:
- if lines:
- lines.extend(self._format_block(padding + ' * ', field))
- else:
- lines.extend(self._format_block(field_type + ' * ', field))
- else:
- lines.extend(self._format_block(field_type + ' ', field))
- if lines and lines[-1]:
- lines.append('')
- return lines
- def _parse_references_section(self, section):
- use_admonition = self._config.napoleon_use_admonition_for_references
- return self._parse_generic_section('References', use_admonition)
- def _parse_returns_section(self, section):
- fields = self._consume_returns_section()
- multi = len(fields) > 1
- if multi:
- use_rtype = False
- else:
- use_rtype = self._config.napoleon_use_rtype
- lines = []
- for _name, _type, _desc in fields:
- if use_rtype:
- field = self._format_field(_name, '', _desc)
- else:
- field = self._format_field(_name, _type, _desc)
- if multi:
- if lines:
- lines.extend(self._format_block(' * ', field))
- else:
- lines.extend(self._format_block(':returns: * ', field))
- else:
- lines.extend(self._format_block(':returns: ', field))
- if _type and use_rtype:
- lines.extend([':rtype: %s' % _type, ''])
- if lines and lines[-1]:
- lines.append('')
- return lines
- def _parse_see_also_section(self, section):
- lines = self._consume_to_next_section()
- return self._format_admonition('seealso', lines)
- def _parse_warning_section(self, section):
- lines = self._consume_to_next_section()
- return self._format_admonition('warning', lines)
- def _parse_warns_section(self, section):
- return self._format_fields('Warns', self._consume_fields())
- def _parse_yields_section(self, section):
- fields = self._consume_returns_section()
- return self._format_fields('Yields', fields)
- def _partition_field_on_colon(self, line):
- before_colon = []
- after_colon = []
- colon = ''
- found_colon = False
- for i, source in enumerate(_xref_regex.split(line)):
- if found_colon:
- after_colon.append(source)
- else:
- if (i % 2) == 0 and ":" in source:
- found_colon = True
- before, colon, after = source.partition(":")
- before_colon.append(before)
- after_colon.append(after)
- else:
- before_colon.append(source)
- return ("".join(before_colon).strip(),
- colon,
- "".join(after_colon).strip())
- def _strip_empty(self, lines):
- if lines:
- start = -1
- for i, line in enumerate(lines):
- if line:
- start = i
- break
- if start == -1:
- lines = []
- end = -1
- for i in reversed(range(len(lines))):
- line = lines[i]
- if line:
- end = i
- break
- if start > 0 or end + 1 < len(lines):
- lines = lines[start:end + 1]
- return lines
- class NumpyDocstring(GoogleDocstring):
- """Convert NumPy style docstrings to reStructuredText.
- Parameters
- ----------
- docstring : str or List[str]
- The docstring to parse, given either as a string or split into
- individual lines.
- config : Optional[sphinxcontrib.napoleon.Config or sphinx.config.Config]
- The configuration settings to use. If not given, defaults to the
- config object on `app`; or if `app` is not given defaults to the
- a new `sphinxcontrib.napoleon.Config` object.
- See Also
- --------
- :class:`sphinxcontrib.napoleon.Config`
- Other Parameters
- ----------------
- app : Optional[sphinx.application.Sphinx]
- Application object representing the Sphinx process.
- what : Optional[str]
- A string specifying the type of the object to which the docstring
- belongs. Valid values: "module", "class", "exception", "function",
- "method", "attribute".
- name : Optional[str]
- The fully qualified name of the object.
- obj : module, class, exception, function, method, or attribute
- The object to which the docstring belongs.
- options : Optional[sphinx.ext.autodoc.Options]
- The options given to the directive: an object with attributes
- inherited_members, undoc_members, show_inheritance and noindex that
- are True if the flag option of same name was given to the auto
- directive.
- Example
- -------
- >>> from sphinxcontrib.napoleon import Config
- >>> config = Config(napoleon_use_param=True, napoleon_use_rtype=True)
- >>> docstring = '''One line summary.
- ...
- ... Extended description.
- ...
- ... Parameters
- ... ----------
- ... arg1 : int
- ... Description of `arg1`
- ... arg2 : str
- ... Description of `arg2`
- ... Returns
- ... -------
- ... str
- ... Description of return value.
- ... '''
- >>> print(NumpyDocstring(docstring, config))
- One line summary.
- <BLANKLINE>
- Extended description.
- <BLANKLINE>
- :param arg1: Description of `arg1`
- :type arg1: int
- :param arg2: Description of `arg2`
- :type arg2: str
- <BLANKLINE>
- :returns: Description of return value.
- :rtype: str
- <BLANKLINE>
- Methods
- -------
- __str__()
- Return the parsed docstring in reStructuredText format.
- Returns
- -------
- str
- UTF-8 encoded version of the docstring.
- __unicode__()
- Return the parsed docstring in reStructuredText format.
- Returns
- -------
- unicode
- Unicode version of the docstring.
- lines()
- Return the parsed lines of the docstring in reStructuredText format.
- Returns
- -------
- List[str]
- The lines of the docstring in a list.
- """
- def __init__(self, docstring, config=None, app=None, what='', name='',
- obj=None, options=None):
- self._directive_sections = ['.. index::']
- super(NumpyDocstring, self).__init__(docstring, config, app, what,
- name, obj, options)
- def _consume_field(self, parse_type=True, prefer_type=False):
- line = next(self._line_iter)
- if parse_type:
- _name, _, _type = self._partition_field_on_colon(line)
- else:
- _name, _type = line, ''
- _name, _type = _name.strip(), _type.strip()
- if prefer_type and not _type:
- _type, _name = _name, _type
- if _name[:2] == '**':
- _name = r'\*\*'+_name[2:]
- elif _name[:1] == '*':
- _name = r'\*'+_name[1:]
- indent = self._get_indent(line)
- _desc = self._dedent(self._consume_indented_block(indent + 1))
- _desc = self.__class__(_desc, self._config).lines()
- return _name, _type, _desc
- def _consume_returns_section(self):
- return self._consume_fields(prefer_type=True)
- def _consume_section_header(self):
- section = next(self._line_iter)
- if not _directive_regex.match(section):
- # Consume the header underline
- next(self._line_iter)
- return section
- def _is_section_break(self):
- line1, line2 = self._line_iter.peek(2)
- return (not self._line_iter.has_next() or
- self._is_section_header() or
- ['', ''] == [line1, line2] or
- (self._is_in_section and
- line1 and
- not self._is_indented(line1, self._section_indent)))
- def _is_section_header(self):
- section, underline = self._line_iter.peek(2)
- section = section.lower()
- if section in self._sections and isinstance(underline, string_types):
- return bool(_numpy_section_regex.match(underline))
- elif self._directive_sections:
- if _directive_regex.match(section):
- for directive_section in self._directive_sections:
- if section.startswith(directive_section):
- return True
- return False
- _name_rgx = re.compile(r"^\s*(:(?P<role>\w+):`(?P<name>[a-zA-Z0-9_.-]+)`|"
- r" (?P<name2>[a-zA-Z0-9_.-]+))\s*", re.X)
- def _parse_see_also_section(self, section):
- lines = self._consume_to_next_section()
- try:
- return self._parse_numpydoc_see_also_section(lines)
- except ValueError:
- return self._format_admonition('seealso', lines)
- def _parse_numpydoc_see_also_section(self, content):
- """
- Derived from the NumpyDoc implementation of _parse_see_also.
- See Also
- --------
- func_name : Descriptive text
- continued text
- another_func_name : Descriptive text
- func_name1, func_name2, :meth:`func_name`, func_name3
- """
- items = []
- def parse_item_name(text):
- """Match ':role:`name`' or 'name'"""
- m = self._name_rgx.match(text)
- if m:
- g = m.groups()
- if g[1] is None:
- return g[3], None
- else:
- return g[2], g[1]
- raise ValueError("%s is not a item name" % text)
- def push_item(name, rest):
- if not name:
- return
- name, role = parse_item_name(name)
- items.append((name, list(rest), role))
- del rest[:]
- current_func = None
- rest = []
- for line in content:
- if not line.strip():
- continue
- m = self._name_rgx.match(line)
- if m and line[m.end():].strip().startswith(':'):
- push_item(current_func, rest)
- current_func, line = line[:m.end()], line[m.end():]
- rest = [line.split(':', 1)[1].strip()]
- if not rest[0]:
- rest = []
- elif not line.startswith(' '):
- push_item(current_func, rest)
- current_func = None
- if ',' in line:
- for func in line.split(','):
- if func.strip():
- push_item(func, [])
- elif line.strip():
- current_func = line
- elif current_func is not None:
- rest.append(line.strip())
- push_item(current_func, rest)
- if not items:
- return []
- roles = {
- 'method': 'meth',
- 'meth': 'meth',
- 'function': 'func',
- 'func': 'func',
- 'class': 'class',
- 'exception': 'exc',
- 'exc': 'exc',
- 'object': 'obj',
- 'obj': 'obj',
- 'module': 'mod',
- 'mod': 'mod',
- 'data': 'data',
- 'constant': 'const',
- 'const': 'const',
- 'attribute': 'attr',
- 'attr': 'attr'
- }
- if self._what is None:
- func_role = 'obj'
- else:
- func_role = roles.get(self._what, '')
- lines = []
- last_had_desc = True
- for func, desc, role in items:
- if role:
- link = ':%s:`%s`' % (role, func)
- elif func_role:
- link = ':%s:`%s`' % (func_role, func)
- else:
- link = "`%s`_" % func
- if desc or last_had_desc:
- lines += ['']
- lines += [link]
- else:
- lines[-1] += ", %s" % link
- if desc:
- lines += self._indent([' '.join(desc)])
- last_had_desc = True
- else:
- last_had_desc = False
- lines += ['']
- return self._format_admonition('seealso', lines)