from __future__ import annotations
import os
import re
from pathlib import Path
from docutils import nodes
from docutils.parsers.rst import Directive
from docutils.parsers.rst import directives as rst_directives
from docutils.statemachine import StringList
from sphinx import addnodes
from sphinx.directives import ObjectDescription
from sphinx.util.parsing import nested_parse_to_nodes
from sphinx_fortran_domain.utils import (
collect_fortran_source_files_from_config,
doc_markers_from_doc_chars,
extract_predoc_before_line,
extract_use_dependencies,
read_lines_utf8,
read_text_utf8,
)
_RE_DOC_SECTION = re.compile(r"^\s*##\s+(?P<title>\S.*?\S)\s*$")
_RE_FOOTNOTE_DEF = re.compile(r"^\s*\.\.\s*\[(?P<label>\d+|#)\]\s+")
_RE_END_PROGRAM = re.compile(r"^\s*end\s*program\b", re.IGNORECASE)
def _doc_markers_from_env(env) -> list[str]:
"""Return configured Fortran doc markers (e.g. ['!>'])."""
app = getattr(env, "app", None)
config = getattr(app, "config", None) if app is not None else None
try:
return doc_markers_from_doc_chars(getattr(config, "fortran_doc_chars", None))
except Exception:
# Keep directives resilient even if config is malformed.
return ["!>"]
def _collect_fortran_files_from_env(env) -> list[str]:
app = getattr(env, "app", None)
if app is None:
return []
confdir = Path(getattr(app, "confdir", os.getcwd()))
config = getattr(app, "config", None)
return collect_fortran_source_files_from_config(confdir=confdir, config=config)
def _find_program_in_file(lines: list[str], progname: str, *, start_at: int = 0) -> int | None:
pat = re.compile(rf"^\s*program\s+{re.escape(str(progname))}\b", re.IGNORECASE)
for i in range(max(start_at, 0), len(lines)):
if pat.match(lines[i]):
return i
return None
def _read_program_source_by_search(env, progname: str, *, doc_markers: list[str]) -> tuple[str | None, str | None]:
"""Find and read a program unit by scanning configured fortran_sources.
Returns (source, predoc) where predoc is doc lines immediately preceding
the program statement (using configured doc_markers).
"""
files = _collect_fortran_files_from_env(env)
if not files:
return (None, None)
for path in files:
try:
text = read_text_utf8(path)
except OSError:
continue
lines = text.splitlines()
start = _find_program_in_file(lines, progname)
if start is None:
continue
predoc = extract_predoc_before_line(lines, start, doc_markers=doc_markers)
buf: list[str] = []
for i in range(start, len(lines)):
buf.append(lines[i])
if _RE_END_PROGRAM.match(lines[i]):
break
return ("\n".join(buf), predoc)
return (None, None)
def _split_out_doc_section_blocks(text: str | None) -> tuple[str | None, str | None]:
"""Split a docstring into (preamble, sections) based on our "## Title" markers.
- preamble: everything before the first "##" section marker
- sections: from the first "##" marker to the end
This is used to control placement: the preamble stays near the top of object
documentation, while section blocks (Notes/References/See Also/...) can be
placed after intrinsic blocks (Arguments/Returns/Attributes/Procedures).
"""
if not text:
return None, None
lines = str(text).splitlines()
first = None
for i, line in enumerate(lines):
if _RE_DOC_SECTION.match(line):
first = i
break
if first is None:
preamble = "\n".join(lines).strip() or None
return preamble, None
preamble = "\n".join(lines[:first]).strip() or None
sections = "\n".join(lines[first:]).strip() or None
return preamble, sections
def _preprocess_fortran_docstring(text: str) -> str:
"""Normalize a lightweight docstring convention into valid reST.
Supported patterns:
- Section markers: "## References" -> ".. rubric:: References"
- Example blocks: contiguous lines starting with ">>>" -> ".. code-block:: fortran"
"""
lines = (text or "").splitlines()
out: list[str] = []
i = 0
while i < len(lines):
line = lines[i]
m = _RE_DOC_SECTION.match(line)
if m:
title = m.group("title")
normalized = (title or "").strip().lower()
# Match NumpyDoc-style rendering for "See Also" by emitting a real
# Sphinx seealso directive (renders as an admonition with special styling).
if normalized in {"see also", "seealso"}:
if out and out[-1].strip() != "":
out.append("")
out.append(".. seealso::")
out.append("")
i += 1
# Consume the See Also body until the next "##" section marker.
# Support a lightweight "term : description" syntax (with spaces
# around the colon) to produce a definition-list style layout.
while i < len(lines) and not _RE_DOC_SECTION.match(lines[i]):
body_line = lines[i]
if not body_line.strip():
out.append("")
i += 1
continue
# Split only on " : " (spaces required) so domain roles like
# ":f:func:`name`" are not misinterpreted.
parts = re.split(r"\s+:\s+", body_line.strip(), maxsplit=1)
if len(parts) == 2:
term, desc = parts
out.append(" " + term.strip())
out.append(" " + desc.strip())
out.append("")
else:
out.append(" " + body_line.strip())
i += 1
continue
i += 1
# Avoid accumulating extra blank lines at the end of the directive.
while out and out[-1] == "":
out.pop()
out.append("")
continue
if out and out[-1].strip() != "":
out.append("")
out.append(f".. rubric:: {title}")
out.append("")
i += 1
continue
if line.lstrip().startswith(">>>"):
block: list[str] = []
while i < len(lines) and lines[i].lstrip().startswith(">>>"):
s = lines[i].lstrip()[3:]
block.append(s.lstrip(" \t"))
i += 1
if out and out[-1].strip() != "":
out.append("")
out.append(".. code-block:: fortran")
out.append("")
for b in block:
out.append(" " + b)
out.append("")
continue
stripped = line.lstrip()
if stripped.startswith("```"):
# Extract language if provided
fence = stripped[3:].strip() # after "```"
language = fence if fence else "fortran"
block: list[str] = []
i += 1
while i < len(lines):
s = lines[i].lstrip()
if s.startswith("```"):
i += 1
break
block.append(s.rstrip("\t"))
i += 1
if out and out[-1].strip() != "":
out.append("")
out.append(f".. code-block:: {language}")
out.append("")
for b in block:
out.append(" " + b)
out.append("")
continue
out.append(line)
i += 1
# Make footnote/citation definitions robust in docstring fragments.
# In reST, a footnote definition must be preceded by a blank line.
# Many docstrings omit that blank line, which causes undefined refs like [1]_.
# We insert it here for common `.. [n] ...` patterns.
if _RE_FOOTNOTE_DEF.match(line):
# If the previous non-empty line isn't blank, insert a blank line *before*
# this definition. Because we've already appended the line, adjust in-place.
if len(out) >= 2 and out[-2].strip() != "":
out.insert(len(out) - 1, "")
return "\n".join(out).rstrip()
def _make_object_id(objtype: str, fullname: str) -> str:
# ``nodes.make_id`` produces a valid HTML id fragment.
return nodes.make_id(f"f-{objtype}-{fullname}")
def _append_doc(section: nodes.Element, doc: str | None, state) -> None:
if not doc:
return
text = _preprocess_fortran_docstring(str(doc))
content = StringList(text.splitlines(), source="<fortran-doc>")
container: nodes.Element = nodes.container()
# Parse doc as a reST fragment so Sphinx roles/directives work (e.g. .. math::).
for n in nested_parse_to_nodes(
state,
content,
source="<fortran-doc>",
offset=0,
allow_section_headings=True,
keep_title_context=True,
):
container += n
section += container
def _read_program_source_from_location(location) -> str | None:
"""Best-effort read of a program unit source from its file location."""
if location is None:
return None
path = getattr(location, "path", None)
lineno = getattr(location, "lineno", None)
if not path or not lineno:
return None
try:
lines = read_lines_utf8(path)
except OSError:
return None
start = max(int(lineno) - 1, 0)
buf: list[str] = []
for i in range(start, len(lines)):
buf.append(lines[i])
if re.match(r"^\s*end\s*program\b", lines[i], flags=re.IGNORECASE):
break
return "\n".join(buf)
def _append_fortran_code_block(section: nodes.Element, *, source: str) -> None:
text = (source or "").rstrip() + "\n"
lit = nodes.literal_block(text, text)
lit["language"] = "fortran"
section += lit
def _append_program_dependencies(section: nodes.Element, *, dependencies, state) -> None:
deps = [str(d).strip() for d in (dependencies or []) if str(d).strip()]
if not deps:
return
items = nodes.bullet_list()
for dep in deps:
xref = addnodes.pending_xref(
"",
refdomain="f",
reftype="module",
reftarget=dep,
refexplicit=True,
)
xref += nodes.literal(text=dep)
items += nodes.list_item("", nodes.paragraph("", "", xref))
section += nodes.subtitle(text="Dependencies")
section += items
def _parse_doc_fragment(doc: str | None, state) -> list[nodes.Node]:
if not doc:
return []
text = _preprocess_fortran_docstring(str(doc))
content = StringList(text.splitlines(), source="<fortran-doc>")
return list(
nested_parse_to_nodes(
state,
content,
source="<fortran-doc>",
offset=0,
allow_section_headings=True,
keep_title_context=True,
)
)
def _field_list(title: str, body: nodes.Element) -> nodes.field_list:
fl = nodes.field_list()
field = nodes.field()
field += nodes.field_name(text=title)
fbody = nodes.field_body()
fbody += body
field += fbody
fl += field
return fl
def _stamp_source_line(node: nodes.Node, *, source: str = "<fortran>", line: int = 1) -> None:
# Sphinx expects certain nodes (notably definition_list_item) to have
# a non-None .line during HTML builds.
if getattr(node, "source", None) is None:
node.source = source # type: ignore[attr-defined]
if getattr(node, "line", None) is None:
node.line = line # type: ignore[attr-defined]
def _append_named_decl_docs(
section: nodes.Element,
title: str,
items,
state,
*,
anchors_by_name: dict[str, str] | None = None,
as_field_list: bool = True,
) -> None:
"""Render a standard name/decl/doc definition list inside a field list.
This is the canonical rendering used for procedure Arguments and module Variables.
"""
if not items:
return
rows = []
for a in items:
name = getattr(a, "name", "")
decl = getattr(a, "decl", None) or ""
doc = getattr(a, "doc", None)
if not name:
continue
rows.append((name, decl, doc))
if not rows:
return
dl = nodes.definition_list()
dl["classes"].append("simple")
_stamp_source_line(dl)
for name, decl, doc in rows:
item = nodes.definition_list_item()
_stamp_source_line(item)
term = nodes.term()
_stamp_source_line(term)
anchor = (anchors_by_name or {}).get(str(name))
if anchor:
# Add an invisible target so xrefs land on the correct row without
# changing the visual rendering.
term += nodes.target(ids=[anchor])
term += nodes.strong(text=str(name))
item += term
if decl:
classifier = nodes.classifier(text=str(decl))
_stamp_source_line(classifier)
item += classifier
definition = nodes.definition()
_stamp_source_line(definition)
for n in _parse_doc_fragment(doc, state):
definition += n
item += definition
dl += item
if as_field_list:
section += _field_list(title, dl)
else:
section += nodes.subtitle(text=str(title))
section += dl
def _append_return_docs(section: nodes.Element, result, state) -> None:
if not result:
return
name = getattr(result, "name", "")
decl = getattr(result, "decl", None) or ""
doc = getattr(result, "doc", None)
if not name:
return
dl = nodes.definition_list()
dl["classes"].append("simple")
_stamp_source_line(dl)
item = nodes.definition_list_item()
_stamp_source_line(item)
term = nodes.term()
_stamp_source_line(term)
term += nodes.strong(text=str(name))
item += term
if decl:
classifier = nodes.classifier(text=str(decl))
_stamp_source_line(classifier)
item += classifier
definition = nodes.definition()
_stamp_source_line(definition)
for n in _parse_doc_fragment(doc, state):
definition += n
item += definition
dl += item
section += _field_list("Returns", dl)
def _append_component_docs(section: nodes.Element, components, state) -> None:
if not components:
return
rows = []
for c in components:
name = getattr(c, "name", "")
decl = getattr(c, "decl", None) or ""
doc = getattr(c, "doc", None)
if not name:
continue
rows.append((name, decl, doc))
if not rows:
return
dl = nodes.definition_list()
dl["classes"].append("simple")
_stamp_source_line(dl)
for name, decl, doc in rows:
item = nodes.definition_list_item()
_stamp_source_line(item)
term = nodes.term()
_stamp_source_line(term)
term += nodes.strong(text=str(name))
item += term
if decl:
classifier = nodes.classifier(text=str(decl))
_stamp_source_line(classifier)
item += classifier
definition = nodes.definition()
_stamp_source_line(definition)
for n in _parse_doc_fragment(doc, state):
definition += n
item += definition
dl += item
section += _field_list("Attributes", dl)
def _find_proc_by_name(procedures, name: str):
for p in procedures or []:
if getattr(p, "name", None) == name:
return p
return None
def _append_type_bound_procedures(section: nodes.Element, bindings, all_procedures, state) -> None:
if not bindings:
return
items = nodes.bullet_list()
for b in bindings:
bname = getattr(b, "name", "")
target = getattr(b, "target", None) or bname
if not bname:
continue
proc = _find_proc_by_name(all_procedures, target)
kind = getattr(proc, "kind", None) if proc is not None else None
para = nodes.paragraph()
if proc is not None and kind in {"function", "subroutine"}:
xref = addnodes.pending_xref(
"",
refdomain="f",
reftype=str(kind),
reftarget=str(target),
refexplicit=True,
)
xref += nodes.literal(text=str(bname))
para += xref
else:
para += nodes.literal(text=str(bname))
item = nodes.list_item("", para)
items += item
section += _field_list("Procedures", items)
def _append_object_description(
section: nodes.Element,
*,
domain: str,
objtype: str,
name: str,
signature: str | None,
doc: str | None,
state,
args=None,
result=None,
components=None,
bindings=None,
all_procedures=None,
) -> None:
"""Render an object in a Sphinx-like <dl class="..."> wrapper.
"""
desc = addnodes.desc()
desc["domain"] = domain
desc["objtype"] = objtype
desc["classes"].extend([domain, objtype])
signode = addnodes.desc_signature()
# Keep signature rendering simple and stable: show the parsed signature text
# as a literal inside the signature node.
text = signature if signature else f"{name} ({objtype})"
signode += nodes.literal(text=str(text))
desc += signode
content = addnodes.desc_content()
preamble_doc, section_blocks_doc = _split_out_doc_section_blocks(doc)
# Keep the opening free-text docstring at the top.
_append_doc(content, preamble_doc, state)
_append_named_decl_docs(content, "Arguments", args, state)
if objtype == "function":
_append_return_docs(content, result, state)
_append_component_docs(content, components, state)
_append_type_bound_procedures(content, bindings, all_procedures, state)
# Place all "## ..." section blocks (including "## Examples") after intrinsic blocks.
_append_doc(content, section_blocks_doc, state)
desc += content
section += desc
[docs]
class FortranObject(ObjectDescription[str]):
"""Base directive for Fortran objects (manual declarations)."""
has_content = True
required_arguments = 1
[docs]
def handle_signature(self, sig: str, signode: addnodes.desc_signature) -> str:
fullname = sig.strip()
signode += addnodes.desc_name(fullname, fullname)
return fullname
[docs]
def add_target_and_index(self, name: str, sig: str, signode: addnodes.desc_signature) -> None:
domain: FortranDomain = self.env.get_domain("f") # type: ignore[assignment]
objtype = self.objtype
anchor = _make_object_id(objtype, name)
if anchor not in signode["ids"]:
signode["ids"].append(anchor)
domain.note_object(name=name, objtype=objtype, anchor=anchor)
index_text = f"{name} ({objtype})"
self.indexnode["entries"].append(("single", index_text, anchor, "", None))
[docs]
class FortranProgramDecl(FortranObject):
objtype = "program"
[docs]
class FortranFunction(FortranObject):
objtype = "function"
[docs]
class FortranSubroutine(FortranObject):
objtype = "subroutine"
[docs]
class FortranType(FortranObject):
objtype = "type"
[docs]
class FortranInterface(FortranObject):
objtype = "interface"
[docs]
class FortranProgram(Directive):
required_arguments = 1
option_spec = {
"procedures": rst_directives.flag,
"no-procedures": rst_directives.flag,
"no-show-code": rst_directives.flag,
}
[docs]
def run(self):
progname = self.arguments[0]
env = self.state.document.settings.env
domain = env.get_domain("f")
program = getattr(domain, "get_program")(progname)
show_procedures = True
if "no-procedures" in self.options:
show_procedures = False
if "procedures" in self.options:
show_procedures = True
anchor = nodes.make_id(f"f-program-{progname}")
index = addnodes.index(entries=[("single", f"{progname} (program)", anchor, "", None)])
section = nodes.section(ids=[anchor])
section += nodes.title(text=f"{progname} (program)")
getattr(domain, "note_object")(name=progname, objtype="program", anchor=anchor)
if program is None:
return [index, nodes.warning(text=f"Fortran program '{progname}' not found (did you configure fortran_sources?)")]
markers = _doc_markers_from_env(env)
src = getattr(program, "source", None) or _read_program_source_from_location(getattr(program, "location", None))
predoc: str | None = None
if not src:
src, predoc = _read_program_source_by_search(env, progname, doc_markers=markers)
else:
# If we have a usable file location, try to re-read to extract only the doc
# block immediately preceding the program statement (important for FORD,
# which may include in-body docs in program.doc).
loc = getattr(program, "location", None)
path = getattr(loc, "path", None) if loc is not None else None
lineno = getattr(loc, "lineno", None) if loc is not None else None
if path and lineno:
try:
lines = Path(str(path)).read_text(encoding="utf-8", errors="replace").splitlines()
start = _find_program_in_file(lines, progname, start_at=max(int(lineno) - 1 - 5, 0))
if start is not None:
predoc = extract_predoc_before_line(lines, start, doc_markers=markers)
except OSError:
pass
# Render program-level docs right under the title.
# Prefer only the pre-program doc block when available (prevents in-body docs
# from being rendered separately, which is especially important for FORD).
doc = predoc if predoc is not None else getattr(program, "doc", None)
_append_doc(section, doc, self.state)
show_source_code = "no-show-code" not in self.options
if src and show_source_code:
_append_fortran_code_block(section, source=src)
deps = list(getattr(program, "dependencies", None) or [])
if not deps and src:
deps = extract_use_dependencies(src)
_append_program_dependencies(section, dependencies=deps, state=self.state)
# Internal procedures (after `contains`) are optionally rendered after the program source.
if show_procedures and getattr(program, "procedures", None):
section += nodes.subtitle(text="Procedures")
for p in program.procedures:
kind = getattr(p, "kind", "procedure")
fullname = f"{progname}.{p.name}"
obj_anchor = _make_object_id(kind, fullname)
getattr(domain, "note_object")(name=p.name, objtype=kind, anchor=obj_anchor)
index["entries"].append(("single", f"{p.name} ({kind})", obj_anchor, "", None))
sub = nodes.section(ids=[obj_anchor])
sub += nodes.title(text=f"{p.name} ({kind})")
_append_object_description(
sub,
domain="f",
objtype=str(kind),
name=str(p.name),
signature=getattr(p, "signature", None),
doc=getattr(p, "doc", None),
state=self.state,
args=getattr(p, "arguments", None),
result=getattr(p, "result", None),
)
section += sub
return [index, section]
[docs]
class FortranModule(Directive):
required_arguments = 1
[docs]
def run(self):
modname = self.arguments[0]
env = self.state.document.settings.env
domain = env.get_domain("f")
module = getattr(domain, "get_module")(modname)
anchor = nodes.make_id(f"f-module-{modname}")
index = addnodes.index(entries=[("single", f"{modname} (module)", anchor, "", None)])
section = nodes.section(ids=[anchor])
section += nodes.title(text=f"{modname} (module)")
getattr(domain, "note_object")(name=modname, objtype="module", anchor=anchor)
if module is None:
return [index, nodes.warning(text=f"Fortran module '{modname}' not found (did you configure fortran_sources?)")]
_append_doc(section, getattr(module, "doc", None), self.state)
if getattr(module, "variables", None):
anchors: dict[str, str] = {}
for v in module.variables:
name = getattr(v, "name", "")
if not name:
continue
fullname = f"{modname}.{name}"
obj_anchor = _make_object_id("variable", fullname)
anchors[str(name)] = obj_anchor
getattr(domain, "note_object")(name=name, objtype="variable", anchor=obj_anchor)
index["entries"].append(("single", f"{name} (variable)", obj_anchor, "", None))
_append_named_decl_docs(
section,
"Variables",
module.variables,
self.state,
anchors_by_name=anchors,
as_field_list=False,
)
if getattr(module, "types", None):
section += nodes.subtitle(text="Types")
for t in module.types:
fullname = f"{modname}.{t.name}"
obj_anchor = _make_object_id("type", fullname)
getattr(domain, "note_object")(name=t.name, objtype="type", anchor=obj_anchor)
index["entries"].append(("single", f"{t.name} (type)", obj_anchor, "", None))
sub = nodes.section(ids=[obj_anchor])
sub += nodes.title(text=f"{t.name} (type)")
_append_object_description(
sub,
domain="f",
objtype="type",
name=str(t.name),
signature=getattr(t, "signature", None),
doc=getattr(t, "doc", None),
state=self.state,
components=getattr(t, "components", None),
bindings=getattr(t, "bound_procedures", None),
all_procedures=getattr(module, "procedures", None),
)
section += sub
if getattr(module, "procedures", None):
section += nodes.subtitle(text="Procedures")
for p in module.procedures:
kind = getattr(p, "kind", "procedure")
fullname = f"{modname}.{p.name}"
obj_anchor = _make_object_id(kind, fullname)
getattr(domain, "note_object")(name=p.name, objtype=kind, anchor=obj_anchor)
index["entries"].append(("single", f"{p.name} ({kind})", obj_anchor, "", None))
sub = nodes.section(ids=[obj_anchor])
sub += nodes.title(text=f"{p.name} ({kind})")
_append_object_description(
sub,
domain="f",
objtype=str(kind),
name=str(p.name),
signature=getattr(p, "signature", None),
doc=getattr(p, "doc", None),
state=self.state,
args=getattr(p, "arguments", None),
result=getattr(p, "result", None),
)
section += sub
if getattr(module, "interfaces", None):
section += nodes.subtitle(text="Interfaces")
for g in module.interfaces:
fullname = f"{modname}.{g.name}"
obj_anchor = _make_object_id("interface", fullname)
getattr(domain, "note_object")(name=g.name, objtype="interface", anchor=obj_anchor)
index["entries"].append(("single", f"{g.name} (interface)", obj_anchor, "", None))
sub = nodes.section(ids=[obj_anchor])
sub += nodes.title(text=f"{g.name} (interface)")
_append_doc(sub, getattr(g, "doc", None), self.state)
section += sub
return [index, section]
[docs]
class FortranSubmodule(Directive):
required_arguments = 1
[docs]
def run(self):
submodname = self.arguments[0]
env = self.state.document.settings.env
domain = env.get_domain("f")
submodule = getattr(domain, "get_submodule")(submodname)
anchor = nodes.make_id(f"f-submodule-{submodname}")
index = addnodes.index(entries=[("single", f"{submodname} (submodule)", anchor, "", None)])
section = nodes.section(ids=[anchor])
section += nodes.title(text=f"{submodname} (submodule)")
getattr(domain, "note_object")(name=submodname, objtype="submodule", anchor=anchor)
if submodule is None:
return [index, nodes.warning(text=f"Fortran submodule '{submodname}' not found (did you configure fortran_sources?)")]
_append_doc(section, getattr(submodule, "doc", None), self.state)
if getattr(submodule, "variables", None):
anchors: dict[str, str] = {}
for v in submodule.variables:
name = getattr(v, "name", "")
if not name:
continue
fullname = f"{submodname}.{name}"
obj_anchor = _make_object_id("variable", fullname)
anchors[str(name)] = obj_anchor
getattr(domain, "note_object")(name=name, objtype="variable", anchor=obj_anchor)
index["entries"].append(("single", f"{name} (variable)", obj_anchor, "", None))
_append_named_decl_docs(
section,
"Variables",
submodule.variables,
self.state,
anchors_by_name=anchors,
as_field_list=False,
)
if getattr(submodule, "types", None):
section += nodes.subtitle(text="Types")
for t in submodule.types:
fullname = f"{submodname}.{t.name}"
obj_anchor = _make_object_id("type", fullname)
getattr(domain, "note_object")(name=t.name, objtype="type", anchor=obj_anchor)
index["entries"].append(("single", f"{t.name} (type)", obj_anchor, "", None))
sub = nodes.section(ids=[obj_anchor])
sub += nodes.title(text=f"{t.name} (type)")
_append_object_description(
sub,
domain="f",
objtype="type",
name=str(t.name),
signature=getattr(t, "signature", None),
doc=getattr(t, "doc", None),
state=self.state,
components=getattr(t, "components", None),
bindings=getattr(t, "bound_procedures", None),
all_procedures=getattr(submodule, "procedures", None),
)
section += sub
if getattr(submodule, "procedures", None):
section += nodes.subtitle(text="Procedures")
for p in submodule.procedures:
kind = getattr(p, "kind", "procedure")
fullname = f"{submodname}.{p.name}"
obj_anchor = _make_object_id(kind, fullname)
getattr(domain, "note_object")(name=p.name, objtype=kind, anchor=obj_anchor)
index["entries"].append(("single", f"{p.name} ({kind})", obj_anchor, "", None))
sub = nodes.section(ids=[obj_anchor])
sub += nodes.title(text=f"{p.name} ({kind})")
_append_object_description(
sub,
domain="f",
objtype=str(kind),
name=str(p.name),
signature=getattr(p, "signature", None),
doc=getattr(p, "doc", None),
state=self.state,
args=getattr(p, "arguments", None),
result=getattr(p, "result", None),
)
section += sub
if getattr(submodule, "interfaces", None):
section += nodes.subtitle(text="Interfaces")
for g in submodule.interfaces:
fullname = f"{submodname}.{g.name}"
obj_anchor = _make_object_id("interface", fullname)
getattr(domain, "note_object")(name=g.name, objtype="interface", anchor=obj_anchor)
index["entries"].append(("single", f"{g.name} (interface)", obj_anchor, "", None))
sub = nodes.section(ids=[obj_anchor])
sub += nodes.title(text=f"{g.name} (interface)")
_append_doc(sub, getattr(g, "doc", None), self.state)
section += sub
return [index, section]