summaryrefslogtreecommitdiffstats
path: root/sphinx/util/docstrings.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/util/docstrings.py')
-rw-r--r--sphinx/util/docstrings.py88
1 files changed, 88 insertions, 0 deletions
diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py
new file mode 100644
index 0000000..6ccc538
--- /dev/null
+++ b/sphinx/util/docstrings.py
@@ -0,0 +1,88 @@
+"""Utilities for docstring processing."""
+
+from __future__ import annotations
+
+import re
+import sys
+
+from docutils.parsers.rst.states import Body
+
+field_list_item_re = re.compile(Body.patterns['field_marker'])
+
+
+def separate_metadata(s: str | None) -> tuple[str | None, dict[str, str]]:
+ """Separate docstring into metadata and others."""
+ in_other_element = False
+ metadata: dict[str, str] = {}
+ lines = []
+
+ if not s:
+ return s, metadata
+
+ for line in prepare_docstring(s):
+ if line.strip() == '':
+ in_other_element = False
+ lines.append(line)
+ else:
+ matched = field_list_item_re.match(line)
+ if matched and not in_other_element:
+ field_name = matched.group()[1:].split(':', 1)[0]
+ if field_name.startswith('meta '):
+ name = field_name[5:].strip()
+ metadata[name] = line[matched.end():].strip()
+ else:
+ lines.append(line)
+ else:
+ in_other_element = True
+ lines.append(line)
+
+ return '\n'.join(lines), metadata
+
+
+def prepare_docstring(s: str, tabsize: int = 8) -> list[str]:
+ """Convert a docstring into lines of parseable reST. Remove common leading
+ indentation, where the indentation of the first line is ignored.
+
+ Return the docstring as a list of lines usable for inserting into a docutils
+ ViewList (used as argument of nested_parse().) An empty line is added to
+ act as a separator between this docstring and following content.
+ """
+ lines = s.expandtabs(tabsize).splitlines()
+ # Find minimum indentation of any non-blank lines after ignored lines.
+ margin = sys.maxsize
+ for line in lines[1:]:
+ content = len(line.lstrip())
+ if content:
+ indent = len(line) - content
+ margin = min(margin, indent)
+ # Remove indentation from the first line.
+ if len(lines):
+ lines[0] = lines[0].lstrip()
+ if margin < sys.maxsize:
+ for i in range(1, len(lines)):
+ lines[i] = lines[i][margin:]
+ # Remove any leading blank lines.
+ while lines and not lines[0]:
+ lines.pop(0)
+ # make sure there is an empty line at the end
+ if lines and lines[-1]:
+ lines.append('')
+ return lines
+
+
+def prepare_commentdoc(s: str) -> list[str]:
+ """Extract documentation comment lines (starting with #:) and return them
+ as a list of lines. Returns an empty list if there is no documentation.
+ """
+ result = []
+ lines = [line.strip() for line in s.expandtabs().splitlines()]
+ for line in lines:
+ if line.startswith('#:'):
+ line = line[2:]
+ # the first space after the comment is ignored
+ if line and line[0] == ' ':
+ line = line[1:]
+ result.append(line)
+ if result and result[-1]:
+ result.append('')
+ return result