Skip to content

Commit 9521c7f

Browse files
igonropawamoy
authored andcommitted
feat: Add support for examples section
1 parent 71a2464 commit 9521c7f

File tree

4 files changed

+134
-4
lines changed

4 files changed

+134
-4
lines changed

src/pytkdocs/parsers/docstrings/base.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,9 @@ class Type:
8989
PARAMETERS = "parameters"
9090
EXCEPTIONS = "exceptions"
9191
RETURN = "return"
92+
EXAMPLES = "examples"
9293

93-
def __init__(
94-
self, section_type: str, value: Union[str, List[Parameter], List[AnnotatedObject], AnnotatedObject]
95-
) -> None:
94+
def __init__(self, section_type: str, value: Any) -> None:
9695
"""
9796
Initialization method.
9897

src/pytkdocs/parsers/docstrings/google.py

+63
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
TITLES_RETURN: Sequence[str] = ("return:", "returns:")
1414
"""Titles to match for "returns" sections."""
1515

16+
TITLES_EXAMPLES: Sequence[str] = ("example:", "examples:")
17+
"""Titles to match for "examples" sections."""
1618

1719
RE_GOOGLE_STYLE_ADMONITION: Pattern = re.compile(r"^(?P<indent>\s*)(?P<type>[\w-]+):((?:\s+)(?P<title>.+))?$")
1820
"""Regular expressions to match lines starting admonitions, of the form `TYPE: [TITLE]`."""
@@ -75,6 +77,15 @@ def parse_sections(self, docstring: str) -> List[Section]: # noqa: D102
7577
if section:
7678
sections.append(section)
7779

80+
elif line_lower in TITLES_EXAMPLES:
81+
if current_section:
82+
if any(current_section):
83+
sections.append(Section(Section.Type.MARKDOWN, "\n".join(current_section)))
84+
current_section = []
85+
section, i = self.read_examples_section(lines, i + 1)
86+
if section:
87+
sections.append(section)
88+
7889
elif line_lower.lstrip(" ").startswith("```"):
7990
in_code_block = True
8091
current_section.append(lines[i])
@@ -343,3 +354,55 @@ def read_return_section(self, lines: List[str], start_index: int) -> Tuple[Optio
343354
return None, i
344355

345356
return Section(Section.Type.RETURN, AnnotatedObject(annotation, text)), i
357+
358+
def read_examples_section(self, lines: List[str], start_index: int) -> Tuple[Optional[Section], int]:
359+
"""
360+
Parse an "examples" section.
361+
362+
Arguments:
363+
lines: The examples block lines.
364+
start_index: The line number to start at.
365+
366+
Returns:
367+
A tuple containing a `Section` (or `None`) and the index at which to continue parsing.
368+
"""
369+
370+
text, i = self.read_block(lines, start_index)
371+
372+
sub_sections = []
373+
in_code_example = False
374+
in_code_block = False
375+
current_text = []
376+
current_example = []
377+
378+
for line in text.split("\n"):
379+
if self.is_empty_line(line):
380+
if in_code_example:
381+
if current_example:
382+
sub_sections.append((Section.Type.EXAMPLES, "\n".join(current_example)))
383+
current_example = []
384+
in_code_example = False
385+
else:
386+
current_text.append(line)
387+
elif in_code_example:
388+
current_example.append(line)
389+
elif line.startswith("```"):
390+
in_code_block = not in_code_block
391+
current_text.append(line)
392+
elif in_code_block:
393+
current_text.append(line)
394+
elif line.startswith(">>>"):
395+
if current_text:
396+
sub_sections.append((Section.Type.MARKDOWN, "\n".join(current_text)))
397+
current_text = []
398+
in_code_example = True
399+
current_example.append(line)
400+
else:
401+
current_text.append(line)
402+
403+
if current_text:
404+
sub_sections.append((Section.Type.MARKDOWN, "\n".join(current_text)))
405+
elif current_example:
406+
sub_sections.append((Section.Type.EXAMPLES, "\n".join(current_example)))
407+
408+
return Section(Section.Type.EXAMPLES, sub_sections), i

src/pytkdocs/serializer.py

+2
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ def serialize_docstring_section(section: Section) -> dict:
166166
serialized.update({"value": [serialize_annotated_object(e) for e in section.value]}) # type: ignore
167167
elif section.type == section.Type.PARAMETERS:
168168
serialized.update({"value": [serialize_parameter(p) for p in section.value]}) # type: ignore
169+
elif section.type == section.Type.EXAMPLES:
170+
serialized.update({"value": section.value}) # type: ignore
169171
return serialized
170172

171173

tests/test_parsers/test_docstrings/test_google.py

+67-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Tests for [the `parsers.docstrings` module][pytkdocs.parsers.docstrings]."""
1+
"""Tests for [the `parsers.docstrings.google` module][pytkdocs.parsers.docstrings.google]."""
22

33
import inspect
44
from textwrap import dedent
@@ -111,6 +111,72 @@ def f(x: int, y: int) -> int:
111111
assert not errors
112112

113113

114+
def test_function_with_examples():
115+
"""Parse a function docstring with signature annotations."""
116+
117+
def f(x: int, y: int) -> int:
118+
"""
119+
This function has annotations.
120+
121+
Examples:
122+
Some examples that will create an unified code block:
123+
124+
>>> 2 + 2 == 5
125+
False
126+
>>> print("examples")
127+
"examples"
128+
129+
This is just a random comment in the examples section.
130+
131+
These examples will generate two different code blocks. Note the blank line.
132+
133+
>>> print("I'm in the first code block!")
134+
"I'm in the first code block!"
135+
136+
>>> print("I'm in other code block!")
137+
"I'm in other code block!"
138+
139+
We also can write multiline examples:
140+
141+
>>> x = 3 + 2
142+
>>> y = x + 10
143+
>>> y
144+
15
145+
146+
This is just a typical Python code block:
147+
148+
```python
149+
print("examples")
150+
return 2 + 2
151+
```
152+
153+
Even if it contains doctests, the following block is still considered a normal code-block.
154+
155+
```python
156+
>>> print("examples")
157+
"examples"
158+
>>> 2 + 2
159+
4
160+
```
161+
162+
The blank line before an example is optional.
163+
>>> x = 3
164+
>>> y = "apple"
165+
>>> z = False
166+
>>> l = [x, y, z]
167+
>>> my_print_list_function(l)
168+
3
169+
"apple"
170+
False
171+
"""
172+
return x + y
173+
174+
sections, errors = parse(inspect.getdoc(f), inspect.signature(f))
175+
assert len(sections) == 2
176+
assert len(sections[1].value) == 9
177+
assert not errors
178+
179+
114180
def test_types_in_docstring():
115181
"""Parse types in docstring."""
116182

0 commit comments

Comments
 (0)