From 0161b8c87901c2605065daa063ce2e01d93fc370 Mon Sep 17 00:00:00 2001 From: Lorenz Neureuter Date: Sun, 24 Jul 2022 20:29:04 -0400 Subject: [PATCH] Fix sphinx multimethod customization (#1120 #1121 #1122) --- doc/conf.py | 26 ++++++----- doc/ext/sphinx_autodoc_multimethod.py | 62 +++++++++++---------------- 2 files changed, 41 insertions(+), 47 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 856945e89..5c1648d6d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -11,7 +11,7 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import sys, os, re import os.path # print "working path is %s" % os.getcwd() @@ -59,8 +59,8 @@ master_doc = "index" # General information about the project. -project = u"CadQuery" -copyright = u"Parametric Products Intellectual Holdings LLC, All Rights Reserved" +project = "CadQuery" +copyright = "Parametric Products Intellectual Holdings LLC, All Rights Reserved" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -227,7 +227,7 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ("index", "CadQuery.tex", u"CadQuery Documentation", u"David Cowden", "manual"), + ("index", "CadQuery.tex", "CadQuery Documentation", "David Cowden", "manual"), ] # The name of an image file (relative to this directory) to place at the top of @@ -255,7 +255,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("index", "cadquery", u"CadQuery Documentation", [u"David Cowden"], 1)] +man_pages = [("index", "cadquery", "CadQuery Documentation", ["David Cowden"], 1)] # If true, show URL addresses after external links. # man_show_urls = False @@ -270,8 +270,8 @@ ( "index", "CadQuery", - u"CadQuery Documentation", - u"David Cowden", + "CadQuery Documentation", + "David Cowden", "CadQuery", "A Fluent CAD api", "Miscellaneous", @@ -288,6 +288,9 @@ # texinfo_show_urls = 'footnote' +patparam = re.compile(r"(\W*):\W*param.*") + + def process_docstring_insert_self(app, what, name, obj, options, lines): """ Insert self in front of documented params for instance methods @@ -295,12 +298,15 @@ def process_docstring_insert_self(app, what, name, obj, options, lines): if ( what == "method" + and app.config.autodoc_typehints in ("both", "description") + and app.config.autodoc_typehints_description_target in ("all") and getattr(obj, "__self__", None) is None and "self" in obj.__annotations__ ): - for i, dstr in enumerate(lines): - if dstr.startswith(":param"): - lines.insert(i, ":param self:") + for i, line in enumerate(lines): + if m := patparam.match(line): + indent = m.group(1) + lines.insert(i, f"{indent}:param self:") break diff --git a/doc/ext/sphinx_autodoc_multimethod.py b/doc/ext/sphinx_autodoc_multimethod.py index 2d442b893..86527d825 100644 --- a/doc/ext/sphinx_autodoc_multimethod.py +++ b/doc/ext/sphinx_autodoc_multimethod.py @@ -27,51 +27,26 @@ def get_first(obj): return next(iter(obj)) -patself = re.compile(r"\W*:\W*param\W+self.*") -patparam = re.compile(r"\W*:\W*param.*") +patindent = re.compile(r"(\W*)") def process_docstring_multimethod(app, what, name, obj, options, lines): """multimethod docstring customization - * formatting and remove extraneous signatures - * insert self in front of param field list for instance methods if type hinted + Remove extraneous signatures and combine docstrings if docstring also defined + in registered methods. Requires sphinx-build -E if rebuilding docs. """ + methods = [] + if what == "method" and isinstance(obj, multimethod): # instance or static method - insert_first_param = False - if ( - app.config.autodoc_typehints in ("both", "description") - and app.config.autodoc_typehints_description_target in ("all") - and get_first(get_type_hints(get_first(obj.values()))) == "self" - ): - insert_first_param = True - - lines_replace = [] - # handle functools.singledispatch style register (multiple names) - methods = set(m.__name__ for m in obj.values()) - patsig = re.compile(rf"\W*[{'|'.join(methods)}]+\(.*\).*") - - iparam = None - for i, line in enumerate(lines): - if patsig.match(line): - lines_replace.append("") - continue - if insert_first_param and patself.match(line): - # exists explicitly in field list - insert_first_param = False - elif insert_first_param and not iparam and patparam.match(line): - iparam = i - lines_replace.append(line.lstrip()) - - if insert_first_param and iparam: - lines_replace.insert(iparam, ":param self:") - - del lines[:] - lines.extend(lines_replace) + if obj.pending: + methods = set(m.__name__ for m in obj.pending) + else: + methods = set(m.__name__ for m in obj.values()) elif what == "method" and inspect.isclassmethod(obj) and hasattr(obj, "pending"): @@ -80,14 +55,23 @@ def process_docstring_multimethod(app, what, name, obj, options, lines): else: methods = set(m.__name__ for m in obj.__func__.values()) + if methods: lines_replace = [] patsig = re.compile(rf"\W*[{'|'.join(methods)}]+\(.*\).*") + indent = -1 for line in lines: + if indent < 0: + # fix indent when multiple docstrings defined + if m := patindent.match(line): + indent = len(m.group(1)) + else: + indent = 0 + if patsig.match(line): lines_replace.append("") else: - lines_replace.append(line.lstrip()) + lines_replace.append(line[indent:]) del lines[:] lines.extend(lines_replace) @@ -243,12 +227,16 @@ def format_signature(self, **kwargs: Any) -> str: sigs.append(documenter.format_signature()) # -- multimethod customization elif isinstance(meth, multimethod): - sigs = self.append_signature_multiple_dispatch(meth.values()) + if meth.pending: + methods = meth.pending + else: + methods = set(meth.values()) + sigs = self.append_signature_multiple_dispatch(methods) elif inspect.isclassmethod(self.object) and hasattr(self.object, "pending"): if self.object.pending: methods = self.object.pending else: - methods = self.object.__func__.values() + methods = set(self.object.__func__.values()) sigs = self.append_signature_multiple_dispatch(methods) elif inspect.isstaticmethod(meth) and isinstance(self.object, multimethod): sigs = []