Skip to content

Commit

Permalink
Replace napoleon.iterators by simpler stack implementation (#9856)
Browse files Browse the repository at this point in the history
  • Loading branch information
anntzer committed Jun 26, 2022
1 parent d730c55 commit 03c1e1b
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGES
Expand Up @@ -12,6 +12,7 @@ Deprecated

* #10467: Deprecated ``sphinx.util.stemmer`` in favour of ``snowballstemmer``.
Patch by Adam Turner.
* #9856: Deprecated ``sphinx.ext.napoleon.iterators``.

Features added
--------------
Expand Down
5 changes: 5 additions & 0 deletions doc/extdev/deprecated.rst
Expand Up @@ -22,6 +22,11 @@ The following is a list of deprecated interfaces.
- (will be) Removed
- Alternatives

* - ``sphinx.ext.napoleon.iterators``
- 5.1
- 7.0
- ``pockets.iterators``

* - ``sphinx.util.stemmer``
- 5.1
- 7.0
Expand Down
74 changes: 43 additions & 31 deletions sphinx/ext/napoleon/docstring.py
Expand Up @@ -10,7 +10,6 @@
from sphinx.application import Sphinx
from sphinx.config import Config as SphinxConfig
from sphinx.deprecation import RemovedInSphinx60Warning
from sphinx.ext.napoleon.iterators import modify_iter
from sphinx.locale import _, __
from sphinx.util import logging
from sphinx.util.inspect import stringify_annotation
Expand Down Expand Up @@ -46,6 +45,19 @@
_SINGLETONS = ("None", "True", "False", "Ellipsis")


class Deque(collections.deque):
"""A subclass of deque with an additional `.Deque.get` method."""

sentinel = object()

def get(self, n: int) -> Any:
"""
Return the nth element of the stack, or ``self.sentinel`` if n is
greater than the stack size.
"""
return self[n] if n < len(self) else self.sentinel


def _convert_type_spec(_type: str, translations: Dict[str, str] = {}) -> str:
"""Convert type specification to reference in reST."""
if _type in translations:
Expand Down Expand Up @@ -153,7 +165,7 @@ def __init__(self, docstring: Union[str, List[str]], config: SphinxConfig = None
lines = docstring.splitlines()
else:
lines = docstring
self._line_iter = modify_iter(lines, modifier=lambda s: s.rstrip())
self._lines = Deque(map(str.rstrip, lines))
self._parsed_lines: List[str] = []
self._is_in_section = False
self._section_indent = 0
Expand Down Expand Up @@ -225,32 +237,32 @@ def lines(self) -> List[str]:

def _consume_indented_block(self, indent: int = 1) -> List[str]:
lines = []
line = self._line_iter.peek()
line = self._lines.get(0)
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()
lines.append(self._lines.popleft())
line = self._lines.get(0)
return lines

def _consume_contiguous(self) -> List[str]:
lines = []
while (self._line_iter.has_next() and
self._line_iter.peek() and
while (self._lines and
self._lines.get(0) and
not self._is_section_header()):
lines.append(next(self._line_iter))
lines.append(self._lines.popleft())
return lines

def _consume_empty(self) -> List[str]:
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()
line = self._lines.get(0)
while self._lines and not line:
lines.append(self._lines.popleft())
line = self._lines.get(0)
return lines

def _consume_field(self, parse_type: bool = True, prefer_type: bool = False
) -> Tuple[str, str, List[str]]:
line = next(self._line_iter)
line = self._lines.popleft()

before, colon, after = self._partition_field_on_colon(line)
_name, _type, _desc = before, '', after
Expand Down Expand Up @@ -288,7 +300,7 @@ def _consume_fields(self, parse_type: bool = True, prefer_type: bool = False,
return fields

def _consume_inline_attribute(self) -> Tuple[str, List[str]]:
line = next(self._line_iter)
line = self._lines.popleft()
_type, colon, _desc = self._partition_field_on_colon(line)
if not colon or not _desc:
_type, _desc = _desc, _type
Expand Down Expand Up @@ -326,23 +338,23 @@ def _consume_usage_section(self) -> List[str]:
return lines

def _consume_section_header(self) -> str:
section = next(self._line_iter)
section = self._lines.popleft()
stripped_section = section.strip(':')
if stripped_section.lower() in self._sections:
section = stripped_section
return section

def _consume_to_end(self) -> List[str]:
lines = []
while self._line_iter.has_next():
lines.append(next(self._line_iter))
while self._lines:
lines.append(self._lines.popleft())
return lines

def _consume_to_next_section(self) -> List[str]:
self._consume_empty()
lines = []
while not self._is_section_break():
lines.append(next(self._line_iter))
lines.append(self._lines.popleft())
return lines + self._consume_empty()

def _dedent(self, lines: List[str], full: bool = False) -> List[str]:
Expand Down Expand Up @@ -468,12 +480,12 @@ def _format_fields(self, field_type: str, fields: List[Tuple[str, str, List[str]
return lines

def _get_current_indent(self, peek_ahead: int = 0) -> int:
line = self._line_iter.peek(peek_ahead + 1)[peek_ahead]
while line != self._line_iter.sentinel:
line = self._lines.get(peek_ahead)
while line is not self._lines.sentinel:
if line:
return self._get_indent(line)
peek_ahead += 1
line = self._line_iter.peek(peek_ahead + 1)[peek_ahead]
line = self._lines.get(peek_ahead)
return 0

def _get_indent(self, line: str) -> int:
Expand Down Expand Up @@ -528,7 +540,7 @@ def _is_list(self, lines: List[str]) -> bool:
return next_indent > indent

def _is_section_header(self) -> bool:
section = self._line_iter.peek().lower()
section = self._lines.get(0).lower()
match = _google_section_regex.match(section)
if match and section.strip(':') in self._sections:
header_indent = self._get_indent(section)
Expand All @@ -542,8 +554,8 @@ def _is_section_header(self) -> bool:
return False

def _is_section_break(self) -> bool:
line = self._line_iter.peek()
return (not self._line_iter.has_next() or
line = self._lines.get(0)
return (not self._lines or
self._is_section_header() or
(self._is_in_section and
line and
Expand Down Expand Up @@ -585,7 +597,7 @@ def _parse(self) -> None:
self._parsed_lines.extend(res)
return

while self._line_iter.has_next():
while self._lines:
if self._is_section_header():
try:
section = self._consume_section_header()
Expand Down Expand Up @@ -1158,7 +1170,7 @@ def _escape_args_and_kwargs(self, name: str) -> str:

def _consume_field(self, parse_type: bool = True, prefer_type: bool = False
) -> Tuple[str, str, List[str]]:
line = next(self._line_iter)
line = self._lines.popleft()
if parse_type:
_name, _, _type = self._partition_field_on_colon(line)
else:
Expand Down Expand Up @@ -1189,23 +1201,23 @@ def _consume_returns_section(self, preprocess_types: bool = False
return self._consume_fields(prefer_type=True)

def _consume_section_header(self) -> str:
section = next(self._line_iter)
section = self._lines.popleft()
if not _directive_regex.match(section):
# Consume the header underline
next(self._line_iter)
self._lines.popleft()
return section

def _is_section_break(self) -> bool:
line1, line2 = self._line_iter.peek(2)
return (not self._line_iter.has_next() or
line1, line2 = self._lines.get(0), self._lines.get(1)
return (not self._lines 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) -> bool:
section, underline = self._line_iter.peek(2)
section, underline = self._lines.get(0), self._lines.get(1)
section = section.lower()
if section in self._sections and isinstance(underline, str):
return bool(_numpy_section_regex.match(underline))
Expand Down
6 changes: 6 additions & 0 deletions sphinx/ext/napoleon/iterators.py
@@ -1,8 +1,14 @@
"""A collection of helpful iterators."""

import collections
import warnings
from typing import Any, Iterable, Optional

from sphinx.deprecation import RemovedInSphinx70Warning

warnings.warn('sphinx.ext.napoleon.iterators is deprecated.',
RemovedInSphinx70Warning)


class peek_iter:
"""An iterator object that supports peeking ahead.
Expand Down
9 changes: 9 additions & 0 deletions tests/test_ext_napoleon_iterators.py
@@ -1,10 +1,19 @@
"""Tests for :mod:`sphinx.ext.napoleon.iterators` module."""

import sys
from unittest import TestCase

from sphinx.deprecation import RemovedInSphinx70Warning
from sphinx.ext.napoleon.iterators import modify_iter, peek_iter


class ModuleIsDeprecatedTest(TestCase):
def test_module_is_deprecated(self):
sys.modules.pop("sphinx.ext.napoleon.iterators")
with self.assertWarns(RemovedInSphinx70Warning):
import sphinx.ext.napoleon.iterators # noqa


class BaseIteratorsTest(TestCase):
def assertEqualTwice(self, expected, func, *args):
self.assertEqual(expected, func(*args))
Expand Down

0 comments on commit 03c1e1b

Please sign in to comment.