Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: numpy admonitions #219

Merged
merged 7 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 52 additions & 12 deletions src/griffe/docstrings/numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
DocstringReceive,
DocstringReturn,
DocstringSection,
DocstringSectionAdmonition,
DocstringSectionAttributes,
DocstringSectionClasses,
DocstringSectionDeprecated,
Expand Down Expand Up @@ -720,6 +721,19 @@ def _read_examples_section(
return None, new_offset


def _append_section(sections: list, current: list[str], admonition_title: str) -> None:
if admonition_title:
sections.append(
DocstringSectionAdmonition(
kind=admonition_title.lower().replace(" ", "-"),
text="\n".join(current).rstrip("\n"),
title=admonition_title,
),
)
elif current and any(current):
sections.append(DocstringSectionText("\n".join(current).rstrip("\n")))


_section_reader = {
DocstringSectionKind.parameters: _read_parameters_section,
DocstringSectionKind.other_parameters: _read_other_parameters_section,
Expand Down Expand Up @@ -762,6 +776,7 @@ def parse(
"""
sections: list[DocstringSection] = []
current_section = []
admonition_title = ""

in_code_block = False
lines = docstring.lines
Expand All @@ -787,32 +802,57 @@ def parse(
while offset < len(lines):
line_lower = lines[offset].lower()

# Code blocks can contain dash lines that we must not interpret.
if in_code_block:
# End of code block.
if line_lower.lstrip(" ").startswith("```"):
in_code_block = False
# Lines in code block must not be interpreted in any way.
current_section.append(lines[offset])

elif line_lower in _section_kind and _is_dash_line(lines[offset + 1]):
if current_section:
if any(current_section):
sections.append(DocstringSectionText("\n".join(current_section).rstrip("\n")))
current_section = []
reader = _section_reader[_section_kind[line_lower]]
section, offset = reader(docstring, offset=offset + 2, **options) # type: ignore[operator]
if section:
sections.append(section)

# Start of code block.
elif line_lower.lstrip(" ").startswith("```"):
in_code_block = True
current_section.append(lines[offset])

# Dash lines after empty lines lose their meaning.
elif _is_empty_line(lines[offset]):
current_section.append("")

# End of the docstring, wrap up.
elif offset == len(lines) - 1:
current_section.append(lines[offset])
_append_section(sections, current_section, admonition_title)
admonition_title = ""
current_section = []

# Dash line after regular, non-empty line.
elif _is_dash_line(lines[offset + 1]):
# Finish reading current section.
_append_section(sections, current_section, admonition_title)
current_section = []

# Start parsing new (known) section.
if line_lower in _section_kind:
admonition_title = ""
reader = _section_reader[_section_kind[line_lower]]
section, offset = reader(docstring, offset=offset + 2, **options) # type: ignore[operator]
if section:
sections.append(section)

# Start parsing admonition.
else:
admonition_title = lines[offset]
offset += 1 # skip next dash line

# Regular line.
else:
current_section.append(lines[offset])

offset += 1

if current_section:
sections.append(DocstringSectionText("\n".join(current_section).rstrip("\n")))
# Finish current section.
_append_section(sections, current_section, admonition_title)

return sections

Expand Down
72 changes: 72 additions & 0 deletions tests/test_docstrings/test_numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,78 @@ def test_doubly_indented_lines_in_section_items(parse_numpy: ParserType) -> None
assert lines[-1].startswith(4 * " " + "- ")


# =============================================================================================
# Admonitions
def test_admonition_see_also(parse_numpy: ParserType) -> None:
"""Test a "See Also" admonition.

Parameters:
parse_numpy: Fixture parser.
"""
docstring = """
Summary text.

See Also
--------
some_function

more text
"""

sections, _ = parse_numpy(docstring)
assert len(sections) == 2
assert sections[0].value == "Summary text."
assert sections[1].title == "See Also"
assert sections[1].value.description == "some_function\n\nmore text"


def test_admonition_empty(parse_numpy: ParserType) -> None:
"""Test an empty "See Also" admonition.

Parameters:
parse_numpy: Fixture parser.
"""
docstring = """
Summary text.

See Also
--------
"""

sections, _ = parse_numpy(docstring)
assert len(sections) == 2
assert sections[0].value == "Summary text."
assert sections[1].title == "See Also"
assert sections[1].value.description == ""


def test_isolated_dash_lines_do_not_create_sections(parse_numpy: ParserType) -> None:
"""An isolated dash-line (`---`) should not be parsed as a section.

Parameters:
parse_numpy: Fixture parser.
"""
docstring = """
Summary text.

---
Text.

Note
----
Note contents.

---
Text.
"""

sections, _ = parse_numpy(docstring)
assert len(sections) == 2
assert sections[0].value == "Summary text.\n\n---\nText."
assert sections[1].title == "Note"
assert sections[1].value.description == "Note contents.\n\n---\nText."


# =============================================================================================
# Annotations
def test_prefer_docstring_type_over_annotation(parse_numpy: ParserType) -> None:
Expand Down