Skip to content

Commit

Permalink
Move documenter to seperate file
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Sep 11, 2024
1 parent 37526d5 commit d13759d
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 120 deletions.
2 changes: 1 addition & 1 deletion conf.in.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ def linkcode_resolve(domain, info):
def setup(app):
try:
from autoautosummary import AutoAutoSummary
from documenters import OverloadedPythonMethodDocumenter
from process_links import (
OverloadedPythonMethodDocumenter,
process_bases,
process_docstring,
process_signature,
Expand Down
126 changes: 126 additions & 0 deletions documenters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import inspect
import re

from sphinx.ext.autodoc import MethodDocumenter


class OverloadedPythonMethodDocumenter(MethodDocumenter):
"""
A method documenter which handles overloaded methods, via processing
the docstrings generated by SIP
"""

objtype = "method"
priority = MethodDocumenter.priority

@classmethod
def can_document_member(cls, member, membername, isattr, parent):
return MethodDocumenter.can_document_member(member, membername, isattr, parent)

@staticmethod
def parse_signatures(docstring):
"""
Extracts each signature from a sip generated docstring
"""
signature_pattern = r"(\w+\(.*?\))(?:\s*->\s*\(?\w+(?:,\s*\w+)*\)?)?"
res = []
current_signature_docs = []
for line in docstring.split("\n"):

if re.match(signature_pattern, line):
if current_signature_docs:
res.append(current_signature_docs)

# Extract just the parameter part of each signature
params = re.search(r"\((.*?)\)", line).group()
current_signature_docs = [params]
else:
current_signature_docs += [line]
if current_signature_docs:
res.append(current_signature_docs)

return res

def parse_signature_blocks(self, docstring):
"""
Extracts each signature from a sip generated docstring, and
returns each signature in a tuple with the docs for just
that signature.
"""
res = []
current_sig = ""
current_desc = ""
for line in docstring.split("\n"):
match = re.match(
r"^\s*\w+(\([^)]*\)(?:\s*->\s*[^:\n]+)?)\s*((?:(?!\w+\().)*)\s*$", line
)
if match:
if current_sig:
res.append((current_sig, current_desc))
current_sig = match.group(1)
current_desc = match.group(2)
if current_desc:
current_desc += "\n"
else:
current_desc += line + "\n"

if current_sig:
res.append((current_sig, current_desc))

return res

def add_content(self, more_content):
"""
Parse the docstring to get all signatures and their descriptions
"""
sourcename = self.get_sourcename()
docstring = inspect.getdoc(self.object)
if docstring:
# does this method have multiple overrides?
signature_blocks = self.parse_signature_blocks(docstring)

if len(signature_blocks) <= 1:
# nope, just use standard formatter then!
super().add_content(more_content)
return

# add a method output for EVERY override
for i, (signature, description) in enumerate(signature_blocks):
# this pattern is used in the autodoc source!
old_indent = self.indent
new_indent = (
" "
* len(self.content_indent)
* (len(self.indent) // len(self.content_indent) - 1)
)
self.indent = new_indent
# skip this class, go straight to super. The add_directive_header
# implementation from this class will omit the signatures of
# overridden methods
super().add_directive_header(signature)
self.indent = old_indent

if i > 0:
# we can only index the first signature!
self.add_line(":no-index:", sourcename)

self.add_line("", sourcename)

doc_for_this_override = self.object_name + signature + "\n" + description
for line in self.process_doc([doc_for_this_override.split("\n")]):
self.add_line(line, sourcename)

def add_directive_header(self, sig):
# Parse the docstring to get all signatures
docstring = inspect.getdoc(self.object)
if docstring:
signatures = self.parse_signatures(docstring)
else:
signatures = [sig] # Use the original signature if no docstring

if len(signatures) > 1:
# skip overridden method directive headers here, we will generate
# them later when we pass the actual docstring
return

return super().add_directive_header(sig)
120 changes: 1 addition & 119 deletions process_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
# This logic has been copied from the existing extension with some tuning for PyQGIS

import enum
import inspect
import re

import yaml

with open("pyqgis_conf.yml") as f:
cfg = yaml.safe_load(f)

from sphinx.ext.autodoc import AttributeDocumenter, Documenter, MethodDocumenter
from sphinx.ext.autodoc import AttributeDocumenter, Documenter

old_get_doc = Documenter.get_doc

Expand Down Expand Up @@ -258,120 +257,3 @@ def process_bases(app, name, obj, option, bases: list) -> None:
# replace 'sip.wrapper' base class with 'object'
if base.__name__ == "wrapper":
bases[i] = object


class OverloadedPythonMethodDocumenter(MethodDocumenter):
objtype = "method"
priority = MethodDocumenter.priority

@classmethod
def can_document_member(cls, member, membername, isattr, parent):
return MethodDocumenter.can_document_member(member, membername, isattr, parent)

@staticmethod
def parse_signatures(docstring):
"""
Extracts each signature from a sip generated docstring
"""
signature_pattern = r"(\w+\(.*?\))(?:\s*->\s*\(?\w+(?:,\s*\w+)*\)?)?"
res = []
current_signature_docs = []
for line in docstring.split("\n"):

if re.match(signature_pattern, line):
if current_signature_docs:
res.append(current_signature_docs)

# Extract just the parameter part of each signature
params = re.search(r"\((.*?)\)", line).group()
current_signature_docs = [params]
else:
current_signature_docs += [line]
if current_signature_docs:
res.append(current_signature_docs)

return res

def parse_signature_blocks(self, docstring):
"""
Extracts each signature from a sip generated docstring, and
returns each signature in a tuple with the docs for just
that signature.
"""
res = []
current_sig = ""
current_desc = ""
for line in docstring.split("\n"):
match = re.match(
r"^\s*\w+(\([^)]*\)(?:\s*->\s*[^:\n]+)?)\s*((?:(?!\w+\().)*)\s*$", line
)
if match:
if current_sig:
res.append((current_sig, current_desc))
current_sig = match.group(1)
current_desc = match.group(2)
if current_desc:
current_desc += "\n"
else:
current_desc += line + "\n"

if current_sig:
res.append((current_sig, current_desc))

return res

def add_content(self, more_content):
"""
Parse the docstring to get all signatures and their descriptions
"""
sourcename = self.get_sourcename()
docstring = inspect.getdoc(self.object)
if docstring:
# does this method have multiple overrides?
signature_blocks = self.parse_signature_blocks(docstring)

if len(signature_blocks) <= 1:
# nope, just use standard formatter then!
super().add_content(more_content)
return

# add a method output for EVERY override
for i, (signature, description) in enumerate(signature_blocks):
# this pattern is used in the autodoc source!
old_indent = self.indent
new_indent = (
" "
* len(self.content_indent)
* (len(self.indent) // len(self.content_indent) - 1)
)
self.indent = new_indent
# skip this class, go straight to super. The add_directive_header
# implementation from this class will omit the signatures of
# overridden methods
super().add_directive_header(signature)
self.indent = old_indent

if i > 0:
# we can only index the first signature!
self.add_line(":no-index:", sourcename)

self.add_line("", sourcename)

doc_for_this_override = self.object_name + signature + "\n" + description
for line in self.process_doc([doc_for_this_override.split("\n")]):
self.add_line(line, sourcename)

def add_directive_header(self, sig):
# Parse the docstring to get all signatures
docstring = inspect.getdoc(self.object)
if docstring:
signatures = self.parse_signatures(docstring)
else:
signatures = [sig] # Use the original signature if no docstring

if len(signatures) > 1:
# skip overridden method directive headers here, we will generate
# them later when we pass the actual docstring
return

return super().add_directive_header(sig)

0 comments on commit d13759d

Please sign in to comment.