Skip to content

Commit 790fbb1

Browse files
committed
simply management of docstring sections
1 parent ab38531 commit 790fbb1

File tree

2 files changed

+50
-80
lines changed

2 files changed

+50
-80
lines changed

src/sphinx_fortran_domain/directives.py

Lines changed: 24 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -86,50 +86,33 @@ def _read_program_source_by_search(env, progname: str, *, doc_markers: list[str]
8686
return (None, None)
8787

8888

89-
def _split_out_examples_sections(text: str | None) -> tuple[str | None, str | None]:
90-
"""Split docstring into (main, examples) where examples are '## Example(s)' sections.
89+
def _split_out_doc_section_blocks(text: str | None) -> tuple[str | None, str | None]:
90+
"""Split a docstring into (preamble, sections) based on our "## Title" markers.
9191
92-
We treat sections introduced by our lightweight marker syntax ("## Title").
93-
If the title is "Example" or "Examples" (case-insensitive), we extract that
94-
section (including its content until the next "##" marker) and return it
95-
separately so callers can render it at the end.
92+
- preamble: everything before the first "##" section marker
93+
- sections: from the first "##" marker to the end
94+
95+
This is used to control placement: the preamble stays near the top of object
96+
documentation, while section blocks (Notes/References/See Also/...) can be
97+
placed after intrinsic blocks (Arguments/Returns/Attributes/Procedures).
9698
"""
9799
if not text:
98100
return None, None
99101

100102
lines = str(text).splitlines()
101-
main: list[str] = []
102-
examples: list[str] = []
103-
i = 0
104-
while i < len(lines):
105-
line = lines[i]
106-
m = _RE_DOC_SECTION.match(line)
107-
if not m:
108-
main.append(line)
109-
i += 1
110-
continue
111-
112-
title = (m.group("title") or "").strip().lower()
113-
is_examples = title in {"example", "examples"}
114-
115-
# Capture this section (header + body until next section header).
116-
section_lines: list[str] = [line]
117-
i += 1
118-
while i < len(lines) and not _RE_DOC_SECTION.match(lines[i]):
119-
section_lines.append(lines[i])
120-
i += 1
103+
first = None
104+
for i, line in enumerate(lines):
105+
if _RE_DOC_SECTION.match(line):
106+
first = i
107+
break
121108

122-
if is_examples:
123-
# Ensure examples start on a clean boundary when appended.
124-
if examples and examples[-1].strip() != "":
125-
examples.append("")
126-
examples.extend(section_lines)
127-
else:
128-
main.extend(section_lines)
109+
if first is None:
110+
preamble = "\n".join(lines).strip() or None
111+
return preamble, None
129112

130-
main_text = "\n".join(main).strip() or None
131-
examples_text = "\n".join(examples).strip() or None
132-
return main_text, examples_text
113+
preamble = "\n".join(lines[:first]).strip() or None
114+
sections = "\n".join(lines[first:]).strip() or None
115+
return preamble, sections
133116

134117

135118
def _preprocess_fortran_docstring(text: str) -> str:
@@ -546,15 +529,16 @@ def _append_object_description(
546529
desc += signode
547530

548531
content = addnodes.desc_content()
549-
main_doc, examples_doc = _split_out_examples_sections(doc)
550-
_append_doc(content, main_doc, state)
532+
preamble_doc, section_blocks_doc = _split_out_doc_section_blocks(doc)
533+
# Keep the opening free-text docstring at the top.
534+
_append_doc(content, preamble_doc, state)
551535
_append_argument_docs(content, args, state)
552536
if objtype == "function":
553537
_append_return_docs(content, result, state)
554538
_append_component_docs(content, components, state)
555539
_append_type_bound_procedures(content, bindings, all_procedures, state)
556-
# Always put Examples at the end of the object documentation.
557-
_append_doc(content, examples_doc, state)
540+
# Place all "## ..." section blocks (including "## Examples") after intrinsic blocks.
541+
_append_doc(content, section_blocks_doc, state)
558542
desc += content
559543

560544
section += desc

tests/test_docstring_format.py

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from sphinx_fortran_domain.directives import (
44
_preprocess_fortran_docstring,
5-
_split_out_examples_sections,
5+
_split_out_doc_section_blocks,
66
)
77

88

@@ -67,64 +67,50 @@ def test_preprocess_see_also_to_seealso_directive() -> None:
6767
assert ".. rubric:: Notes" in out
6868

6969

70-
def test_split_out_examples_sections_moves_examples_to_end() -> None:
70+
def test_split_out_doc_section_blocks_includes_examples_in_sections() -> None:
7171
text = """Summary
7272
73+
Intro paragraph.
74+
7375
## Examples
7476
>>> call foo(a)
7577
7678
## Notes
7779
Something important.
7880
"""
7981

80-
main, examples = _split_out_examples_sections(text)
81-
assert main and "## Examples" not in main
82-
assert "## Notes" in main
83-
assert examples and "## Examples" in examples
84-
85-
86-
def test_split_out_examples_sections_keeps_non_examples_in_place() -> None:
87-
text = """## Notes
88-
Text.
89-
"""
90-
main, examples = _split_out_examples_sections(text)
91-
assert main == text.strip()
92-
assert examples is None
82+
preamble, sections = _split_out_doc_section_blocks(text)
83+
assert preamble and "## Examples" not in preamble
84+
assert sections and "## Examples" in sections
85+
assert sections and "## Notes" in sections
9386

9487

95-
def test_split_out_examples_sections_keeps_fenced_blocks_in_place() -> None:
96-
text = """Summary
88+
def test_split_out_doc_section_blocks_splits_preamble_from_sections() -> None:
89+
text = """Short summary line.
9790
98-
```fortran
99-
print *, 'hello'
100-
```
91+
More details.
10192
10293
## Notes
103-
More text.
104-
"""
94+
Some notes.
10595
106-
main, examples = _split_out_examples_sections(text)
107-
assert main and "```fortran" in main
108-
assert "## Notes" in main
109-
assert examples is None
96+
## References
97+
- A
98+
"""
11099

100+
preamble, sections = _split_out_doc_section_blocks(text)
101+
assert preamble and "Short summary" in preamble
102+
assert preamble and "## Notes" not in preamble
103+
assert sections and sections.lstrip().startswith("## Notes")
104+
assert sections and "## References" in sections
111105

112-
def test_split_out_examples_sections_moves_examples_section_with_fences() -> None:
113-
text = """Summary
114106

115-
## Examples
116-
```fortran
117-
print *, 'hello'
118-
```
107+
def test_split_out_doc_section_blocks_no_sections_returns_all_preamble() -> None:
108+
text = """Just prose.
119109
120-
## Notes
121-
More text.
110+
No explicit sections.
122111
"""
123-
124-
main, examples = _split_out_examples_sections(text)
125-
assert main and "## Examples" not in main
126-
assert "## Notes" in main
127-
assert examples and "## Examples" in examples
128-
assert "```fortran" in examples
112+
preamble, sections = _split_out_doc_section_blocks(text)
113+
assert preamble == text.strip()
114+
assert sections is None
129115

130116

0 commit comments

Comments
 (0)